diff --git a/Source/vs/base/browser/ui/button/button.ts b/Source/vs/base/browser/ui/button/button.ts index cf6ff945d559a..4f11066fcb6b9 100644 --- a/Source/vs/base/browser/ui/button/button.ts +++ b/Source/vs/base/browser/ui/button/button.ts @@ -141,12 +141,16 @@ export class Button extends Disposable implements IButton { })); // Also set hover background when button is focused for feedback this.focusTracker = this._register(trackFocus(this._element)); - this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { - this.updateBackground(true); - } })); - this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { - this.updateBackground(false); - } })); + this._register(this.focusTracker.onDidFocus(() => { + if (this.enabled) { + this.updateBackground(true); + } + })); + this._register(this.focusTracker.onDidBlur(() => { + if (this.enabled) { + this.updateBackground(false); + } + })); } public override dispose(): void { super.dispose(); diff --git a/Source/vs/base/browser/ui/tree/abstractTree.ts b/Source/vs/base/browser/ui/tree/abstractTree.ts index 9acbd1e8febcb..20b6764f4dda4 100644 --- a/Source/vs/base/browser/ui/tree/abstractTree.ts +++ b/Source/vs/base/browser/ui/tree/abstractTree.ts @@ -2137,14 +2137,18 @@ export abstract class AbstractTree implements IDisposable private readonly _onWillRefilter = new Emitter(); readonly onWillRefilter: Event = this._onWillRefilter.event; get findMode(): TreeFindMode { return this.findController?.mode ?? TreeFindMode.Highlight; } - set findMode(findMode: TreeFindMode) { if (this.findController) { - this.findController.mode = findMode; - } } + set findMode(findMode: TreeFindMode) { + if (this.findController) { + this.findController.mode = findMode; + } + } readonly onDidChangeFindMode: Event; get findMatchType(): TreeFindMatchType { return this.findController?.matchType ?? TreeFindMatchType.Fuzzy; } - set findMatchType(findFuzzy: TreeFindMatchType) { if (this.findController) { - this.findController.matchType = findFuzzy; - } } + set findMatchType(findFuzzy: TreeFindMatchType) { + if (this.findController) { + this.findController.matchType = findFuzzy; + } + } readonly onDidChangeFindMatchType: Event; get onDidChangeFindPattern(): Event { return this.findController ? this.findController.onDidChangePattern : Event.None; } get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; } diff --git a/Source/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/Source/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index f41c12466c80e..ea4f7723a6a3a 100644 --- a/Source/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/Source/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -60,12 +60,16 @@ export class DiffEditorViewZones extends Disposable { const updateImmediately = this._register(new RunOnceScheduler(() => { state.set(state.get() + 1, undefined); }, 0)); - this._register(this._editors.original.onDidChangeViewZones((_args) => { if (!this._canIgnoreViewZoneUpdateEvent()) { - updateImmediately.schedule(); - } })); - this._register(this._editors.modified.onDidChangeViewZones((_args) => { if (!this._canIgnoreViewZoneUpdateEvent()) { - updateImmediately.schedule(); - } })); + this._register(this._editors.original.onDidChangeViewZones((_args) => { + if (!this._canIgnoreViewZoneUpdateEvent()) { + updateImmediately.schedule(); + } + })); + this._register(this._editors.modified.onDidChangeViewZones((_args) => { + if (!this._canIgnoreViewZoneUpdateEvent()) { + updateImmediately.schedule(); + } + })); this._register(this._editors.original.onDidChangeConfiguration((args) => { if (args.hasChanged(EditorOption.wrappingInfo) || args.hasChanged(EditorOption.lineHeight)) { updateImmediately.schedule(); diff --git a/Source/vs/editor/browser/widget/diffEditor/utils.ts b/Source/vs/editor/browser/widget/diffEditor/utils.ts index 7d545273740bd..b1a1f67f69000 100644 --- a/Source/vs/editor/browser/widget/diffEditor/utils.ts +++ b/Source/vs/editor/browser/widget/diffEditor/utils.ts @@ -300,9 +300,11 @@ export function applyViewZones(editor: ICodeEditor, viewZones: IObservable { for (const id of changeSummary.zoneIds) { - a.layoutZone(id); - } }); + editor.changeViewZones(a => { + for (const id of changeSummary.zoneIds) { + a.layoutZone(id); + } + }); if (setIsUpdating) { setIsUpdating(false); } @@ -313,9 +315,11 @@ export function applyViewZones(editor: ICodeEditor, viewZones: IObservable { for (const id of lastViewZoneIds) { - a.removeZone(id); - } }); + editor.changeViewZones(a => { + for (const id of lastViewZoneIds) { + a.removeZone(id); + } + }); zoneIds?.clear(); if (setIsUpdating) { setIsUpdating(false); diff --git a/Source/vs/platform/languagePacks/node/languagePacks.ts b/Source/vs/platform/languagePacks/node/languagePacks.ts index 704445b62a387..55960d6c8eafb 100644 --- a/Source/vs/platform/languagePacks/node/languagePacks.ts +++ b/Source/vs/platform/languagePacks/node/languagePacks.ts @@ -185,12 +185,14 @@ class LanguagePacksCache extends Disposable { .then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err)) .then<{ [language: string]: ILanguagePack; - }>(raw => { try { - return JSON.parse(raw); - } - catch (e) { - return {}; - } }) + }>(raw => { + try { + return JSON.parse(raw); + } + catch (e) { + return {}; + } + }) .then(languagePacks => { result = fn(languagePacks); return languagePacks; }) .then(languagePacks => { for (const language of Object.keys(languagePacks)) { diff --git a/Source/vs/platform/userDataSync/common/extensionsMerge.ts b/Source/vs/platform/userDataSync/common/extensionsMerge.ts index 3a17db18f95c3..6bcfd3bffaf72 100644 --- a/Source/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/Source/vs/platform/userDataSync/common/extensionsMerge.ts @@ -45,9 +45,11 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: remoteExtensions = remoteExtensions.map(massageIncomingExtension); lastSyncExtensions = lastSyncExtensions ? lastSyncExtensions.map(massageIncomingExtension) : null; const uuids: Map = new Map(); - const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { - uuids.set(identifier.id.toLowerCase(), identifier.uuid); - } }; + const addUUID = (identifier: IExtensionIdentifier) => { + if (identifier.uuid) { + uuids.set(identifier.id.toLowerCase(), identifier.uuid); + } + }; localExtensions.forEach(({ identifier }) => addUUID(identifier)); remoteExtensions.forEach(({ identifier }) => addUUID(identifier)); lastSyncExtensions?.forEach(({ identifier }) => addUUID(identifier)); diff --git a/Source/vs/workbench/api/common/extHostDiagnostics.ts b/Source/vs/workbench/api/common/extHostDiagnostics.ts index 405be271f4a67..3ca5292afee25 100644 --- a/Source/vs/workbench/api/common/extHostDiagnostics.ts +++ b/Source/vs/workbench/api/common/extHostDiagnostics.ts @@ -332,6 +332,8 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { const name = '_generated_mirror'; const collection = new DiagnosticCollection(name, name, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, // no limits because this collection is just a mirror of "sanitized" data // no limits because this collection is just a mirror of "sanitized" data + // no limits because this collection is just a mirror of "sanitized" data + // no limits because this collection is just a mirror of "sanitized" data _uri => undefined, this._fileSystemInfoService.extUri, undefined, this._onDidChangeDiagnostics); this._collections.set(name, collection); this._mirrorCollection = collection; diff --git a/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts index dd9135dc87e5b..f9654895e899b 100644 --- a/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/Source/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1030,9 +1030,11 @@ export class DropDownExtensionActionViewItem extends ActionViewItem { getAnchor: () => anchor, getActions: () => actions, actionRunner: this.actionRunner, - onHide: () => { if (disposeActionsOnHide) { - disposeIfDisposable(actions); - } } + onHide: () => { + if (disposeActionsOnHide) { + disposeIfDisposable(actions); + } + } }); } } diff --git a/Source/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/Source/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 430e709afd3dd..e1a1a45f83b63 100644 --- a/Source/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/Source/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -508,9 +508,11 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService); this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService); this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); - this._register(this.paneCompositeService.onDidPaneCompositeOpen(e => { if (e.viewContainerLocation === ViewContainerLocation.Sidebar) { - this.onViewletOpen(e.composite); - } }, this)); + this._register(this.paneCompositeService.onDidPaneCompositeOpen(e => { + if (e.viewContainerLocation === ViewContainerLocation.Sidebar) { + this.onViewletOpen(e.composite); + } + }, this)); this._register(extensionsWorkbenchService.onReset(() => this.refresh())); this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); } diff --git a/Source/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/Source/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 994935b6f2d75..8a0530a9cbf52 100644 --- a/Source/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/Source/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -290,9 +290,12 @@ class MarkerWidget extends Disposable { action.enabled = !!viewModel && marker.lines.length > 1; action.tooltip = multiline ? localize('single line', "Show message in single line") : localize('multi line', "Show message in multiple lines"); action.class = ThemeIcon.asClassName(multiline ? expandedIcon : collapsedIcon); - action.run = () => { if (viewModel) { - viewModel.multiline = !viewModel.multiline; - } return Promise.resolve(); }; + action.run = () => { + if (viewModel) { + viewModel.multiline = !viewModel.multiline; + } + return Promise.resolve(); + }; multilineActionbar.push([action], { icon: true, label: false }); } private renderMessageAndDetails(element: Marker, filterData: MarkerFilterData | undefined): void { diff --git a/Source/vs/workbench/contrib/mergeEditor/browser/model/mapping.ts b/Source/vs/workbench/contrib/mergeEditor/browser/model/mapping.ts index c9f1f8d621ad9..015d23d43d45e 100644 --- a/Source/vs/workbench/contrib/mergeEditor/browser/model/mapping.ts +++ b/Source/vs/workbench/contrib/mergeEditor/browser/model/mapping.ts @@ -201,9 +201,9 @@ export class DocumentRangeMap { public readonly rangeMappings: RangeMapping[], public readonly inputLineCount: number) { assertFn(() => checkAdjacentItems(rangeMappings, (m1, m2) => rangeIsBeforeOrTouching(m1.inputRange, m2.inputRange) && rangeIsBeforeOrTouching(m1.outputRange, m2.outputRange) /*&& - lengthBetweenPositions(m1.inputRange.getEndPosition(), m2.inputRange.getStartPosition()).equals( - lengthBetweenPositions(m1.outputRange.getEndPosition(), m2.outputRange.getStartPosition()) - )*/)); +lengthBetweenPositions(m1.inputRange.getEndPosition(), m2.inputRange.getStartPosition()).equals( + lengthBetweenPositions(m1.outputRange.getEndPosition(), m2.outputRange.getStartPosition()) +)*/)); } public project(position: Position): RangeMapping { const lastBefore = findLast(this.rangeMappings, r => r.inputRange.getStartPosition().isBeforeOrEqual(position)); diff --git a/Source/vs/workbench/contrib/remote/browser/remote.ts b/Source/vs/workbench/contrib/remote/browser/remote.ts index 358b9a5e63f56..8424f5e6a8a93 100644 --- a/Source/vs/workbench/contrib/remote/browser/remote.ts +++ b/Source/vs/workbench/contrib/remote/browser/remote.ts @@ -554,9 +554,12 @@ class VisibleProgress { this._currentProgress = null; this._currentTimer = null; const promise = new Promise((resolve) => this._currentProgressPromiseResolve = resolve); - progressService.withProgress({ location: location, buttons: buttons }, (progress) => { if (!this._isDisposed) { - this._currentProgress = progress; - } return promise; }, (choice) => onDidCancel(choice, this._lastReport)); + progressService.withProgress({ location: location, buttons: buttons }, (progress) => { + if (!this._isDisposed) { + this._currentProgress = progress; + } + return promise; + }, (choice) => onDidCancel(choice, this._lastReport)); if (this._lastReport) { this.report(); } diff --git a/Source/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts b/Source/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts index e8d1ae9fc5fd2..a25f146f70201 100644 --- a/Source/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts +++ b/Source/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts @@ -150,9 +150,11 @@ export class SearchModelImpl extends Disposable implements ISearchModel { const notebookResult = this.notebookSearchService.notebookSearch(query, tokenSource.token, searchInstanceID, asyncGenerateOnProgress); const textResult = this.searchService.textSearchSplitSyncAsync(searchQuery, tokenSource.token, asyncGenerateOnProgress, notebookResult.openFilesToScan, notebookResult.allScannedFiles); const syncResults = textResult.syncResults.results; - syncResults.forEach(p => { if (p) { - syncGenerateOnProgress(p); - } }); + syncResults.forEach(p => { + if (p) { + syncGenerateOnProgress(p); + } + }); const getAsyncResults = async (): Promise => { const searchStart = Date.now(); // resolve async parts of search diff --git a/Source/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts b/Source/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts index be9c7cbab17b6..5a4c76a2cf6d0 100644 --- a/Source/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts +++ b/Source/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts @@ -93,9 +93,11 @@ export class BufferContentTracker extends Disposable { let linesToRemove = this._priorEditorViewportLineCount; let index = 1; while (linesToRemove) { - this.bufferToEditorLineMapping.forEach((value, key) => { if (value === this._lines.length - index) { - this.bufferToEditorLineMapping.delete(key); - } }); + this.bufferToEditorLineMapping.forEach((value, key) => { + if (value === this._lines.length - index) { + this.bufferToEditorLineMapping.delete(key); + } + }); this._lines.pop(); index++; linesToRemove--; diff --git a/Source/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedList.ts b/Source/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedList.ts index fc31393691718..9bb4175135176 100644 --- a/Source/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedList.ts +++ b/Source/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedList.ts @@ -58,12 +58,14 @@ export class GettingStartedIndexList void) { this._register(this.onDidChangeEntries(listener)); } - register(d: IDisposable) { if (this.isDisposed) { - d.dispose(); + register(d: IDisposable) { + if (this.isDisposed) { + d.dispose(); + } + else { + this._register(d); + } } - else { - this._register(d); - } } override dispose() { this.isDisposed = true; super.dispose(); diff --git a/Source/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/Source/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index 141eb8ecf2211..b4c039c77dff0 100644 --- a/Source/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/Source/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -65,9 +65,11 @@ class NewFileTemplatesManager extends Disposable { menuService: IMenuService) { super(); NewFileTemplatesManager.Instance = this; - this._register({ dispose() { if (NewFileTemplatesManager.Instance === this) { - NewFileTemplatesManager.Instance = undefined; - } } }); + this._register({ dispose() { + if (NewFileTemplatesManager.Instance === this) { + NewFileTemplatesManager.Instance = undefined; + } + } }); this.menu = menuService.createMenu(MenuId.NewFile, contextKeyService); } private allEntries(): NewFileItem[] { diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts index 53d6c501ea9a6..fa381bc56b92f 100644 --- a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts @@ -2,16 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; - const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); const shasum = crypto.createHash('sha256'); - for (const ext of productjson.builtInExtensions) { - shasum.update(`${ext.name}@${ext.version}`); + shasum.update(`${ext.name}@${ext.version}`); } - process.stdout.write(shasum.digest('hex')); diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts index 0940c929b5401..cf54b94c18235 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts @@ -2,41 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; const { dirs } = require('../../npm/dirs'); - const ROOT = path.join(__dirname, '../../../'); - const shasum = crypto.createHash('sha256'); - shasum.update(fs.readFileSync(path.join(ROOT, 'build/.cachesalt'))); shasum.update(fs.readFileSync(path.join(ROOT, '.npmrc'))); shasum.update(fs.readFileSync(path.join(ROOT, 'build', '.npmrc'))); shasum.update(fs.readFileSync(path.join(ROOT, 'remote', '.npmrc'))); - // Add `package.json` and `package-lock.json` files for (const dir of dirs) { - const packageJsonPath = path.join(ROOT, dir, 'package.json'); - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()); - const relevantPackageJsonSections = { - dependencies: packageJson.dependencies, - devDependencies: packageJson.devDependencies, - optionalDependencies: packageJson.optionalDependencies, - resolutions: packageJson.resolutions, - distro: packageJson.distro - }; - shasum.update(JSON.stringify(relevantPackageJsonSections)); - - const packageLockPath = path.join(ROOT, dir, 'package-lock.json'); - shasum.update(fs.readFileSync(packageLockPath)); + const packageJsonPath = path.join(ROOT, dir, 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()); + const relevantPackageJsonSections = { + dependencies: packageJson.dependencies, + devDependencies: packageJson.devDependencies, + optionalDependencies: packageJson.optionalDependencies, + resolutions: packageJson.resolutions, + distro: packageJson.distro + }; + shasum.update(JSON.stringify(relevantPackageJsonSections)); + const packageLockPath = path.join(ROOT, dir, 'package-lock.json'); + shasum.update(fs.readFileSync(packageLockPath)); } - // Add any other command line arguments for (let i = 2; i < process.argv.length; i++) { - shasum.update(process.argv[i]); + shasum.update(process.argv[i]); } - process.stdout.write(shasum.digest('hex')); diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index afc5f59003a26..6257cf13583c3 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -2,61 +2,51 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ClientSecretCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; - if (process.argv.length !== 3) { - console.error('Usage: node createBuild.js VERSION'); - process.exit(-1); + console.error('Usage: node createBuild.js VERSION'); + process.exit(-1); } - function getEnv(name: string): string { - const result = process.env[name]; - - if (typeof result === 'undefined') { - throw new Error('Missing env: ' + name); - } - - return result; + const result = process.env[name]; + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + return result; } - async function main(): Promise { - const [, , _version] = process.argv; - const quality = getEnv('VSCODE_QUALITY'); - const commit = getEnv('BUILD_SOURCEVERSION'); - const queuedBy = getEnv('BUILD_QUEUEDBY'); - const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); - const version = _version + (quality === 'stable' ? '' : `-${quality}`); - - console.log('Creating build...'); - console.log('Quality:', quality); - console.log('Version:', version); - console.log('Commit:', commit); - - const build = { - id: commit, - timestamp: (new Date()).getTime(), - version, - isReleased: false, - private: process.env['VSCODE_PRIVATE_BUILD']?.toLowerCase() === 'true', - sourceBranch, - queuedBy, - assets: [], - updates: {} - }; - - const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); - const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); - const scripts = client.database('builds').container(quality).scripts; - await retry(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); + const [, , _version] = process.argv; + const quality = getEnv('VSCODE_QUALITY'); + const commit = getEnv('BUILD_SOURCEVERSION'); + const queuedBy = getEnv('BUILD_QUEUEDBY'); + const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); + const version = _version + (quality === 'stable' ? '' : `-${quality}`); + console.log('Creating build...'); + console.log('Quality:', quality); + console.log('Version:', version); + console.log('Commit:', commit); + const build = { + id: commit, + timestamp: (new Date()).getTime(), + version, + isReleased: false, + private: process.env['VSCODE_PRIVATE_BUILD']?.toLowerCase() === 'true', + sourceBranch, + queuedBy, + assets: [], + updates: {} + }; + const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); + const scripts = client.database('builds').container(quality).scripts; + await retry(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); } - main().then(() => { - console.log('Build successfully created'); - process.exit(0); + console.log('Build successfully created'); + process.exit(0); }, err => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }); diff --git a/build/azure-pipelines/common/listNodeModules.ts b/build/azure-pipelines/common/listNodeModules.ts index aca461f8b5f2f..1e4a917452778 100644 --- a/build/azure-pipelines/common/listNodeModules.ts +++ b/build/azure-pipelines/common/listNodeModules.ts @@ -2,43 +2,37 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; - if (process.argv.length !== 3) { - console.error('Usage: node listNodeModules.js OUTPUT_FILE'); - process.exit(-1); + console.error('Usage: node listNodeModules.js OUTPUT_FILE'); + process.exit(-1); } - const ROOT = path.join(__dirname, '../../../'); - function findNodeModulesFiles(location: string, inNodeModules: boolean, result: string[]) { - const entries = fs.readdirSync(path.join(ROOT, location)); - for (const entry of entries) { - const entryPath = `${location}/${entry}`; - - if (/(^\/out)|(^\/src$)|(^\/.git$)|(^\/.build$)/.test(entryPath)) { - continue; - } - - let stat: fs.Stats; - try { - stat = fs.statSync(path.join(ROOT, entryPath)); - } catch (err) { - continue; - } - - if (stat.isDirectory()) { - findNodeModulesFiles(entryPath, inNodeModules || (entry === 'node_modules'), result); - } else { - if (inNodeModules) { - result.push(entryPath.substr(1)); - } - } - } + const entries = fs.readdirSync(path.join(ROOT, location)); + for (const entry of entries) { + const entryPath = `${location}/${entry}`; + if (/(^\/out)|(^\/src$)|(^\/.git$)|(^\/.build$)/.test(entryPath)) { + continue; + } + let stat: fs.Stats; + try { + stat = fs.statSync(path.join(ROOT, entryPath)); + } + catch (err) { + continue; + } + if (stat.isDirectory()) { + findNodeModulesFiles(entryPath, inNodeModules || (entry === 'node_modules'), result); + } + else { + if (inNodeModules) { + result.push(entryPath.substr(1)); + } + } + } } - const result: string[] = []; findNodeModulesFiles('', false, result); fs.writeFileSync(process.argv[2], result.join('\n') + '\n'); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 652cd1683351f..4c3b7296c1492 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import { Readable } from 'stream'; @@ -16,673 +15,551 @@ import { ClientSecretCredential } from '@azure/identity'; import * as cp from 'child_process'; import * as os from 'os'; import { Worker, isMainThread, workerData } from 'node:worker_threads'; - function e(name: string): string { - const result = process.env[name]; - - if (typeof result !== 'string') { - throw new Error(`Missing env: ${name}`); - } - - return result; + const result = process.env[name]; + if (typeof result !== 'string') { + throw new Error(`Missing env: ${name}`); + } + return result; } - class Temp { - private _files: string[] = []; - - tmpNameSync(): string { - const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); - this._files.push(file); - return file; - } - - dispose(): void { - for (const file of this._files) { - try { - fs.unlinkSync(file); - } catch (err) { - // noop - } - } - } + private _files: string[] = []; + tmpNameSync(): string { + const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); + this._files.push(file); + return file; + } + dispose(): void { + for (const file of this._files) { + try { + fs.unlinkSync(file); + } + catch (err) { + // noop + } + } + } } - interface RequestOptions { - readonly body?: string; + readonly body?: string; } - interface CreateProvisionedFilesSuccessResponse { - IsSuccess: true; - ErrorDetails: null; + IsSuccess: true; + ErrorDetails: null; } - interface CreateProvisionedFilesErrorResponse { - IsSuccess: false; - ErrorDetails: { - Code: string; - Category: string; - Message: string; - CanRetry: boolean; - AdditionalProperties: Record; - }; + IsSuccess: false; + ErrorDetails: { + Code: string; + Category: string; + Message: string; + CanRetry: boolean; + AdditionalProperties: Record; + }; } - type CreateProvisionedFilesResponse = CreateProvisionedFilesSuccessResponse | CreateProvisionedFilesErrorResponse; - function isCreateProvisionedFilesErrorResponse(response: unknown): response is CreateProvisionedFilesErrorResponse { - return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails?.Code !== undefined; + return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails?.Code !== undefined; } - class ProvisionService { - - constructor( - private readonly log: (...args: any[]) => void, - private readonly accessToken: string - ) { } - - async provision(releaseId: string, fileId: string, fileName: string) { - const body = JSON.stringify({ - ReleaseId: releaseId, - PortalName: 'VSCode', - PublisherCode: 'VSCode', - ProvisionedFilesCollection: [{ - PublisherKey: fileId, - IsStaticFriendlyFileName: true, - FriendlyFileName: fileName, - MaxTTL: '1440', - CdnMappings: ['ECN'] - }] - }); - - this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); - const res = await retry(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); - - if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { - this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); - return; - } - - if (!res.IsSuccess) { - throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); - } - - this.log(`Successfully provisioned ${fileName}`); - } - - private async request(method: string, url: string, options?: RequestOptions): Promise { - const opts: RequestInit = { - method, - body: options?.body, - headers: { - Authorization: `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json' - } - }; - - const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); - - - // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless - // Otherwise log the text body and headers. We do text because some responses are not JSON. - if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { - throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); - } - - return await res.json(); - } + constructor(private readonly log: (...args: any[]) => void, private readonly accessToken: string) { } + async provision(releaseId: string, fileId: string, fileName: string) { + const body = JSON.stringify({ + ReleaseId: releaseId, + PortalName: 'VSCode', + PublisherCode: 'VSCode', + ProvisionedFilesCollection: [{ + PublisherKey: fileId, + IsStaticFriendlyFileName: true, + FriendlyFileName: fileName, + MaxTTL: '1440', + CdnMappings: ['ECN'] + }] + }); + this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); + const res = await retry(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); + if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { + this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); + return; + } + if (!res.IsSuccess) { + throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); + } + this.log(`Successfully provisioned ${fileName}`); + } + private async request(method: string, url: string, options?: RequestOptions): Promise { + const opts: RequestInit = { + method, + body: options?.body, + headers: { + Authorization: `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json' + } + }; + const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); + // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless + // Otherwise log the text body and headers. We do text because some responses are not JSON. + if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { + throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); + } + return await res.json(); + } } - function hashStream(hashName: string, stream: Readable): Promise { - return new Promise((c, e) => { - const shasum = crypto.createHash(hashName); - - stream - .on('data', shasum.update.bind(shasum)) - .on('error', e) - .on('close', () => c(shasum.digest('hex'))); - }); + return new Promise((c, e) => { + const shasum = crypto.createHash(hashName); + stream + .on('data', shasum.update.bind(shasum)) + .on('error', e) + .on('close', () => c(shasum.digest('hex'))); + }); } - interface Release { - readonly releaseId: string; - readonly fileId: string; + readonly releaseId: string; + readonly fileId: string; } - interface SubmitReleaseResult { - submissionResponse: { - operationId: string; - statusCode: string; - }; + submissionResponse: { + operationId: string; + statusCode: string; + }; } - interface ReleaseDetailsResult { - releaseDetails: [{ - fileDetails: [{ publisherKey: string }]; - statusCode: 'inprogress' | 'pass'; - }]; + releaseDetails: [ + { + fileDetails: [ + { + publisherKey: string; + } + ]; + statusCode: 'inprogress' | 'pass'; + } + ]; } - class ESRPClient { - - private readonly authPath: string; - - constructor( - private readonly log: (...args: any[]) => void, - private readonly tmp: Temp, - tenantId: string, - clientId: string, - authCertSubjectName: string, - requestSigningCertSubjectName: string, - ) { - this.authPath = this.tmp.tmpNameSync(); - fs.writeFileSync(this.authPath, JSON.stringify({ - Version: '1.0.0', - AuthenticationType: 'AAD_CERT', - TenantId: tenantId, - ClientId: clientId, - AuthCert: { - SubjectName: authCertSubjectName, - StoreLocation: 'LocalMachine', - StoreName: 'My', - SendX5c: 'true' - }, - RequestSigningCert: { - SubjectName: requestSigningCertSubjectName, - StoreLocation: 'LocalMachine', - StoreName: 'My' - } - })); - } - - async release( - version: string, - filePath: string - ): Promise { - this.log(`Submitting release for ${version}: ${filePath}`); - const submitReleaseResult = await this.SubmitRelease(version, filePath); - - if (submitReleaseResult.submissionResponse.statusCode !== 'pass') { - throw new Error(`Unexpected status code: ${submitReleaseResult.submissionResponse.statusCode}`); - } - - const releaseId = submitReleaseResult.submissionResponse.operationId; - this.log(`Successfully submitted release ${releaseId}. Polling for completion...`); - - let details!: ReleaseDetailsResult; - - // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times - for (let i = 0; i < 720; i++) { - details = await this.ReleaseDetails(releaseId); - - if (details.releaseDetails[0].statusCode === 'pass') { - break; - } else if (details.releaseDetails[0].statusCode !== 'inprogress') { - throw new Error(`Failed to submit release: ${JSON.stringify(details)}`); - } - - await new Promise(c => setTimeout(c, 5000)); - } - - if (details.releaseDetails[0].statusCode !== 'pass') { - throw new Error(`Timed out waiting for release ${releaseId}: ${JSON.stringify(details)}`); - } - - const fileId = details.releaseDetails[0].fileDetails[0].publisherKey; - this.log('Release completed successfully with fileId: ', fileId); - - return { releaseId, fileId }; - } - - private async SubmitRelease( - version: string, - filePath: string - ): Promise { - const policyPath = this.tmp.tmpNameSync(); - fs.writeFileSync(policyPath, JSON.stringify({ - Version: '1.0.0', - Audience: 'InternalLimited', - Intent: 'distribution', - ContentType: 'InstallPackage' - })); - - const inputPath = this.tmp.tmpNameSync(); - const size = fs.statSync(filePath).size; - const istream = fs.createReadStream(filePath); - const sha256 = await hashStream('sha256', istream); - fs.writeFileSync(inputPath, JSON.stringify({ - Version: '1.0.0', - ReleaseInfo: { - ReleaseMetadata: { - Title: 'VS Code', - Properties: { - ReleaseContentType: 'InstallPackage' - }, - MinimumNumberOfApprovers: 1 - }, - ProductInfo: { - Name: 'VS Code', - Version: version, - Description: path.basename(filePath, path.extname(filePath)), - }, - Owners: [ - { - Owner: { - UserPrincipalName: 'jomo@microsoft.com' - } - } - ], - Approvers: [ - { - Approver: { - UserPrincipalName: 'jomo@microsoft.com' - }, - IsAutoApproved: true, - IsMandatory: false - } - ], - AccessPermissions: { - MainPublisher: 'VSCode', - ChannelDownloadEntityDetails: { - Consumer: ['VSCode'] - } - }, - CreatedBy: { - UserPrincipalName: 'jomo@microsoft.com' - } - }, - ReleaseBatches: [ - { - ReleaseRequestFiles: [ - { - SizeInBytes: size, - SourceHash: sha256, - HashType: 'SHA256', - SourceLocation: path.basename(filePath) - } - ], - SourceLocationType: 'UNC', - SourceRootDirectory: path.dirname(filePath), - DestinationLocationType: 'AzureBlob' - } - ] - })); - - const outputPath = this.tmp.tmpNameSync(); - cp.execSync(`ESRPClient SubmitRelease -a ${this.authPath} -p ${policyPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); - - const output = fs.readFileSync(outputPath, 'utf8'); - return JSON.parse(output) as SubmitReleaseResult; - } - - private async ReleaseDetails( - releaseId: string - ): Promise { - const inputPath = this.tmp.tmpNameSync(); - fs.writeFileSync(inputPath, JSON.stringify({ - Version: '1.0.0', - OperationIds: [releaseId] - })); - - const outputPath = this.tmp.tmpNameSync(); - cp.execSync(`ESRPClient ReleaseDetails -a ${this.authPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); - - const output = fs.readFileSync(outputPath, 'utf8'); - return JSON.parse(output) as ReleaseDetailsResult; - } + private readonly authPath: string; + constructor(private readonly log: (...args: any[]) => void, private readonly tmp: Temp, tenantId: string, clientId: string, authCertSubjectName: string, requestSigningCertSubjectName: string) { + this.authPath = this.tmp.tmpNameSync(); + fs.writeFileSync(this.authPath, JSON.stringify({ + Version: '1.0.0', + AuthenticationType: 'AAD_CERT', + TenantId: tenantId, + ClientId: clientId, + AuthCert: { + SubjectName: authCertSubjectName, + StoreLocation: 'LocalMachine', + StoreName: 'My', + SendX5c: 'true' + }, + RequestSigningCert: { + SubjectName: requestSigningCertSubjectName, + StoreLocation: 'LocalMachine', + StoreName: 'My' + } + })); + } + async release(version: string, filePath: string): Promise { + this.log(`Submitting release for ${version}: ${filePath}`); + const submitReleaseResult = await this.SubmitRelease(version, filePath); + if (submitReleaseResult.submissionResponse.statusCode !== 'pass') { + throw new Error(`Unexpected status code: ${submitReleaseResult.submissionResponse.statusCode}`); + } + const releaseId = submitReleaseResult.submissionResponse.operationId; + this.log(`Successfully submitted release ${releaseId}. Polling for completion...`); + let details!: ReleaseDetailsResult; + // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times + for (let i = 0; i < 720; i++) { + details = await this.ReleaseDetails(releaseId); + if (details.releaseDetails[0].statusCode === 'pass') { + break; + } + else if (details.releaseDetails[0].statusCode !== 'inprogress') { + throw new Error(`Failed to submit release: ${JSON.stringify(details)}`); + } + await new Promise(c => setTimeout(c, 5000)); + } + if (details.releaseDetails[0].statusCode !== 'pass') { + throw new Error(`Timed out waiting for release ${releaseId}: ${JSON.stringify(details)}`); + } + const fileId = details.releaseDetails[0].fileDetails[0].publisherKey; + this.log('Release completed successfully with fileId: ', fileId); + return { releaseId, fileId }; + } + private async SubmitRelease(version: string, filePath: string): Promise { + const policyPath = this.tmp.tmpNameSync(); + fs.writeFileSync(policyPath, JSON.stringify({ + Version: '1.0.0', + Audience: 'InternalLimited', + Intent: 'distribution', + ContentType: 'InstallPackage' + })); + const inputPath = this.tmp.tmpNameSync(); + const size = fs.statSync(filePath).size; + const istream = fs.createReadStream(filePath); + const sha256 = await hashStream('sha256', istream); + fs.writeFileSync(inputPath, JSON.stringify({ + Version: '1.0.0', + ReleaseInfo: { + ReleaseMetadata: { + Title: 'VS Code', + Properties: { + ReleaseContentType: 'InstallPackage' + }, + MinimumNumberOfApprovers: 1 + }, + ProductInfo: { + Name: 'VS Code', + Version: version, + Description: path.basename(filePath, path.extname(filePath)), + }, + Owners: [ + { + Owner: { + UserPrincipalName: 'jomo@microsoft.com' + } + } + ], + Approvers: [ + { + Approver: { + UserPrincipalName: 'jomo@microsoft.com' + }, + IsAutoApproved: true, + IsMandatory: false + } + ], + AccessPermissions: { + MainPublisher: 'VSCode', + ChannelDownloadEntityDetails: { + Consumer: ['VSCode'] + } + }, + CreatedBy: { + UserPrincipalName: 'jomo@microsoft.com' + } + }, + ReleaseBatches: [ + { + ReleaseRequestFiles: [ + { + SizeInBytes: size, + SourceHash: sha256, + HashType: 'SHA256', + SourceLocation: path.basename(filePath) + } + ], + SourceLocationType: 'UNC', + SourceRootDirectory: path.dirname(filePath), + DestinationLocationType: 'AzureBlob' + } + ] + })); + const outputPath = this.tmp.tmpNameSync(); + cp.execSync(`ESRPClient SubmitRelease -a ${this.authPath} -p ${policyPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); + const output = fs.readFileSync(outputPath, 'utf8'); + return JSON.parse(output) as SubmitReleaseResult; + } + private async ReleaseDetails(releaseId: string): Promise { + const inputPath = this.tmp.tmpNameSync(); + fs.writeFileSync(inputPath, JSON.stringify({ + Version: '1.0.0', + OperationIds: [releaseId] + })); + const outputPath = this.tmp.tmpNameSync(); + cp.execSync(`ESRPClient ReleaseDetails -a ${this.authPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); + const output = fs.readFileSync(outputPath, 'utf8'); + return JSON.parse(output) as ReleaseDetailsResult; + } } - -async function releaseAndProvision( - log: (...args: any[]) => void, - releaseTenantId: string, - releaseClientId: string, - releaseAuthCertSubjectName: string, - releaseRequestSigningCertSubjectName: string, - provisionTenantId: string, - provisionAADUsername: string, - provisionAADPassword: string, - version: string, - quality: string, - filePath: string -): Promise { - const fileName = `${quality}/${version}/${path.basename(filePath)}`; - const result = `${e('PRSS_CDN_URL')}/${fileName}`; - - const res = await retry(() => fetch(result)); - - if (res.status === 200) { - log(`Already released and provisioned: ${result}`); - return result; - } - - const tmp = new Temp(); - process.on('exit', () => tmp.dispose()); - - const esrpclient = new ESRPClient(log, tmp, releaseTenantId, releaseClientId, releaseAuthCertSubjectName, releaseRequestSigningCertSubjectName); - const release = await esrpclient.release(version, filePath); - - const credential = new ClientSecretCredential(provisionTenantId, provisionAADUsername, provisionAADPassword); - const accessToken = await credential.getToken(['https://microsoft.onmicrosoft.com/DS.Provisioning.WebApi/.default']); - const service = new ProvisionService(log, accessToken.token); - await service.provision(release.releaseId, release.fileId, fileName); - - return result; +async function releaseAndProvision(log: (...args: any[]) => void, releaseTenantId: string, releaseClientId: string, releaseAuthCertSubjectName: string, releaseRequestSigningCertSubjectName: string, provisionTenantId: string, provisionAADUsername: string, provisionAADPassword: string, version: string, quality: string, filePath: string): Promise { + const fileName = `${quality}/${version}/${path.basename(filePath)}`; + const result = `${e('PRSS_CDN_URL')}/${fileName}`; + const res = await retry(() => fetch(result)); + if (res.status === 200) { + log(`Already released and provisioned: ${result}`); + return result; + } + const tmp = new Temp(); + process.on('exit', () => tmp.dispose()); + const esrpclient = new ESRPClient(log, tmp, releaseTenantId, releaseClientId, releaseAuthCertSubjectName, releaseRequestSigningCertSubjectName); + const release = await esrpclient.release(version, filePath); + const credential = new ClientSecretCredential(provisionTenantId, provisionAADUsername, provisionAADPassword); + const accessToken = await credential.getToken(['https://microsoft.onmicrosoft.com/DS.Provisioning.WebApi/.default']); + const service = new ProvisionService(log, accessToken.token); + await service.provision(release.releaseId, release.fileId, fileName); + return result; } - class State { - - private statePath: string; - private set = new Set(); - - constructor() { - const pipelineWorkspacePath = e('PIPELINE_WORKSPACE'); - const previousState = fs.readdirSync(pipelineWorkspacePath) - .map(name => /^artifacts_processed_(\d+)$/.exec(name)) - .filter((match): match is RegExpExecArray => !!match) - .map(match => ({ name: match![0], attempt: Number(match![1]) })) - .sort((a, b) => b.attempt - a.attempt)[0]; - - if (previousState) { - const previousStatePath = path.join(pipelineWorkspacePath, previousState.name, previousState.name + '.txt'); - fs.readFileSync(previousStatePath, 'utf8').split(/\n/).filter(name => !!name).forEach(name => this.set.add(name)); - } - - const stageAttempt = e('SYSTEM_STAGEATTEMPT'); - this.statePath = path.join(pipelineWorkspacePath, `artifacts_processed_${stageAttempt}`, `artifacts_processed_${stageAttempt}.txt`); - fs.mkdirSync(path.dirname(this.statePath), { recursive: true }); - fs.writeFileSync(this.statePath, [...this.set.values()].map(name => `${name}\n`).join('')); - } - - get size(): number { - return this.set.size; - } - - has(name: string): boolean { - return this.set.has(name); - } - - add(name: string): void { - this.set.add(name); - fs.appendFileSync(this.statePath, `${name}\n`); - } - - [Symbol.iterator](): IterableIterator { - return this.set[Symbol.iterator](); - } + private statePath: string; + private set = new Set(); + constructor() { + const pipelineWorkspacePath = e('PIPELINE_WORKSPACE'); + const previousState = fs.readdirSync(pipelineWorkspacePath) + .map(name => /^artifacts_processed_(\d+)$/.exec(name)) + .filter((match): match is RegExpExecArray => !!match) + .map(match => ({ name: match![0], attempt: Number(match![1]) })) + .sort((a, b) => b.attempt - a.attempt)[0]; + if (previousState) { + const previousStatePath = path.join(pipelineWorkspacePath, previousState.name, previousState.name + '.txt'); + fs.readFileSync(previousStatePath, 'utf8').split(/\n/).filter(name => !!name).forEach(name => this.set.add(name)); + } + const stageAttempt = e('SYSTEM_STAGEATTEMPT'); + this.statePath = path.join(pipelineWorkspacePath, `artifacts_processed_${stageAttempt}`, `artifacts_processed_${stageAttempt}.txt`); + fs.mkdirSync(path.dirname(this.statePath), { recursive: true }); + fs.writeFileSync(this.statePath, [...this.set.values()].map(name => `${name}\n`).join('')); + } + get size(): number { + return this.set.size; + } + has(name: string): boolean { + return this.set.has(name); + } + add(name: string): void { + this.set.add(name); + fs.appendFileSync(this.statePath, `${name}\n`); + } + [Symbol.iterator](): IterableIterator { + return this.set[Symbol.iterator](); + } } - const azdoFetchOptions = { - headers: { - // Pretend we're a web browser to avoid download rate limits - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Encoding': 'gzip, deflate, br', - 'Accept-Language': 'en-US,en;q=0.9', - 'Referer': 'https://dev.azure.com', - Authorization: `Bearer ${e('SYSTEM_ACCESSTOKEN')}` - } + headers: { + // Pretend we're a web browser to avoid download rate limits + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-US,en;q=0.9', + 'Referer': 'https://dev.azure.com', + Authorization: `Bearer ${e('SYSTEM_ACCESSTOKEN')}` + } }; - async function requestAZDOAPI(path: string): Promise { - const abortController = new AbortController(); - const timeout = setTimeout(() => abortController.abort(), 2 * 60 * 1000); - - try { - const res = await fetch(`${e('BUILDS_API_URL')}${path}?api-version=6.0`, { ...azdoFetchOptions, signal: abortController.signal }); - - if (!res.ok) { - throw new Error(`Unexpected status code: ${res.status}`); - } - - return await res.json(); - } finally { - clearTimeout(timeout); - } + const abortController = new AbortController(); + const timeout = setTimeout(() => abortController.abort(), 2 * 60 * 1000); + try { + const res = await fetch(`${e('BUILDS_API_URL')}${path}?api-version=6.0`, { ...azdoFetchOptions, signal: abortController.signal }); + if (!res.ok) { + throw new Error(`Unexpected status code: ${res.status}`); + } + return await res.json(); + } + finally { + clearTimeout(timeout); + } } - interface Artifact { - readonly name: string; - readonly resource: { - readonly downloadUrl: string; - readonly properties: { - readonly artifactsize: number; - }; - }; + readonly name: string; + readonly resource: { + readonly downloadUrl: string; + readonly properties: { + readonly artifactsize: number; + }; + }; } - async function getPipelineArtifacts(): Promise { - const result = await requestAZDOAPI<{ readonly value: Artifact[] }>('artifacts'); - return result.value.filter(a => /^vscode_/.test(a.name) && !/sbom$/.test(a.name)); + const result = await requestAZDOAPI<{ + readonly value: Artifact[]; + }>('artifacts'); + return result.value.filter(a => /^vscode_/.test(a.name) && !/sbom$/.test(a.name)); } - interface Timeline { - readonly records: { - readonly name: string; - readonly type: string; - readonly state: string; - }[]; + readonly records: { + readonly name: string; + readonly type: string; + readonly state: string; + }[]; } - async function getPipelineTimeline(): Promise { - return await requestAZDOAPI('timeline'); + return await requestAZDOAPI('timeline'); } - async function downloadArtifact(artifact: Artifact, downloadPath: string): Promise { - const abortController = new AbortController(); - const timeout = setTimeout(() => abortController.abort(), 4 * 60 * 1000); - - try { - const res = await fetch(artifact.resource.downloadUrl, { ...azdoFetchOptions, signal: abortController.signal }); - - if (!res.ok) { - throw new Error(`Unexpected status code: ${res.status}`); - } - - await pipeline(Readable.fromWeb(res.body as ReadableStream), fs.createWriteStream(downloadPath)); - } finally { - clearTimeout(timeout); - } + const abortController = new AbortController(); + const timeout = setTimeout(() => abortController.abort(), 4 * 60 * 1000); + try { + const res = await fetch(artifact.resource.downloadUrl, { ...azdoFetchOptions, signal: abortController.signal }); + if (!res.ok) { + throw new Error(`Unexpected status code: ${res.status}`); + } + await pipeline(Readable.fromWeb(res.body as ReadableStream), fs.createWriteStream(downloadPath)); + } + finally { + clearTimeout(timeout); + } } - async function unzip(packagePath: string, outputPath: string): Promise { - return new Promise((resolve, reject) => { - yauzl.open(packagePath, { lazyEntries: true, autoClose: true }, (err, zipfile) => { - if (err) { - return reject(err); - } - - const result: string[] = []; - zipfile!.on('entry', entry => { - if (/\/$/.test(entry.fileName)) { - zipfile!.readEntry(); - } else { - zipfile!.openReadStream(entry, (err, istream) => { - if (err) { - return reject(err); - } - - const filePath = path.join(outputPath, entry.fileName); - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - - const ostream = fs.createWriteStream(filePath); - ostream.on('finish', () => { - result.push(filePath); - zipfile!.readEntry(); - }); - istream?.on('error', err => reject(err)); - istream!.pipe(ostream); - }); - } - }); - - zipfile!.on('close', () => resolve(result)); - zipfile!.readEntry(); - }); - }); + return new Promise((resolve, reject) => { + yauzl.open(packagePath, { lazyEntries: true, autoClose: true }, (err, zipfile) => { + if (err) { + return reject(err); + } + const result: string[] = []; + zipfile!.on('entry', entry => { + if (/\/$/.test(entry.fileName)) { + zipfile!.readEntry(); + } + else { + zipfile!.openReadStream(entry, (err, istream) => { + if (err) { + return reject(err); + } + const filePath = path.join(outputPath, entry.fileName); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + const ostream = fs.createWriteStream(filePath); + ostream.on('finish', () => { + result.push(filePath); + zipfile!.readEntry(); + }); + istream?.on('error', err => reject(err)); + istream!.pipe(ostream); + }); + } + }); + zipfile!.on('close', () => resolve(result)); + zipfile!.readEntry(); + }); + }); } - interface Asset { - platform: string; - type: string; - url: string; - mooncakeUrl?: string; - prssUrl?: string; - hash: string; - sha256hash: string; - size: number; - supportsFastUpdate?: boolean; + platform: string; + type: string; + url: string; + mooncakeUrl?: string; + prssUrl?: string; + hash: string; + sha256hash: string; + size: number; + supportsFastUpdate?: boolean; } - // Contains all of the logic for mapping details to our actual product names in CosmosDB function getPlatform(product: string, os: string, arch: string, type: string, isLegacy: boolean): string { - switch (os) { - case 'win32': - switch (product) { - case 'client': { - switch (type) { - case 'archive': - return `win32-${arch}-archive`; - case 'setup': - return `win32-${arch}`; - case 'user-setup': - return `win32-${arch}-user`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - } - case 'server': - return `server-win32-${arch}`; - case 'web': - return `server-win32-${arch}-web`; - case 'cli': - return `cli-win32-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'alpine': - switch (product) { - case 'server': - return `server-alpine-${arch}`; - case 'web': - return `server-alpine-${arch}-web`; - case 'cli': - return `cli-alpine-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'linux': - switch (type) { - case 'snap': - return `linux-snap-${arch}`; - case 'archive-unsigned': - switch (product) { - case 'client': - return `linux-${arch}`; - case 'server': - return isLegacy ? `server-linux-legacy-${arch}` : `server-linux-${arch}`; - case 'web': - if (arch === 'standalone') { - return 'web-standalone'; - } - return isLegacy ? `server-linux-legacy-${arch}-web` : `server-linux-${arch}-web`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'deb-package': - return `linux-deb-${arch}`; - case 'rpm-package': - return `linux-rpm-${arch}`; - case 'cli': - return `cli-linux-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'darwin': - switch (product) { - case 'client': - if (arch === 'x64') { - return 'darwin'; - } - return `darwin-${arch}`; - case 'server': - if (arch === 'x64') { - return 'server-darwin'; - } - return `server-darwin-${arch}`; - case 'web': - if (arch === 'x64') { - return 'server-darwin-web'; - } - return `server-darwin-${arch}-web`; - case 'cli': - return `cli-darwin-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } + switch (os) { + case 'win32': + switch (product) { + case 'client': { + switch (type) { + case 'archive': + return `win32-${arch}-archive`; + case 'setup': + return `win32-${arch}`; + case 'user-setup': + return `win32-${arch}-user`; + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } + } + case 'server': + return `server-win32-${arch}`; + case 'web': + return `server-win32-${arch}-web`; + case 'cli': + return `cli-win32-${arch}`; + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } + case 'alpine': + switch (product) { + case 'server': + return `server-alpine-${arch}`; + case 'web': + return `server-alpine-${arch}-web`; + case 'cli': + return `cli-alpine-${arch}`; + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } + case 'linux': + switch (type) { + case 'snap': + return `linux-snap-${arch}`; + case 'archive-unsigned': + switch (product) { + case 'client': + return `linux-${arch}`; + case 'server': + return isLegacy ? `server-linux-legacy-${arch}` : `server-linux-${arch}`; + case 'web': + if (arch === 'standalone') { + return 'web-standalone'; + } + return isLegacy ? `server-linux-legacy-${arch}-web` : `server-linux-${arch}-web`; + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } + case 'deb-package': + return `linux-deb-${arch}`; + case 'rpm-package': + return `linux-rpm-${arch}`; + case 'cli': + return `cli-linux-${arch}`; + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } + case 'darwin': + switch (product) { + case 'client': + if (arch === 'x64') { + return 'darwin'; + } + return `darwin-${arch}`; + case 'server': + if (arch === 'x64') { + return 'server-darwin'; + } + return `server-darwin-${arch}`; + case 'web': + if (arch === 'x64') { + return 'server-darwin-web'; + } + return `server-darwin-${arch}-web`; + case 'cli': + return `cli-darwin-${arch}`; + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } + default: + throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); + } } - // Contains all of the logic for mapping types to our actual types in CosmosDB function getRealType(type: string) { - switch (type) { - case 'user-setup': - return 'setup'; - case 'deb-package': - case 'rpm-package': - return 'package'; - default: - return type; - } + switch (type) { + case 'user-setup': + return 'setup'; + case 'deb-package': + case 'rpm-package': + return 'package'; + default: + return type; + } } - async function processArtifact(artifact: Artifact, artifactFilePath: string): Promise { - const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); - const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); - - if (!match) { - throw new Error(`Invalid artifact name: ${artifact.name}`); - } - - // getPlatform needs the unprocessedType - const quality = e('VSCODE_QUALITY'); - const commit = e('BUILD_SOURCEVERSION'); - const { product, os, arch, unprocessedType } = match.groups!; - const isLegacy = artifact.name.includes('_legacy'); - const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); - const type = getRealType(unprocessedType); - const size = fs.statSync(artifactFilePath).size; - const stream = fs.createReadStream(artifactFilePath); - const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - - const url = await releaseAndProvision( - log, - e('RELEASE_TENANT_ID'), - e('RELEASE_CLIENT_ID'), - e('RELEASE_AUTH_CERT_SUBJECT_NAME'), - e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), - e('PROVISION_TENANT_ID'), - e('PROVISION_AAD_USERNAME'), - e('PROVISION_AAD_PASSWORD'), - commit, - quality, - artifactFilePath - ); - - const asset: Asset = { platform, type, url, hash, sha256hash, size, supportsFastUpdate: true }; - log('Creating asset...', JSON.stringify(asset, undefined, 2)); - - await retry(async (attempt) => { - log(`Creating asset in Cosmos DB (attempt ${attempt})...`); - const aadCredentials = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET')); - const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), aadCredentials }); - const scripts = client.database('builds').container(quality).scripts; - await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); - }); - - log('Asset successfully created'); + const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); + const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); + if (!match) { + throw new Error(`Invalid artifact name: ${artifact.name}`); + } + // getPlatform needs the unprocessedType + const quality = e('VSCODE_QUALITY'); + const commit = e('BUILD_SOURCEVERSION'); + const { product, os, arch, unprocessedType } = match.groups!; + const isLegacy = artifact.name.includes('_legacy'); + const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); + const type = getRealType(unprocessedType); + const size = fs.statSync(artifactFilePath).size; + const stream = fs.createReadStream(artifactFilePath); + const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 + const url = await releaseAndProvision(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT_SUBJECT_NAME'), e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), e('PROVISION_TENANT_ID'), e('PROVISION_AAD_USERNAME'), e('PROVISION_AAD_PASSWORD'), commit, quality, artifactFilePath); + const asset: Asset = { platform, type, url, hash, sha256hash, size, supportsFastUpdate: true }; + log('Creating asset...', JSON.stringify(asset, undefined, 2)); + await retry(async (attempt) => { + log(`Creating asset in Cosmos DB (attempt ${attempt})...`); + const aadCredentials = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET')); + const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), aadCredentials }); + const scripts = client.database('builds').container(quality).scripts; + await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); + }); + log('Asset successfully created'); } - // It is VERY important that we don't download artifacts too much too fast from AZDO. // AZDO throttles us SEVERELY if we do. Not just that, but they also close open // sockets, so the whole things turns to a grinding halt. So, downloading and extracting @@ -690,124 +567,119 @@ async function processArtifact(artifact: Artifact, artifactFilePath: string): Pr // properly. For each extracted artifact, we spawn a worker thread to upload it to // the CDN and finally update the build in Cosmos DB. async function main() { - if (!isMainThread) { - const { artifact, artifactFilePath } = workerData; - await processArtifact(artifact, artifactFilePath); - return; - } - - const done = new State(); - const processing = new Set(); - - for (const name of done) { - console.log(`\u2705 ${name}`); - } - - const stages = new Set(['Compile', 'CompileCLI']); - if (e('VSCODE_BUILD_STAGE_WINDOWS') === 'True') { stages.add('Windows'); } - if (e('VSCODE_BUILD_STAGE_LINUX') === 'True') { stages.add('Linux'); } - if (e('VSCODE_BUILD_STAGE_LINUX_LEGACY_SERVER') === 'True') { stages.add('LinuxLegacyServer'); } - if (e('VSCODE_BUILD_STAGE_ALPINE') === 'True') { stages.add('Alpine'); } - if (e('VSCODE_BUILD_STAGE_MACOS') === 'True') { stages.add('macOS'); } - if (e('VSCODE_BUILD_STAGE_WEB') === 'True') { stages.add('Web'); } - - let resultPromise = Promise.resolve[]>([]); - const operations: { name: string; operation: Promise }[] = []; - - while (true) { - const [timeline, artifacts] = await Promise.all([retry(() => getPipelineTimeline()), retry(() => getPipelineArtifacts())]); - const stagesCompleted = new Set(timeline.records.filter(r => r.type === 'Stage' && r.state === 'completed' && stages.has(r.name)).map(r => r.name)); - const stagesInProgress = [...stages].filter(s => !stagesCompleted.has(s)); - const artifactsInProgress = artifacts.filter(a => processing.has(a.name)); - - if (stagesInProgress.length === 0 && artifacts.length === done.size + processing.size) { - break; - } else if (stagesInProgress.length > 0) { - console.log('Stages in progress:', stagesInProgress.join(', ')); - } else if (artifactsInProgress.length > 0) { - console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', ')); - } else { - console.log(`Waiting for a total of ${artifacts.length}, ${done.size} done, ${processing.size} in progress...`); - } - - for (const artifact of artifacts) { - if (done.has(artifact.name) || processing.has(artifact.name)) { - continue; - } - - console.log(`[${artifact.name}] Found new artifact`); - - const artifactZipPath = path.join(e('AGENT_TEMPDIRECTORY'), `${artifact.name}.zip`); - - await retry(async (attempt) => { - const start = Date.now(); - console.log(`[${artifact.name}] Downloading (attempt ${attempt})...`); - await downloadArtifact(artifact, artifactZipPath); - const archiveSize = fs.statSync(artifactZipPath).size; - const downloadDurationS = (Date.now() - start) / 1000; - const downloadSpeedKBS = Math.round((archiveSize / 1024) / downloadDurationS); - console.log(`[${artifact.name}] Successfully downloaded after ${Math.floor(downloadDurationS)} seconds(${downloadSpeedKBS} KB/s).`); - }); - - const artifactFilePaths = await unzip(artifactZipPath, e('AGENT_TEMPDIRECTORY')); - const artifactFilePath = artifactFilePaths.filter(p => !/_manifest/.test(p))[0]; - - processing.add(artifact.name); - const promise = new Promise((resolve, reject) => { - const worker = new Worker(__filename, { workerData: { artifact, artifactFilePath } }); - worker.on('error', reject); - worker.on('exit', code => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`[${artifact.name}] Worker stopped with exit code ${code}`)); - } - }); - }); - - const operation = promise.then(() => { - processing.delete(artifact.name); - done.add(artifact.name); - console.log(`\u2705 ${artifact.name} `); - }); - - operations.push({ name: artifact.name, operation }); - resultPromise = Promise.allSettled(operations.map(o => o.operation)); - } - - await new Promise(c => setTimeout(c, 10_000)); - } - - console.log(`Found all ${done.size + processing.size} artifacts, waiting for ${processing.size} artifacts to finish publishing...`); - - const artifactsInProgress = operations.filter(o => processing.has(o.name)); - - if (artifactsInProgress.length > 0) { - console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', ')); - } - - const results = await resultPromise; - - for (let i = 0; i < operations.length; i++) { - const result = results[i]; - - if (result.status === 'rejected') { - console.error(`[${operations[i].name}]`, result.reason); - } - } - - if (results.some(r => r.status === 'rejected')) { - throw new Error('Some artifacts failed to publish'); - } - - console.log(`All ${done.size} artifacts published!`); + if (!isMainThread) { + const { artifact, artifactFilePath } = workerData; + await processArtifact(artifact, artifactFilePath); + return; + } + const done = new State(); + const processing = new Set(); + for (const name of done) { + console.log(`\u2705 ${name}`); + } + const stages = new Set(['Compile', 'CompileCLI']); + if (e('VSCODE_BUILD_STAGE_WINDOWS') === 'True') { + stages.add('Windows'); + } + if (e('VSCODE_BUILD_STAGE_LINUX') === 'True') { + stages.add('Linux'); + } + if (e('VSCODE_BUILD_STAGE_LINUX_LEGACY_SERVER') === 'True') { + stages.add('LinuxLegacyServer'); + } + if (e('VSCODE_BUILD_STAGE_ALPINE') === 'True') { + stages.add('Alpine'); + } + if (e('VSCODE_BUILD_STAGE_MACOS') === 'True') { + stages.add('macOS'); + } + if (e('VSCODE_BUILD_STAGE_WEB') === 'True') { + stages.add('Web'); + } + let resultPromise = Promise.resolve[]>([]); + const operations: { + name: string; + operation: Promise; + }[] = []; + while (true) { + const [timeline, artifacts] = await Promise.all([retry(() => getPipelineTimeline()), retry(() => getPipelineArtifacts())]); + const stagesCompleted = new Set(timeline.records.filter(r => r.type === 'Stage' && r.state === 'completed' && stages.has(r.name)).map(r => r.name)); + const stagesInProgress = [...stages].filter(s => !stagesCompleted.has(s)); + const artifactsInProgress = artifacts.filter(a => processing.has(a.name)); + if (stagesInProgress.length === 0 && artifacts.length === done.size + processing.size) { + break; + } + else if (stagesInProgress.length > 0) { + console.log('Stages in progress:', stagesInProgress.join(', ')); + } + else if (artifactsInProgress.length > 0) { + console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', ')); + } + else { + console.log(`Waiting for a total of ${artifacts.length}, ${done.size} done, ${processing.size} in progress...`); + } + for (const artifact of artifacts) { + if (done.has(artifact.name) || processing.has(artifact.name)) { + continue; + } + console.log(`[${artifact.name}] Found new artifact`); + const artifactZipPath = path.join(e('AGENT_TEMPDIRECTORY'), `${artifact.name}.zip`); + await retry(async (attempt) => { + const start = Date.now(); + console.log(`[${artifact.name}] Downloading (attempt ${attempt})...`); + await downloadArtifact(artifact, artifactZipPath); + const archiveSize = fs.statSync(artifactZipPath).size; + const downloadDurationS = (Date.now() - start) / 1000; + const downloadSpeedKBS = Math.round((archiveSize / 1024) / downloadDurationS); + console.log(`[${artifact.name}] Successfully downloaded after ${Math.floor(downloadDurationS)} seconds(${downloadSpeedKBS} KB/s).`); + }); + const artifactFilePaths = await unzip(artifactZipPath, e('AGENT_TEMPDIRECTORY')); + const artifactFilePath = artifactFilePaths.filter(p => !/_manifest/.test(p))[0]; + processing.add(artifact.name); + const promise = new Promise((resolve, reject) => { + const worker = new Worker(__filename, { workerData: { artifact, artifactFilePath } }); + worker.on('error', reject); + worker.on('exit', code => { + if (code === 0) { + resolve(); + } + else { + reject(new Error(`[${artifact.name}] Worker stopped with exit code ${code}`)); + } + }); + }); + const operation = promise.then(() => { + processing.delete(artifact.name); + done.add(artifact.name); + console.log(`\u2705 ${artifact.name} `); + }); + operations.push({ name: artifact.name, operation }); + resultPromise = Promise.allSettled(operations.map(o => o.operation)); + } + await new Promise(c => setTimeout(c, 10000)); + } + console.log(`Found all ${done.size + processing.size} artifacts, waiting for ${processing.size} artifacts to finish publishing...`); + const artifactsInProgress = operations.filter(o => processing.has(o.name)); + if (artifactsInProgress.length > 0) { + console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', ')); + } + const results = await resultPromise; + for (let i = 0; i < operations.length; i++) { + const result = results[i]; + if (result.status === 'rejected') { + console.error(`[${operations[i].name}]`, result.reason); + } + } + if (results.some(r => r.status === 'rejected')) { + throw new Error('Some artifacts failed to publish'); + } + console.log(`All ${done.size} artifacts published!`); } - if (require.main === module) { - main().then(() => { - process.exit(0); - }, err => { - console.error(err); - process.exit(1); - }); + main().then(() => { + process.exit(0); + }, err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index 2e8fff04fb409..10095b1670a3d 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -2,77 +2,57 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ClientSecretCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; - function getEnv(name: string): string { - const result = process.env[name]; - - if (typeof result === 'undefined') { - throw new Error('Missing env: ' + name); - } - - return result; + const result = process.env[name]; + if (typeof result === 'undefined') { + throw new Error('Missing env: ' + name); + } + return result; } - interface Config { - id: string; - frozen: boolean; + id: string; + frozen: boolean; } - function createDefaultConfig(quality: string): Config { - return { - id: quality, - frozen: false - }; + return { + id: quality, + frozen: false + }; } - async function getConfig(client: CosmosClient, quality: string): Promise { - const query = `SELECT TOP 1 * FROM c WHERE c.id = "${quality}"`; - - const res = await client.database('builds').container('config').items.query(query).fetchAll(); - - if (res.resources.length === 0) { - return createDefaultConfig(quality); - } - - return res.resources[0] as Config; + const query = `SELECT TOP 1 * FROM c WHERE c.id = "${quality}"`; + const res = await client.database('builds').container('config').items.query(query).fetchAll(); + if (res.resources.length === 0) { + return createDefaultConfig(quality); + } + return res.resources[0] as Config; } - async function main(force: boolean): Promise { - const commit = getEnv('BUILD_SOURCEVERSION'); - const quality = getEnv('VSCODE_QUALITY'); - - const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); - const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); - - if (!force) { - const config = await getConfig(client, quality); - - console.log('Quality config:', config); - - if (config.frozen) { - console.log(`Skipping release because quality ${quality} is frozen.`); - return; - } - } - - console.log(`Releasing build ${commit}...`); - - const scripts = client.database('builds').container(quality).scripts; - await retry(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); + const commit = getEnv('BUILD_SOURCEVERSION'); + const quality = getEnv('VSCODE_QUALITY'); + const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); + if (!force) { + const config = await getConfig(client, quality); + console.log('Quality config:', config); + if (config.frozen) { + console.log(`Skipping release because quality ${quality} is frozen.`); + return; + } + } + console.log(`Releasing build ${commit}...`); + const scripts = client.database('builds').container(quality).scripts; + await retry(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); } - const [, , force] = process.argv; - console.log(process.argv); - main(/^true$/i.test(force)).then(() => { - console.log('Build successfully released'); - process.exit(0); + console.log('Build successfully released'); + process.exit(0); }, err => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }); diff --git a/build/azure-pipelines/common/retry.ts b/build/azure-pipelines/common/retry.ts index 9697093c84a5f..df8be5c760ca8 100644 --- a/build/azure-pipelines/common/retry.ts +++ b/build/azure-pipelines/common/retry.ts @@ -2,26 +2,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export async function retry(fn: (attempt: number) => Promise): Promise { - let lastError: Error | undefined; - - for (let run = 1; run <= 10; run++) { - try { - return await fn(run); - } catch (err) { - if (!/fetch failed|terminated|aborted|timeout|TimeoutError|Timeout Error|RestError|Client network socket disconnected|socket hang up|ECONNRESET|CredentialUnavailableError|endpoints_resolution_error|Audience validation failed|end of central directory record signature not found/i.test(err.message)) { - throw err; - } - - lastError = err; - - // maximum delay is 10th retry: ~3 seconds - const millis = Math.floor((Math.random() * 200) + (50 * Math.pow(1.5, run))); - await new Promise(c => setTimeout(c, millis)); - } - } - - console.error(`Too many retries, aborting.`); - throw lastError; + let lastError: Error | undefined; + for (let run = 1; run <= 10; run++) { + try { + return await fn(run); + } + catch (err) { + if (!/fetch failed|terminated|aborted|timeout|TimeoutError|Timeout Error|RestError|Client network socket disconnected|socket hang up|ECONNRESET|CredentialUnavailableError|endpoints_resolution_error|Audience validation failed|end of central directory record signature not found/i.test(err.message)) { + throw err; + } + lastError = err; + // maximum delay is 10th retry: ~3 seconds + const millis = Math.floor((Math.random() * 200) + (50 * Math.pow(1.5, run))); + await new Promise(c => setTimeout(c, millis)); + } + } + console.error(`Too many retries, aborting.`); + throw lastError; } diff --git a/build/azure-pipelines/common/sign-win32.ts b/build/azure-pipelines/common/sign-win32.ts index 76828b42e1eb5..e834a39c796ec 100644 --- a/build/azure-pipelines/common/sign-win32.ts +++ b/build/azure-pipelines/common/sign-win32.ts @@ -2,16 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { main } from './sign'; import * as path from 'path'; - main([ - process.env['EsrpCliDllPath']!, - 'sign-windows', - process.env['ESRPPKI']!, - process.env['ESRPAADUsername']!, - process.env['ESRPAADPassword']!, - path.dirname(process.argv[2]), - path.basename(process.argv[2]) + process.env['EsrpCliDllPath']!, + 'sign-windows', + process.env['ESRPPKI']!, + process.env['ESRPAADUsername']!, + process.env['ESRPAADPassword']!, + path.dirname(process.argv[2]), + path.basename(process.argv[2]) ]); diff --git a/build/azure-pipelines/common/sign.ts b/build/azure-pipelines/common/sign.ts index 28fca31205e93..c3466c7d06172 100644 --- a/build/azure-pipelines/common/sign.ts +++ b/build/azure-pipelines/common/sign.ts @@ -2,188 +2,175 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as cp from 'child_process'; import * as fs from 'fs'; import * as crypto from 'crypto'; import * as path from 'path'; import * as os from 'os'; - export class Temp { - private _files: string[] = []; - - tmpNameSync(): string { - const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); - this._files.push(file); - return file; - } - - dispose(): void { - for (const file of this._files) { - try { - fs.unlinkSync(file); - } catch (err) { - // noop - } - } - } + private _files: string[] = []; + tmpNameSync(): string { + const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); + this._files.push(file); + return file; + } + dispose(): void { + for (const file of this._files) { + try { + fs.unlinkSync(file); + } + catch (err) { + // noop + } + } + } } - interface Params { - readonly keyCode: string; - readonly operationSetCode: string; - readonly parameters: { - readonly parameterName: string; - readonly parameterValue: string; - }[]; - readonly toolName: string; - readonly toolVersion: string; + readonly keyCode: string; + readonly operationSetCode: string; + readonly parameters: { + readonly parameterName: string; + readonly parameterValue: string; + }[]; + readonly toolName: string; + readonly toolVersion: string; } - function getParams(type: string): Params[] { - switch (type) { - case 'sign-windows': - return [ - { - keyCode: 'CP-230012', - operationSetCode: 'SigntoolSign', - parameters: [ - { parameterName: 'OpusName', parameterValue: 'VS Code' }, - { parameterName: 'OpusInfo', parameterValue: 'https://code.visualstudio.com/' }, - { parameterName: 'Append', parameterValue: '/as' }, - { parameterName: 'FileDigest', parameterValue: '/fd "SHA256"' }, - { parameterName: 'PageHash', parameterValue: '/NPH' }, - { parameterName: 'TimeStamp', parameterValue: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256' } - ], - toolName: 'sign', - toolVersion: '1.0' - }, - { - keyCode: 'CP-230012', - operationSetCode: 'SigntoolVerify', - parameters: [ - { parameterName: 'VerifyAll', parameterValue: '/all' } - ], - toolName: 'sign', - toolVersion: '1.0' - } - ]; - case 'sign-windows-appx': - return [ - { - keyCode: 'CP-229979', - operationSetCode: 'SigntoolSign', - parameters: [ - { parameterName: 'OpusName', parameterValue: 'VS Code' }, - { parameterName: 'OpusInfo', parameterValue: 'https://code.visualstudio.com/' }, - { parameterName: 'FileDigest', parameterValue: '/fd "SHA256"' }, - { parameterName: 'PageHash', parameterValue: '/NPH' }, - { parameterName: 'TimeStamp', parameterValue: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256' } - ], - toolName: 'sign', - toolVersion: '1.0' - }, - { - keyCode: 'CP-229979', - operationSetCode: 'SigntoolVerify', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - } - ]; - case 'sign-pgp': - return [{ - keyCode: 'CP-450779-Pgp', - operationSetCode: 'LinuxSign', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - }]; - case 'sign-darwin': - return [{ - keyCode: 'CP-401337-Apple', - operationSetCode: 'MacAppDeveloperSign', - parameters: [{ parameterName: 'Hardening', parameterValue: '--options=runtime' }], - toolName: 'sign', - toolVersion: '1.0' - }]; - case 'notarize-darwin': - return [{ - keyCode: 'CP-401337-Apple', - operationSetCode: 'MacAppNotarize', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - }]; - default: - throw new Error(`Sign type ${type} not found`); - } + switch (type) { + case 'sign-windows': + return [ + { + keyCode: 'CP-230012', + operationSetCode: 'SigntoolSign', + parameters: [ + { parameterName: 'OpusName', parameterValue: 'VS Code' }, + { parameterName: 'OpusInfo', parameterValue: 'https://code.visualstudio.com/' }, + { parameterName: 'Append', parameterValue: '/as' }, + { parameterName: 'FileDigest', parameterValue: '/fd "SHA256"' }, + { parameterName: 'PageHash', parameterValue: '/NPH' }, + { parameterName: 'TimeStamp', parameterValue: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256' } + ], + toolName: 'sign', + toolVersion: '1.0' + }, + { + keyCode: 'CP-230012', + operationSetCode: 'SigntoolVerify', + parameters: [ + { parameterName: 'VerifyAll', parameterValue: '/all' } + ], + toolName: 'sign', + toolVersion: '1.0' + } + ]; + case 'sign-windows-appx': + return [ + { + keyCode: 'CP-229979', + operationSetCode: 'SigntoolSign', + parameters: [ + { parameterName: 'OpusName', parameterValue: 'VS Code' }, + { parameterName: 'OpusInfo', parameterValue: 'https://code.visualstudio.com/' }, + { parameterName: 'FileDigest', parameterValue: '/fd "SHA256"' }, + { parameterName: 'PageHash', parameterValue: '/NPH' }, + { parameterName: 'TimeStamp', parameterValue: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256' } + ], + toolName: 'sign', + toolVersion: '1.0' + }, + { + keyCode: 'CP-229979', + operationSetCode: 'SigntoolVerify', + parameters: [], + toolName: 'sign', + toolVersion: '1.0' + } + ]; + case 'sign-pgp': + return [{ + keyCode: 'CP-450779-Pgp', + operationSetCode: 'LinuxSign', + parameters: [], + toolName: 'sign', + toolVersion: '1.0' + }]; + case 'sign-darwin': + return [{ + keyCode: 'CP-401337-Apple', + operationSetCode: 'MacAppDeveloperSign', + parameters: [{ parameterName: 'Hardening', parameterValue: '--options=runtime' }], + toolName: 'sign', + toolVersion: '1.0' + }]; + case 'notarize-darwin': + return [{ + keyCode: 'CP-401337-Apple', + operationSetCode: 'MacAppNotarize', + parameters: [], + toolName: 'sign', + toolVersion: '1.0' + }]; + default: + throw new Error(`Sign type ${type} not found`); + } } - export function main([esrpCliPath, type, cert, username, password, folderPath, pattern]: string[]) { - const tmp = new Temp(); - process.on('exit', () => tmp.dispose()); - - const patternPath = tmp.tmpNameSync(); - fs.writeFileSync(patternPath, pattern); - - const paramsPath = tmp.tmpNameSync(); - fs.writeFileSync(paramsPath, JSON.stringify(getParams(type))); - - const keyFile = tmp.tmpNameSync(); - const key = crypto.randomBytes(32); - const iv = crypto.randomBytes(16); - fs.writeFileSync(keyFile, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') })); - - const clientkeyPath = tmp.tmpNameSync(); - const clientkeyCypher = crypto.createCipheriv('aes-256-cbc', key, iv); - let clientkey = clientkeyCypher.update(password, 'utf8', 'hex'); - clientkey += clientkeyCypher.final('hex'); - fs.writeFileSync(clientkeyPath, clientkey); - - const clientcertPath = tmp.tmpNameSync(); - const clientcertCypher = crypto.createCipheriv('aes-256-cbc', key, iv); - let clientcert = clientcertCypher.update(cert, 'utf8', 'hex'); - clientcert += clientcertCypher.final('hex'); - fs.writeFileSync(clientcertPath, clientcert); - - const args = [ - esrpCliPath, - 'vsts.sign', - '-a', username, - '-k', clientkeyPath, - '-z', clientcertPath, - '-f', folderPath, - '-p', patternPath, - '-u', 'false', - '-x', 'regularSigning', - '-b', 'input.json', - '-l', 'AzSecPack_PublisherPolicyProd.xml', - '-y', 'inlineSignParams', - '-j', paramsPath, - '-c', '9997', - '-t', '120', - '-g', '10', - '-v', 'Tls12', - '-s', 'https://api.esrp.microsoft.com/api/v1', - '-m', '0', - '-o', 'Microsoft', - '-i', 'https://www.microsoft.com', - '-n', '5', - '-r', 'true', - '-e', keyFile, - ]; - - try { - cp.execFileSync('dotnet', args, { stdio: 'inherit' }); - } catch (err) { - console.error('ESRP failed'); - console.error(err); - process.exit(1); - } + const tmp = new Temp(); + process.on('exit', () => tmp.dispose()); + const patternPath = tmp.tmpNameSync(); + fs.writeFileSync(patternPath, pattern); + const paramsPath = tmp.tmpNameSync(); + fs.writeFileSync(paramsPath, JSON.stringify(getParams(type))); + const keyFile = tmp.tmpNameSync(); + const key = crypto.randomBytes(32); + const iv = crypto.randomBytes(16); + fs.writeFileSync(keyFile, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') })); + const clientkeyPath = tmp.tmpNameSync(); + const clientkeyCypher = crypto.createCipheriv('aes-256-cbc', key, iv); + let clientkey = clientkeyCypher.update(password, 'utf8', 'hex'); + clientkey += clientkeyCypher.final('hex'); + fs.writeFileSync(clientkeyPath, clientkey); + const clientcertPath = tmp.tmpNameSync(); + const clientcertCypher = crypto.createCipheriv('aes-256-cbc', key, iv); + let clientcert = clientcertCypher.update(cert, 'utf8', 'hex'); + clientcert += clientcertCypher.final('hex'); + fs.writeFileSync(clientcertPath, clientcert); + const args = [ + esrpCliPath, + 'vsts.sign', + '-a', username, + '-k', clientkeyPath, + '-z', clientcertPath, + '-f', folderPath, + '-p', patternPath, + '-u', 'false', + '-x', 'regularSigning', + '-b', 'input.json', + '-l', 'AzSecPack_PublisherPolicyProd.xml', + '-y', 'inlineSignParams', + '-j', paramsPath, + '-c', '9997', + '-t', '120', + '-g', '10', + '-v', 'Tls12', + '-s', 'https://api.esrp.microsoft.com/api/v1', + '-m', '0', + '-o', 'Microsoft', + '-i', 'https://www.microsoft.com', + '-n', '5', + '-r', 'true', + '-e', keyFile, + ]; + try { + cp.execFileSync('dotnet', args, { stdio: 'inherit' }); + } + catch (err) { + console.error('ESRP failed'); + console.error(err); + process.exit(1); + } } - if (require.main === module) { - main(process.argv.slice(2)); - process.exit(0); + main(process.argv.slice(2)); + process.exit(0); } diff --git a/build/azure-pipelines/distro/mixin-npm.ts b/build/azure-pipelines/distro/mixin-npm.ts index da5eb24ca28b0..75d07c0299443 100644 --- a/build/azure-pipelines/distro/mixin-npm.ts +++ b/build/azure-pipelines/distro/mixin-npm.ts @@ -2,42 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; -const { dirs } = require('../../npm/dirs') as { dirs: string[] }; - +const { dirs } = require('../../npm/dirs') as { + dirs: string[]; +}; function log(...args: any[]): void { - console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); } - function mixin(mixinPath: string) { - if (!fs.existsSync(`${mixinPath}/node_modules`)) { - log(`Skipping distro npm dependencies: ${mixinPath} (no node_modules)`); - return; - } - - log(`Mixing in distro npm dependencies: ${mixinPath}`); - - const distroPackageJson = JSON.parse(fs.readFileSync(`${mixinPath}/package.json`, 'utf8')); - const targetPath = path.relative('.build/distro/npm', mixinPath); - - for (const dependency of Object.keys(distroPackageJson.dependencies)) { - fs.rmSync(`./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true }); - fs.cpSync(`${mixinPath}/node_modules/${dependency}`, `./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true, dereference: true }); - } - - log(`Mixed in distro npm dependencies: ${mixinPath} ✔︎`); + if (!fs.existsSync(`${mixinPath}/node_modules`)) { + log(`Skipping distro npm dependencies: ${mixinPath} (no node_modules)`); + return; + } + log(`Mixing in distro npm dependencies: ${mixinPath}`); + const distroPackageJson = JSON.parse(fs.readFileSync(`${mixinPath}/package.json`, 'utf8')); + const targetPath = path.relative('.build/distro/npm', mixinPath); + for (const dependency of Object.keys(distroPackageJson.dependencies)) { + fs.rmSync(`./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true }); + fs.cpSync(`${mixinPath}/node_modules/${dependency}`, `./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true, dereference: true }); + } + log(`Mixed in distro npm dependencies: ${mixinPath} ✔︎`); } - function main() { - log(`Mixing in distro npm dependencies...`); - - const mixinPaths = dirs.filter(d => /^.build\/distro\/npm/.test(d)); - - for (const mixinPath of mixinPaths) { - mixin(mixinPath); - } + log(`Mixing in distro npm dependencies...`); + const mixinPaths = dirs.filter(d => /^.build\/distro\/npm/.test(d)); + for (const mixinPath of mixinPaths) { + mixin(mixinPath); + } } - main(); diff --git a/build/azure-pipelines/distro/mixin-quality.ts b/build/azure-pipelines/distro/mixin-quality.ts index b9b3c4f6c42bd..8a376487636d9 100644 --- a/build/azure-pipelines/distro/mixin-quality.ts +++ b/build/azure-pipelines/distro/mixin-quality.ts @@ -2,79 +2,66 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; - interface IBuiltInExtension { - readonly name: string; - readonly version: string; - readonly repo: string; - readonly metadata: any; + readonly name: string; + readonly version: string; + readonly repo: string; + readonly metadata: any; } - interface OSSProduct { - readonly builtInExtensions: IBuiltInExtension[]; - readonly webBuiltInExtensions?: IBuiltInExtension[]; + readonly builtInExtensions: IBuiltInExtension[]; + readonly webBuiltInExtensions?: IBuiltInExtension[]; } - interface Product { - readonly builtInExtensions?: IBuiltInExtension[] | { 'include'?: IBuiltInExtension[]; 'exclude'?: string[] }; - readonly webBuiltInExtensions?: IBuiltInExtension[]; + readonly builtInExtensions?: IBuiltInExtension[] | { + 'include'?: IBuiltInExtension[]; + 'exclude'?: string[]; + }; + readonly webBuiltInExtensions?: IBuiltInExtension[]; } - function log(...args: any[]): void { - console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); } - function main() { - const quality = process.env['VSCODE_QUALITY']; - - if (!quality) { - throw new Error('Missing VSCODE_QUALITY, skipping mixin'); - } - - log(`Mixing in distro quality...`); - - const basePath = `.build/distro/mixin/${quality}`; - - for (const name of fs.readdirSync(basePath)) { - const distroPath = path.join(basePath, name); - const ossPath = path.relative(basePath, distroPath); - - if (ossPath === 'product.json') { - const distro = JSON.parse(fs.readFileSync(distroPath, 'utf8')) as Product; - const oss = JSON.parse(fs.readFileSync(ossPath, 'utf8')) as OSSProduct; - let builtInExtensions = oss.builtInExtensions; - - if (Array.isArray(distro.builtInExtensions)) { - log('Overwriting built-in extensions:', distro.builtInExtensions.map(e => e.name)); - - builtInExtensions = distro.builtInExtensions; - } else if (distro.builtInExtensions) { - const include = distro.builtInExtensions['include'] ?? []; - const exclude = distro.builtInExtensions['exclude'] ?? []; - - log('OSS built-in extensions:', builtInExtensions.map(e => e.name)); - log('Including built-in extensions:', include.map(e => e.name)); - log('Excluding built-in extensions:', exclude); - - builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); - builtInExtensions = [...builtInExtensions, ...include]; - - log('Final built-in extensions:', builtInExtensions.map(e => e.name)); - } else { - log('Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); - } - - const result = { webBuiltInExtensions: oss.webBuiltInExtensions, ...distro, builtInExtensions }; - fs.writeFileSync(ossPath, JSON.stringify(result, null, '\t'), 'utf8'); - } else { - fs.cpSync(distroPath, ossPath, { force: true, recursive: true }); - } - - log(distroPath, '✔︎'); - } + const quality = process.env['VSCODE_QUALITY']; + if (!quality) { + throw new Error('Missing VSCODE_QUALITY, skipping mixin'); + } + log(`Mixing in distro quality...`); + const basePath = `.build/distro/mixin/${quality}`; + for (const name of fs.readdirSync(basePath)) { + const distroPath = path.join(basePath, name); + const ossPath = path.relative(basePath, distroPath); + if (ossPath === 'product.json') { + const distro = JSON.parse(fs.readFileSync(distroPath, 'utf8')) as Product; + const oss = JSON.parse(fs.readFileSync(ossPath, 'utf8')) as OSSProduct; + let builtInExtensions = oss.builtInExtensions; + if (Array.isArray(distro.builtInExtensions)) { + log('Overwriting built-in extensions:', distro.builtInExtensions.map(e => e.name)); + builtInExtensions = distro.builtInExtensions; + } + else if (distro.builtInExtensions) { + const include = distro.builtInExtensions['include'] ?? []; + const exclude = distro.builtInExtensions['exclude'] ?? []; + log('OSS built-in extensions:', builtInExtensions.map(e => e.name)); + log('Including built-in extensions:', include.map(e => e.name)); + log('Excluding built-in extensions:', exclude); + builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); + builtInExtensions = [...builtInExtensions, ...include]; + log('Final built-in extensions:', builtInExtensions.map(e => e.name)); + } + else { + log('Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); + } + const result = { webBuiltInExtensions: oss.webBuiltInExtensions, ...distro, builtInExtensions }; + fs.writeFileSync(ossPath, JSON.stringify(result, null, '\t'), 'utf8'); + } + else { + fs.cpSync(distroPath, ossPath, { force: true, recursive: true }); + } + log(distroPath, '✔︎'); + } } - main(); diff --git a/build/azure-pipelines/publish-types/check-version.ts b/build/azure-pipelines/publish-types/check-version.ts index 35c5a51159395..721bf3fa413c8 100644 --- a/build/azure-pipelines/publish-types/check-version.ts +++ b/build/azure-pipelines/publish-types/check-version.ts @@ -2,40 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as cp from 'child_process'; - let tag = ''; try { - tag = cp - .execSync('git describe --tags `git rev-list --tags --max-count=1`') - .toString() - .trim(); - - if (!isValidTag(tag)) { - throw Error(`Invalid tag ${tag}`); - } -} catch (err) { - console.error(err); - console.error('Failed to update types'); - process.exit(1); + tag = cp + .execSync('git describe --tags `git rev-list --tags --max-count=1`') + .toString() + .trim(); + if (!isValidTag(tag)) { + throw Error(`Invalid tag ${tag}`); + } +} +catch (err) { + console.error(err); + console.error('Failed to update types'); + process.exit(1); } - function isValidTag(t: string) { - if (t.split('.').length !== 3) { - return false; - } - - const [major, minor, bug] = t.split('.'); - - // Only release for tags like 1.34.0 - if (bug !== '0') { - return false; - } - - if (isNaN(parseInt(major, 10)) || isNaN(parseInt(minor, 10))) { - return false; - } - - return true; + if (t.split('.').length !== 3) { + return false; + } + const [major, minor, bug] = t.split('.'); + // Only release for tags like 1.34.0 + if (bug !== '0') { + return false; + } + if (isNaN(parseInt(major, 10)) || isNaN(parseInt(minor, 10))) { + return false; + } + return true; } diff --git a/build/azure-pipelines/publish-types/update-types.ts b/build/azure-pipelines/publish-types/update-types.ts index a727647e64a29..85877941c3ed3 100644 --- a/build/azure-pipelines/publish-types/update-types.ts +++ b/build/azure-pipelines/publish-types/update-types.ts @@ -2,82 +2,69 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as cp from 'child_process'; import * as path from 'path'; - let tag = ''; try { - tag = cp - .execSync('git describe --tags `git rev-list --tags --max-count=1`') - .toString() - .trim(); - - const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vscode-dts/vscode.d.ts`; - const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts'); - cp.execSync(`curl ${dtsUri} --output ${outPath}`); - - updateDTSFile(outPath, tag); - - console.log(`Done updating vscode.d.ts at ${outPath}`); -} catch (err) { - console.error(err); - console.error('Failed to update types'); - process.exit(1); + tag = cp + .execSync('git describe --tags `git rev-list --tags --max-count=1`') + .toString() + .trim(); + const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vscode-dts/vscode.d.ts`; + const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts'); + cp.execSync(`curl ${dtsUri} --output ${outPath}`); + updateDTSFile(outPath, tag); + console.log(`Done updating vscode.d.ts at ${outPath}`); +} +catch (err) { + console.error(err); + console.error('Failed to update types'); + process.exit(1); } - function updateDTSFile(outPath: string, tag: string) { - const oldContent = fs.readFileSync(outPath, 'utf-8'); - const newContent = getNewFileContent(oldContent, tag); - - fs.writeFileSync(outPath, newContent); + const oldContent = fs.readFileSync(outPath, 'utf-8'); + const newContent = getNewFileContent(oldContent, tag); + fs.writeFileSync(outPath, newContent); } - function repeat(str: string, times: number): string { - const result = new Array(times); - for (let i = 0; i < times; i++) { - result[i] = str; - } - return result.join(''); + const result = new Array(times); + for (let i = 0; i < times; i++) { + result[i] = str; + } + return result.join(''); } - function convertTabsToSpaces(str: string): string { - return str.replace(/\t/gm, value => repeat(' ', value.length)); + return str.replace(/\t/gm, value => repeat(' ', value.length)); } - function getNewFileContent(content: string, tag: string) { - const oldheader = [ - `/*---------------------------------------------------------------------------------------------`, - ` * Copyright (c) Microsoft Corporation. All rights reserved.`, - ` * Licensed under the MIT License. See License.txt in the project root for license information.`, - ` *--------------------------------------------------------------------------------------------*/` - ].join('\n'); - - return convertTabsToSpaces(getNewFileHeader(tag) + content.slice(oldheader.length)); + const oldheader = [ + `/*---------------------------------------------------------------------------------------------`, + ` * Copyright (c) Microsoft Corporation. All rights reserved.`, + ` * Licensed under the MIT License. See License.txt in the project root for license information.`, + ` *--------------------------------------------------------------------------------------------*/` + ].join('\n'); + return convertTabsToSpaces(getNewFileHeader(tag) + content.slice(oldheader.length)); } - function getNewFileHeader(tag: string) { - const [major, minor] = tag.split('.'); - const shorttag = `${major}.${minor}`; - - const header = [ - `// Type definitions for Visual Studio Code ${shorttag}`, - `// Project: https://github.com/microsoft/vscode`, - `// Definitions by: Visual Studio Code Team, Microsoft `, - `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`, - ``, - `/*---------------------------------------------------------------------------------------------`, - ` * Copyright (c) Microsoft Corporation. All rights reserved.`, - ` * Licensed under the MIT License.`, - ` * See https://github.com/microsoft/vscode/blob/main/LICENSE.txt for license information.`, - ` *--------------------------------------------------------------------------------------------*/`, - ``, - `/**`, - ` * Type Definition for Visual Studio Code ${shorttag} Extension API`, - ` * See https://code.visualstudio.com/api for more information`, - ` */` - ].join('\n'); - - return header; + const [major, minor] = tag.split('.'); + const shorttag = `${major}.${minor}`; + const header = [ + `// Type definitions for Visual Studio Code ${shorttag}`, + `// Project: https://github.com/microsoft/vscode`, + `// Definitions by: Visual Studio Code Team, Microsoft `, + `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`, + ``, + `/*---------------------------------------------------------------------------------------------`, + ` * Copyright (c) Microsoft Corporation. All rights reserved.`, + ` * Licensed under the MIT License.`, + ` * See https://github.com/microsoft/vscode/blob/main/LICENSE.txt for license information.`, + ` *--------------------------------------------------------------------------------------------*/`, + ``, + `/**`, + ` * Type Definition for Visual Studio Code ${shorttag} Extension API`, + ` * See https://code.visualstudio.com/api for more information`, + ` */` + ].join('\n'); + return header; } diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 81a4ac14eabb6..8d5933b4708f6 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; @@ -11,119 +10,105 @@ import * as gzip from 'gulp-gzip'; import * as mime from 'mime'; import { ClientSecretCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); - const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); - mime.define({ - 'application/typescript': ['ts'], - 'application/json': ['code-snippets'], + 'application/typescript': ['ts'], + 'application/json': ['code-snippets'], }); - // From default AFD configuration const MimeTypesToCompress = new Set([ - 'application/eot', - 'application/font', - 'application/font-sfnt', - 'application/javascript', - 'application/json', - 'application/opentype', - 'application/otf', - 'application/pkcs7-mime', - 'application/truetype', - 'application/ttf', - 'application/typescript', - 'application/vnd.ms-fontobject', - 'application/xhtml+xml', - 'application/xml', - 'application/xml+rss', - 'application/x-font-opentype', - 'application/x-font-truetype', - 'application/x-font-ttf', - 'application/x-httpd-cgi', - 'application/x-javascript', - 'application/x-mpegurl', - 'application/x-opentype', - 'application/x-otf', - 'application/x-perl', - 'application/x-ttf', - 'font/eot', - 'font/ttf', - 'font/otf', - 'font/opentype', - 'image/svg+xml', - 'text/css', - 'text/csv', - 'text/html', - 'text/javascript', - 'text/js', - 'text/markdown', - 'text/plain', - 'text/richtext', - 'text/tab-separated-values', - 'text/xml', - 'text/x-script', - 'text/x-component', - 'text/x-java-source' + 'application/eot', + 'application/font', + 'application/font-sfnt', + 'application/javascript', + 'application/json', + 'application/opentype', + 'application/otf', + 'application/pkcs7-mime', + 'application/truetype', + 'application/ttf', + 'application/typescript', + 'application/vnd.ms-fontobject', + 'application/xhtml+xml', + 'application/xml', + 'application/xml+rss', + 'application/x-font-opentype', + 'application/x-font-truetype', + 'application/x-font-ttf', + 'application/x-httpd-cgi', + 'application/x-javascript', + 'application/x-mpegurl', + 'application/x-opentype', + 'application/x-otf', + 'application/x-perl', + 'application/x-ttf', + 'font/eot', + 'font/ttf', + 'font/otf', + 'font/opentype', + 'image/svg+xml', + 'text/css', + 'text/csv', + 'text/html', + 'text/javascript', + 'text/js', + 'text/markdown', + 'text/plain', + 'text/richtext', + 'text/tab-separated-values', + 'text/xml', + 'text/x-script', + 'text/x-component', + 'text/x-java-source' ]); - function wait(stream: es.ThroughStream): Promise { - return new Promise((c, e) => { - stream.on('end', () => c()); - stream.on('error', (err: any) => e(err)); - }); + return new Promise((c, e) => { + stream.on('end', () => c()); + stream.on('error', (err: any) => e(err)); + }); } - async function main(): Promise { - const files: string[] = []; - const options = (compressed: boolean) => ({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: process.env.VSCODE_QUALITY, - prefix: commit + '/', - contentSettings: { - contentEncoding: compressed ? 'gzip' : undefined, - cacheControl: 'max-age=31536000, public' - } - }); - - const all = vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) - .pipe(filter(f => !f.isDirectory())); - - const compressed = all - .pipe(filter(f => MimeTypesToCompress.has(mime.lookup(f.path)))) - .pipe(gzip({ append: false })) - .pipe(azure.upload(options(true))); - - const uncompressed = all - .pipe(filter(f => !MimeTypesToCompress.has(mime.lookup(f.path)))) - .pipe(azure.upload(options(false))); - - const out = es.merge(compressed, uncompressed) - .pipe(es.through(function (f) { - console.log('Uploaded:', f.relative); - files.push(f.relative); - this.emit('data', f); - })); - - console.log(`Uploading files to CDN...`); // debug - await wait(out); - - const listing = new Vinyl({ - path: 'files.txt', - contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } as any - }); - - const filesOut = es.readArray([listing]) - .pipe(gzip({ append: false })) - .pipe(azure.upload(options(true))); - - console.log(`Uploading: files.txt (${files.length} files)`); // debug - await wait(filesOut); + const files: string[] = []; + const options = (compressed: boolean) => ({ + account: process.env.AZURE_STORAGE_ACCOUNT, + credential, + container: process.env.VSCODE_QUALITY, + prefix: commit + '/', + contentSettings: { + contentEncoding: compressed ? 'gzip' : undefined, + cacheControl: 'max-age=31536000, public' + } + }); + const all = vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) + .pipe(filter(f => !f.isDirectory())); + const compressed = all + .pipe(filter(f => MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + const uncompressed = all + .pipe(filter(f => !MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(azure.upload(options(false))); + const out = es.merge(compressed, uncompressed) + .pipe(es.through(function (f) { + console.log('Uploaded:', f.relative); + files.push(f.relative); + this.emit('data', f); + })); + console.log(`Uploading files to CDN...`); // debug + await wait(out); + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } as any + }); + const filesOut = es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + console.log(`Uploading: files.txt (${files.length} files)`); // debug + await wait(filesOut); } - main().catch(err => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }); diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index 030cc8f0e5a6c..27e32853d7bfb 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; @@ -12,133 +11,123 @@ import { ClientSecretCredential } from '@azure/identity'; import path = require('path'); import { readFileSync } from 'fs'; const azure = require('gulp-azure-storage'); - const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); - interface NlsMetadata { - keys: { [module: string]: string }; - messages: { [module: string]: string }; - bundles: { [bundle: string]: string[] }; + keys: { + [module: string]: string; + }; + messages: { + [module: string]: string; + }; + bundles: { + [bundle: string]: string[]; + }; } - function main(): Promise { - return new Promise((c, e) => { - const combinedMetadataJson = es.merge( - // vscode: we are not using `out-build/nls.metadata.json` here because - // it includes metadata for translators for `keys`. but for our purpose - // we want only the `keys` and `messages` as `string`. - es.merge( - vfs.src('out-build/nls.keys.json', { base: 'out-build' }), - vfs.src('out-build/nls.messages.json', { base: 'out-build' })) - .pipe(merge({ - fileName: 'vscode.json', - jsonSpace: '', - concatArrays: true, - edit: (parsedJson, file) => { - if (file.base === 'out-build') { - if (file.basename === 'nls.keys.json') { - return { keys: parsedJson }; - } else { - return { messages: parsedJson }; - } - } - } - })), - - // extensions - vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), - vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), - vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }) - ).pipe(merge({ - fileName: 'combined.nls.metadata.json', - jsonSpace: '', - concatArrays: true, - edit: (parsedJson, file) => { - if (file.basename === 'vscode.json') { - return { vscode: parsedJson }; - } - - // Handle extensions and follow the same structure as the Core nls file. - switch (file.basename) { - case 'package.nls.json': - // put package.nls.json content in Core NlsMetadata format - // language packs use the key "package" to specify that - // translations are for the package.json file - parsedJson = { - messages: { - package: Object.values(parsedJson) - }, - keys: { - package: Object.keys(parsedJson) - }, - bundles: { - main: ['package'] - } - }; - break; - - case 'nls.metadata.header.json': - parsedJson = { header: parsedJson }; - break; - - case 'nls.metadata.json': { - // put nls.metadata.json content in Core NlsMetadata format - const modules = Object.keys(parsedJson); - - const json: NlsMetadata = { - keys: {}, - messages: {}, - bundles: { - main: [] - } - }; - for (const module of modules) { - json.messages[module] = parsedJson[module].messages; - json.keys[module] = parsedJson[module].keys; - json.bundles.main.push(module); - } - parsedJson = json; - break; - } - } - - // Get extension id and use that as the key - const folderPath = path.join(file.base, file.relative.split('/')[0]); - const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8'); - const manifestJson = JSON.parse(manifest); - const key = manifestJson.publisher + '.' + manifestJson.name; - return { [key]: parsedJson }; - }, - })); - - const nlsMessagesJs = vfs.src('out-build/nls.messages.js', { base: 'out-build' }); - - es.merge(combinedMetadataJson, nlsMessagesJs) - .pipe(gzip({ append: false })) - .pipe(vfs.dest('./nlsMetadata')) - .pipe(es.through(function (data: Vinyl) { - console.log(`Uploading ${data.path}`); - // trigger artifact upload - console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`); - this.emit('data', data); - })) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: 'nlsmetadata', - prefix: commit + '/', - contentSettings: { - contentEncoding: 'gzip', - cacheControl: 'max-age=31536000, public' - } - })) - .on('end', () => c()) - .on('error', (err: any) => e(err)); - }); + return new Promise((c, e) => { + const combinedMetadataJson = es.merge( + // vscode: we are not using `out-build/nls.metadata.json` here because + // it includes metadata for translators for `keys`. but for our purpose + // we want only the `keys` and `messages` as `string`. + es.merge(vfs.src('out-build/nls.keys.json', { base: 'out-build' }), vfs.src('out-build/nls.messages.json', { base: 'out-build' })) + .pipe(merge({ + fileName: 'vscode.json', + jsonSpace: '', + concatArrays: true, + edit: (parsedJson, file) => { + if (file.base === 'out-build') { + if (file.basename === 'nls.keys.json') { + return { keys: parsedJson }; + } + else { + return { messages: parsedJson }; + } + } + } + })), + // extensions + vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })).pipe(merge({ + fileName: 'combined.nls.metadata.json', + jsonSpace: '', + concatArrays: true, + edit: (parsedJson, file) => { + if (file.basename === 'vscode.json') { + return { vscode: parsedJson }; + } + // Handle extensions and follow the same structure as the Core nls file. + switch (file.basename) { + case 'package.nls.json': + // put package.nls.json content in Core NlsMetadata format + // language packs use the key "package" to specify that + // translations are for the package.json file + parsedJson = { + messages: { + package: Object.values(parsedJson) + }, + keys: { + package: Object.keys(parsedJson) + }, + bundles: { + main: ['package'] + } + }; + break; + case 'nls.metadata.header.json': + parsedJson = { header: parsedJson }; + break; + case 'nls.metadata.json': { + // put nls.metadata.json content in Core NlsMetadata format + const modules = Object.keys(parsedJson); + const json: NlsMetadata = { + keys: {}, + messages: {}, + bundles: { + main: [] + } + }; + for (const module of modules) { + json.messages[module] = parsedJson[module].messages; + json.keys[module] = parsedJson[module].keys; + json.bundles.main.push(module); + } + parsedJson = json; + break; + } + } + // Get extension id and use that as the key + const folderPath = path.join(file.base, file.relative.split('/')[0]); + const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const key = manifestJson.publisher + '.' + manifestJson.name; + return { [key]: parsedJson }; + }, + })); + const nlsMessagesJs = vfs.src('out-build/nls.messages.js', { base: 'out-build' }); + es.merge(combinedMetadataJson, nlsMessagesJs) + .pipe(gzip({ append: false })) + .pipe(vfs.dest('./nlsMetadata')) + .pipe(es.through(function (data: Vinyl) { + console.log(`Uploading ${data.path}`); + // trigger artifact upload + console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`); + this.emit('data', data); + })) + .pipe(azure.upload({ + account: process.env.AZURE_STORAGE_ACCOUNT, + credential, + container: 'nlsmetadata', + prefix: commit + '/', + contentSettings: { + contentEncoding: 'gzip', + cacheControl: 'max-age=31536000, public' + } + })) + .on('end', () => c()) + .on('error', (err: any) => e(err)); + }); } - main().catch(err => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }); diff --git a/build/azure-pipelines/upload-sourcemaps.ts b/build/azure-pipelines/upload-sourcemaps.ts index 8e148c6095f2c..121922da812c7 100644 --- a/build/azure-pipelines/upload-sourcemaps.ts +++ b/build/azure-pipelines/upload-sourcemaps.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as es from 'event-stream'; import * as Vinyl from 'vinyl'; @@ -12,65 +11,54 @@ import * as util from '../lib/util'; import * as deps from '../lib/dependencies'; import { ClientSecretCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); - const root = path.dirname(path.dirname(__dirname)); const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); - // optionally allow to pass in explicit base/maps to upload const [, , base, maps] = process.argv; - function src(base: string, maps = `${base}/**/*.map`) { - return vfs.src(maps, { base }) - .pipe(es.mapSync((f: Vinyl) => { - f.path = `${f.base}/core/${f.relative}`; - return f; - })); + return vfs.src(maps, { base }) + .pipe(es.mapSync((f: Vinyl) => { + f.path = `${f.base}/core/${f.relative}`; + return f; + })); } - function main(): Promise { - const sources: any[] = []; - - // vscode client maps (default) - if (!base) { - const vs = src('out-vscode-min'); // client source-maps only - sources.push(vs); - - const productionDependencies = deps.getProductionDependencies(root); - const productionDependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => `./${d}/**/*.map`); - const nodeModules = vfs.src(productionDependenciesSrc, { base: '.' }) - .pipe(util.cleanNodeModules(path.join(root, 'build', '.moduleignore'))) - .pipe(util.cleanNodeModules(path.join(root, 'build', `.moduleignore.${process.platform}`))); - sources.push(nodeModules); - - const extensionsOut = vfs.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); - sources.push(extensionsOut); - } - - // specific client base/maps - else { - sources.push(src(base, maps)); - } - - return new Promise((c, e) => { - es.merge(...sources) - .pipe(es.through(function (data: Vinyl) { - console.log('Uploading Sourcemap', data.relative); // debug - this.emit('data', data); - })) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: 'sourcemaps', - prefix: commit + '/' - })) - .on('end', () => c()) - .on('error', (err: any) => e(err)); - }); + const sources: any[] = []; + // vscode client maps (default) + if (!base) { + const vs = src('out-vscode-min'); // client source-maps only + sources.push(vs); + const productionDependencies = deps.getProductionDependencies(root); + const productionDependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => `./${d}/**/*.map`); + const nodeModules = vfs.src(productionDependenciesSrc, { base: '.' }) + .pipe(util.cleanNodeModules(path.join(root, 'build', '.moduleignore'))) + .pipe(util.cleanNodeModules(path.join(root, 'build', `.moduleignore.${process.platform}`))); + sources.push(nodeModules); + const extensionsOut = vfs.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); + sources.push(extensionsOut); + } + // specific client base/maps + else { + sources.push(src(base, maps)); + } + return new Promise((c, e) => { + es.merge(...sources) + .pipe(es.through(function (data: Vinyl) { + console.log('Uploading Sourcemap', data.relative); // debug + this.emit('data', data); + })) + .pipe(azure.upload({ + account: process.env.AZURE_STORAGE_ACCOUNT, + credential, + container: 'sourcemaps', + prefix: commit + '/' + })) + .on('end', () => c()) + .on('error', (err: any) => e(err)); + }); } - main().catch(err => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }); - diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 1f19053494d27..12b1c6a4f322d 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -2,62 +2,53 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as fs from 'fs'; import * as minimatch from 'minimatch'; import { makeUniversalApp } from 'vscode-universal-bundler'; - const root = path.dirname(path.dirname(__dirname)); - async function main(buildDir?: string) { - const arch = process.env['VSCODE_ARCH']; - - if (!buildDir) { - throw new Error('Build dir not provided'); - } - - const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); - const appName = product.nameLong + '.app'; - const x64AppPath = path.join(buildDir, 'VSCode-darwin-x64', appName); - const arm64AppPath = path.join(buildDir, 'VSCode-darwin-arm64', appName); - const asarRelativePath = path.join('Contents', 'Resources', 'app', 'node_modules.asar'); - const outAppPath = path.join(buildDir, `VSCode-darwin-${arch}`, appName); - const productJsonPath = path.resolve(outAppPath, 'Contents', 'Resources', 'app', 'product.json'); - - const filesToSkip = [ - '**/CodeResources', - '**/Credits.rtf', - ]; - - await makeUniversalApp({ - x64AppPath, - arm64AppPath, - asarPath: asarRelativePath, - outAppPath, - force: true, - mergeASARs: true, - x64ArchFiles: '*/kerberos.node', - filesToSkipComparison: (file: string) => { - for (const expected of filesToSkip) { - if (minimatch(file, expected)) { - return true; - } - } - return false; - } - }); - - const productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); - Object.assign(productJson, { - darwinUniversalAssetId: 'darwin-universal' - }); - fs.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t')); + const arch = process.env['VSCODE_ARCH']; + if (!buildDir) { + throw new Error('Build dir not provided'); + } + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); + const appName = product.nameLong + '.app'; + const x64AppPath = path.join(buildDir, 'VSCode-darwin-x64', appName); + const arm64AppPath = path.join(buildDir, 'VSCode-darwin-arm64', appName); + const asarRelativePath = path.join('Contents', 'Resources', 'app', 'node_modules.asar'); + const outAppPath = path.join(buildDir, `VSCode-darwin-${arch}`, appName); + const productJsonPath = path.resolve(outAppPath, 'Contents', 'Resources', 'app', 'product.json'); + const filesToSkip = [ + '**/CodeResources', + '**/Credits.rtf', + ]; + await makeUniversalApp({ + x64AppPath, + arm64AppPath, + asarPath: asarRelativePath, + outAppPath, + force: true, + mergeASARs: true, + x64ArchFiles: '*/kerberos.node', + filesToSkipComparison: (file: string) => { + for (const expected of filesToSkip) { + if (minimatch(file, expected)) { + return true; + } + } + return false; + } + }); + const productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); + Object.assign(productJson, { + darwinUniversalAssetId: 'darwin-universal' + }); + fs.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t')); } - if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index 5b3413b79e127..3a352a597014e 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -2,124 +2,109 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as codesign from 'electron-osx-sign'; import { spawn } from '@malept/cross-spawn-promise'; - const root = path.dirname(path.dirname(__dirname)); - function getElectronVersion(): string { - const npmrc = fs.readFileSync(path.join(root, '.npmrc'), 'utf8'); - const target = /^target="(.*)"$/m.exec(npmrc)![1]; - return target; + const npmrc = fs.readFileSync(path.join(root, '.npmrc'), 'utf8'); + const target = /^target="(.*)"$/m.exec(npmrc)![1]; + return target; } - async function main(buildDir?: string): Promise { - const tempDir = process.env['AGENT_TEMPDIRECTORY']; - const arch = process.env['VSCODE_ARCH']; - const identity = process.env['CODESIGN_IDENTITY']; - - if (!buildDir) { - throw new Error('$AGENT_BUILDDIRECTORY not set'); - } - - if (!tempDir) { - throw new Error('$AGENT_TEMPDIRECTORY not set'); - } - - const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); - const baseDir = path.dirname(__dirname); - const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`); - const appName = product.nameLong + '.app'; - const appFrameworkPath = path.join(appRoot, appName, 'Contents', 'Frameworks'); - const helperAppBaseName = product.nameShort; - const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; - const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; - const pluginHelperAppName = helperAppBaseName + ' Helper (Plugin).app'; - const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist'); - - const defaultOpts: codesign.SignOptions = { - app: path.join(appRoot, appName), - platform: 'darwin', - entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'), - 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'), - hardenedRuntime: true, - 'pre-auto-entitlements': false, - 'pre-embed-provisioning-profile': false, - keychain: path.join(tempDir, 'buildagent.keychain'), - version: getElectronVersion(), - identity, - 'gatekeeper-assess': false - }; - - const appOpts = { - ...defaultOpts, - // TODO(deepak1556): Incorrectly declared type in electron-osx-sign - ignore: (filePath: string) => { - return filePath.includes(gpuHelperAppName) || - filePath.includes(rendererHelperAppName) || - filePath.includes(pluginHelperAppName); - } - }; - - const gpuHelperOpts: codesign.SignOptions = { - ...defaultOpts, - app: path.join(appFrameworkPath, gpuHelperAppName), - entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist'), - 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist'), - }; - - const rendererHelperOpts: codesign.SignOptions = { - ...defaultOpts, - app: path.join(appFrameworkPath, rendererHelperAppName), - entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), - 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), - }; - - const pluginHelperOpts: codesign.SignOptions = { - ...defaultOpts, - app: path.join(appFrameworkPath, pluginHelperAppName), - entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), - 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), - }; - - // Only overwrite plist entries for x64 and arm64 builds, - // universal will get its copy from the x64 build. - if (arch !== 'universal') { - await spawn('plutil', [ - '-insert', - 'NSAppleEventsUsageDescription', - '-string', - 'An application in Visual Studio Code wants to use AppleScript.', - `${infoPlistPath}` - ]); - await spawn('plutil', [ - '-replace', - 'NSMicrophoneUsageDescription', - '-string', - 'An application in Visual Studio Code wants to use the Microphone.', - `${infoPlistPath}` - ]); - await spawn('plutil', [ - '-replace', - 'NSCameraUsageDescription', - '-string', - 'An application in Visual Studio Code wants to use the Camera.', - `${infoPlistPath}` - ]); - } - - await codesign.signAsync(gpuHelperOpts); - await codesign.signAsync(rendererHelperOpts); - await codesign.signAsync(pluginHelperOpts); - await codesign.signAsync(appOpts as any); + const tempDir = process.env['AGENT_TEMPDIRECTORY']; + const arch = process.env['VSCODE_ARCH']; + const identity = process.env['CODESIGN_IDENTITY']; + if (!buildDir) { + throw new Error('$AGENT_BUILDDIRECTORY not set'); + } + if (!tempDir) { + throw new Error('$AGENT_TEMPDIRECTORY not set'); + } + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); + const baseDir = path.dirname(__dirname); + const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`); + const appName = product.nameLong + '.app'; + const appFrameworkPath = path.join(appRoot, appName, 'Contents', 'Frameworks'); + const helperAppBaseName = product.nameShort; + const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; + const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; + const pluginHelperAppName = helperAppBaseName + ' Helper (Plugin).app'; + const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist'); + const defaultOpts: codesign.SignOptions = { + app: path.join(appRoot, appName), + platform: 'darwin', + entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'), + 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'), + hardenedRuntime: true, + 'pre-auto-entitlements': false, + 'pre-embed-provisioning-profile': false, + keychain: path.join(tempDir, 'buildagent.keychain'), + version: getElectronVersion(), + identity, + 'gatekeeper-assess': false + }; + const appOpts = { + ...defaultOpts, + // TODO(deepak1556): Incorrectly declared type in electron-osx-sign + ignore: (filePath: string) => { + return filePath.includes(gpuHelperAppName) || + filePath.includes(rendererHelperAppName) || + filePath.includes(pluginHelperAppName); + } + }; + const gpuHelperOpts: codesign.SignOptions = { + ...defaultOpts, + app: path.join(appFrameworkPath, gpuHelperAppName), + entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist'), + 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist'), + }; + const rendererHelperOpts: codesign.SignOptions = { + ...defaultOpts, + app: path.join(appFrameworkPath, rendererHelperAppName), + entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), + 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), + }; + const pluginHelperOpts: codesign.SignOptions = { + ...defaultOpts, + app: path.join(appFrameworkPath, pluginHelperAppName), + entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), + 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), + }; + // Only overwrite plist entries for x64 and arm64 builds, + // universal will get its copy from the x64 build. + if (arch !== 'universal') { + await spawn('plutil', [ + '-insert', + 'NSAppleEventsUsageDescription', + '-string', + 'An application in Visual Studio Code wants to use AppleScript.', + `${infoPlistPath}` + ]); + await spawn('plutil', [ + '-replace', + 'NSMicrophoneUsageDescription', + '-string', + 'An application in Visual Studio Code wants to use the Microphone.', + `${infoPlistPath}` + ]); + await spawn('plutil', [ + '-replace', + 'NSCameraUsageDescription', + '-string', + 'An application in Visual Studio Code wants to use the Camera.', + `${infoPlistPath}` + ]); + } + await codesign.signAsync(gpuHelperOpts); + await codesign.signAsync(rendererHelperOpts); + await codesign.signAsync(pluginHelperOpts); + await codesign.signAsync(appOpts as any); } - if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/darwin/verify-macho.ts b/build/darwin/verify-macho.ts index f418c44a23013..bb1c3192cc40a 100644 --- a/build/darwin/verify-macho.ts +++ b/build/darwin/verify-macho.ts @@ -2,119 +2,119 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as assert from 'assert'; import * as path from 'path'; import { open, stat, readdir, realpath } from 'fs/promises'; import { spawn, ExitCodeError } from '@malept/cross-spawn-promise'; - const MACHO_PREFIX = 'Mach-O '; const MACHO_64_MAGIC_LE = 0xfeedfacf; const MACHO_UNIVERSAL_MAGIC_LE = 0xbebafeca; const MACHO_ARM64_CPU_TYPE = new Set([ - 0x0c000001, - 0x0100000c, + 0x0c000001, + 0x0100000c, ]); const MACHO_X86_64_CPU_TYPE = new Set([ - 0x07000001, - 0x01000007, + 0x07000001, + 0x01000007, ]); - async function read(file: string, buf: Buffer, offset: number, length: number, position: number) { - let filehandle; - try { - filehandle = await open(file); - await filehandle.read(buf, offset, length, position); - } finally { - await filehandle?.close(); - } + let filehandle; + try { + filehandle = await open(file); + await filehandle.read(buf, offset, length, position); + } + finally { + await filehandle?.close(); + } } - async function checkMachOFiles(appPath: string, arch: string) { - const visited = new Set(); - const invalidFiles: string[] = []; - const header = Buffer.alloc(8); - const file_header_entry_size = 20; - const checkx86_64Arch = (arch === 'x64'); - const checkArm64Arch = (arch === 'arm64'); - const checkUniversalArch = (arch === 'universal'); - const traverse = async (p: string) => { - p = await realpath(p); - if (visited.has(p)) { - return; - } - visited.add(p); - - const info = await stat(p); - if (info.isSymbolicLink()) { - return; - } - if (info.isFile()) { - let fileOutput = ''; - try { - fileOutput = await spawn('file', ['--brief', '--no-pad', p]); - } catch (e) { - if (e instanceof ExitCodeError) { - /* silently accept error codes from "file" */ - } else { - throw e; - } - } - if (fileOutput.startsWith(MACHO_PREFIX)) { - console.log(`Verifying architecture of ${p}`); - read(p, header, 0, 8, 0).then(_ => { - const header_magic = header.readUInt32LE(); - if (header_magic === MACHO_64_MAGIC_LE) { - const cpu_type = header.readUInt32LE(4); - if (checkUniversalArch) { - invalidFiles.push(p); - } else if (checkArm64Arch && !MACHO_ARM64_CPU_TYPE.has(cpu_type)) { - invalidFiles.push(p); - } else if (checkx86_64Arch && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) { - invalidFiles.push(p); - } - } else if (header_magic === MACHO_UNIVERSAL_MAGIC_LE) { - const num_binaries = header.readUInt32BE(4); - assert.equal(num_binaries, 2); - const file_entries_size = file_header_entry_size * num_binaries; - const file_entries = Buffer.alloc(file_entries_size); - read(p, file_entries, 0, file_entries_size, 8).then(_ => { - for (let i = 0; i < num_binaries; i++) { - const cpu_type = file_entries.readUInt32LE(file_header_entry_size * i); - if (!MACHO_ARM64_CPU_TYPE.has(cpu_type) && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) { - invalidFiles.push(p); - } - } - }); - } - }); - } - } - - if (info.isDirectory()) { - for (const child of await readdir(p)) { - await traverse(path.resolve(p, child)); - } - } - }; - await traverse(appPath); - return invalidFiles; + const visited = new Set(); + const invalidFiles: string[] = []; + const header = Buffer.alloc(8); + const file_header_entry_size = 20; + const checkx86_64Arch = (arch === 'x64'); + const checkArm64Arch = (arch === 'arm64'); + const checkUniversalArch = (arch === 'universal'); + const traverse = async (p: string) => { + p = await realpath(p); + if (visited.has(p)) { + return; + } + visited.add(p); + const info = await stat(p); + if (info.isSymbolicLink()) { + return; + } + if (info.isFile()) { + let fileOutput = ''; + try { + fileOutput = await spawn('file', ['--brief', '--no-pad', p]); + } + catch (e) { + if (e instanceof ExitCodeError) { + /* silently accept error codes from "file" */ + } + else { + throw e; + } + } + if (fileOutput.startsWith(MACHO_PREFIX)) { + console.log(`Verifying architecture of ${p}`); + read(p, header, 0, 8, 0).then(_ => { + const header_magic = header.readUInt32LE(); + if (header_magic === MACHO_64_MAGIC_LE) { + const cpu_type = header.readUInt32LE(4); + if (checkUniversalArch) { + invalidFiles.push(p); + } + else if (checkArm64Arch && !MACHO_ARM64_CPU_TYPE.has(cpu_type)) { + invalidFiles.push(p); + } + else if (checkx86_64Arch && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) { + invalidFiles.push(p); + } + } + else if (header_magic === MACHO_UNIVERSAL_MAGIC_LE) { + const num_binaries = header.readUInt32BE(4); + assert.equal(num_binaries, 2); + const file_entries_size = file_header_entry_size * num_binaries; + const file_entries = Buffer.alloc(file_entries_size); + read(p, file_entries, 0, file_entries_size, 8).then(_ => { + for (let i = 0; i < num_binaries; i++) { + const cpu_type = file_entries.readUInt32LE(file_header_entry_size * i); + if (!MACHO_ARM64_CPU_TYPE.has(cpu_type) && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) { + invalidFiles.push(p); + } + } + }); + } + }); + } + } + if (info.isDirectory()) { + for (const child of await readdir(p)) { + await traverse(path.resolve(p, child)); + } + } + }; + await traverse(appPath); + return invalidFiles; } - const archToCheck = process.argv[2]; assert(process.env['APP_PATH'], 'APP_PATH not set'); assert(archToCheck === 'x64' || archToCheck === 'arm64' || archToCheck === 'universal', `Invalid architecture ${archToCheck} to check`); checkMachOFiles(process.env['APP_PATH'], archToCheck).then(invalidFiles => { - if (invalidFiles.length > 0) { - console.error('\x1b[31mThe following files are built for the wrong architecture:\x1b[0m'); - for (const file of invalidFiles) { - console.error(`\x1b[31m${file}\x1b[0m`); - } - process.exit(1); - } else { - console.log('\x1b[32mAll files are valid\x1b[0m'); - } + if (invalidFiles.length > 0) { + console.error('\x1b[31mThe following files are built for the wrong architecture:\x1b[0m'); + for (const file of invalidFiles) { + console.error(`\x1b[31m${file}\x1b[0m`); + } + process.exit(1); + } + else { + console.log('\x1b[32mAll files are valid\x1b[0m'); + } }).catch(err => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }); diff --git a/build/lib/asar.ts b/build/lib/asar.ts index 0b225ab1624f3..eabba6caf310b 100644 --- a/build/lib/asar.ts +++ b/build/lib/asar.ts @@ -2,172 +2,164 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as es from 'event-stream'; const pickle = require('chromium-pickle-js'); const Filesystem = require('asar/lib/filesystem'); import * as VinylFile from 'vinyl'; import * as minimatch from 'minimatch'; - declare class AsarFilesystem { - readonly header: unknown; - constructor(src: string); - insertDirectory(path: string, shouldUnpack?: boolean): unknown; - insertFile(path: string, shouldUnpack: boolean, file: { stat: { size: number; mode: number } }, options: {}): Promise; + readonly header: unknown; + constructor(src: string); + insertDirectory(path: string, shouldUnpack?: boolean): unknown; + insertFile(path: string, shouldUnpack: boolean, file: { + stat: { + size: number; + mode: number; + }; + }, options: {}): Promise; } - export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: string[], duplicateGlobs: string[], destFilename: string): NodeJS.ReadWriteStream { - - const shouldUnpackFile = (file: VinylFile): boolean => { - for (let i = 0; i < unpackGlobs.length; i++) { - if (minimatch(file.relative, unpackGlobs[i])) { - return true; - } - } - return false; - }; - - const shouldSkipFile = (file: VinylFile): boolean => { - for (const skipGlob of skipGlobs) { - if (minimatch(file.relative, skipGlob)) { - return true; - } - } - return false; - }; - - // Files that should be duplicated between - // node_modules.asar and node_modules - const shouldDuplicateFile = (file: VinylFile): boolean => { - for (const duplicateGlob of duplicateGlobs) { - if (minimatch(file.relative, duplicateGlob)) { - return true; - } - } - return false; - }; - - const filesystem = new Filesystem(folderPath); - const out: Buffer[] = []; - - // Keep track of pending inserts - let pendingInserts = 0; - let onFileInserted = () => { pendingInserts--; }; - - // Do not insert twice the same directory - const seenDir: { [key: string]: boolean } = {}; - const insertDirectoryRecursive = (dir: string) => { - if (seenDir[dir]) { - return; - } - - let lastSlash = dir.lastIndexOf('/'); - if (lastSlash === -1) { - lastSlash = dir.lastIndexOf('\\'); - } - if (lastSlash !== -1) { - insertDirectoryRecursive(dir.substring(0, lastSlash)); - } - seenDir[dir] = true; - filesystem.insertDirectory(dir); - }; - - const insertDirectoryForFile = (file: string) => { - let lastSlash = file.lastIndexOf('/'); - if (lastSlash === -1) { - lastSlash = file.lastIndexOf('\\'); - } - if (lastSlash !== -1) { - insertDirectoryRecursive(file.substring(0, lastSlash)); - } - }; - - const insertFile = (relativePath: string, stat: { size: number; mode: number }, shouldUnpack: boolean) => { - insertDirectoryForFile(relativePath); - pendingInserts++; - // Do not pass `onFileInserted` directly because it gets overwritten below. - // Create a closure capturing `onFileInserted`. - filesystem.insertFile(relativePath, shouldUnpack, { stat: stat }, {}).then(() => onFileInserted(), () => onFileInserted()); - }; - - return es.through(function (file) { - if (file.stat.isDirectory()) { - return; - } - if (!file.stat.isFile()) { - throw new Error(`unknown item in stream!`); - } - if (shouldSkipFile(file)) { - this.queue(new VinylFile({ - base: '.', - path: file.path, - stat: file.stat, - contents: file.contents - })); - return; - } - if (shouldDuplicateFile(file)) { - this.queue(new VinylFile({ - base: '.', - path: file.path, - stat: file.stat, - contents: file.contents - })); - } - const shouldUnpack = shouldUnpackFile(file); - insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); - - if (shouldUnpack) { - // The file goes outside of xx.asar, in a folder xx.asar.unpacked - const relative = path.relative(folderPath, file.path); - this.queue(new VinylFile({ - base: '.', - path: path.join(destFilename + '.unpacked', relative), - stat: file.stat, - contents: file.contents - })); - } else { - // The file goes inside of xx.asar - out.push(file.contents); - } - }, function () { - - const finish = () => { - { - const headerPickle = pickle.createEmpty(); - headerPickle.writeString(JSON.stringify(filesystem.header)); - const headerBuf = headerPickle.toBuffer(); - - const sizePickle = pickle.createEmpty(); - sizePickle.writeUInt32(headerBuf.length); - const sizeBuf = sizePickle.toBuffer(); - - out.unshift(headerBuf); - out.unshift(sizeBuf); - } - - const contents = Buffer.concat(out); - out.length = 0; - - this.queue(new VinylFile({ - base: '.', - path: destFilename, - contents: contents - })); - this.queue(null); - }; - - // Call finish() only when all file inserts have finished... - if (pendingInserts === 0) { - finish(); - } else { - onFileInserted = () => { - pendingInserts--; - if (pendingInserts === 0) { - finish(); - } - }; - } - }); + const shouldUnpackFile = (file: VinylFile): boolean => { + for (let i = 0; i < unpackGlobs.length; i++) { + if (minimatch(file.relative, unpackGlobs[i])) { + return true; + } + } + return false; + }; + const shouldSkipFile = (file: VinylFile): boolean => { + for (const skipGlob of skipGlobs) { + if (minimatch(file.relative, skipGlob)) { + return true; + } + } + return false; + }; + // Files that should be duplicated between + // node_modules.asar and node_modules + const shouldDuplicateFile = (file: VinylFile): boolean => { + for (const duplicateGlob of duplicateGlobs) { + if (minimatch(file.relative, duplicateGlob)) { + return true; + } + } + return false; + }; + const filesystem = new Filesystem(folderPath); + const out: Buffer[] = []; + // Keep track of pending inserts + let pendingInserts = 0; + let onFileInserted = () => { pendingInserts--; }; + // Do not insert twice the same directory + const seenDir: { + [key: string]: boolean; + } = {}; + const insertDirectoryRecursive = (dir: string) => { + if (seenDir[dir]) { + return; + } + let lastSlash = dir.lastIndexOf('/'); + if (lastSlash === -1) { + lastSlash = dir.lastIndexOf('\\'); + } + if (lastSlash !== -1) { + insertDirectoryRecursive(dir.substring(0, lastSlash)); + } + seenDir[dir] = true; + filesystem.insertDirectory(dir); + }; + const insertDirectoryForFile = (file: string) => { + let lastSlash = file.lastIndexOf('/'); + if (lastSlash === -1) { + lastSlash = file.lastIndexOf('\\'); + } + if (lastSlash !== -1) { + insertDirectoryRecursive(file.substring(0, lastSlash)); + } + }; + const insertFile = (relativePath: string, stat: { + size: number; + mode: number; + }, shouldUnpack: boolean) => { + insertDirectoryForFile(relativePath); + pendingInserts++; + // Do not pass `onFileInserted` directly because it gets overwritten below. + // Create a closure capturing `onFileInserted`. + filesystem.insertFile(relativePath, shouldUnpack, { stat: stat }, {}).then(() => onFileInserted(), () => onFileInserted()); + }; + return es.through(function (file) { + if (file.stat.isDirectory()) { + return; + } + if (!file.stat.isFile()) { + throw new Error(`unknown item in stream!`); + } + if (shouldSkipFile(file)) { + this.queue(new VinylFile({ + base: '.', + path: file.path, + stat: file.stat, + contents: file.contents + })); + return; + } + if (shouldDuplicateFile(file)) { + this.queue(new VinylFile({ + base: '.', + path: file.path, + stat: file.stat, + contents: file.contents + })); + } + const shouldUnpack = shouldUnpackFile(file); + insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); + if (shouldUnpack) { + // The file goes outside of xx.asar, in a folder xx.asar.unpacked + const relative = path.relative(folderPath, file.path); + this.queue(new VinylFile({ + base: '.', + path: path.join(destFilename + '.unpacked', relative), + stat: file.stat, + contents: file.contents + })); + } + else { + // The file goes inside of xx.asar + out.push(file.contents); + } + }, function () { + const finish = () => { + { + const headerPickle = pickle.createEmpty(); + headerPickle.writeString(JSON.stringify(filesystem.header)); + const headerBuf = headerPickle.toBuffer(); + const sizePickle = pickle.createEmpty(); + sizePickle.writeUInt32(headerBuf.length); + const sizeBuf = sizePickle.toBuffer(); + out.unshift(headerBuf); + out.unshift(sizeBuf); + } + const contents = Buffer.concat(out); + out.length = 0; + this.queue(new VinylFile({ + base: '.', + path: destFilename, + contents: contents + })); + this.queue(null); + }; + // Call finish() only when all file inserts have finished... + if (pendingInserts === 0) { + finish(); + } + else { + onFileInserted = () => { + pendingInserts--; + if (pendingInserts === 0) { + finish(); + } + }; + } + }); } diff --git a/build/lib/builtInExtensions.ts b/build/lib/builtInExtensions.ts index 8b831d42d44f4..e77a74563c741 100644 --- a/build/lib/builtInExtensions.ts +++ b/build/lib/builtInExtensions.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; @@ -14,167 +13,139 @@ import * as ext from './extensions'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import { Stream } from 'stream'; - export interface IExtensionDefinition { - name: string; - version: string; - sha256: string; - repo: string; - platforms?: string[]; - metadata: { - id: string; - publisherId: { - publisherId: string; - publisherName: string; - displayName: string; - flags: string; - }; - publisherDisplayName: string; - }; + name: string; + version: string; + sha256: string; + repo: string; + platforms?: string[]; + metadata: { + id: string; + publisherId: { + publisherId: string; + publisherName: string; + displayName: string; + flags: string; + }; + publisherDisplayName: string; + }; } - const root = path.dirname(path.dirname(__dirname)); const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); const builtInExtensions = productjson.builtInExtensions || []; const webBuiltInExtensions = productjson.webBuiltInExtensions || []; const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); const ENABLE_LOGGING = !process.env['VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE']; - function log(...messages: string[]): void { - if (ENABLE_LOGGING) { - fancyLog(...messages); - } + if (ENABLE_LOGGING) { + fancyLog(...messages); + } } - function getExtensionPath(extension: IExtensionDefinition): string { - return path.join(root, '.build', 'builtInExtensions', extension.name); + return path.join(root, '.build', 'builtInExtensions', extension.name); } - function isUpToDate(extension: IExtensionDefinition): boolean { - const packagePath = path.join(getExtensionPath(extension), 'package.json'); - - if (!fs.existsSync(packagePath)) { - return false; - } - - const packageContents = fs.readFileSync(packagePath, { encoding: 'utf8' }); - - try { - const diskVersion = JSON.parse(packageContents).version; - return (diskVersion === extension.version); - } catch (err) { - return false; - } + const packagePath = path.join(getExtensionPath(extension), 'package.json'); + if (!fs.existsSync(packagePath)) { + return false; + } + const packageContents = fs.readFileSync(packagePath, { encoding: 'utf8' }); + try { + const diskVersion = JSON.parse(packageContents).version; + return (diskVersion === extension.version); + } + catch (err) { + return false; + } } - function getExtensionDownloadStream(extension: IExtensionDefinition) { - const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; - return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) - .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); + const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; + return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); } - export function getExtensionStream(extension: IExtensionDefinition) { - // if the extension exists on disk, use those files instead of downloading anew - if (isUpToDate(extension)) { - log('[extensions]', `${extension.name}@${extension.version} up to date`, ansiColors.green('✔︎')); - return vfs.src(['**'], { cwd: getExtensionPath(extension), dot: true }) - .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); - } - - return getExtensionDownloadStream(extension); + // if the extension exists on disk, use those files instead of downloading anew + if (isUpToDate(extension)) { + log('[extensions]', `${extension.name}@${extension.version} up to date`, ansiColors.green('✔︎')); + return vfs.src(['**'], { cwd: getExtensionPath(extension), dot: true }) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); + } + return getExtensionDownloadStream(extension); } - function syncMarketplaceExtension(extension: IExtensionDefinition): Stream { - const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; - const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); - if (isUpToDate(extension)) { - log(source, `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); - return es.readArray([]); - } - - rimraf.sync(getExtensionPath(extension)); - - return getExtensionDownloadStream(extension) - .pipe(vfs.dest('.build/builtInExtensions')) - .on('end', () => log(source, extension.name, ansiColors.green('✔︎'))); + const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; + const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); + if (isUpToDate(extension)) { + log(source, `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); + return es.readArray([]); + } + rimraf.sync(getExtensionPath(extension)); + return getExtensionDownloadStream(extension) + .pipe(vfs.dest('.build/builtInExtensions')) + .on('end', () => log(source, extension.name, ansiColors.green('✔︎'))); } - function syncExtension(extension: IExtensionDefinition, controlState: 'disabled' | 'marketplace'): Stream { - if (extension.platforms) { - const platforms = new Set(extension.platforms); - - if (!platforms.has(process.platform)) { - log(ansiColors.gray('[skip]'), `${extension.name}@${extension.version}: Platform '${process.platform}' not supported: [${extension.platforms}]`, ansiColors.green('✔︎')); - return es.readArray([]); - } - } - - switch (controlState) { - case 'disabled': - log(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); - return es.readArray([]); - - case 'marketplace': - return syncMarketplaceExtension(extension); - - default: - if (!fs.existsSync(controlState)) { - log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); - return es.readArray([]); - - } else if (!fs.existsSync(path.join(controlState, 'package.json'))) { - log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); - return es.readArray([]); - } - - log(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); - return es.readArray([]); - } + if (extension.platforms) { + const platforms = new Set(extension.platforms); + if (!platforms.has(process.platform)) { + log(ansiColors.gray('[skip]'), `${extension.name}@${extension.version}: Platform '${process.platform}' not supported: [${extension.platforms}]`, ansiColors.green('✔︎')); + return es.readArray([]); + } + } + switch (controlState) { + case 'disabled': + log(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); + return es.readArray([]); + case 'marketplace': + return syncMarketplaceExtension(extension); + default: + if (!fs.existsSync(controlState)) { + log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); + return es.readArray([]); + } + else if (!fs.existsSync(path.join(controlState, 'package.json'))) { + log(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); + return es.readArray([]); + } + log(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); + return es.readArray([]); + } } - interface IControlFile { - [name: string]: 'disabled' | 'marketplace'; + [name: string]: 'disabled' | 'marketplace'; } - function readControlFile(): IControlFile { - try { - return JSON.parse(fs.readFileSync(controlFilePath, 'utf8')); - } catch (err) { - return {}; - } + try { + return JSON.parse(fs.readFileSync(controlFilePath, 'utf8')); + } + catch (err) { + return {}; + } } - function writeControlFile(control: IControlFile): void { - fs.mkdirSync(path.dirname(controlFilePath), { recursive: true }); - fs.writeFileSync(controlFilePath, JSON.stringify(control, null, 2)); + fs.mkdirSync(path.dirname(controlFilePath), { recursive: true }); + fs.writeFileSync(controlFilePath, JSON.stringify(control, null, 2)); } - export function getBuiltInExtensions(): Promise { - log('Synchronizing built-in extensions...'); - log(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); - - const control = readControlFile(); - const streams: Stream[] = []; - - for (const extension of [...builtInExtensions, ...webBuiltInExtensions]) { - const controlState = control[extension.name] || 'marketplace'; - control[extension.name] = controlState; - - streams.push(syncExtension(extension, controlState)); - } - - writeControlFile(control); - - return new Promise((resolve, reject) => { - es.merge(streams) - .on('error', reject) - .on('end', resolve); - }); + log('Synchronizing built-in extensions...'); + log(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); + const control = readControlFile(); + const streams: Stream[] = []; + for (const extension of [...builtInExtensions, ...webBuiltInExtensions]) { + const controlState = control[extension.name] || 'marketplace'; + control[extension.name] = controlState; + streams.push(syncExtension(extension, controlState)); + } + writeControlFile(control); + return new Promise((resolve, reject) => { + es.merge(streams) + .on('error', reject) + .on('end', resolve); + }); } - if (require.main === module) { - getBuiltInExtensions().then(() => process.exit(0)).catch(err => { - console.error(err); - process.exit(1); - }); + getBuiltInExtensions().then(() => process.exit(0)).catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/lib/builtInExtensionsCG.ts b/build/lib/builtInExtensionsCG.ts index 9d11dea3dcae3..b6d8ba216da22 100644 --- a/build/lib/builtInExtensionsCG.ts +++ b/build/lib/builtInExtensionsCG.ts @@ -2,81 +2,78 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as url from 'url'; import ansiColors = require('ansi-colors'); import { IExtensionDefinition } from './builtInExtensions'; - const root = path.dirname(path.dirname(__dirname)); const rootCG = path.join(root, 'extensionsCG'); const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); const builtInExtensions = productjson.builtInExtensions || []; const webBuiltInExtensions = productjson.webBuiltInExtensions || []; const token = process.env['GITHUB_TOKEN']; - const contentBasePath = 'raw.githubusercontent.com'; const contentFileNames = ['package.json', 'package-lock.json']; - async function downloadExtensionDetails(extension: IExtensionDefinition): Promise { - const extensionLabel = `${extension.name}@${extension.version}`; - const repository = url.parse(extension.repo).path!.substr(1); - const repositoryContentBaseUrl = `https://${token ? `${token}@` : ''}${contentBasePath}/${repository}/v${extension.version}`; - - - async function getContent(fileName: string): Promise<{ fileName: string; body: Buffer | undefined | null }> { - try { - const response = await fetch(`${repositoryContentBaseUrl}/${fileName}`); - if (response.ok) { - return { fileName, body: Buffer.from(await response.arrayBuffer()) }; - } else if (response.status === 404) { - return { fileName, body: undefined }; - } else { - return { fileName, body: null }; - } - } catch (e) { - return { fileName, body: null }; - } - } - - const promises = contentFileNames.map(getContent); - - console.log(extensionLabel); - const results = await Promise.all(promises); - for (const result of results) { - if (result.body) { - const extensionFolder = path.join(rootCG, extension.name); - fs.mkdirSync(extensionFolder, { recursive: true }); - fs.writeFileSync(path.join(extensionFolder, result.fileName), result.body); - console.log(` - ${result.fileName} ${ansiColors.green('✔︎')}`); - } else if (result.body === undefined) { - console.log(` - ${result.fileName} ${ansiColors.yellow('⚠️')}`); - } else { - console.log(` - ${result.fileName} ${ansiColors.red('🛑')}`); - } - } - - // Validation - if (!results.find(r => r.fileName === 'package.json')?.body) { - // throw new Error(`The "package.json" file could not be found for the built-in extension - ${extensionLabel}`); - } - if (!results.find(r => r.fileName === 'package-lock.json')?.body) { - // throw new Error(`The "package-lock.json" could not be found for the built-in extension - ${extensionLabel}`); - } + const extensionLabel = `${extension.name}@${extension.version}`; + const repository = url.parse(extension.repo).path!.substr(1); + const repositoryContentBaseUrl = `https://${token ? `${token}@` : ''}${contentBasePath}/${repository}/v${extension.version}`; + async function getContent(fileName: string): Promise<{ + fileName: string; + body: Buffer | undefined | null; + }> { + try { + const response = await fetch(`${repositoryContentBaseUrl}/${fileName}`); + if (response.ok) { + return { fileName, body: Buffer.from(await response.arrayBuffer()) }; + } + else if (response.status === 404) { + return { fileName, body: undefined }; + } + else { + return { fileName, body: null }; + } + } + catch (e) { + return { fileName, body: null }; + } + } + const promises = contentFileNames.map(getContent); + console.log(extensionLabel); + const results = await Promise.all(promises); + for (const result of results) { + if (result.body) { + const extensionFolder = path.join(rootCG, extension.name); + fs.mkdirSync(extensionFolder, { recursive: true }); + fs.writeFileSync(path.join(extensionFolder, result.fileName), result.body); + console.log(` - ${result.fileName} ${ansiColors.green('✔︎')}`); + } + else if (result.body === undefined) { + console.log(` - ${result.fileName} ${ansiColors.yellow('⚠️')}`); + } + else { + console.log(` - ${result.fileName} ${ansiColors.red('🛑')}`); + } + } + // Validation + if (!results.find(r => r.fileName === 'package.json')?.body) { + // throw new Error(`The "package.json" file could not be found for the built-in extension - ${extensionLabel}`); + } + if (!results.find(r => r.fileName === 'package-lock.json')?.body) { + // throw new Error(`The "package-lock.json" could not be found for the built-in extension - ${extensionLabel}`); + } } - async function main(): Promise { - for (const extension of [...builtInExtensions, ...webBuiltInExtensions]) { - await downloadExtensionDetails(extension); - } + for (const extension of [...builtInExtensions, ...webBuiltInExtensions]) { + await downloadExtensionDetails(extension); + } } - main().then(() => { - console.log(`Built-in extensions component data downloaded ${ansiColors.green('✔︎')}`); - process.exit(0); + console.log(`Built-in extensions component data downloaded ${ansiColors.green('✔︎')}`); + process.exit(0); }, err => { - console.log(`Built-in extensions component data could not be downloaded ${ansiColors.red('🛑')}`); - console.error(err); - process.exit(1); + console.log(`Built-in extensions component data could not be downloaded ${ansiColors.red('🛑')}`); + console.error(err); + process.exit(1); }); diff --git a/build/lib/bundle.ts b/build/lib/bundle.ts index 58995b7d5d1bc..f2825f35699d9 100644 --- a/build/lib/bundle.ts +++ b/build/lib/bundle.ts @@ -2,680 +2,582 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as vm from 'vm'; - interface IPosition { - line: number; - col: number; + line: number; + col: number; } - interface IBuildModuleInfo { - id: string; - path: string; - defineLocation: IPosition | null; - dependencies: string[]; - shim: string; - exports: any; -} - + id: string; + path: string; + defineLocation: IPosition | null; + dependencies: string[]; + shim: string; + exports: any; +} interface IBuildModuleInfoMap { - [moduleId: string]: IBuildModuleInfo; + [moduleId: string]: IBuildModuleInfo; } - interface ILoaderPlugin { - write(pluginName: string, moduleName: string, write: ILoaderPluginWriteFunc): void; - writeFile(pluginName: string, entryPoint: string, req: ILoaderPluginReqFunc, write: (filename: string, contents: string) => void, config: any): void; - finishBuild(write: (filename: string, contents: string) => void): void; + write(pluginName: string, moduleName: string, write: ILoaderPluginWriteFunc): void; + writeFile(pluginName: string, entryPoint: string, req: ILoaderPluginReqFunc, write: (filename: string, contents: string) => void, config: any): void; + finishBuild(write: (filename: string, contents: string) => void): void; } - interface ILoaderPluginWriteFunc { - (something: string): void; - getEntryPoint(): string; - asModule(moduleId: string, code: string): void; + (something: string): void; + getEntryPoint(): string; + asModule(moduleId: string, code: string): void; } - interface ILoaderPluginReqFunc { - (something: string): void; - toUrl(something: string): string; + (something: string): void; + toUrl(something: string): string; } - export interface IExtraFile { - path: string; - amdModuleId?: string; + path: string; + amdModuleId?: string; } - export interface IEntryPoint { - name: string; - include?: string[]; - exclude?: string[]; - /** @deprecated unsupported by ESM */ - prepend?: IExtraFile[]; - dest?: string; -} - + name: string; + include?: string[]; + exclude?: string[]; + /** @deprecated unsupported by ESM */ + prepend?: IExtraFile[]; + dest?: string; +} interface IEntryPointMap { - [moduleId: string]: IEntryPoint; + [moduleId: string]: IEntryPoint; } - export interface IGraph { - [node: string]: string[]; + [node: string]: string[]; } - interface INodeSet { - [node: string]: boolean; + [node: string]: boolean; } - export interface IFile { - path: string | null; - contents: string; + path: string | null; + contents: string; } - export interface IConcatFile { - dest: string; - sources: IFile[]; + dest: string; + sources: IFile[]; } - export interface IBundleData { - graph: IGraph; - bundles: { [moduleId: string]: string[] }; + graph: IGraph; + bundles: { + [moduleId: string]: string[]; + }; } - export interface IBundleResult { - files: IConcatFile[]; - cssInlinedResources: string[]; - bundleData: IBundleData; + files: IConcatFile[]; + cssInlinedResources: string[]; + bundleData: IBundleData; } - interface IPartialBundleResult { - files: IConcatFile[]; - bundleData: IBundleData; + files: IConcatFile[]; + bundleData: IBundleData; } - export interface ILoaderConfig { - isBuild?: boolean; - paths?: { [path: string]: any }; - /* - * Normally, during a build, no module factories are invoked. This can be used - * to forcefully execute a module's factory. - */ - buildForceInvokeFactory: { - [moduleId: string]: boolean; - }; -} - + isBuild?: boolean; + paths?: { + [path: string]: any; + }; + /* + * Normally, during a build, no module factories are invoked. This can be used + * to forcefully execute a module's factory. + */ + buildForceInvokeFactory: { + [moduleId: string]: boolean; + }; +} /** * Bundle `entryPoints` given config `config`. */ export function bundle(entryPoints: IEntryPoint[], config: ILoaderConfig, callback: (err: any, result: IBundleResult | null) => void): void { - const entryPointsMap: IEntryPointMap = {}; - entryPoints.forEach((module: IEntryPoint) => { - if (entryPointsMap[module.name]) { - throw new Error(`Cannot have two entry points with the same name '${module.name}'`); - } - entryPointsMap[module.name] = module; - }); - - const allMentionedModulesMap: { [modules: string]: boolean } = {}; - entryPoints.forEach((module: IEntryPoint) => { - allMentionedModulesMap[module.name] = true; - module.include?.forEach(function (includedModule) { - allMentionedModulesMap[includedModule] = true; - }); - module.exclude?.forEach(function (excludedModule) { - allMentionedModulesMap[excludedModule] = true; - }); - }); - - - const code = require('fs').readFileSync(path.join(__dirname, '../../src/vs/loader.js')); - const r: Function = vm.runInThisContext('(function(require, module, exports) { ' + code + '\n});'); - const loaderModule = { exports: {} }; - r.call({}, require, loaderModule, loaderModule.exports); - - const loader: any = loaderModule.exports; - config.isBuild = true; - config.paths = config.paths || {}; - if (!config.paths['vs/css']) { - config.paths['vs/css'] = 'out-build/vs/css.build'; - } - config.buildForceInvokeFactory = config.buildForceInvokeFactory || {}; - config.buildForceInvokeFactory['vs/css'] = true; - loader.config(config); - - loader(['require'], (localRequire: any) => { - const resolvePath = (entry: IExtraFile) => { - let r = localRequire.toUrl(entry.path); - if (!r.endsWith('.js')) { - r += '.js'; - } - // avoid packaging the build version of plugins: - r = r.replace('vs/css.build.js', 'vs/css.js'); - return { path: r, amdModuleId: entry.amdModuleId }; - }; - for (const moduleId in entryPointsMap) { - const entryPoint = entryPointsMap[moduleId]; - if (entryPoint.prepend) { - entryPoint.prepend = entryPoint.prepend.map(resolvePath); - } - } - }); - - loader(Object.keys(allMentionedModulesMap), () => { - const modules = loader.getBuildInfo(); - const partialResult = emitEntryPoints(modules, entryPointsMap); - const cssInlinedResources = loader('vs/css').getInlinedResources(); - callback(null, { - files: partialResult.files, - cssInlinedResources: cssInlinedResources, - bundleData: partialResult.bundleData - }); - }, (err: any) => callback(err, null)); -} - + const entryPointsMap: IEntryPointMap = {}; + entryPoints.forEach((module: IEntryPoint) => { + if (entryPointsMap[module.name]) { + throw new Error(`Cannot have two entry points with the same name '${module.name}'`); + } + entryPointsMap[module.name] = module; + }); + const allMentionedModulesMap: { + [modules: string]: boolean; + } = {}; + entryPoints.forEach((module: IEntryPoint) => { + allMentionedModulesMap[module.name] = true; + module.include?.forEach(function (includedModule) { + allMentionedModulesMap[includedModule] = true; + }); + module.exclude?.forEach(function (excludedModule) { + allMentionedModulesMap[excludedModule] = true; + }); + }); + const code = require('fs').readFileSync(path.join(__dirname, '../../src/vs/loader.js')); + const r: Function = vm.runInThisContext('(function(require, module, exports) { ' + code + '\n});'); + const loaderModule = { exports: {} }; + r.call({}, require, loaderModule, loaderModule.exports); + const loader: any = loaderModule.exports; + config.isBuild = true; + config.paths = config.paths || {}; + if (!config.paths['vs/css']) { + config.paths['vs/css'] = 'out-build/vs/css.build'; + } + config.buildForceInvokeFactory = config.buildForceInvokeFactory || {}; + config.buildForceInvokeFactory['vs/css'] = true; + loader.config(config); + loader(['require'], (localRequire: any) => { + const resolvePath = (entry: IExtraFile) => { + let r = localRequire.toUrl(entry.path); + if (!r.endsWith('.js')) { + r += '.js'; + } + // avoid packaging the build version of plugins: + r = r.replace('vs/css.build.js', 'vs/css.js'); + return { path: r, amdModuleId: entry.amdModuleId }; + }; + for (const moduleId in entryPointsMap) { + const entryPoint = entryPointsMap[moduleId]; + if (entryPoint.prepend) { + entryPoint.prepend = entryPoint.prepend.map(resolvePath); + } + } + }); + loader(Object.keys(allMentionedModulesMap), () => { + const modules = loader.getBuildInfo(); + const partialResult = emitEntryPoints(modules, entryPointsMap); + const cssInlinedResources = loader('vs/css').getInlinedResources(); + callback(null, { + files: partialResult.files, + cssInlinedResources: cssInlinedResources, + bundleData: partialResult.bundleData + }); + }, (err: any) => callback(err, null)); +} function emitEntryPoints(modules: IBuildModuleInfo[], entryPoints: IEntryPointMap): IPartialBundleResult { - const modulesMap: IBuildModuleInfoMap = {}; - modules.forEach((m: IBuildModuleInfo) => { - modulesMap[m.id] = m; - }); - - const modulesGraph: IGraph = {}; - modules.forEach((m: IBuildModuleInfo) => { - modulesGraph[m.id] = m.dependencies; - }); - - const sortedModules = topologicalSort(modulesGraph); - - let result: IConcatFile[] = []; - const usedPlugins: IPluginMap = {}; - const bundleData: IBundleData = { - graph: modulesGraph, - bundles: {} - }; - - Object.keys(entryPoints).forEach((moduleToBundle: string) => { - const info = entryPoints[moduleToBundle]; - const rootNodes = [moduleToBundle].concat(info.include || []); - const allDependencies = visit(rootNodes, modulesGraph); - const excludes: string[] = ['require', 'exports', 'module'].concat(info.exclude || []); - - excludes.forEach((excludeRoot: string) => { - const allExcludes = visit([excludeRoot], modulesGraph); - Object.keys(allExcludes).forEach((exclude: string) => { - delete allDependencies[exclude]; - }); - }); - - const includedModules = sortedModules.filter((module: string) => { - return allDependencies[module]; - }); - - bundleData.bundles[moduleToBundle] = includedModules; - - const res = emitEntryPoint( - modulesMap, - modulesGraph, - moduleToBundle, - includedModules, - info.prepend || [], - info.dest - ); - - result = result.concat(res.files); - for (const pluginName in res.usedPlugins) { - usedPlugins[pluginName] = usedPlugins[pluginName] || res.usedPlugins[pluginName]; - } - }); - - Object.keys(usedPlugins).forEach((pluginName: string) => { - const plugin = usedPlugins[pluginName]; - if (typeof plugin.finishBuild === 'function') { - const write = (filename: string, contents: string) => { - result.push({ - dest: filename, - sources: [{ - path: null, - contents: contents - }] - }); - }; - plugin.finishBuild(write); - } - }); - - return { - // TODO@TS 2.1.2 - files: extractStrings(removeAllDuplicateTSBoilerplate(result)), - bundleData: bundleData - }; -} - + const modulesMap: IBuildModuleInfoMap = {}; + modules.forEach((m: IBuildModuleInfo) => { + modulesMap[m.id] = m; + }); + const modulesGraph: IGraph = {}; + modules.forEach((m: IBuildModuleInfo) => { + modulesGraph[m.id] = m.dependencies; + }); + const sortedModules = topologicalSort(modulesGraph); + let result: IConcatFile[] = []; + const usedPlugins: IPluginMap = {}; + const bundleData: IBundleData = { + graph: modulesGraph, + bundles: {} + }; + Object.keys(entryPoints).forEach((moduleToBundle: string) => { + const info = entryPoints[moduleToBundle]; + const rootNodes = [moduleToBundle].concat(info.include || []); + const allDependencies = visit(rootNodes, modulesGraph); + const excludes: string[] = ['require', 'exports', 'module'].concat(info.exclude || []); + excludes.forEach((excludeRoot: string) => { + const allExcludes = visit([excludeRoot], modulesGraph); + Object.keys(allExcludes).forEach((exclude: string) => { + delete allDependencies[exclude]; + }); + }); + const includedModules = sortedModules.filter((module: string) => { + return allDependencies[module]; + }); + bundleData.bundles[moduleToBundle] = includedModules; + const res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules, info.prepend || [], info.dest); + result = result.concat(res.files); + for (const pluginName in res.usedPlugins) { + usedPlugins[pluginName] = usedPlugins[pluginName] || res.usedPlugins[pluginName]; + } + }); + Object.keys(usedPlugins).forEach((pluginName: string) => { + const plugin = usedPlugins[pluginName]; + if (typeof plugin.finishBuild === 'function') { + const write = (filename: string, contents: string) => { + result.push({ + dest: filename, + sources: [{ + path: null, + contents: contents + }] + }); + }; + plugin.finishBuild(write); + } + }); + return { + // TODO@TS 2.1.2 + files: extractStrings(removeAllDuplicateTSBoilerplate(result)), + bundleData: bundleData + }; +} function extractStrings(destFiles: IConcatFile[]): IConcatFile[] { - const parseDefineCall = (moduleMatch: string, depsMatch: string) => { - const module = moduleMatch.replace(/^"|"$/g, ''); - let deps = depsMatch.split(','); - deps = deps.map((dep) => { - dep = dep.trim(); - dep = dep.replace(/^"|"$/g, ''); - dep = dep.replace(/^'|'$/g, ''); - let prefix: string | null = null; - let _path: string | null = null; - const pieces = dep.split('!'); - if (pieces.length > 1) { - prefix = pieces[0] + '!'; - _path = pieces[1]; - } else { - prefix = ''; - _path = pieces[0]; - } - - if (/^\.\//.test(_path) || /^\.\.\//.test(_path)) { - const res = path.join(path.dirname(module), _path).replace(/\\/g, '/'); - return prefix + res; - } - return prefix + _path; - }); - return { - module: module, - deps: deps - }; - }; - - destFiles.forEach((destFile) => { - if (!/\.js$/.test(destFile.dest)) { - return; - } - if (/\.nls\.js$/.test(destFile.dest)) { - return; - } - - // Do one pass to record the usage counts for each module id - const useCounts: { [moduleId: string]: number } = {}; - destFile.sources.forEach((source) => { - const matches = source.contents.match(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/); - if (!matches) { - return; - } - - const defineCall = parseDefineCall(matches[1], matches[2]); - useCounts[defineCall.module] = (useCounts[defineCall.module] || 0) + 1; - defineCall.deps.forEach((dep) => { - useCounts[dep] = (useCounts[dep] || 0) + 1; - }); - }); - - const sortedByUseModules = Object.keys(useCounts); - sortedByUseModules.sort((a, b) => { - return useCounts[b] - useCounts[a]; - }); - - const replacementMap: { [moduleId: string]: number } = {}; - sortedByUseModules.forEach((module, index) => { - replacementMap[module] = index; - }); - - destFile.sources.forEach((source) => { - source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, (_, moduleMatch, depsMatch) => { - const defineCall = parseDefineCall(moduleMatch, depsMatch); - return `define(__m[${replacementMap[defineCall.module]}/*${defineCall.module}*/], __M([${defineCall.deps.map(dep => replacementMap[dep] + '/*' + dep + '*/').join(',')}])`; - }); - }); - - destFile.sources.unshift({ - path: null, - contents: [ - '(function() {', - `var __m = ${JSON.stringify(sortedByUseModules)};`, - `var __M = function(deps) {`, - ` var result = [];`, - ` for (var i = 0, len = deps.length; i < len; i++) {`, - ` result[i] = __m[deps[i]];`, - ` }`, - ` return result;`, - `};` - ].join('\n') - }); - - destFile.sources.push({ - path: null, - contents: '}).call(this);' - }); - }); - return destFiles; -} - + const parseDefineCall = (moduleMatch: string, depsMatch: string) => { + const module = moduleMatch.replace(/^"|"$/g, ''); + let deps = depsMatch.split(','); + deps = deps.map((dep) => { + dep = dep.trim(); + dep = dep.replace(/^"|"$/g, ''); + dep = dep.replace(/^'|'$/g, ''); + let prefix: string | null = null; + let _path: string | null = null; + const pieces = dep.split('!'); + if (pieces.length > 1) { + prefix = pieces[0] + '!'; + _path = pieces[1]; + } + else { + prefix = ''; + _path = pieces[0]; + } + if (/^\.\//.test(_path) || /^\.\.\//.test(_path)) { + const res = path.join(path.dirname(module), _path).replace(/\\/g, '/'); + return prefix + res; + } + return prefix + _path; + }); + return { + module: module, + deps: deps + }; + }; + destFiles.forEach((destFile) => { + if (!/\.js$/.test(destFile.dest)) { + return; + } + if (/\.nls\.js$/.test(destFile.dest)) { + return; + } + // Do one pass to record the usage counts for each module id + const useCounts: { + [moduleId: string]: number; + } = {}; + destFile.sources.forEach((source) => { + const matches = source.contents.match(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/); + if (!matches) { + return; + } + const defineCall = parseDefineCall(matches[1], matches[2]); + useCounts[defineCall.module] = (useCounts[defineCall.module] || 0) + 1; + defineCall.deps.forEach((dep) => { + useCounts[dep] = (useCounts[dep] || 0) + 1; + }); + }); + const sortedByUseModules = Object.keys(useCounts); + sortedByUseModules.sort((a, b) => { + return useCounts[b] - useCounts[a]; + }); + const replacementMap: { + [moduleId: string]: number; + } = {}; + sortedByUseModules.forEach((module, index) => { + replacementMap[module] = index; + }); + destFile.sources.forEach((source) => { + source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, (_, moduleMatch, depsMatch) => { + const defineCall = parseDefineCall(moduleMatch, depsMatch); + return `define(__m[${replacementMap[defineCall.module]}/*${defineCall.module}*/], __M([${defineCall.deps.map(dep => replacementMap[dep] + '/*' + dep + '*/').join(',')}])`; + }); + }); + destFile.sources.unshift({ + path: null, + contents: [ + '(function() {', + `var __m = ${JSON.stringify(sortedByUseModules)};`, + `var __M = function(deps) {`, + ` var result = [];`, + ` for (var i = 0, len = deps.length; i < len; i++) {`, + ` result[i] = __m[deps[i]];`, + ` }`, + ` return result;`, + `};` + ].join('\n') + }); + destFile.sources.push({ + path: null, + contents: '}).call(this);' + }); + }); + return destFiles; +} function removeAllDuplicateTSBoilerplate(destFiles: IConcatFile[]): IConcatFile[] { - destFiles.forEach((destFile) => { - const SEEN_BOILERPLATE: boolean[] = []; - destFile.sources.forEach((source) => { - source.contents = removeDuplicateTSBoilerplate(source.contents, SEEN_BOILERPLATE); - }); - }); - - return destFiles; -} - + destFiles.forEach((destFile) => { + const SEEN_BOILERPLATE: boolean[] = []; + destFile.sources.forEach((source) => { + source.contents = removeDuplicateTSBoilerplate(source.contents, SEEN_BOILERPLATE); + }); + }); + return destFiles; +} export function removeAllTSBoilerplate(source: string) { - const seen = new Array(BOILERPLATE.length).fill(true, 0, 10); - return removeDuplicateTSBoilerplate(source, seen); + const seen = new Array(BOILERPLATE.length).fill(true, 0, 10); + return removeDuplicateTSBoilerplate(source, seen); } - // Taken from typescript compiler => emitFiles const BOILERPLATE = [ - { start: /^var __extends/, end: /^}\)\(\);$/ }, - { start: /^var __assign/, end: /^};$/ }, - { start: /^var __decorate/, end: /^};$/ }, - { start: /^var __metadata/, end: /^};$/ }, - { start: /^var __param/, end: /^};$/ }, - { start: /^var __awaiter/, end: /^};$/ }, - { start: /^var __generator/, end: /^};$/ }, - { start: /^var __createBinding/, end: /^}\)\);$/ }, - { start: /^var __setModuleDefault/, end: /^}\);$/ }, - { start: /^var __importStar/, end: /^};$/ }, + { start: /^var __extends/, end: /^}\)\(\);$/ }, + { start: /^var __assign/, end: /^};$/ }, + { start: /^var __decorate/, end: /^};$/ }, + { start: /^var __metadata/, end: /^};$/ }, + { start: /^var __param/, end: /^};$/ }, + { start: /^var __awaiter/, end: /^};$/ }, + { start: /^var __generator/, end: /^};$/ }, + { start: /^var __createBinding/, end: /^}\)\);$/ }, + { start: /^var __setModuleDefault/, end: /^}\);$/ }, + { start: /^var __importStar/, end: /^};$/ }, ]; - function removeDuplicateTSBoilerplate(source: string, SEEN_BOILERPLATE: boolean[] = []): string { - const lines = source.split(/\r\n|\n|\r/); - const newLines: string[] = []; - let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE: RegExp; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - if (END_BOILERPLATE!.test(line)) { - IS_REMOVING_BOILERPLATE = false; - } - } else { - for (let j = 0; j < BOILERPLATE.length; j++) { - const boilerplate = BOILERPLATE[j]; - if (boilerplate.start.test(line)) { - if (SEEN_BOILERPLATE[j]) { - IS_REMOVING_BOILERPLATE = true; - END_BOILERPLATE = boilerplate.end; - } else { - SEEN_BOILERPLATE[j] = true; - } - } - } - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - } else { - newLines.push(line); - } - } - } - return newLines.join('\n'); -} - + const lines = source.split(/\r\n|\n|\r/); + const newLines: string[] = []; + let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE: RegExp; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (IS_REMOVING_BOILERPLATE) { + newLines.push(''); + if (END_BOILERPLATE!.test(line)) { + IS_REMOVING_BOILERPLATE = false; + } + } + else { + for (let j = 0; j < BOILERPLATE.length; j++) { + const boilerplate = BOILERPLATE[j]; + if (boilerplate.start.test(line)) { + if (SEEN_BOILERPLATE[j]) { + IS_REMOVING_BOILERPLATE = true; + END_BOILERPLATE = boilerplate.end; + } + else { + SEEN_BOILERPLATE[j] = true; + } + } + } + if (IS_REMOVING_BOILERPLATE) { + newLines.push(''); + } + else { + newLines.push(line); + } + } + } + return newLines.join('\n'); +} interface IPluginMap { - [moduleId: string]: ILoaderPlugin; + [moduleId: string]: ILoaderPlugin; } - interface IEmitEntryPointResult { - files: IConcatFile[]; - usedPlugins: IPluginMap; -} - -function emitEntryPoint( - modulesMap: IBuildModuleInfoMap, - deps: IGraph, - entryPoint: string, - includedModules: string[], - prepend: IExtraFile[], - dest: string | undefined -): IEmitEntryPointResult { - if (!dest) { - dest = entryPoint + '.js'; - } - const mainResult: IConcatFile = { - sources: [], - dest: dest - }, - results: IConcatFile[] = [mainResult]; - - const usedPlugins: IPluginMap = {}; - const getLoaderPlugin = (pluginName: string): ILoaderPlugin => { - if (!usedPlugins[pluginName]) { - usedPlugins[pluginName] = modulesMap[pluginName].exports; - } - return usedPlugins[pluginName]; - }; - - includedModules.forEach((c: string) => { - const bangIndex = c.indexOf('!'); - - if (bangIndex >= 0) { - const pluginName = c.substr(0, bangIndex); - const plugin = getLoaderPlugin(pluginName); - mainResult.sources.push(emitPlugin(entryPoint, plugin, pluginName, c.substr(bangIndex + 1))); - return; - } - - const module = modulesMap[c]; - - if (module.path === 'empty:') { - return; - } - - const contents = readFileAndRemoveBOM(module.path); - - if (module.shim) { - mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents)); - } else if (module.defineLocation) { - mainResult.sources.push(emitNamedModule(c, module.defineLocation, module.path, contents)); - } else { - const moduleCopy = { - id: module.id, - path: module.path, - defineLocation: module.defineLocation, - dependencies: module.dependencies - }; - throw new Error(`Cannot bundle module '${module.id}' for entry point '${entryPoint}' because it has no shim and it lacks a defineLocation: ${JSON.stringify(moduleCopy)}`); - } - }); - - Object.keys(usedPlugins).forEach((pluginName: string) => { - const plugin = usedPlugins[pluginName]; - if (typeof plugin.writeFile === 'function') { - const req: ILoaderPluginReqFunc = (() => { - throw new Error('no-no!'); - }); - req.toUrl = something => something; - - const write = (filename: string, contents: string) => { - results.push({ - dest: filename, - sources: [{ - path: null, - contents: contents - }] - }); - }; - plugin.writeFile(pluginName, entryPoint, req, write, {}); - } - }); - - const toIFile = (entry: IExtraFile): IFile => { - let contents = readFileAndRemoveBOM(entry.path); - if (entry.amdModuleId) { - contents = contents.replace(/^define\(/m, `define("${entry.amdModuleId}",`); - } - return { - path: entry.path, - contents: contents - }; - }; - - const toPrepend = (prepend || []).map(toIFile); - - mainResult.sources = toPrepend.concat(mainResult.sources); - - return { - files: results, - usedPlugins: usedPlugins - }; -} - + files: IConcatFile[]; + usedPlugins: IPluginMap; +} +function emitEntryPoint(modulesMap: IBuildModuleInfoMap, deps: IGraph, entryPoint: string, includedModules: string[], prepend: IExtraFile[], dest: string | undefined): IEmitEntryPointResult { + if (!dest) { + dest = entryPoint + '.js'; + } + const mainResult: IConcatFile = { + sources: [], + dest: dest + }, results: IConcatFile[] = [mainResult]; + const usedPlugins: IPluginMap = {}; + const getLoaderPlugin = (pluginName: string): ILoaderPlugin => { + if (!usedPlugins[pluginName]) { + usedPlugins[pluginName] = modulesMap[pluginName].exports; + } + return usedPlugins[pluginName]; + }; + includedModules.forEach((c: string) => { + const bangIndex = c.indexOf('!'); + if (bangIndex >= 0) { + const pluginName = c.substr(0, bangIndex); + const plugin = getLoaderPlugin(pluginName); + mainResult.sources.push(emitPlugin(entryPoint, plugin, pluginName, c.substr(bangIndex + 1))); + return; + } + const module = modulesMap[c]; + if (module.path === 'empty:') { + return; + } + const contents = readFileAndRemoveBOM(module.path); + if (module.shim) { + mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents)); + } + else if (module.defineLocation) { + mainResult.sources.push(emitNamedModule(c, module.defineLocation, module.path, contents)); + } + else { + const moduleCopy = { + id: module.id, + path: module.path, + defineLocation: module.defineLocation, + dependencies: module.dependencies + }; + throw new Error(`Cannot bundle module '${module.id}' for entry point '${entryPoint}' because it has no shim and it lacks a defineLocation: ${JSON.stringify(moduleCopy)}`); + } + }); + Object.keys(usedPlugins).forEach((pluginName: string) => { + const plugin = usedPlugins[pluginName]; + if (typeof plugin.writeFile === 'function') { + const req: ILoaderPluginReqFunc = (() => { + throw new Error('no-no!'); + }); + req.toUrl = something => something; + const write = (filename: string, contents: string) => { + results.push({ + dest: filename, + sources: [{ + path: null, + contents: contents + }] + }); + }; + plugin.writeFile(pluginName, entryPoint, req, write, {}); + } + }); + const toIFile = (entry: IExtraFile): IFile => { + let contents = readFileAndRemoveBOM(entry.path); + if (entry.amdModuleId) { + contents = contents.replace(/^define\(/m, `define("${entry.amdModuleId}",`); + } + return { + path: entry.path, + contents: contents + }; + }; + const toPrepend = (prepend || []).map(toIFile); + mainResult.sources = toPrepend.concat(mainResult.sources); + return { + files: results, + usedPlugins: usedPlugins + }; +} function readFileAndRemoveBOM(path: string): string { - const BOM_CHAR_CODE = 65279; - let contents = fs.readFileSync(path, 'utf8'); - // Remove BOM - if (contents.charCodeAt(0) === BOM_CHAR_CODE) { - contents = contents.substring(1); - } - return contents; -} - + const BOM_CHAR_CODE = 65279; + let contents = fs.readFileSync(path, 'utf8'); + // Remove BOM + if (contents.charCodeAt(0) === BOM_CHAR_CODE) { + contents = contents.substring(1); + } + return contents; +} function emitPlugin(entryPoint: string, plugin: ILoaderPlugin, pluginName: string, moduleName: string): IFile { - let result = ''; - if (typeof plugin.write === 'function') { - const write: ILoaderPluginWriteFunc = ((what: string) => { - result += what; - }); - write.getEntryPoint = () => { - return entryPoint; - }; - write.asModule = (moduleId: string, code: string) => { - code = code.replace(/^define\(/, 'define("' + moduleId + '",'); - result += code; - }; - plugin.write(pluginName, moduleName, write); - } - return { - path: null, - contents: result - }; -} - + let result = ''; + if (typeof plugin.write === 'function') { + const write: ILoaderPluginWriteFunc = ((what: string) => { + result += what; + }); + write.getEntryPoint = () => { + return entryPoint; + }; + write.asModule = (moduleId: string, code: string) => { + code = code.replace(/^define\(/, 'define("' + moduleId + '",'); + result += code; + }; + plugin.write(pluginName, moduleName, write); + } + return { + path: null, + contents: result + }; +} function emitNamedModule(moduleId: string, defineCallPosition: IPosition, path: string, contents: string): IFile { - - // `defineCallPosition` is the position in code: |define() - const defineCallOffset = positionToOffset(contents, defineCallPosition.line, defineCallPosition.col); - - // `parensOffset` is the position in code: define|() - const parensOffset = contents.indexOf('(', defineCallOffset); - - const insertStr = '"' + moduleId + '", '; - - return { - path: path, - contents: contents.substr(0, parensOffset + 1) + insertStr + contents.substr(parensOffset + 1) - }; -} - + // `defineCallPosition` is the position in code: |define() + const defineCallOffset = positionToOffset(contents, defineCallPosition.line, defineCallPosition.col); + // `parensOffset` is the position in code: define|() + const parensOffset = contents.indexOf('(', defineCallOffset); + const insertStr = '"' + moduleId + '", '; + return { + path: path, + contents: contents.substr(0, parensOffset + 1) + insertStr + contents.substr(parensOffset + 1) + }; +} function emitShimmedModule(moduleId: string, myDeps: string[], factory: string, path: string, contents: string): IFile { - const strDeps = (myDeps.length > 0 ? '"' + myDeps.join('", "') + '"' : ''); - const strDefine = 'define("' + moduleId + '", [' + strDeps + '], ' + factory + ');'; - return { - path: path, - contents: contents + '\n;\n' + strDefine - }; -} - + const strDeps = (myDeps.length > 0 ? '"' + myDeps.join('", "') + '"' : ''); + const strDefine = 'define("' + moduleId + '", [' + strDeps + '], ' + factory + ');'; + return { + path: path, + contents: contents + '\n;\n' + strDefine + }; +} /** * Convert a position (line:col) to (offset) in string `str` */ function positionToOffset(str: string, desiredLine: number, desiredCol: number): number { - if (desiredLine === 1) { - return desiredCol - 1; - } - - let line = 1; - let lastNewLineOffset = -1; - - do { - if (desiredLine === line) { - return lastNewLineOffset + 1 + desiredCol - 1; - } - lastNewLineOffset = str.indexOf('\n', lastNewLineOffset + 1); - line++; - } while (lastNewLineOffset >= 0); - - return -1; -} - - + if (desiredLine === 1) { + return desiredCol - 1; + } + let line = 1; + let lastNewLineOffset = -1; + do { + if (desiredLine === line) { + return lastNewLineOffset + 1 + desiredCol - 1; + } + lastNewLineOffset = str.indexOf('\n', lastNewLineOffset + 1); + line++; + } while (lastNewLineOffset >= 0); + return -1; +} /** * Return a set of reachable nodes in `graph` starting from `rootNodes` */ function visit(rootNodes: string[], graph: IGraph): INodeSet { - const result: INodeSet = {}; - const queue = rootNodes; - - rootNodes.forEach((node) => { - result[node] = true; - }); - - while (queue.length > 0) { - const el = queue.shift(); - const myEdges = graph[el!] || []; - myEdges.forEach((toNode) => { - if (!result[toNode]) { - result[toNode] = true; - queue.push(toNode); - } - }); - } - - return result; -} - + const result: INodeSet = {}; + const queue = rootNodes; + rootNodes.forEach((node) => { + result[node] = true; + }); + while (queue.length > 0) { + const el = queue.shift(); + const myEdges = graph[el!] || []; + myEdges.forEach((toNode) => { + if (!result[toNode]) { + result[toNode] = true; + queue.push(toNode); + } + }); + } + return result; +} /** * Perform a topological sort on `graph` */ function topologicalSort(graph: IGraph): string[] { - - const allNodes: INodeSet = {}, - outgoingEdgeCount: { [node: string]: number } = {}, - inverseEdges: IGraph = {}; - - Object.keys(graph).forEach((fromNode: string) => { - allNodes[fromNode] = true; - outgoingEdgeCount[fromNode] = graph[fromNode].length; - - graph[fromNode].forEach((toNode) => { - allNodes[toNode] = true; - outgoingEdgeCount[toNode] = outgoingEdgeCount[toNode] || 0; - - inverseEdges[toNode] = inverseEdges[toNode] || []; - inverseEdges[toNode].push(fromNode); - }); - }); - - // https://en.wikipedia.org/wiki/Topological_sorting - const S: string[] = [], - L: string[] = []; - - Object.keys(allNodes).forEach((node: string) => { - if (outgoingEdgeCount[node] === 0) { - delete outgoingEdgeCount[node]; - S.push(node); - } - }); - - while (S.length > 0) { - // Ensure the exact same order all the time with the same inputs - S.sort(); - - const n: string = S.shift()!; - L.push(n); - - const myInverseEdges = inverseEdges[n] || []; - myInverseEdges.forEach((m: string) => { - outgoingEdgeCount[m]--; - if (outgoingEdgeCount[m] === 0) { - delete outgoingEdgeCount[m]; - S.push(m); - } - }); - } - - if (Object.keys(outgoingEdgeCount).length > 0) { - throw new Error('Cannot do topological sort on cyclic graph, remaining nodes: ' + Object.keys(outgoingEdgeCount)); - } - - return L; + const allNodes: INodeSet = {}, outgoingEdgeCount: { + [node: string]: number; + } = {}, inverseEdges: IGraph = {}; + Object.keys(graph).forEach((fromNode: string) => { + allNodes[fromNode] = true; + outgoingEdgeCount[fromNode] = graph[fromNode].length; + graph[fromNode].forEach((toNode) => { + allNodes[toNode] = true; + outgoingEdgeCount[toNode] = outgoingEdgeCount[toNode] || 0; + inverseEdges[toNode] = inverseEdges[toNode] || []; + inverseEdges[toNode].push(fromNode); + }); + }); + // https://en.wikipedia.org/wiki/Topological_sorting + const S: string[] = [], L: string[] = []; + Object.keys(allNodes).forEach((node: string) => { + if (outgoingEdgeCount[node] === 0) { + delete outgoingEdgeCount[node]; + S.push(node); + } + }); + while (S.length > 0) { + // Ensure the exact same order all the time with the same inputs + S.sort(); + const n: string = S.shift()!; + L.push(n); + const myInverseEdges = inverseEdges[n] || []; + myInverseEdges.forEach((m: string) => { + outgoingEdgeCount[m]--; + if (outgoingEdgeCount[m] === 0) { + delete outgoingEdgeCount[m]; + S.push(m); + } + }); + } + if (Object.keys(outgoingEdgeCount).length > 0) { + throw new Error('Cannot do topological sort on cyclic graph, remaining nodes: ' + Object.keys(outgoingEdgeCount)); + } + return L; } diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 124bcc17c17c6..91bce537f2618 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as fs from 'fs'; import * as gulp from 'gulp'; @@ -21,345 +20,303 @@ import { RawSourceMap } from 'source-map'; import { gulpPostcss } from './postcss'; import ts = require('typescript'); const watch = require('./watch'); - - // --- gulp-tsb: compile and transpile -------------------------------- - const reporter = createReporter(); - function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { - const rootDir = path.join(__dirname, `../../${src}`); - const options: ts.CompilerOptions = {}; - options.verbose = false; - options.sourceMap = true; - if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry - options.sourceMap = false; - } - options.rootDir = rootDir; - options.baseUrl = rootDir; - options.sourceRoot = util.toFileUri(rootDir); - options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 0 : 1; - return options; + const rootDir = path.join(__dirname, `../../${src}`); + const options: ts.CompilerOptions = {}; + options.verbose = false; + options.sourceMap = true; + if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry + options.sourceMap = false; + } + options.rootDir = rootDir; + options.baseUrl = rootDir; + options.sourceRoot = util.toFileUri(rootDir); + options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 0 : 1; + return options; } - interface ICompileTaskOptions { - readonly build: boolean; - readonly emitError: boolean; - readonly transpileOnly: boolean | { esbuild: boolean }; - readonly preserveEnglish: boolean; + readonly build: boolean; + readonly emitError: boolean; + readonly transpileOnly: boolean | { + esbuild: boolean; + }; + readonly preserveEnglish: boolean; } - function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish }: ICompileTaskOptions) { - const tsb = require('./tsb') as typeof import('./tsb'); - const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); - - - const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json'); - const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) }; - if (!build) { - overrideOptions.inlineSourceMap = true; - } - - const compilation = tsb.create(projectPath, overrideOptions, { - verbose: false, - transpileOnly: Boolean(transpileOnly), - transpileWithSwc: typeof transpileOnly !== 'boolean' && transpileOnly.esbuild - }, err => reporter(err)); - - function pipeline(token?: util.ICancellationToken) { - const bom = require('gulp-bom') as typeof import('gulp-bom'); - - const tsFilter = util.filter(data => /\.ts$/.test(data.path)); - const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); - const isRuntimeJs = (f: File) => f.path.endsWith('.js') && !f.path.includes('fixtures'); - const isCSS = (f: File) => f.path.endsWith('.css') && !f.path.includes('fixtures'); - const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); - - const postcssNesting = require('postcss-nesting'); - - const input = es.through(); - const output = input - .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise - .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) - .pipe(util.$if(isCSS, gulpPostcss([postcssNesting()], err => reporter(String(err))))) - .pipe(tsFilter) - .pipe(util.loadSourcemaps()) - .pipe(compilation(token)) - .pipe(noDeclarationsFilter) - .pipe(util.$if(build, nls.nls({ preserveEnglish }))) - .pipe(noDeclarationsFilter.restore) - .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { - addComment: false, - includeContent: !!build, - sourceRoot: overrideOptions.sourceRoot - }))) - .pipe(tsFilter.restore) - .pipe(reporter.end(!!emitError)); - - return es.duplex(input, output); - } - pipeline.tsProjectSrc = () => { - return compilation.src({ base: src }); - }; - pipeline.projectPath = projectPath; - return pipeline; + const tsb = require('./tsb') as typeof import('./tsb'); + const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); + const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json'); + const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) }; + if (!build) { + overrideOptions.inlineSourceMap = true; + } + const compilation = tsb.create(projectPath, overrideOptions, { + verbose: false, + transpileOnly: Boolean(transpileOnly), + transpileWithSwc: typeof transpileOnly !== 'boolean' && transpileOnly.esbuild + }, err => reporter(err)); + function pipeline(token?: util.ICancellationToken) { + const bom = require('gulp-bom') as typeof import('gulp-bom'); + const tsFilter = util.filter(data => /\.ts$/.test(data.path)); + const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); + const isRuntimeJs = (f: File) => f.path.endsWith('.js') && !f.path.includes('fixtures'); + const isCSS = (f: File) => f.path.endsWith('.css') && !f.path.includes('fixtures'); + const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); + const postcssNesting = require('postcss-nesting'); + const input = es.through(); + const output = input + .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise + .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) + .pipe(util.$if(isCSS, gulpPostcss([postcssNesting()], err => reporter(String(err))))) + .pipe(tsFilter) + .pipe(util.loadSourcemaps()) + .pipe(compilation(token)) + .pipe(noDeclarationsFilter) + .pipe(util.$if(build, nls.nls({ preserveEnglish }))) + .pipe(noDeclarationsFilter.restore) + .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { + addComment: false, + includeContent: !!build, + sourceRoot: overrideOptions.sourceRoot + }))) + .pipe(tsFilter.restore) + .pipe(reporter.end(!!emitError)); + return es.duplex(input, output); + } + pipeline.tsProjectSrc = () => { + return compilation.src({ base: src }); + }; + pipeline.projectPath = projectPath; + return pipeline; } - export function transpileTask(src: string, out: string, esbuild: boolean): task.StreamTask { - - const task = () => { - - const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild }, preserveEnglish: false }); - const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); - - return srcPipe - .pipe(transpile()) - .pipe(gulp.dest(out)); - }; - - task.taskName = `transpile-${path.basename(src)}`; - return task; + const task = () => { + const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild }, preserveEnglish: false }); + const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); + return srcPipe + .pipe(transpile()) + .pipe(gulp.dest(out)); + }; + task.taskName = `transpile-${path.basename(src)}`; + return task; } - -export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean; preserveEnglish?: boolean } = {}): task.StreamTask { - - const task = () => { - - if (os.totalmem() < 4_000_000_000) { - throw new Error('compilation requires 4GB of RAM'); - } - - const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish }); - const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); - const generator = new MonacoGenerator(false); - if (src === 'src') { - generator.execute(); - } - - // mangle: TypeScript to TypeScript - let mangleStream = es.through(); - if (build && !options.disableMangle) { - let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); - const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); - mangleStream = es.through(async function write(data: File & { sourceMap?: RawSourceMap }) { - type TypeScriptExt = typeof ts & { normalizePath(path: string): string }; - const tsNormalPath = (ts).normalizePath(data.path); - const newContents = (await newContentsByFileName).get(tsNormalPath); - if (newContents !== undefined) { - data.contents = Buffer.from(newContents.out); - data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap); - } - this.push(data); - }, async function end() { - // free resources - (await newContentsByFileName).clear(); - - this.push(null); - (ts2tsMangler) = undefined; - }); - } - - return srcPipe - .pipe(mangleStream) - .pipe(generator.stream) - .pipe(compile()) - .pipe(gulp.dest(out)); - }; - - task.taskName = `compile-${path.basename(src)}`; - return task; +export function compileTask(src: string, out: string, build: boolean, options: { + disableMangle?: boolean; + preserveEnglish?: boolean; +} = {}): task.StreamTask { + const task = () => { + if (os.totalmem() < 4000000000) { + throw new Error('compilation requires 4GB of RAM'); + } + const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish }); + const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); + const generator = new MonacoGenerator(false); + if (src === 'src') { + generator.execute(); + } + // mangle: TypeScript to TypeScript + let mangleStream = es.through(); + if (build && !options.disableMangle) { + let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); + const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); + mangleStream = es.through(async function write(data: File & { + sourceMap?: RawSourceMap; + }) { + type TypeScriptExt = typeof ts & { + normalizePath(path: string): string; + }; + const tsNormalPath = (ts).normalizePath(data.path); + const newContents = (await newContentsByFileName).get(tsNormalPath); + if (newContents !== undefined) { + data.contents = Buffer.from(newContents.out); + data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap); + } + this.push(data); + }, async function end() { + // free resources + (await newContentsByFileName).clear(); + this.push(null); + (ts2tsMangler) = undefined; + }); + } + return srcPipe + .pipe(mangleStream) + .pipe(generator.stream) + .pipe(compile()) + .pipe(gulp.dest(out)); + }; + task.taskName = `compile-${path.basename(src)}`; + return task; } - export function watchTask(out: string, build: boolean, srcPath: string = 'src'): task.StreamTask { - - const task = () => { - const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); - - const src = gulp.src(`${srcPath}/**`, { base: srcPath }); - const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); - - const generator = new MonacoGenerator(true); - generator.execute(); - - return watchSrc - .pipe(generator.stream) - .pipe(util.incremental(compile, src, true)) - .pipe(gulp.dest(out)); - }; - task.taskName = `watch-${path.basename(out)}`; - return task; + const task = () => { + const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); + const src = gulp.src(`${srcPath}/**`, { base: srcPath }); + const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); + const generator = new MonacoGenerator(true); + generator.execute(); + return watchSrc + .pipe(generator.stream) + .pipe(util.incremental(compile, src, true)) + .pipe(gulp.dest(out)); + }; + task.taskName = `watch-${path.basename(out)}`; + return task; } - const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); - class MonacoGenerator { - private readonly _isWatch: boolean; - public readonly stream: NodeJS.ReadWriteStream; - - private readonly _watchedFiles: { [filePath: string]: boolean }; - private readonly _fsProvider: monacodts.FSProvider; - private readonly _declarationResolver: monacodts.DeclarationResolver; - - constructor(isWatch: boolean) { - this._isWatch = isWatch; - this.stream = es.through(); - this._watchedFiles = {}; - const onWillReadFile = (moduleId: string, filePath: string) => { - if (!this._isWatch) { - return; - } - if (this._watchedFiles[filePath]) { - return; - } - this._watchedFiles[filePath] = true; - - fs.watchFile(filePath, () => { - this._declarationResolver.invalidateCache(moduleId); - this._executeSoon(); - }); - }; - this._fsProvider = new class extends monacodts.FSProvider { - public readFileSync(moduleId: string, filePath: string): Buffer { - onWillReadFile(moduleId, filePath); - return super.readFileSync(moduleId, filePath); - } - }; - this._declarationResolver = new monacodts.DeclarationResolver(this._fsProvider); - - if (this._isWatch) { - fs.watchFile(monacodts.RECIPE_PATH, () => { - this._executeSoon(); - }); - } - } - - private _executeSoonTimer: NodeJS.Timeout | null = null; - private _executeSoon(): void { - if (this._executeSoonTimer !== null) { - clearTimeout(this._executeSoonTimer); - this._executeSoonTimer = null; - } - this._executeSoonTimer = setTimeout(() => { - this._executeSoonTimer = null; - this.execute(); - }, 20); - } - - private _run(): monacodts.IMonacoDeclarationResult | null { - const r = monacodts.run3(this._declarationResolver); - if (!r && !this._isWatch) { - // The build must always be able to generate the monaco.d.ts - throw new Error(`monaco.d.ts generation error - Cannot continue`); - } - return r; - } - - private _log(message: any, ...rest: any[]): void { - fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest); - } - - public execute(): void { - const startTime = Date.now(); - const result = this._run(); - if (!result) { - // nothing really changed - return; - } - if (result.isTheSame) { - return; - } - - fs.writeFileSync(result.filePath, result.content); - fs.writeFileSync(path.join(REPO_SRC_FOLDER, 'vs/editor/common/standalone/standaloneEnums.ts'), result.enums); - this._log(`monaco.d.ts is changed - total time took ${Date.now() - startTime} ms`); - if (!this._isWatch) { - this.stream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); - } - } + private readonly _isWatch: boolean; + public readonly stream: NodeJS.ReadWriteStream; + private readonly _watchedFiles: { + [filePath: string]: boolean; + }; + private readonly _fsProvider: monacodts.FSProvider; + private readonly _declarationResolver: monacodts.DeclarationResolver; + constructor(isWatch: boolean) { + this._isWatch = isWatch; + this.stream = es.through(); + this._watchedFiles = {}; + const onWillReadFile = (moduleId: string, filePath: string) => { + if (!this._isWatch) { + return; + } + if (this._watchedFiles[filePath]) { + return; + } + this._watchedFiles[filePath] = true; + fs.watchFile(filePath, () => { + this._declarationResolver.invalidateCache(moduleId); + this._executeSoon(); + }); + }; + this._fsProvider = new class extends monacodts.FSProvider { + public readFileSync(moduleId: string, filePath: string): Buffer { + onWillReadFile(moduleId, filePath); + return super.readFileSync(moduleId, filePath); + } + }; + this._declarationResolver = new monacodts.DeclarationResolver(this._fsProvider); + if (this._isWatch) { + fs.watchFile(monacodts.RECIPE_PATH, () => { + this._executeSoon(); + }); + } + } + private _executeSoonTimer: NodeJS.Timeout | null = null; + private _executeSoon(): void { + if (this._executeSoonTimer !== null) { + clearTimeout(this._executeSoonTimer); + this._executeSoonTimer = null; + } + this._executeSoonTimer = setTimeout(() => { + this._executeSoonTimer = null; + this.execute(); + }, 20); + } + private _run(): monacodts.IMonacoDeclarationResult | null { + const r = monacodts.run3(this._declarationResolver); + if (!r && !this._isWatch) { + // The build must always be able to generate the monaco.d.ts + throw new Error(`monaco.d.ts generation error - Cannot continue`); + } + return r; + } + private _log(message: any, ...rest: any[]): void { + fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest); + } + public execute(): void { + const startTime = Date.now(); + const result = this._run(); + if (!result) { + // nothing really changed + return; + } + if (result.isTheSame) { + return; + } + fs.writeFileSync(result.filePath, result.content); + fs.writeFileSync(path.join(REPO_SRC_FOLDER, 'vs/editor/common/standalone/standaloneEnums.ts'), result.enums); + this._log(`monaco.d.ts is changed - total time took ${Date.now() - startTime} ms`); + if (!this._isWatch) { + this.stream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); + } + } } - function generateApiProposalNames() { - let eol: string; - - try { - const src = fs.readFileSync('src/vs/platform/extensions/common/extensionsApiProposals.ts', 'utf-8'); - const match = /\r?\n/m.exec(src); - eol = match ? match[0] : os.EOL; - } catch { - eol = os.EOL; - } - - const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; - const versionPattern = /^\s*\/\/\s*version\s*:\s*(\d+)\s*$/mi; - const proposals = new Map(); - - const input = es.through(); - const output = input - .pipe(util.filter((f: File) => pattern.test(f.path))) - .pipe(es.through((f: File) => { - const name = path.basename(f.path); - const match = pattern.exec(name); - - if (!match) { - return; - } - - const proposalName = match[1]; - - const contents = f.contents.toString('utf8'); - const versionMatch = versionPattern.exec(contents); - const version = versionMatch ? versionMatch[1] : undefined; - - proposals.set(proposalName, { - proposal: `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${proposalName}.d.ts`, - version: version ? parseInt(version) : undefined - }); - }, function () { - const names = [...proposals.keys()].sort(); - const contents = [ - '/*---------------------------------------------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' * Licensed under the MIT License. See License.txt in the project root for license information.', - ' *--------------------------------------------------------------------------------------------*/', - '', - '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', - '', - 'const _allApiProposals = {', - `${names.map(proposalName => { - const proposal = proposals.get(proposalName)!; - return `\t${proposalName}: {${eol}\t\tproposal: '${proposal.proposal}',${eol}${proposal.version ? `\t\tversion: ${proposal.version}${eol}` : ''}\t}`; - }).join(`,${eol}`)}`, - '};', - 'export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals);', - 'export type ApiProposalName = keyof typeof _allApiProposals;', - '', - ].join(eol); - - this.emit('data', new File({ - path: 'vs/platform/extensions/common/extensionsApiProposals.ts', - contents: Buffer.from(contents) - })); - this.emit('end'); - })); - - return es.duplex(input, output); + let eol: string; + try { + const src = fs.readFileSync('src/vs/platform/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const match = /\r?\n/m.exec(src); + eol = match ? match[0] : os.EOL; + } + catch { + eol = os.EOL; + } + const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; + const versionPattern = /^\s*\/\/\s*version\s*:\s*(\d+)\s*$/mi; + const proposals = new Map(); + const input = es.through(); + const output = input + .pipe(util.filter((f: File) => pattern.test(f.path))) + .pipe(es.through((f: File) => { + const name = path.basename(f.path); + const match = pattern.exec(name); + if (!match) { + return; + } + const proposalName = match[1]; + const contents = f.contents.toString('utf8'); + const versionMatch = versionPattern.exec(contents); + const version = versionMatch ? versionMatch[1] : undefined; + proposals.set(proposalName, { + proposal: `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${proposalName}.d.ts`, + version: version ? parseInt(version) : undefined + }); + }, function () { + const names = [...proposals.keys()].sort(); + const contents = [ + '/*---------------------------------------------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' * Licensed under the MIT License. See License.txt in the project root for license information.', + ' *--------------------------------------------------------------------------------------------*/', + '', + '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', + '', + 'const _allApiProposals = {', + `${names.map(proposalName => { + const proposal = proposals.get(proposalName)!; + return `\t${proposalName}: {${eol}\t\tproposal: '${proposal.proposal}',${eol}${proposal.version ? `\t\tversion: ${proposal.version}${eol}` : ''}\t}`; + }).join(`,${eol}`)}`, + '};', + 'export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals);', + 'export type ApiProposalName = keyof typeof _allApiProposals;', + '', + ].join(eol); + this.emit('data', new File({ + path: 'vs/platform/extensions/common/extensionsApiProposals.ts', + contents: Buffer.from(contents) + })); + this.emit('end'); + })); + return es.duplex(input, output); } - const apiProposalNamesReporter = createReporter('api-proposal-names'); - export const compileApiProposalNamesTask = task.define('compile-api-proposal-names', () => { - return gulp.src('src/vscode-dts/**') - .pipe(generateApiProposalNames()) - .pipe(gulp.dest('src')) - .pipe(apiProposalNamesReporter.end(true)); + return gulp.src('src/vscode-dts/**') + .pipe(generateApiProposalNames()) + .pipe(gulp.dest('src')) + .pipe(apiProposalNamesReporter.end(true)); }); - export const watchApiProposalNamesTask = task.define('watch-api-proposal-names', () => { - const task = () => gulp.src('src/vscode-dts/**') - .pipe(generateApiProposalNames()) - .pipe(apiProposalNamesReporter.end(true)); - - return watch('src/vscode-dts/**', { readDelay: 200 }) - .pipe(util.debounce(task)) - .pipe(gulp.dest('src')); + const task = () => gulp.src('src/vscode-dts/**') + .pipe(generateApiProposalNames()) + .pipe(apiProposalNamesReporter.end(true)); + return watch('src/vscode-dts/**', { readDelay: 200 }) + .pipe(util.debounce(task)) + .pipe(gulp.dest('src')); }); diff --git a/build/lib/date.ts b/build/lib/date.ts index 998e89f8e6ab1..df2d3bbc72ddf 100644 --- a/build/lib/date.ts +++ b/build/lib/date.ts @@ -2,32 +2,26 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as fs from 'fs'; - const root = path.join(__dirname, '..', '..'); - /** * Writes a `outDir/date` file with the contents of the build * so that other tasks during the build process can use it and * all use the same date. */ export function writeISODate(outDir: string) { - const result = () => new Promise((resolve, _) => { - const outDirectory = path.join(root, outDir); - fs.mkdirSync(outDirectory, { recursive: true }); - - const date = new Date().toISOString(); - fs.writeFileSync(path.join(outDirectory, 'date'), date, 'utf8'); - - resolve(); - }); - result.taskName = 'build-date-file'; - return result; + const result = () => new Promise((resolve, _) => { + const outDirectory = path.join(root, outDir); + fs.mkdirSync(outDirectory, { recursive: true }); + const date = new Date().toISOString(); + fs.writeFileSync(path.join(outDirectory, 'date'), date, 'utf8'); + resolve(); + }); + result.taskName = 'build-date-file'; + return result; } - export function readISODate(outDir: string): string { - const outDirectory = path.join(root, outDir); - return fs.readFileSync(path.join(outDirectory, 'date'), 'utf8'); + const outDirectory = path.join(root, outDir); + return fs.readFileSync(path.join(outDirectory, 'date'), 'utf8'); } diff --git a/build/lib/dependencies.ts b/build/lib/dependencies.ts index 45368ffd26dbc..aca4c89e60216 100644 --- a/build/lib/dependencies.ts +++ b/build/lib/dependencies.ts @@ -2,55 +2,49 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as cp from 'child_process'; const root = fs.realpathSync(path.dirname(path.dirname(__dirname))); - function getNpmProductionDependencies(folder: string): string[] { - let raw: string; - - try { - raw = cp.execSync('npm ls --all --omit=dev --parseable', { cwd: folder, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, null] }); - } catch (err) { - const regex = /^npm ERR! .*$/gm; - let match: RegExpExecArray | null; - - while (match = regex.exec(err.message)) { - if (/ELSPROBLEMS/.test(match[0])) { - continue; - } else if (/invalid: xterm/.test(match[0])) { - continue; - } else if (/A complete log of this run/.test(match[0])) { - continue; - } else { - throw err; - } - } - - raw = err.stdout; - } - - return raw.split(/\r?\n/).filter(line => { - return !!line.trim() && path.relative(root, line) !== path.relative(root, folder); - }); + let raw: string; + try { + raw = cp.execSync('npm ls --all --omit=dev --parseable', { cwd: folder, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, null] }); + } + catch (err) { + const regex = /^npm ERR! .*$/gm; + let match: RegExpExecArray | null; + while (match = regex.exec(err.message)) { + if (/ELSPROBLEMS/.test(match[0])) { + continue; + } + else if (/invalid: xterm/.test(match[0])) { + continue; + } + else if (/A complete log of this run/.test(match[0])) { + continue; + } + else { + throw err; + } + } + raw = err.stdout; + } + return raw.split(/\r?\n/).filter(line => { + return !!line.trim() && path.relative(root, line) !== path.relative(root, folder); + }); } - export function getProductionDependencies(folderPath: string): string[] { - const result = getNpmProductionDependencies(folderPath); - // Account for distro npm dependencies - const realFolderPath = fs.realpathSync(folderPath); - const relativeFolderPath = path.relative(root, realFolderPath); - const distroFolderPath = `${root}/.build/distro/npm/${relativeFolderPath}`; - - if (fs.existsSync(distroFolderPath)) { - result.push(...getNpmProductionDependencies(distroFolderPath)); - } - - return [...new Set(result)]; + const result = getNpmProductionDependencies(folderPath); + // Account for distro npm dependencies + const realFolderPath = fs.realpathSync(folderPath); + const relativeFolderPath = path.relative(root, realFolderPath); + const distroFolderPath = `${root}/.build/distro/npm/${relativeFolderPath}`; + if (fs.existsSync(distroFolderPath)) { + result.push(...getNpmProductionDependencies(distroFolderPath)); + } + return [...new Set(result)]; } - if (require.main === module) { - console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); + console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); } diff --git a/build/lib/electron.ts b/build/lib/electron.ts index da2387f68f637..d089417338448 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -2,42 +2,35 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as vfs from 'vinyl-fs'; import * as filter from 'gulp-filter'; import * as util from './util'; import { getVersion } from './getVersion'; - type DarwinDocumentSuffix = 'document' | 'script' | 'file' | 'source code'; type DarwinDocumentType = { - name: string; - role: string; - ostypes: string[]; - extensions: string[]; - iconFile: string; - utis?: string[]; + name: string; + role: string; + ostypes: string[]; + extensions: string[]; + iconFile: string; + utis?: string[]; }; - function isDocumentSuffix(str?: string): str is DarwinDocumentSuffix { - return str === 'document' || str === 'script' || str === 'file' || str === 'source code'; + return str === 'document' || str === 'script' || str === 'file' || str === 'source code'; } - const root = path.dirname(path.dirname(__dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const commit = getVersion(root); - function createTemplate(input: string): (params: Record) => string { - return (params: Record) => { - return input.replace(/<%=\s*([^\s]+)\s*%>/g, (match, key) => { - return params[key] || match; - }); - }; + return (params: Record) => { + return input.replace(/<%=\s*([^\s]+)\s*%>/g, (match, key) => { + return params[key] || match; + }); + }; } - const darwinCreditsTemplate = product.darwinCredits && createTemplate(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); - /** * Generate a `DarwinDocumentType` given a list of file extensions, an icon name, and an optional suffix or file type name. * @param extensions A list of file extensions, such as `['bat', 'cmd']` @@ -58,21 +51,19 @@ const darwinCreditsTemplate = product.darwinCredits && createTemplate(fs.readFil * and the `'bat'` darwin icon is used. */ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuffix?: string | DarwinDocumentSuffix, utis?: string[]): DarwinDocumentType { - // If given a suffix, generate a name from it. If not given anything, default to 'document' - if (isDocumentSuffix(nameOrSuffix) || !nameOrSuffix) { - nameOrSuffix = icon.charAt(0).toUpperCase() + icon.slice(1) + ' ' + (nameOrSuffix ?? 'document'); - } - - return { - name: nameOrSuffix, - role: 'Editor', - ostypes: ['TEXT', 'utxt', 'TUTX', '****'], - extensions, - iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', - utis - }; + // If given a suffix, generate a name from it. If not given anything, default to 'document' + if (isDocumentSuffix(nameOrSuffix) || !nameOrSuffix) { + nameOrSuffix = icon.charAt(0).toUpperCase() + icon.slice(1) + ' ' + (nameOrSuffix ?? 'document'); + } + return { + name: nameOrSuffix, + role: 'Editor', + ostypes: ['TEXT', 'utxt', 'TUTX', '****'], + extensions, + iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', + utis + }; } - /** * Generate several `DarwinDocumentType`s with unique names and a shared icon. * @param types A map of file type names to their associated file extensions. @@ -84,161 +75,155 @@ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuff * darwinBundleDocumentTypes({ 'React source code': ['jsx', 'tsx'] }, 'react') * ``` */ -function darwinBundleDocumentTypes(types: { [name: string]: string | string[] }, icon: string): DarwinDocumentType[] { - return Object.keys(types).map((name: string): DarwinDocumentType => { - const extensions = types[name]; - return { - name, - role: 'Editor', - ostypes: ['TEXT', 'utxt', 'TUTX', '****'], - extensions: Array.isArray(extensions) ? extensions : [extensions], - iconFile: 'resources/darwin/' + icon + '.icns' - }; - }); +function darwinBundleDocumentTypes(types: { + [name: string]: string | string[]; +}, icon: string): DarwinDocumentType[] { + return Object.keys(types).map((name: string): DarwinDocumentType => { + const extensions = types[name]; + return { + name, + role: 'Editor', + ostypes: ['TEXT', 'utxt', 'TUTX', '****'], + extensions: Array.isArray(extensions) ? extensions : [extensions], + iconFile: 'resources/darwin/' + icon + '.icns' + }; + }); } - const { electronVersion, msBuildId } = util.getElectronVersion(); - export const config = { - version: electronVersion, - tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, - productAppName: product.nameLong, - companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2024 Microsoft. All rights reserved', - darwinIcon: 'resources/darwin/code.icns', - darwinBundleIdentifier: product.darwinBundleIdentifier, - darwinApplicationCategoryType: 'public.app-category.developer-tools', - darwinHelpBookFolder: 'VS Code HelpBook', - darwinHelpBookName: 'VS Code HelpBook', - darwinBundleDocumentTypes: [ - ...darwinBundleDocumentTypes({ 'C header file': 'h', 'C source code': 'c' }, 'c'), - ...darwinBundleDocumentTypes({ 'Git configuration file': ['gitattributes', 'gitconfig', 'gitignore'] }, 'config'), - ...darwinBundleDocumentTypes({ 'HTML template document': ['asp', 'aspx', 'cshtml', 'jshtm', 'jsp', 'phtml', 'shtml'] }, 'html'), - darwinBundleDocumentType(['bat', 'cmd'], 'bat', 'Windows command script'), - darwinBundleDocumentType(['bowerrc'], 'Bower'), - darwinBundleDocumentType(['config', 'editorconfig', 'ini', 'cfg'], 'config', 'Configuration file'), - darwinBundleDocumentType(['hh', 'hpp', 'hxx', 'h++'], 'cpp', 'C++ header file'), - darwinBundleDocumentType(['cc', 'cpp', 'cxx', 'c++'], 'cpp', 'C++ source code'), - darwinBundleDocumentType(['m'], 'default', 'Objective-C source code'), - darwinBundleDocumentType(['mm'], 'cpp', 'Objective-C++ source code'), - darwinBundleDocumentType(['cs', 'csx'], 'csharp', 'C# source code'), - darwinBundleDocumentType(['css'], 'css', 'CSS'), - darwinBundleDocumentType(['go'], 'go', 'Go source code'), - darwinBundleDocumentType(['htm', 'html', 'xhtml'], 'HTML'), - darwinBundleDocumentType(['jade'], 'Jade'), - darwinBundleDocumentType(['jav', 'java'], 'Java'), - darwinBundleDocumentType(['js', 'jscsrc', 'jshintrc', 'mjs', 'cjs'], 'Javascript', 'file'), - darwinBundleDocumentType(['json'], 'JSON'), - darwinBundleDocumentType(['less'], 'Less'), - darwinBundleDocumentType(['markdown', 'md', 'mdoc', 'mdown', 'mdtext', 'mdtxt', 'mdwn', 'mkd', 'mkdn'], 'Markdown'), - darwinBundleDocumentType(['php'], 'PHP', 'source code'), - darwinBundleDocumentType(['ps1', 'psd1', 'psm1'], 'Powershell', 'script'), - darwinBundleDocumentType(['py', 'pyi'], 'Python', 'script'), - darwinBundleDocumentType(['gemspec', 'rb', 'erb'], 'Ruby', 'source code'), - darwinBundleDocumentType(['scss', 'sass'], 'SASS', 'file'), - darwinBundleDocumentType(['sql'], 'SQL', 'script'), - darwinBundleDocumentType(['ts'], 'TypeScript', 'file'), - darwinBundleDocumentType(['tsx', 'jsx'], 'React', 'source code'), - darwinBundleDocumentType(['vue'], 'Vue', 'source code'), - darwinBundleDocumentType(['ascx', 'csproj', 'dtd', 'plist', 'wxi', 'wxl', 'wxs', 'xml', 'xaml'], 'XML'), - darwinBundleDocumentType(['eyaml', 'eyml', 'yaml', 'yml'], 'YAML'), - darwinBundleDocumentType([ - 'bash', 'bash_login', 'bash_logout', 'bash_profile', 'bashrc', - 'profile', 'rhistory', 'rprofile', 'sh', 'zlogin', 'zlogout', - 'zprofile', 'zsh', 'zshenv', 'zshrc' - ], 'Shell', 'script'), - // Default icon with specified names - ...darwinBundleDocumentTypes({ - 'Clojure source code': ['clj', 'cljs', 'cljx', 'clojure'], - 'VS Code workspace file': 'code-workspace', - 'CoffeeScript source code': 'coffee', - 'Comma Separated Values': 'csv', - 'CMake script': 'cmake', - 'Dart script': 'dart', - 'Diff file': 'diff', - 'Dockerfile': 'dockerfile', - 'Gradle file': 'gradle', - 'Groovy script': 'groovy', - 'Makefile': ['makefile', 'mk'], - 'Lua script': 'lua', - 'Pug document': 'pug', - 'Jupyter': 'ipynb', - 'Lockfile': 'lock', - 'Log file': 'log', - 'Plain Text File': 'txt', - 'Xcode project file': 'xcodeproj', - 'Xcode workspace file': 'xcworkspace', - 'Visual Basic script': 'vb', - 'R source code': 'r', - 'Rust source code': 'rs', - 'Restructured Text document': 'rst', - 'LaTeX document': ['tex', 'cls'], - 'F# source code': 'fs', - 'F# signature file': 'fsi', - 'F# script': ['fsx', 'fsscript'], - 'SVG document': ['svg', 'svgz'], - 'TOML document': 'toml', - 'Swift source code': 'swift', - }, 'default'), - // Default icon with default name - darwinBundleDocumentType([ - 'containerfile', 'ctp', 'dot', 'edn', 'handlebars', 'hbs', 'ml', 'mli', - 'pl', 'pl6', 'pm', 'pm6', 'pod', 'pp', 'properties', 'psgi', 'rt', 't' - ], 'default', product.nameLong + ' document'), - // Folder support () - darwinBundleDocumentType([], 'default', 'Folder', ['public.folder']) - ], - darwinBundleURLTypes: [{ - role: 'Viewer', - name: product.nameLong, - urlSchemes: [product.urlProtocol] - }], - darwinForceDarkModeSupport: true, - darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, - linuxExecutableName: product.applicationName, - winIcon: 'resources/win32/code.ico', - token: process.env['GITHUB_TOKEN'], - repo: product.electronRepository || undefined, - validateChecksum: true, - checksumFile: path.join(root, 'build', 'checksums', 'electron.txt'), + version: electronVersion, + tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, + productAppName: product.nameLong, + companyName: 'Microsoft Corporation', + copyright: 'Copyright (C) 2024 Microsoft. All rights reserved', + darwinIcon: 'resources/darwin/code.icns', + darwinBundleIdentifier: product.darwinBundleIdentifier, + darwinApplicationCategoryType: 'public.app-category.developer-tools', + darwinHelpBookFolder: 'VS Code HelpBook', + darwinHelpBookName: 'VS Code HelpBook', + darwinBundleDocumentTypes: [ + ...darwinBundleDocumentTypes({ 'C header file': 'h', 'C source code': 'c' }, 'c'), + ...darwinBundleDocumentTypes({ 'Git configuration file': ['gitattributes', 'gitconfig', 'gitignore'] }, 'config'), + ...darwinBundleDocumentTypes({ 'HTML template document': ['asp', 'aspx', 'cshtml', 'jshtm', 'jsp', 'phtml', 'shtml'] }, 'html'), + darwinBundleDocumentType(['bat', 'cmd'], 'bat', 'Windows command script'), + darwinBundleDocumentType(['bowerrc'], 'Bower'), + darwinBundleDocumentType(['config', 'editorconfig', 'ini', 'cfg'], 'config', 'Configuration file'), + darwinBundleDocumentType(['hh', 'hpp', 'hxx', 'h++'], 'cpp', 'C++ header file'), + darwinBundleDocumentType(['cc', 'cpp', 'cxx', 'c++'], 'cpp', 'C++ source code'), + darwinBundleDocumentType(['m'], 'default', 'Objective-C source code'), + darwinBundleDocumentType(['mm'], 'cpp', 'Objective-C++ source code'), + darwinBundleDocumentType(['cs', 'csx'], 'csharp', 'C# source code'), + darwinBundleDocumentType(['css'], 'css', 'CSS'), + darwinBundleDocumentType(['go'], 'go', 'Go source code'), + darwinBundleDocumentType(['htm', 'html', 'xhtml'], 'HTML'), + darwinBundleDocumentType(['jade'], 'Jade'), + darwinBundleDocumentType(['jav', 'java'], 'Java'), + darwinBundleDocumentType(['js', 'jscsrc', 'jshintrc', 'mjs', 'cjs'], 'Javascript', 'file'), + darwinBundleDocumentType(['json'], 'JSON'), + darwinBundleDocumentType(['less'], 'Less'), + darwinBundleDocumentType(['markdown', 'md', 'mdoc', 'mdown', 'mdtext', 'mdtxt', 'mdwn', 'mkd', 'mkdn'], 'Markdown'), + darwinBundleDocumentType(['php'], 'PHP', 'source code'), + darwinBundleDocumentType(['ps1', 'psd1', 'psm1'], 'Powershell', 'script'), + darwinBundleDocumentType(['py', 'pyi'], 'Python', 'script'), + darwinBundleDocumentType(['gemspec', 'rb', 'erb'], 'Ruby', 'source code'), + darwinBundleDocumentType(['scss', 'sass'], 'SASS', 'file'), + darwinBundleDocumentType(['sql'], 'SQL', 'script'), + darwinBundleDocumentType(['ts'], 'TypeScript', 'file'), + darwinBundleDocumentType(['tsx', 'jsx'], 'React', 'source code'), + darwinBundleDocumentType(['vue'], 'Vue', 'source code'), + darwinBundleDocumentType(['ascx', 'csproj', 'dtd', 'plist', 'wxi', 'wxl', 'wxs', 'xml', 'xaml'], 'XML'), + darwinBundleDocumentType(['eyaml', 'eyml', 'yaml', 'yml'], 'YAML'), + darwinBundleDocumentType([ + 'bash', 'bash_login', 'bash_logout', 'bash_profile', 'bashrc', + 'profile', 'rhistory', 'rprofile', 'sh', 'zlogin', 'zlogout', + 'zprofile', 'zsh', 'zshenv', 'zshrc' + ], 'Shell', 'script'), + // Default icon with specified names + ...darwinBundleDocumentTypes({ + 'Clojure source code': ['clj', 'cljs', 'cljx', 'clojure'], + 'VS Code workspace file': 'code-workspace', + 'CoffeeScript source code': 'coffee', + 'Comma Separated Values': 'csv', + 'CMake script': 'cmake', + 'Dart script': 'dart', + 'Diff file': 'diff', + 'Dockerfile': 'dockerfile', + 'Gradle file': 'gradle', + 'Groovy script': 'groovy', + 'Makefile': ['makefile', 'mk'], + 'Lua script': 'lua', + 'Pug document': 'pug', + 'Jupyter': 'ipynb', + 'Lockfile': 'lock', + 'Log file': 'log', + 'Plain Text File': 'txt', + 'Xcode project file': 'xcodeproj', + 'Xcode workspace file': 'xcworkspace', + 'Visual Basic script': 'vb', + 'R source code': 'r', + 'Rust source code': 'rs', + 'Restructured Text document': 'rst', + 'LaTeX document': ['tex', 'cls'], + 'F# source code': 'fs', + 'F# signature file': 'fsi', + 'F# script': ['fsx', 'fsscript'], + 'SVG document': ['svg', 'svgz'], + 'TOML document': 'toml', + 'Swift source code': 'swift', + }, 'default'), + // Default icon with default name + darwinBundleDocumentType([ + 'containerfile', 'ctp', 'dot', 'edn', 'handlebars', 'hbs', 'ml', 'mli', + 'pl', 'pl6', 'pm', 'pm6', 'pod', 'pp', 'properties', 'psgi', 'rt', 't' + ], 'default', product.nameLong + ' document'), + // Folder support () + darwinBundleDocumentType([], 'default', 'Folder', ['public.folder']) + ], + darwinBundleURLTypes: [{ + role: 'Viewer', + name: product.nameLong, + urlSchemes: [product.urlProtocol] + }], + darwinForceDarkModeSupport: true, + darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, + linuxExecutableName: product.applicationName, + winIcon: 'resources/win32/code.ico', + token: process.env['GITHUB_TOKEN'], + repo: product.electronRepository || undefined, + validateChecksum: true, + checksumFile: path.join(root, 'build', 'checksums', 'electron.txt'), }; - function getElectron(arch: string): () => NodeJS.ReadWriteStream { - return () => { - const electron = require('@vscode/gulp-electron'); - const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); - - const electronOpts = { - ...config, - platform: process.platform, - arch: arch === 'armhf' ? 'arm' : arch, - ffmpegChromium: false, - keepDefaultApp: true - }; - - return vfs.src('package.json') - .pipe(json({ name: product.nameShort })) - .pipe(electron(electronOpts)) - .pipe(filter(['**', '!**/app/package.json'])) - .pipe(vfs.dest('.build/electron')); - }; + return () => { + const electron = require('@vscode/gulp-electron'); + const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); + const electronOpts = { + ...config, + platform: process.platform, + arch: arch === 'armhf' ? 'arm' : arch, + ffmpegChromium: false, + keepDefaultApp: true + }; + return vfs.src('package.json') + .pipe(json({ name: product.nameShort })) + .pipe(electron(electronOpts)) + .pipe(filter(['**', '!**/app/package.json'])) + .pipe(vfs.dest('.build/electron')); + }; } - async function main(arch: string = process.arch): Promise { - const version = electronVersion; - const electronPath = path.join(root, '.build', 'electron'); - const versionFile = path.join(electronPath, 'version'); - const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; - - if (!isUpToDate) { - await util.rimraf(electronPath)(); - await util.streamToPromise(getElectron(arch)()); - } + const version = electronVersion; + const electronPath = path.join(root, '.build', 'electron'); + const versionFile = path.join(electronPath, 'version'); + const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; + if (!isUpToDate) { + await util.rimraf(electronPath)(); + await util.streamToPromise(getElectron(arch)()); + } } - if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 628cf90c4c919..64a6712af0590 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as fs from 'fs'; import * as cp from 'child_process'; @@ -25,563 +24,503 @@ import { getProductionDependencies } from './dependencies'; import { IExtensionDefinition, getExtensionStream } from './builtInExtensions'; import { getVersion } from './getVersion'; import { fetchUrls, fetchGithub } from './fetch'; - const root = path.dirname(path.dirname(__dirname)); const commit = getVersion(root); const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; - function minifyExtensionResources(input: Stream): Stream { - const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); - return input - .pipe(jsonFilter) - .pipe(buffer()) - .pipe(es.mapSync((f: File) => { - const errors: jsoncParser.ParseError[] = []; - const value = jsoncParser.parse(f.contents.toString('utf8'), errors, { allowTrailingComma: true }); - if (errors.length === 0) { - // file parsed OK => just stringify to drop whitespace and comments - f.contents = Buffer.from(JSON.stringify(value)); - } - return f; - })) - .pipe(jsonFilter.restore); + const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); + return input + .pipe(jsonFilter) + .pipe(buffer()) + .pipe(es.mapSync((f: File) => { + const errors: jsoncParser.ParseError[] = []; + const value = jsoncParser.parse(f.contents.toString('utf8'), errors, { allowTrailingComma: true }); + if (errors.length === 0) { + // file parsed OK => just stringify to drop whitespace and comments + f.contents = Buffer.from(JSON.stringify(value)); + } + return f; + })) + .pipe(jsonFilter.restore); } - function updateExtensionPackageJSON(input: Stream, update: (data: any) => any): Stream { - const packageJsonFilter = filter('extensions/*/package.json', { restore: true }); - return input - .pipe(packageJsonFilter) - .pipe(buffer()) - .pipe(es.mapSync((f: File) => { - const data = JSON.parse(f.contents.toString('utf8')); - f.contents = Buffer.from(JSON.stringify(update(data))); - return f; - })) - .pipe(packageJsonFilter.restore); + const packageJsonFilter = filter('extensions/*/package.json', { restore: true }); + return input + .pipe(packageJsonFilter) + .pipe(buffer()) + .pipe(es.mapSync((f: File) => { + const data = JSON.parse(f.contents.toString('utf8')); + f.contents = Buffer.from(JSON.stringify(update(data))); + return f; + })) + .pipe(packageJsonFilter.restore); } - function fromLocal(extensionPath: string, forWeb: boolean, disableMangle: boolean): Stream { - const webpackConfigFileName = forWeb ? 'extension-browser.webpack.config.js' : 'extension.webpack.config.js'; - - const isWebPacked = fs.existsSync(path.join(extensionPath, webpackConfigFileName)); - let input = isWebPacked - ? fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) - : fromLocalNormal(extensionPath); - - if (isWebPacked) { - input = updateExtensionPackageJSON(input, (data: any) => { - delete data.scripts; - delete data.dependencies; - delete data.devDependencies; - if (data.main) { - data.main = data.main.replace('/out/', '/dist/'); - } - return data; - }); - } - - return input; + const webpackConfigFileName = forWeb ? 'extension-browser.webpack.config.js' : 'extension.webpack.config.js'; + const isWebPacked = fs.existsSync(path.join(extensionPath, webpackConfigFileName)); + let input = isWebPacked + ? fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) + : fromLocalNormal(extensionPath); + if (isWebPacked) { + input = updateExtensionPackageJSON(input, (data: any) => { + delete data.scripts; + delete data.dependencies; + delete data.devDependencies; + if (data.main) { + data.main = data.main.replace('/out/', '/dist/'); + } + return data; + }); + } + return input; } - - function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, disableMangle: boolean): Stream { - const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce'); - const webpack = require('webpack'); - const webpackGulp = require('webpack-stream'); - const result = es.through(); - - const packagedDependencies: string[] = []; - const packageJsonConfig = require(path.join(extensionPath, 'package.json')); - if (packageJsonConfig.dependencies) { - const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName)); - for (const key in webpackRootConfig.externals) { - if (key in packageJsonConfig.dependencies) { - packagedDependencies.push(key); - } - } - } - - // TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar - // to vsce.PackageManager.Yarn. - // A static analysis showed there are no webpack externals that are dependencies of the current - // local extensions so we can use the vsce.PackageManager.None config to ignore dependencies list - // as a temporary workaround. - vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.None, packagedDependencies }).then(fileNames => { - const files = fileNames - .map(fileName => path.join(extensionPath, fileName)) - .map(filePath => new File({ - path: filePath, - stat: fs.statSync(filePath), - base: extensionPath, - contents: fs.createReadStream(filePath) as any - })); - - // check for a webpack configuration files, then invoke webpack - // and merge its output with the files stream. - const webpackConfigLocations = (glob.sync( - path.join(extensionPath, '**', webpackConfigFileName), - { ignore: ['**/node_modules'] } - )); - - const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { - - const webpackDone = (err: any, stats: any) => { - fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); - if (err) { - result.emit('error', err); - } - const { compilation } = stats; - if (compilation.errors.length > 0) { - result.emit('error', compilation.errors.join('\n')); - } - if (compilation.warnings.length > 0) { - result.emit('error', compilation.warnings.join('\n')); - } - }; - - const exportedConfig = require(webpackConfigPath); - return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { - const webpackConfig = { - ...config, - ...{ mode: 'production' } - }; - if (disableMangle) { - if (Array.isArray(config.module.rules)) { - for (const rule of config.module.rules) { - if (Array.isArray(rule.use)) { - for (const use of rule.use) { - if (String(use.loader).endsWith('mangle-loader.js')) { - use.options.disabled = true; - } - } - } - } - } - } - const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); - - return webpackGulp(webpackConfig, webpack, webpackDone) - .pipe(es.through(function (data) { - data.stat = data.stat || {}; - data.base = extensionPath; - this.emit('data', data); - })) - .pipe(es.through(function (data: File) { - // source map handling: - // * rewrite sourceMappingURL - // * save to disk so that upload-task picks this up - const contents = (data.contents).toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); - - this.emit('data', data); - })); - }); - }); - - es.merge(...webpackStreams, es.readArray(files)) - // .pipe(es.through(function (data) { - // // debug - // console.log('out', data.path, data.contents.length); - // this.emit('data', data); - // })) - .pipe(result); - - }).catch(err => { - console.error(extensionPath); - console.error(packagedDependencies); - result.emit('error', err); - }); - - return result.pipe(createStatsStream(path.basename(extensionPath))); + const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce'); + const webpack = require('webpack'); + const webpackGulp = require('webpack-stream'); + const result = es.through(); + const packagedDependencies: string[] = []; + const packageJsonConfig = require(path.join(extensionPath, 'package.json')); + if (packageJsonConfig.dependencies) { + const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName)); + for (const key in webpackRootConfig.externals) { + if (key in packageJsonConfig.dependencies) { + packagedDependencies.push(key); + } + } + } + // TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar + // to vsce.PackageManager.Yarn. + // A static analysis showed there are no webpack externals that are dependencies of the current + // local extensions so we can use the vsce.PackageManager.None config to ignore dependencies list + // as a temporary workaround. + vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.None, packagedDependencies }).then(fileNames => { + const files = fileNames + .map(fileName => path.join(extensionPath, fileName)) + .map(filePath => new File({ + path: filePath, + stat: fs.statSync(filePath), + base: extensionPath, + contents: fs.createReadStream(filePath) as any + })); + // check for a webpack configuration files, then invoke webpack + // and merge its output with the files stream. + const webpackConfigLocations = (glob.sync(path.join(extensionPath, '**', webpackConfigFileName), { ignore: ['**/node_modules'] })); + const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { + const webpackDone = (err: any, stats: any) => { + fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); + if (err) { + result.emit('error', err); + } + const { compilation } = stats; + if (compilation.errors.length > 0) { + result.emit('error', compilation.errors.join('\n')); + } + if (compilation.warnings.length > 0) { + result.emit('error', compilation.warnings.join('\n')); + } + }; + const exportedConfig = require(webpackConfigPath); + return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { + const webpackConfig = { + ...config, + ...{ mode: 'production' } + }; + if (disableMangle) { + if (Array.isArray(config.module.rules)) { + for (const rule of config.module.rules) { + if (Array.isArray(rule.use)) { + for (const use of rule.use) { + if (String(use.loader).endsWith('mangle-loader.js')) { + use.options.disabled = true; + } + } + } + } + } + } + const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); + return webpackGulp(webpackConfig, webpack, webpackDone) + .pipe(es.through(function (data) { + data.stat = data.stat || {}; + data.base = extensionPath; + this.emit('data', data); + })) + .pipe(es.through(function (data: File) { + // source map handling: + // * rewrite sourceMappingURL + // * save to disk so that upload-task picks this up + const contents = (data.contents).toString('utf8'); + data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { + return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; + }), 'utf8'); + this.emit('data', data); + })); + }); + }); + es.merge(...webpackStreams, es.readArray(files)) + // .pipe(es.through(function (data) { + // // debug + // console.log('out', data.path, data.contents.length); + // this.emit('data', data); + // })) + .pipe(result); + }).catch(err => { + console.error(extensionPath); + console.error(packagedDependencies); + result.emit('error', err); + }); + return result.pipe(createStatsStream(path.basename(extensionPath))); } - function fromLocalNormal(extensionPath: string): Stream { - const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce'); - const result = es.through(); - - vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Npm }) - .then(fileNames => { - const files = fileNames - .map(fileName => path.join(extensionPath, fileName)) - .map(filePath => new File({ - path: filePath, - stat: fs.statSync(filePath), - base: extensionPath, - contents: fs.createReadStream(filePath) as any - })); - - es.readArray(files).pipe(result); - }) - .catch(err => result.emit('error', err)); - - return result.pipe(createStatsStream(path.basename(extensionPath))); + const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce'); + const result = es.through(); + vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Npm }) + .then(fileNames => { + const files = fileNames + .map(fileName => path.join(extensionPath, fileName)) + .map(filePath => new File({ + path: filePath, + stat: fs.statSync(filePath), + base: extensionPath, + contents: fs.createReadStream(filePath) as any + })); + es.readArray(files).pipe(result); + }) + .catch(err => result.emit('error', err)); + return result.pipe(createStatsStream(path.basename(extensionPath))); } - const userAgent = 'VSCode Build'; const baseHeaders = { - 'X-Market-Client-Id': 'VSCode Build', - 'User-Agent': userAgent, - 'X-Market-User-Id': '291C1CD0-051A-4123-9B4B-30D60EF52EE2', + 'X-Market-Client-Id': 'VSCode Build', + 'User-Agent': userAgent, + 'X-Market-User-Id': '291C1CD0-051A-4123-9B4B-30D60EF52EE2', }; - export function fromMarketplace(serviceUrl: string, { name: extensionName, version, sha256, metadata }: IExtensionDefinition): Stream { - const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); - - const [publisher, name] = extensionName.split('.'); - const url = `${serviceUrl}/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`; - - fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...'); - - const packageJsonFilter = filter('package.json', { restore: true }); - - return fetchUrls('', { - base: url, - nodeFetchOptions: { - headers: baseHeaders - }, - checksumSha256: sha256 - }) - .pipe(vzip.src()) - .pipe(filter('extension/**')) - .pipe(rename(p => p.dirname = p.dirname!.replace(/^extension\/?/, ''))) - .pipe(packageJsonFilter) - .pipe(buffer()) - .pipe(json({ __metadata: metadata })) - .pipe(packageJsonFilter.restore); + const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); + const [publisher, name] = extensionName.split('.'); + const url = `${serviceUrl}/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`; + fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...'); + const packageJsonFilter = filter('package.json', { restore: true }); + return fetchUrls('', { + base: url, + nodeFetchOptions: { + headers: baseHeaders + }, + checksumSha256: sha256 + }) + .pipe(vzip.src()) + .pipe(filter('extension/**')) + .pipe(rename(p => p.dirname = p.dirname!.replace(/^extension\/?/, ''))) + .pipe(packageJsonFilter) + .pipe(buffer()) + .pipe(json({ __metadata: metadata })) + .pipe(packageJsonFilter.restore); } - - export function fromGithub({ name, version, repo, sha256, metadata }: IExtensionDefinition): Stream { - const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); - - fancyLog('Downloading extension from GH:', ansiColors.yellow(`${name}@${version}`), '...'); - - const packageJsonFilter = filter('package.json', { restore: true }); - - return fetchGithub(new URL(repo).pathname, { - version, - name: name => name.endsWith('.vsix'), - checksumSha256: sha256 - }) - .pipe(buffer()) - .pipe(vzip.src()) - .pipe(filter('extension/**')) - .pipe(rename(p => p.dirname = p.dirname!.replace(/^extension\/?/, ''))) - .pipe(packageJsonFilter) - .pipe(buffer()) - .pipe(json({ __metadata: metadata })) - .pipe(packageJsonFilter.restore); + const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); + fancyLog('Downloading extension from GH:', ansiColors.yellow(`${name}@${version}`), '...'); + const packageJsonFilter = filter('package.json', { restore: true }); + return fetchGithub(new URL(repo).pathname, { + version, + name: name => name.endsWith('.vsix'), + checksumSha256: sha256 + }) + .pipe(buffer()) + .pipe(vzip.src()) + .pipe(filter('extension/**')) + .pipe(rename(p => p.dirname = p.dirname!.replace(/^extension\/?/, ''))) + .pipe(packageJsonFilter) + .pipe(buffer()) + .pipe(json({ __metadata: metadata })) + .pipe(packageJsonFilter.restore); } - const excludedExtensions = [ - 'vscode-api-tests', - 'vscode-colorize-tests', - 'vscode-test-resolver', - 'ms-vscode.node-debug', - 'ms-vscode.node-debug2', + 'vscode-api-tests', + 'vscode-colorize-tests', + 'vscode-test-resolver', + 'ms-vscode.node-debug', + 'ms-vscode.node-debug2', ]; - const marketplaceWebExtensionsExclude = new Set([ - 'ms-vscode.node-debug', - 'ms-vscode.node-debug2', - 'ms-vscode.js-debug-companion', - 'ms-vscode.js-debug', - 'ms-vscode.vscode-js-profile-table' + 'ms-vscode.node-debug', + 'ms-vscode.node-debug2', + 'ms-vscode.js-debug-companion', + 'ms-vscode.js-debug', + 'ms-vscode.vscode-js-profile-table' ]); - const productJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); const builtInExtensions: IExtensionDefinition[] = productJson.builtInExtensions || []; const webBuiltInExtensions: IExtensionDefinition[] = productJson.webBuiltInExtensions || []; - type ExtensionKind = 'ui' | 'workspace' | 'web'; interface IExtensionManifest { - main?: string; - browser?: string; - extensionKind?: ExtensionKind | ExtensionKind[]; - extensionPack?: string[]; - extensionDependencies?: string[]; - contributes?: { [id: string]: any }; + main?: string; + browser?: string; + extensionKind?: ExtensionKind | ExtensionKind[]; + extensionPack?: string[]; + extensionDependencies?: string[]; + contributes?: { + [id: string]: any; + }; } /** * Loosely based on `getExtensionKind` from `src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts` */ function isWebExtension(manifest: IExtensionManifest): boolean { - if (Boolean(manifest.browser)) { - return true; - } - if (Boolean(manifest.main)) { - return false; - } - // neither browser nor main - if (typeof manifest.extensionKind !== 'undefined') { - const extensionKind = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : [manifest.extensionKind]; - if (extensionKind.indexOf('web') >= 0) { - return true; - } - } - if (typeof manifest.contributes !== 'undefined') { - for (const id of ['debuggers', 'terminal', 'typescriptServerPlugins']) { - if (manifest.contributes.hasOwnProperty(id)) { - return false; - } - } - } - return true; + if (Boolean(manifest.browser)) { + return true; + } + if (Boolean(manifest.main)) { + return false; + } + // neither browser nor main + if (typeof manifest.extensionKind !== 'undefined') { + const extensionKind = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : [manifest.extensionKind]; + if (extensionKind.indexOf('web') >= 0) { + return true; + } + } + if (typeof manifest.contributes !== 'undefined') { + for (const id of ['debuggers', 'terminal', 'typescriptServerPlugins']) { + if (manifest.contributes.hasOwnProperty(id)) { + return false; + } + } + } + return true; } - export function packageLocalExtensionsStream(forWeb: boolean, disableMangle: boolean): Stream { - const localExtensionsDescriptions = ( - (glob.sync('extensions/*/package.json')) - .map(manifestPath => { - const absoluteManifestPath = path.join(root, manifestPath); - const extensionPath = path.dirname(path.join(root, manifestPath)); - const extensionName = path.basename(extensionPath); - return { name: extensionName, path: extensionPath, manifestPath: absoluteManifestPath }; - }) - .filter(({ name }) => excludedExtensions.indexOf(name) === -1) - .filter(({ name }) => builtInExtensions.every(b => b.name !== name)) - .filter(({ manifestPath }) => (forWeb ? isWebExtension(require(manifestPath)) : true)) - ); - const localExtensionsStream = minifyExtensionResources( - es.merge( - ...localExtensionsDescriptions.map(extension => { - return fromLocal(extension.path, forWeb, disableMangle) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - }) - ) - ); - - let result: Stream; - if (forWeb) { - result = localExtensionsStream; - } else { - // also include shared production node modules - const productionDependencies = getProductionDependencies('extensions/'); - const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); - - result = es.merge( - localExtensionsStream, - gulp.src(dependenciesSrc, { base: '.' }) - .pipe(util2.cleanNodeModules(path.join(root, 'build', '.moduleignore'))) - .pipe(util2.cleanNodeModules(path.join(root, 'build', `.moduleignore.${process.platform}`)))); - } - - return ( - result - .pipe(util2.setExecutableBit(['**/*.sh'])) - ); + const localExtensionsDescriptions = ((glob.sync('extensions/*/package.json')) + .map(manifestPath => { + const absoluteManifestPath = path.join(root, manifestPath); + const extensionPath = path.dirname(path.join(root, manifestPath)); + const extensionName = path.basename(extensionPath); + return { name: extensionName, path: extensionPath, manifestPath: absoluteManifestPath }; + }) + .filter(({ name }) => excludedExtensions.indexOf(name) === -1) + .filter(({ name }) => builtInExtensions.every(b => b.name !== name)) + .filter(({ manifestPath }) => (forWeb ? isWebExtension(require(manifestPath)) : true))); + const localExtensionsStream = minifyExtensionResources(es.merge(...localExtensionsDescriptions.map(extension => { + return fromLocal(extension.path, forWeb, disableMangle) + .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); + }))); + let result: Stream; + if (forWeb) { + result = localExtensionsStream; + } + else { + // also include shared production node modules + const productionDependencies = getProductionDependencies('extensions/'); + const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); + result = es.merge(localExtensionsStream, gulp.src(dependenciesSrc, { base: '.' }) + .pipe(util2.cleanNodeModules(path.join(root, 'build', '.moduleignore'))) + .pipe(util2.cleanNodeModules(path.join(root, 'build', `.moduleignore.${process.platform}`)))); + } + return (result + .pipe(util2.setExecutableBit(['**/*.sh']))); } - export function packageMarketplaceExtensionsStream(forWeb: boolean): Stream { - const marketplaceExtensionsDescriptions = [ - ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), - ...(forWeb ? webBuiltInExtensions : []) - ]; - const marketplaceExtensionsStream = minifyExtensionResources( - es.merge( - ...marketplaceExtensionsDescriptions - .map(extension => { - const src = getExtensionStream(extension).pipe(rename(p => p.dirname = `extensions/${p.dirname}`)); - return updateExtensionPackageJSON(src, (data: any) => { - delete data.scripts; - delete data.dependencies; - delete data.devDependencies; - return data; - }); - }) - ) - ); - - return ( - marketplaceExtensionsStream - .pipe(util2.setExecutableBit(['**/*.sh'])) - ); + const marketplaceExtensionsDescriptions = [ + ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), + ...(forWeb ? webBuiltInExtensions : []) + ]; + const marketplaceExtensionsStream = minifyExtensionResources(es.merge(...marketplaceExtensionsDescriptions + .map(extension => { + const src = getExtensionStream(extension).pipe(rename(p => p.dirname = `extensions/${p.dirname}`)); + return updateExtensionPackageJSON(src, (data: any) => { + delete data.scripts; + delete data.dependencies; + delete data.devDependencies; + return data; + }); + }))); + return (marketplaceExtensionsStream + .pipe(util2.setExecutableBit(['**/*.sh']))); } - export interface IScannedBuiltinExtension { - extensionPath: string; - packageJSON: any; - packageNLS?: any; - readmePath?: string; - changelogPath?: string; + extensionPath: string; + packageJSON: any; + packageNLS?: any; + readmePath?: string; + changelogPath?: string; } - export function scanBuiltinExtensions(extensionsRoot: string, exclude: string[] = []): IScannedBuiltinExtension[] { - const scannedExtensions: IScannedBuiltinExtension[] = []; - - try { - const extensionsFolders = fs.readdirSync(extensionsRoot); - for (const extensionFolder of extensionsFolders) { - if (exclude.indexOf(extensionFolder) >= 0) { - continue; - } - const packageJSONPath = path.join(extensionsRoot, extensionFolder, 'package.json'); - if (!fs.existsSync(packageJSONPath)) { - continue; - } - const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath).toString('utf8')); - if (!isWebExtension(packageJSON)) { - continue; - } - const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder)); - const packageNLSPath = children.filter(child => child === 'package.nls.json')[0]; - const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined; - const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0]; - const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; - - scannedExtensions.push({ - extensionPath: extensionFolder, - packageJSON, - packageNLS, - readmePath: readme ? path.join(extensionFolder, readme) : undefined, - changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined, - }); - } - return scannedExtensions; - } catch (ex) { - return scannedExtensions; - } + const scannedExtensions: IScannedBuiltinExtension[] = []; + try { + const extensionsFolders = fs.readdirSync(extensionsRoot); + for (const extensionFolder of extensionsFolders) { + if (exclude.indexOf(extensionFolder) >= 0) { + continue; + } + const packageJSONPath = path.join(extensionsRoot, extensionFolder, 'package.json'); + if (!fs.existsSync(packageJSONPath)) { + continue; + } + const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath).toString('utf8')); + if (!isWebExtension(packageJSON)) { + continue; + } + const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder)); + const packageNLSPath = children.filter(child => child === 'package.nls.json')[0]; + const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined; + const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0]; + const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; + scannedExtensions.push({ + extensionPath: extensionFolder, + packageJSON, + packageNLS, + readmePath: readme ? path.join(extensionFolder, readme) : undefined, + changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined, + }); + } + return scannedExtensions; + } + catch (ex) { + return scannedExtensions; + } } - export function translatePackageJSON(packageJSON: string, packageNLSPath: string) { - interface NLSFormat { - [key: string]: string | { message: string; comment: string[] }; - } - const CharCode_PC = '%'.charCodeAt(0); - const packageNls: NLSFormat = JSON.parse(fs.readFileSync(packageNLSPath).toString()); - const translate = (obj: any) => { - for (const key in obj) { - const val = obj[key]; - if (Array.isArray(val)) { - val.forEach(translate); - } else if (val && typeof val === 'object') { - translate(val); - } else if (typeof val === 'string' && val.charCodeAt(0) === CharCode_PC && val.charCodeAt(val.length - 1) === CharCode_PC) { - const translated = packageNls[val.substr(1, val.length - 2)]; - if (translated) { - obj[key] = typeof translated === 'string' ? translated : (typeof translated.message === 'string' ? translated.message : val); - } - } - } - }; - translate(packageJSON); - return packageJSON; + interface NLSFormat { + [key: string]: string | { + message: string; + comment: string[]; + }; + } + const CharCode_PC = '%'.charCodeAt(0); + const packageNls: NLSFormat = JSON.parse(fs.readFileSync(packageNLSPath).toString()); + const translate = (obj: any) => { + for (const key in obj) { + const val = obj[key]; + if (Array.isArray(val)) { + val.forEach(translate); + } + else if (val && typeof val === 'object') { + translate(val); + } + else if (typeof val === 'string' && val.charCodeAt(0) === CharCode_PC && val.charCodeAt(val.length - 1) === CharCode_PC) { + const translated = packageNls[val.substr(1, val.length - 2)]; + if (translated) { + obj[key] = typeof translated === 'string' ? translated : (typeof translated.message === 'string' ? translated.message : val); + } + } + } + }; + translate(packageJSON); + return packageJSON; } - const extensionsPath = path.join(root, 'extensions'); - // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ - 'markdown-language-features/esbuild-notebook.js', - 'markdown-language-features/esbuild-preview.js', - 'markdown-math/esbuild.js', - 'notebook-renderers/esbuild.js', - 'ipynb/esbuild.js', - 'simple-browser/esbuild-preview.js', + 'markdown-language-features/esbuild-notebook.js', + 'markdown-language-features/esbuild-preview.js', + 'markdown-math/esbuild.js', + 'notebook-renderers/esbuild.js', + 'ipynb/esbuild.js', + 'simple-browser/esbuild-preview.js', ]; - -export async function webpackExtensions(taskName: string, isWatch: boolean, webpackConfigLocations: { configPath: string; outputRoot?: string }[]) { - const webpack = require('webpack') as typeof import('webpack'); - - const webpackConfigs: webpack.Configuration[] = []; - - for (const { configPath, outputRoot } of webpackConfigLocations) { - const configOrFnOrArray = require(configPath); - function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown, args: unknown) => webpack.Configuration) | webpack.Configuration[]) { - for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { - const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; - if (outputRoot) { - config.output!.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output!.path!)); - } - webpackConfigs.push(config); - } - } - addConfig(configOrFnOrArray); - } - function reporter(fullStats: any) { - if (Array.isArray(fullStats.children)) { - for (const stats of fullStats.children) { - const outputPath = stats.outputPath; - if (outputPath) { - const relativePath = path.relative(extensionsPath, outputPath).replace(/\\/g, '/'); - const match = relativePath.match(/[^\/]+(\/server|\/client)?/); - fancyLog(`Finished ${ansiColors.green(taskName)} ${ansiColors.cyan(match![0])} with ${stats.errors.length} errors.`); - } - if (Array.isArray(stats.errors)) { - stats.errors.forEach((error: any) => { - fancyLog.error(error); - }); - } - if (Array.isArray(stats.warnings)) { - stats.warnings.forEach((warning: any) => { - fancyLog.warn(warning); - }); - } - } - } - } - return new Promise((resolve, reject) => { - if (isWatch) { - webpack(webpackConfigs).watch({}, (err, stats) => { - if (err) { - reject(); - } else { - reporter(stats?.toJson()); - } - }); - } else { - webpack(webpackConfigs).run((err, stats) => { - if (err) { - fancyLog.error(err); - reject(); - } else { - reporter(stats?.toJson()); - resolve(); - } - }); - } - }); +export async function webpackExtensions(taskName: string, isWatch: boolean, webpackConfigLocations: { + configPath: string; + outputRoot?: string; +}[]) { + const webpack = require('webpack') as typeof import('webpack'); + const webpackConfigs: webpack.Configuration[] = []; + for (const { configPath, outputRoot } of webpackConfigLocations) { + const configOrFnOrArray = require(configPath); + function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown, args: unknown) => webpack.Configuration) | webpack.Configuration[]) { + for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { + const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; + if (outputRoot) { + config.output!.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output!.path!)); + } + webpackConfigs.push(config); + } + } + addConfig(configOrFnOrArray); + } + function reporter(fullStats: any) { + if (Array.isArray(fullStats.children)) { + for (const stats of fullStats.children) { + const outputPath = stats.outputPath; + if (outputPath) { + const relativePath = path.relative(extensionsPath, outputPath).replace(/\\/g, '/'); + const match = relativePath.match(/[^\/]+(\/server|\/client)?/); + fancyLog(`Finished ${ansiColors.green(taskName)} ${ansiColors.cyan(match![0])} with ${stats.errors.length} errors.`); + } + if (Array.isArray(stats.errors)) { + stats.errors.forEach((error: any) => { + fancyLog.error(error); + }); + } + if (Array.isArray(stats.warnings)) { + stats.warnings.forEach((warning: any) => { + fancyLog.warn(warning); + }); + } + } + } + } + return new Promise((resolve, reject) => { + if (isWatch) { + webpack(webpackConfigs).watch({}, (err, stats) => { + if (err) { + reject(); + } + else { + reporter(stats?.toJson()); + } + }); + } + else { + webpack(webpackConfigs).run((err, stats) => { + if (err) { + fancyLog.error(err); + reject(); + } + else { + reporter(stats?.toJson()); + resolve(); + } + }); + } + }); } - -async function esbuildExtensions(taskName: string, isWatch: boolean, scripts: { script: string; outputRoot?: string }[]) { - function reporter(stdError: string, script: string) { - const matches = (stdError || '').match(/\> (.+): error: (.+)?/g); - fancyLog(`Finished ${ansiColors.green(taskName)} ${script} with ${matches ? matches.length : 0} errors.`); - for (const match of matches || []) { - fancyLog.error(match); - } - } - - const tasks = scripts.map(({ script, outputRoot }) => { - return new Promise((resolve, reject) => { - const args = [script]; - if (isWatch) { - args.push('--watch'); - } - if (outputRoot) { - args.push('--outputRoot', outputRoot); - } - const proc = cp.execFile(process.argv[0], args, {}, (error, _stdout, stderr) => { - if (error) { - return reject(error); - } - reporter(stderr, script); - return resolve(); - }); - - proc.stdout!.on('data', (data) => { - fancyLog(`${ansiColors.green(taskName)}: ${data.toString('utf8')}`); - }); - }); - }); - return Promise.all(tasks); +async function esbuildExtensions(taskName: string, isWatch: boolean, scripts: { + script: string; + outputRoot?: string; +}[]) { + function reporter(stdError: string, script: string) { + const matches = (stdError || '').match(/\> (.+): error: (.+)?/g); + fancyLog(`Finished ${ansiColors.green(taskName)} ${script} with ${matches ? matches.length : 0} errors.`); + for (const match of matches || []) { + fancyLog.error(match); + } + } + const tasks = scripts.map(({ script, outputRoot }) => { + return new Promise((resolve, reject) => { + const args = [script]; + if (isWatch) { + args.push('--watch'); + } + if (outputRoot) { + args.push('--outputRoot', outputRoot); + } + const proc = cp.execFile(process.argv[0], args, {}, (error, _stdout, stderr) => { + if (error) { + return reject(error); + } + reporter(stderr, script); + return resolve(); + }); + proc.stdout!.on('data', (data) => { + fancyLog(`${ansiColors.green(taskName)}: ${data.toString('utf8')}`); + }); + }); + }); + return Promise.all(tasks); } - export async function buildExtensionMedia(isWatch: boolean, outputRoot?: string) { - return esbuildExtensions('esbuilding extension media', isWatch, esbuildMediaScripts.map(p => ({ - script: path.join(extensionsPath, p), - outputRoot: outputRoot ? path.join(root, outputRoot, path.dirname(p)) : undefined - }))); + return esbuildExtensions('esbuilding extension media', isWatch, esbuildMediaScripts.map(p => ({ + script: path.join(extensionsPath, p), + outputRoot: outputRoot ? path.join(root, outputRoot, path.dirname(p)) : undefined + }))); } diff --git a/build/lib/fetch.ts b/build/lib/fetch.ts index 0c44b8e567f1f..c211c0773d814 100644 --- a/build/lib/fetch.ts +++ b/build/lib/fetch.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as VinylFile from 'vinyl'; import * as log from 'fancy-log'; @@ -10,116 +9,111 @@ import * as ansiColors from 'ansi-colors'; import * as crypto from 'crypto'; import * as through2 from 'through2'; import { Stream } from 'stream'; - export interface IFetchOptions { - base?: string; - nodeFetchOptions?: RequestInit; - verbose?: boolean; - checksumSha256?: string; + base?: string; + nodeFetchOptions?: RequestInit; + verbose?: boolean; + checksumSha256?: string; } - export function fetchUrls(urls: string[] | string, options: IFetchOptions): es.ThroughStream { - if (options === undefined) { - options = {}; - } - - if (typeof options.base !== 'string' && options.base !== null) { - options.base = '/'; - } - - if (!Array.isArray(urls)) { - urls = [urls]; - } - - return es.readArray(urls).pipe(es.map((data: string, cb) => { - const url = [options.base, data].join(''); - fetchUrl(url, options).then(file => { - cb(undefined, file); - }, error => { - cb(error); - }); - })); + if (options === undefined) { + options = {}; + } + if (typeof options.base !== 'string' && options.base !== null) { + options.base = '/'; + } + if (!Array.isArray(urls)) { + urls = [urls]; + } + return es.readArray(urls).pipe(es.map((data: string, cb) => { + const url = [options.base, data].join(''); + fetchUrl(url, options).then(file => { + cb(undefined, file); + }, error => { + cb(error); + }); + })); } - export async function fetchUrl(url: string, options: IFetchOptions, retries = 10, retryDelay = 1000): Promise { - const verbose = !!options.verbose || !!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']; - try { - let startTime = 0; - if (verbose) { - log(`Start fetching ${ansiColors.magenta(url)}${retries !== 10 ? ` (${10 - retries} retry)` : ''}`); - startTime = new Date().getTime(); - } - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 30 * 1000); - try { - const response = await fetch(url, { - ...options.nodeFetchOptions, - signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ - }); - if (verbose) { - log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`); - } - if (response.ok && (response.status >= 200 && response.status < 300)) { - const contents = Buffer.from(await response.arrayBuffer()); - if (options.checksumSha256) { - const actualSHA256Checksum = crypto.createHash('sha256').update(contents).digest('hex'); - if (actualSHA256Checksum !== options.checksumSha256) { - throw new Error(`Checksum mismatch for ${ansiColors.cyan(url)} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`); - } else if (verbose) { - log(`Verified SHA256 checksums match for ${ansiColors.cyan(url)}`); - } - } else if (verbose) { - log(`Skipping checksum verification for ${ansiColors.cyan(url)} because no expected checksum was provided`); - } - if (verbose) { - log(`Fetched response body buffer: ${ansiColors.magenta(`${(contents as Buffer).byteLength} bytes`)}`); - } - return new VinylFile({ - cwd: '/', - base: options.base, - path: url, - contents - }); - } - let err = `Request ${ansiColors.magenta(url)} failed with status code: ${response.status}`; - if (response.status === 403) { - err += ' (you may be rate limited)'; - } - throw new Error(err); - } finally { - clearTimeout(timeout); - } - } catch (e) { - if (verbose) { - log(`Fetching ${ansiColors.cyan(url)} failed: ${e}`); - } - if (retries > 0) { - await new Promise(resolve => setTimeout(resolve, retryDelay)); - return fetchUrl(url, options, retries - 1, retryDelay); - } - throw e; - } + const verbose = !!options.verbose || !!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']; + try { + let startTime = 0; + if (verbose) { + log(`Start fetching ${ansiColors.magenta(url)}${retries !== 10 ? ` (${10 - retries} retry)` : ''}`); + startTime = new Date().getTime(); + } + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 30 * 1000); + try { + const response = await fetch(url, { + ...options.nodeFetchOptions, + signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ + }); + if (verbose) { + log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`); + } + if (response.ok && (response.status >= 200 && response.status < 300)) { + const contents = Buffer.from(await response.arrayBuffer()); + if (options.checksumSha256) { + const actualSHA256Checksum = crypto.createHash('sha256').update(contents).digest('hex'); + if (actualSHA256Checksum !== options.checksumSha256) { + throw new Error(`Checksum mismatch for ${ansiColors.cyan(url)} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`); + } + else if (verbose) { + log(`Verified SHA256 checksums match for ${ansiColors.cyan(url)}`); + } + } + else if (verbose) { + log(`Skipping checksum verification for ${ansiColors.cyan(url)} because no expected checksum was provided`); + } + if (verbose) { + log(`Fetched response body buffer: ${ansiColors.magenta(`${(contents as Buffer).byteLength} bytes`)}`); + } + return new VinylFile({ + cwd: '/', + base: options.base, + path: url, + contents + }); + } + let err = `Request ${ansiColors.magenta(url)} failed with status code: ${response.status}`; + if (response.status === 403) { + err += ' (you may be rate limited)'; + } + throw new Error(err); + } + finally { + clearTimeout(timeout); + } + } + catch (e) { + if (verbose) { + log(`Fetching ${ansiColors.cyan(url)} failed: ${e}`); + } + if (retries > 0) { + await new Promise(resolve => setTimeout(resolve, retryDelay)); + return fetchUrl(url, options, retries - 1, retryDelay); + } + throw e; + } } - const ghApiHeaders: Record = { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': 'VSCode Build', + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'VSCode Build', }; if (process.env.GITHUB_TOKEN) { - ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); + ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); } const ghDownloadHeaders = { - ...ghApiHeaders, - Accept: 'application/octet-stream', + ...ghApiHeaders, + Accept: 'application/octet-stream', }; - export interface IGitHubAssetOptions { - version: string; - name: string | ((name: string) => boolean); - checksumSha256?: string; - verbose?: boolean; + version: string; + name: string | ((name: string) => boolean); + checksumSha256?: string; + verbose?: boolean; } - /** * @param repo for example `Microsoft/vscode` * @param version for example `16.17.1` - must be a valid releases tag @@ -127,24 +121,27 @@ export interface IGitHubAssetOptions { * @returns a stream with the asset as file */ export function fetchGithub(repo: string, options: IGitHubAssetOptions): Stream { - return fetchUrls(`/repos/${repo.replace(/^\/|\/$/g, '')}/releases/tags/v${options.version}`, { - base: 'https://api.github.com', - verbose: options.verbose, - nodeFetchOptions: { headers: ghApiHeaders } - }).pipe(through2.obj(async function (file, _enc, callback) { - const assetFilter = typeof options.name === 'string' ? (name: string) => name === options.name : options.name; - const asset = JSON.parse(file.contents.toString()).assets.find((a: { name: string }) => assetFilter(a.name)); - if (!asset) { - return callback(new Error(`Could not find asset in release of ${repo} @ ${options.version}`)); - } - try { - callback(null, await fetchUrl(asset.url, { - nodeFetchOptions: { headers: ghDownloadHeaders }, - verbose: options.verbose, - checksumSha256: options.checksumSha256 - })); - } catch (error) { - callback(error); - } - })); + return fetchUrls(`/repos/${repo.replace(/^\/|\/$/g, '')}/releases/tags/v${options.version}`, { + base: 'https://api.github.com', + verbose: options.verbose, + nodeFetchOptions: { headers: ghApiHeaders } + }).pipe(through2.obj(async function (file, _enc, callback) { + const assetFilter = typeof options.name === 'string' ? (name: string) => name === options.name : options.name; + const asset = JSON.parse(file.contents.toString()).assets.find((a: { + name: string; + }) => assetFilter(a.name)); + if (!asset) { + return callback(new Error(`Could not find asset in release of ${repo} @ ${options.version}`)); + } + try { + callback(null, await fetchUrl(asset.url, { + nodeFetchOptions: { headers: ghDownloadHeaders }, + verbose: options.verbose, + checksumSha256: options.checksumSha256 + })); + } + catch (error) { + callback(error); + } + })); } diff --git a/build/lib/formatter.ts b/build/lib/formatter.ts index 0d9035b3d87c2..9d0244e914a38 100644 --- a/build/lib/formatter.ts +++ b/build/lib/formatter.ts @@ -5,80 +5,68 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; - - class LanguageServiceHost implements ts.LanguageServiceHost { - files: ts.MapLike = {}; - addFile(fileName: string, text: string) { - this.files[fileName] = ts.ScriptSnapshot.fromString(text); - } - - fileExists(path: string): boolean { - return !!this.files[path]; - } - - readFile(path: string): string | undefined { - return this.files[path]?.getText(0, this.files[path]!.getLength()); - } - - // for ts.LanguageServiceHost - - getCompilationSettings = () => ts.getDefaultCompilerOptions(); - getScriptFileNames = () => Object.keys(this.files); - getScriptVersion = (_fileName: string) => '0'; - getScriptSnapshot = (fileName: string) => this.files[fileName]; - getCurrentDirectory = () => process.cwd(); - getDefaultLibFileName = (options: ts.CompilerOptions) => ts.getDefaultLibFilePath(options); + files: ts.MapLike = {}; + addFile(fileName: string, text: string) { + this.files[fileName] = ts.ScriptSnapshot.fromString(text); + } + fileExists(path: string): boolean { + return !!this.files[path]; + } + readFile(path: string): string | undefined { + return this.files[path]?.getText(0, this.files[path]!.getLength()); + } + // for ts.LanguageServiceHost + getCompilationSettings = () => ts.getDefaultCompilerOptions(); + getScriptFileNames = () => Object.keys(this.files); + getScriptVersion = (_fileName: string) => '0'; + getScriptSnapshot = (fileName: string) => this.files[fileName]; + getCurrentDirectory = () => process.cwd(); + getDefaultLibFileName = (options: ts.CompilerOptions) => ts.getDefaultLibFilePath(options); } - const defaults: ts.FormatCodeSettings = { - baseIndentSize: 0, - indentSize: 4, - tabSize: 4, - indentStyle: ts.IndentStyle.Smart, - newLineCharacter: '\r\n', - convertTabsToSpaces: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterConstructor: false, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceAfterTypeAssertion: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - insertSpaceBeforeTypeAnnotation: false, + baseIndentSize: 0, + indentSize: 4, + tabSize: 4, + indentStyle: ts.IndentStyle.Smart, + newLineCharacter: '\r\n', + convertTabsToSpaces: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterConstructor: false, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceAfterTypeAssertion: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + insertSpaceBeforeTypeAnnotation: false, }; - const getOverrides = (() => { - let value: ts.FormatCodeSettings | undefined; - return () => { - value ??= JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'tsfmt.json'), 'utf8')); - return value; - }; + let value: ts.FormatCodeSettings | undefined; + return () => { + value ??= JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'tsfmt.json'), 'utf8')); + return value; + }; })(); - export function format(fileName: string, text: string) { - - const host = new LanguageServiceHost(); - host.addFile(fileName, text); - - const languageService = ts.createLanguageService(host); - const edits = languageService.getFormattingEditsForDocument(fileName, { ...defaults, ...getOverrides() }); - edits - .sort((a, b) => a.span.start - b.span.start) - .reverse() - .forEach(edit => { - const head = text.slice(0, edit.span.start); - const tail = text.slice(edit.span.start + edit.span.length); - text = `${head}${edit.newText}${tail}`; - }); - - return text; + const host = new LanguageServiceHost(); + host.addFile(fileName, text); + const languageService = ts.createLanguageService(host); + const edits = languageService.getFormattingEditsForDocument(fileName, { ...defaults, ...getOverrides() }); + edits + .sort((a, b) => a.span.start - b.span.start) + .reverse() + .forEach(edit => { + const head = text.slice(0, edit.span.start); + const tail = text.slice(edit.span.start + edit.span.length); + text = `${head}${edit.newText}${tail}`; + }); + return text; } diff --git a/build/lib/getVersion.ts b/build/lib/getVersion.ts index 2fddb309f83e7..c328c194ef542 100644 --- a/build/lib/getVersion.ts +++ b/build/lib/getVersion.ts @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as git from './git'; - export function getVersion(root: string): string | undefined { - let version = process.env['BUILD_SOURCEVERSION']; - - if (!version || !/^[0-9a-f]{40}$/i.test(version.trim())) { - version = git.getVersion(root); - } - - return version; + let version = process.env['BUILD_SOURCEVERSION']; + if (!version || !/^[0-9a-f]{40}$/i.test(version.trim())) { + version = git.getVersion(root); + } + return version; } diff --git a/build/lib/git.ts b/build/lib/git.ts index dbb424f21df72..bffa563148de4 100644 --- a/build/lib/git.ts +++ b/build/lib/git.ts @@ -4,56 +4,49 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; import * as fs from 'fs'; - /** * Returns the sha1 commit version of a repository or undefined in case of failure. */ export function getVersion(repo: string): string | undefined { - const git = path.join(repo, '.git'); - const headPath = path.join(git, 'HEAD'); - let head: string; - - try { - head = fs.readFileSync(headPath, 'utf8').trim(); - } catch (e) { - return undefined; - } - - if (/^[0-9a-f]{40}$/i.test(head)) { - return head; - } - - const refMatch = /^ref: (.*)$/.exec(head); - - if (!refMatch) { - return undefined; - } - - const ref = refMatch[1]; - const refPath = path.join(git, ref); - - try { - return fs.readFileSync(refPath, 'utf8').trim(); - } catch (e) { - // noop - } - - const packedRefsPath = path.join(git, 'packed-refs'); - let refsRaw: string; - - try { - refsRaw = fs.readFileSync(packedRefsPath, 'utf8').trim(); - } catch (e) { - return undefined; - } - - const refsRegex = /^([0-9a-f]{40})\s+(.+)$/gm; - let refsMatch: RegExpExecArray | null; - const refs: { [ref: string]: string } = {}; - - while (refsMatch = refsRegex.exec(refsRaw)) { - refs[refsMatch[2]] = refsMatch[1]; - } - - return refs[ref]; + const git = path.join(repo, '.git'); + const headPath = path.join(git, 'HEAD'); + let head: string; + try { + head = fs.readFileSync(headPath, 'utf8').trim(); + } + catch (e) { + return undefined; + } + if (/^[0-9a-f]{40}$/i.test(head)) { + return head; + } + const refMatch = /^ref: (.*)$/.exec(head); + if (!refMatch) { + return undefined; + } + const ref = refMatch[1]; + const refPath = path.join(git, ref); + try { + return fs.readFileSync(refPath, 'utf8').trim(); + } + catch (e) { + // noop + } + const packedRefsPath = path.join(git, 'packed-refs'); + let refsRaw: string; + try { + refsRaw = fs.readFileSync(packedRefsPath, 'utf8').trim(); + } + catch (e) { + return undefined; + } + const refsRegex = /^([0-9a-f]{40})\s+(.+)$/gm; + let refsMatch: RegExpExecArray | null; + const refs: { + [ref: string]: string; + } = {}; + while (refsMatch = refsRegex.exec(refsRaw)) { + refs[refsMatch[2]] = refsMatch[1]; + } + return refs[ref]; } diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index cd7e522ad3619..72ce169aee66d 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -2,10 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as fs from 'fs'; - import { map, merge, through, ThroughStream } from 'event-stream'; import * as jsonMerge from 'gulp-merge-json'; import * as File from 'vinyl'; @@ -15,879 +13,809 @@ import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import * as iconv from '@vscode/iconv-lite-umd'; import { l10nJsonFormat, getL10nXlf, l10nJsonDetails, getL10nFilesFromXlf, getL10nJson } from '@vscode/l10n-dev'; - const REPO_ROOT_PATH = path.join(__dirname, '../..'); - function log(message: any, ...rest: any[]): void { - fancyLog(ansiColors.green('[i18n]'), message, ...rest); + fancyLog(ansiColors.green('[i18n]'), message, ...rest); } - export interface Language { - id: string; // language id, e.g. zh-tw, de - translationId?: string; // language id used in translation tools, e.g. zh-hant, de (optional, if not set, the id is used) - folderName?: string; // language specific folder name, e.g. cht, deu (optional, if not set, the id is used) + id: string; // language id, e.g. zh-tw, de + translationId?: string; // language id used in translation tools, e.g. zh-hant, de (optional, if not set, the id is used) + folderName?: string; // language specific folder name, e.g. cht, deu (optional, if not set, the id is used) } - export interface InnoSetup { - codePage: string; //code page for encoding (http://www.jrsoftware.org/ishelp/index.php?topic=langoptionssection) + codePage: string; //code page for encoding (http://www.jrsoftware.org/ishelp/index.php?topic=langoptionssection) } - export const defaultLanguages: Language[] = [ - { id: 'zh-tw', folderName: 'cht', translationId: 'zh-hant' }, - { id: 'zh-cn', folderName: 'chs', translationId: 'zh-hans' }, - { id: 'ja', folderName: 'jpn' }, - { id: 'ko', folderName: 'kor' }, - { id: 'de', folderName: 'deu' }, - { id: 'fr', folderName: 'fra' }, - { id: 'es', folderName: 'esn' }, - { id: 'ru', folderName: 'rus' }, - { id: 'it', folderName: 'ita' } + { id: 'zh-tw', folderName: 'cht', translationId: 'zh-hant' }, + { id: 'zh-cn', folderName: 'chs', translationId: 'zh-hans' }, + { id: 'ja', folderName: 'jpn' }, + { id: 'ko', folderName: 'kor' }, + { id: 'de', folderName: 'deu' }, + { id: 'fr', folderName: 'fra' }, + { id: 'es', folderName: 'esn' }, + { id: 'ru', folderName: 'rus' }, + { id: 'it', folderName: 'ita' } ]; - // languages requested by the community to non-stable builds export const extraLanguages: Language[] = [ - { id: 'pt-br', folderName: 'ptb' }, - { id: 'hu', folderName: 'hun' }, - { id: 'tr', folderName: 'trk' } + { id: 'pt-br', folderName: 'ptb' }, + { id: 'hu', folderName: 'hun' }, + { id: 'tr', folderName: 'trk' } ]; - interface Item { - id: string; - message: string; - comment?: string; + id: string; + message: string; + comment?: string; } - export interface Resource { - name: string; - project: string; + name: string; + project: string; } - interface LocalizeInfo { - key: string; - comment: string[]; + key: string; + comment: string[]; } - module LocalizeInfo { - export function is(value: any): value is LocalizeInfo { - const candidate = value as LocalizeInfo; - return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); - } + export function is(value: any): value is LocalizeInfo { + const candidate = value as LocalizeInfo; + return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); + } } - interface BundledFormat { - keys: Record; - messages: Record; - bundles: Record; + keys: Record; + messages: Record; + bundles: Record; } - module BundledFormat { - export function is(value: any): value is BundledFormat { - if (value === undefined) { - return false; - } - - const candidate = value as BundledFormat; - const length = Object.keys(value).length; - - return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles; - } -} - -type NLSKeysFormat = [string /* module ID */, string[] /* keys */]; - + export function is(value: any): value is BundledFormat { + if (value === undefined) { + return false; + } + const candidate = value as BundledFormat; + const length = Object.keys(value).length; + return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles; + } +} +type NLSKeysFormat = [ + string /* module ID */, + string[] /* keys */ +]; module NLSKeysFormat { - export function is(value: any): value is NLSKeysFormat { - if (value === undefined) { - return false; - } - - const candidate = value as NLSKeysFormat; - return Array.isArray(candidate) && Array.isArray(candidate[1]); - } -} - + export function is(value: any): value is NLSKeysFormat { + if (value === undefined) { + return false; + } + const candidate = value as NLSKeysFormat; + return Array.isArray(candidate) && Array.isArray(candidate[1]); + } +} interface BundledExtensionFormat { - [key: string]: { - messages: string[]; - keys: (string | LocalizeInfo)[]; - }; + [key: string]: { + messages: string[]; + keys: (string | LocalizeInfo)[]; + }; } - interface I18nFormat { - version: string; - contents: { - [module: string]: { - [messageKey: string]: string; - }; - }; -} - + version: string; + contents: { + [module: string]: { + [messageKey: string]: string; + }; + }; +} export class Line { - private buffer: string[] = []; - - constructor(indent: number = 0) { - if (indent > 0) { - this.buffer.push(new Array(indent + 1).join(' ')); - } - } - - public append(value: string): Line { - this.buffer.push(value); - return this; - } - - public toString(): string { - return this.buffer.join(''); - } -} - + private buffer: string[] = []; + constructor(indent: number = 0) { + if (indent > 0) { + this.buffer.push(new Array(indent + 1).join(' ')); + } + } + public append(value: string): Line { + this.buffer.push(value); + return this; + } + public toString(): string { + return this.buffer.join(''); + } +} class TextModel { - private _lines: string[]; - - constructor(contents: string) { - this._lines = contents.split(/\r\n|\r|\n/); - } - - public get lines(): string[] { - return this._lines; - } -} - + private _lines: string[]; + constructor(contents: string) { + this._lines = contents.split(/\r\n|\r|\n/); + } + public get lines(): string[] { + return this._lines; + } +} export class XLF { - private buffer: string[]; - private files: Record; - public numberOfMessages: number; - - constructor(public project: string) { - this.buffer = []; - this.files = Object.create(null); - this.numberOfMessages = 0; - } - - public toString(): string { - this.appendHeader(); - - const files = Object.keys(this.files).sort(); - for (const file of files) { - this.appendNewLine(``, 2); - const items = this.files[file].sort((a: Item, b: Item) => { - return a.id < b.id ? -1 : a.id > b.id ? 1 : 0; - }); - for (const item of items) { - this.addStringItem(file, item); - } - this.appendNewLine(''); - } - this.appendFooter(); - return this.buffer.join('\r\n'); - } - - public addFile(original: string, keys: (string | LocalizeInfo)[], messages: string[]) { - if (keys.length === 0) { - console.log('No keys in ' + original); - return; - } - if (keys.length !== messages.length) { - throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); - } - this.numberOfMessages += keys.length; - this.files[original] = []; - const existingKeys = new Set(); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - let realKey: string | undefined; - let comment: string | undefined; - if (typeof key === 'string') { - realKey = key; - comment = undefined; - } else if (LocalizeInfo.is(key)) { - realKey = key.key; - if (key.comment && key.comment.length > 0) { - comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); - } - } - if (!realKey || existingKeys.has(realKey)) { - continue; - } - existingKeys.add(realKey); - const message: string = encodeEntities(messages[i]); - this.files[original].push({ id: realKey, message: message, comment: comment }); - } - } - - private addStringItem(file: string, item: Item): void { - if (!item.id || item.message === undefined || item.message === null) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); - } - if (item.message.length === 0) { - log(`Item with id ${item.id} in file ${file} has an empty message.`); - } - - this.appendNewLine(``, 4); - this.appendNewLine(`${item.message}`, 6); - - if (item.comment) { - this.appendNewLine(`${item.comment}`, 6); - } - - this.appendNewLine('', 4); - } - - private appendHeader(): void { - this.appendNewLine('', 0); - this.appendNewLine('', 0); - } - - private appendFooter(): void { - this.appendNewLine('', 0); - } - - private appendNewLine(content: string, indent?: number): void { - const line = new Line(indent); - line.append(content); - this.buffer.push(line.toString()); - } - - static parse = function (xlfString: string): Promise { - return new Promise((resolve, reject) => { - const parser = new xml2js.Parser(); - - const files: { messages: Record; name: string; language: string }[] = []; - - parser.parseString(xlfString, function (err: any, result: any) { - if (err) { - reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); - } - - const fileNodes: any[] = result['xliff']['file']; - if (!fileNodes) { - reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); - } - - fileNodes.forEach((file) => { - const name = file.$.original; - if (!name) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); - } - const language = file.$['target-language']; - if (!language) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); - } - const messages: Record = {}; - - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit: any) => { - const key = unit.$.id; - if (!unit.target) { - return; // No translation available - } - - let val = unit.target[0]; - if (typeof val !== 'string') { - // We allow empty source values so support them for translations as well. - val = val._ ? val._ : ''; - } - if (!key) { - reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${name} is missing the ID attribute.`)); - return; - } - messages[key] = decodeEntities(val); - }); - files.push({ messages, name, language: language.toLowerCase() }); - } - }); - - resolve(files); - }); - }); - }; -} - + private buffer: string[]; + private files: Record; + public numberOfMessages: number; + constructor(public project: string) { + this.buffer = []; + this.files = Object.create(null); + this.numberOfMessages = 0; + } + public toString(): string { + this.appendHeader(); + const files = Object.keys(this.files).sort(); + for (const file of files) { + this.appendNewLine(``, 2); + const items = this.files[file].sort((a: Item, b: Item) => { + return a.id < b.id ? -1 : a.id > b.id ? 1 : 0; + }); + for (const item of items) { + this.addStringItem(file, item); + } + this.appendNewLine(''); + } + this.appendFooter(); + return this.buffer.join('\r\n'); + } + public addFile(original: string, keys: (string | LocalizeInfo)[], messages: string[]) { + if (keys.length === 0) { + console.log('No keys in ' + original); + return; + } + if (keys.length !== messages.length) { + throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); + } + this.numberOfMessages += keys.length; + this.files[original] = []; + const existingKeys = new Set(); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + let realKey: string | undefined; + let comment: string | undefined; + if (typeof key === 'string') { + realKey = key; + comment = undefined; + } + else if (LocalizeInfo.is(key)) { + realKey = key.key; + if (key.comment && key.comment.length > 0) { + comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); + } + } + if (!realKey || existingKeys.has(realKey)) { + continue; + } + existingKeys.add(realKey); + const message: string = encodeEntities(messages[i]); + this.files[original].push({ id: realKey, message: message, comment: comment }); + } + } + private addStringItem(file: string, item: Item): void { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); + } + this.appendNewLine(``, 4); + this.appendNewLine(`${item.message}`, 6); + if (item.comment) { + this.appendNewLine(`${item.comment}`, 6); + } + this.appendNewLine('', 4); + } + private appendHeader(): void { + this.appendNewLine('', 0); + this.appendNewLine('', 0); + } + private appendFooter(): void { + this.appendNewLine('', 0); + } + private appendNewLine(content: string, indent?: number): void { + const line = new Line(indent); + line.append(content); + this.buffer.push(line.toString()); + } + static parse = function (xlfString: string): Promise { + return new Promise((resolve, reject) => { + const parser = new xml2js.Parser(); + const files: { + messages: Record; + name: string; + language: string; + }[] = []; + parser.parseString(xlfString, function (err: any, result: any) { + if (err) { + reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); + } + const fileNodes: any[] = result['xliff']['file']; + if (!fileNodes) { + reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); + } + fileNodes.forEach((file) => { + const name = file.$.original; + if (!name) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); + } + const language = file.$['target-language']; + if (!language) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); + } + const messages: Record = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit: any) => { + const key = unit.$.id; + if (!unit.target) { + return; // No translation available + } + let val = unit.target[0]; + if (typeof val !== 'string') { + // We allow empty source values so support them for translations as well. + val = val._ ? val._ : ''; + } + if (!key) { + reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${name} is missing the ID attribute.`)); + return; + } + messages[key] = decodeEntities(val); + }); + files.push({ messages, name, language: language.toLowerCase() }); + } + }); + resolve(files); + }); + }); + }; +} function sortLanguages(languages: Language[]): Language[] { - return languages.sort((a: Language, b: Language): number => { - return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0); - }); + return languages.sort((a: Language, b: Language): number => { + return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0); + }); } - function stripComments(content: string): string { - // Copied from stripComments.js - // - // First group matches a double quoted string - // Second group matches a single quoted string - // Third group matches a multi line comment - // Forth group matches a single line comment - // Fifth group matches a trailing comma - const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g; - const result = content.replace(regexp, (match, _m1: string, _m2: string, m3: string, m4: string, m5: string) => { - // Only one of m1, m2, m3, m4, m5 matches - if (m3) { - // A block comment. Replace with nothing - return ''; - } else if (m4) { - // Since m4 is a single line comment is is at least of length 2 (e.g. //) - // If it ends in \r?\n then keep it. - const length = m4.length; - if (m4[length - 1] === '\n') { - return m4[length - 2] === '\r' ? '\r\n' : '\n'; - } else { - return ''; - } - } else if (m5) { - // Remove the trailing comma - return match.substring(1); - } else { - // We match a string - return match; - } - }); - return result; -} - + // Copied from stripComments.js + // + // First group matches a double quoted string + // Second group matches a single quoted string + // Third group matches a multi line comment + // Forth group matches a single line comment + // Fifth group matches a trailing comma + const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g; + const result = content.replace(regexp, (match, _m1: string, _m2: string, m3: string, m4: string, m5: string) => { + // Only one of m1, m2, m3, m4, m5 matches + if (m3) { + // A block comment. Replace with nothing + return ''; + } + else if (m4) { + // Since m4 is a single line comment is is at least of length 2 (e.g. //) + // If it ends in \r?\n then keep it. + const length = m4.length; + if (m4[length - 1] === '\n') { + return m4[length - 2] === '\r' ? '\r\n' : '\n'; + } + else { + return ''; + } + } + else if (m5) { + // Remove the trailing comma + return match.substring(1); + } + else { + // We match a string + return match; + } + }); + return result; +} function processCoreBundleFormat(base: string, fileHeader: string, languages: Language[], json: NLSKeysFormat, emitter: ThroughStream) { - const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n'); - if (!fs.existsSync(languageDirectory)) { - log(`No VS Code localization repository found. Looking at ${languageDirectory}`); - log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`); - } - const sortedLanguages = sortLanguages(languages); - sortedLanguages.forEach((language) => { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log(`Generating nls bundles for: ${language.id}`); - } - - const languageFolderName = language.translationId || language.id; - const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json'); - let allMessages: I18nFormat | undefined; - if (fs.existsSync(i18nFile)) { - const content = stripComments(fs.readFileSync(i18nFile, 'utf8')); - allMessages = JSON.parse(content); - } - - let nlsIndex = 0; - const nlsResult: Array = []; - for (const [moduleId, nlsKeys] of json) { - const moduleTranslations = allMessages?.contents[moduleId]; - for (const nlsKey of nlsKeys) { - nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build - nlsIndex++; - } - } - - emitter.queue(new File({ - contents: Buffer.from(`${fileHeader} + const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n'); + if (!fs.existsSync(languageDirectory)) { + log(`No VS Code localization repository found. Looking at ${languageDirectory}`); + log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`); + } + const sortedLanguages = sortLanguages(languages); + sortedLanguages.forEach((language) => { + if (process.env['VSCODE_BUILD_VERBOSE']) { + log(`Generating nls bundles for: ${language.id}`); + } + const languageFolderName = language.translationId || language.id; + const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json'); + let allMessages: I18nFormat | undefined; + if (fs.existsSync(i18nFile)) { + const content = stripComments(fs.readFileSync(i18nFile, 'utf8')); + allMessages = JSON.parse(content); + } + let nlsIndex = 0; + const nlsResult: Array = []; + for (const [moduleId, nlsKeys] of json) { + const moduleTranslations = allMessages?.contents[moduleId]; + for (const nlsKey of nlsKeys) { + nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build + nlsIndex++; + } + } + emitter.queue(new File({ + contents: Buffer.from(`${fileHeader} globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)}; globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`), - base, - path: `${base}/nls.messages.${language.id}.js` - })); - }); -} - -export function processNlsFiles(opts: { out: string; fileHeader: string; languages: Language[] }): ThroughStream { - return through(function (this: ThroughStream, file: File) { - const fileName = path.basename(file.path); - if (fileName === 'bundleInfo.json') { // pick a root level file to put the core bundles (TODO@esm this file is not created anymore, pick another) - try { - const json = JSON.parse(fs.readFileSync(path.join(REPO_ROOT_PATH, opts.out, 'nls.keys.json')).toString()); - if (NLSKeysFormat.is(json)) { - processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this); - } - } catch (error) { - this.emit('error', `Failed to read component file: ${error}`); - } - } - this.queue(file); - }); -} - -const editorProject: string = 'vscode-editor', - workbenchProject: string = 'vscode-workbench', - extensionsProject: string = 'vscode-extensions', - setupProject: string = 'vscode-setup', - serverProject: string = 'vscode-server'; - + base, + path: `${base}/nls.messages.${language.id}.js` + })); + }); +} +export function processNlsFiles(opts: { + out: string; + fileHeader: string; + languages: Language[]; +}): ThroughStream { + return through(function (this: ThroughStream, file: File) { + const fileName = path.basename(file.path); + if (fileName === 'bundleInfo.json') { // pick a root level file to put the core bundles (TODO@esm this file is not created anymore, pick another) + try { + const json = JSON.parse(fs.readFileSync(path.join(REPO_ROOT_PATH, opts.out, 'nls.keys.json')).toString()); + if (NLSKeysFormat.is(json)) { + processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this); + } + } + catch (error) { + this.emit('error', `Failed to read component file: ${error}`); + } + } + this.queue(file); + }); +} +const editorProject: string = 'vscode-editor', workbenchProject: string = 'vscode-workbench', extensionsProject: string = 'vscode-extensions', setupProject: string = 'vscode-setup', serverProject: string = 'vscode-server'; export function getResource(sourceFile: string): Resource { - let resource: string; - - if (/^vs\/platform/.test(sourceFile)) { - return { name: 'vs/platform', project: editorProject }; - } else if (/^vs\/editor\/contrib/.test(sourceFile)) { - return { name: 'vs/editor/contrib', project: editorProject }; - } else if (/^vs\/editor/.test(sourceFile)) { - return { name: 'vs/editor', project: editorProject }; - } else if (/^vs\/base/.test(sourceFile)) { - return { name: 'vs/base', project: editorProject }; - } else if (/^vs\/code/.test(sourceFile)) { - return { name: 'vs/code', project: workbenchProject }; - } else if (/^vs\/server/.test(sourceFile)) { - return { name: 'vs/server', project: serverProject }; - } else if (/^vs\/workbench\/contrib/.test(sourceFile)) { - resource = sourceFile.split('/', 4).join('/'); - return { name: resource, project: workbenchProject }; - } else if (/^vs\/workbench\/services/.test(sourceFile)) { - resource = sourceFile.split('/', 4).join('/'); - return { name: resource, project: workbenchProject }; - } else if (/^vs\/workbench/.test(sourceFile)) { - return { name: 'vs/workbench', project: workbenchProject }; - } - - throw new Error(`Could not identify the XLF bundle for ${sourceFile}`); -} - - + let resource: string; + if (/^vs\/platform/.test(sourceFile)) { + return { name: 'vs/platform', project: editorProject }; + } + else if (/^vs\/editor\/contrib/.test(sourceFile)) { + return { name: 'vs/editor/contrib', project: editorProject }; + } + else if (/^vs\/editor/.test(sourceFile)) { + return { name: 'vs/editor', project: editorProject }; + } + else if (/^vs\/base/.test(sourceFile)) { + return { name: 'vs/base', project: editorProject }; + } + else if (/^vs\/code/.test(sourceFile)) { + return { name: 'vs/code', project: workbenchProject }; + } + else if (/^vs\/server/.test(sourceFile)) { + return { name: 'vs/server', project: serverProject }; + } + else if (/^vs\/workbench\/contrib/.test(sourceFile)) { + resource = sourceFile.split('/', 4).join('/'); + return { name: resource, project: workbenchProject }; + } + else if (/^vs\/workbench\/services/.test(sourceFile)) { + resource = sourceFile.split('/', 4).join('/'); + return { name: resource, project: workbenchProject }; + } + else if (/^vs\/workbench/.test(sourceFile)) { + return { name: 'vs/workbench', project: workbenchProject }; + } + throw new Error(`Could not identify the XLF bundle for ${sourceFile}`); +} export function createXlfFilesForCoreBundle(): ThroughStream { - return through(function (this: ThroughStream, file: File) { - const basename = path.basename(file.path); - if (basename === 'nls.metadata.json') { - if (file.isBuffer()) { - const xlfs: Record = Object.create(null); - const json: BundledFormat = JSON.parse((file.contents as Buffer).toString('utf8')); - for (const coreModule in json.keys) { - const projectResource = getResource(coreModule); - const resource = projectResource.name; - const project = projectResource.project; - - const keys = json.keys[coreModule]; - const messages = json.messages[coreModule]; - if (keys.length !== messages.length) { - this.emit('error', `There is a mismatch between keys and messages in ${file.relative} for module ${coreModule}`); - return; - } else { - let xlf = xlfs[resource]; - if (!xlf) { - xlf = new XLF(project); - xlfs[resource] = xlf; - } - xlf.addFile(`src/${coreModule}`, keys, messages); - } - } - for (const resource in xlfs) { - const xlf = xlfs[resource]; - const filePath = `${xlf.project}/${resource.replace(/\//g, '_')}.xlf`; - const xlfFile = new File({ - path: filePath, - contents: Buffer.from(xlf.toString(), 'utf8') - }); - this.queue(xlfFile); - } - } else { - this.emit('error', new Error(`File ${file.relative} is not using a buffer content`)); - return; - } - } else { - this.emit('error', new Error(`File ${file.relative} is not a core meta data file.`)); - return; - } - }); -} - + return through(function (this: ThroughStream, file: File) { + const basename = path.basename(file.path); + if (basename === 'nls.metadata.json') { + if (file.isBuffer()) { + const xlfs: Record = Object.create(null); + const json: BundledFormat = JSON.parse((file.contents as Buffer).toString('utf8')); + for (const coreModule in json.keys) { + const projectResource = getResource(coreModule); + const resource = projectResource.name; + const project = projectResource.project; + const keys = json.keys[coreModule]; + const messages = json.messages[coreModule]; + if (keys.length !== messages.length) { + this.emit('error', `There is a mismatch between keys and messages in ${file.relative} for module ${coreModule}`); + return; + } + else { + let xlf = xlfs[resource]; + if (!xlf) { + xlf = new XLF(project); + xlfs[resource] = xlf; + } + xlf.addFile(`src/${coreModule}`, keys, messages); + } + } + for (const resource in xlfs) { + const xlf = xlfs[resource]; + const filePath = `${xlf.project}/${resource.replace(/\//g, '_')}.xlf`; + const xlfFile = new File({ + path: filePath, + contents: Buffer.from(xlf.toString(), 'utf8') + }); + this.queue(xlfFile); + } + } + else { + this.emit('error', new Error(`File ${file.relative} is not using a buffer content`)); + return; + } + } + else { + this.emit('error', new Error(`File ${file.relative} is not a core meta data file.`)); + return; + } + }); +} function createL10nBundleForExtension(extensionFolderName: string, prefixWithBuildFolder: boolean): NodeJS.ReadWriteStream { - const prefix = prefixWithBuildFolder ? '.build/' : ''; - return gulp - .src([ - // For source code of extensions - `${prefix}extensions/${extensionFolderName}/{src,client,server}/**/*.{ts,tsx}`, - // // For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper) - `${prefix}extensions/${extensionFolderName}/**/node_modules/{@vscode,vscode-*}/**/*.{js,jsx}`, - // // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle - `${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`, - ]) - .pipe(map(function (data, callback) { - const file = data as File; - if (!file.isBuffer()) { - // Not a buffer so we drop it - callback(); - return; - } - const extension = path.extname(file.relative); - if (extension !== '.json') { - const contents = file.contents.toString('utf8'); - getL10nJson([{ contents, extension }]) - .then((json) => { - callback(undefined, new File({ - path: `extensions/${extensionFolderName}/bundle.l10n.json`, - contents: Buffer.from(JSON.stringify(json), 'utf8') - })); - }) - .catch((err) => { - callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); - }); - // signal pause? - return false; - } - - // for bundle.l10n.jsons - let bundleJson; - try { - bundleJson = JSON.parse(file.contents.toString('utf8')); - } catch (err) { - callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); - return; - } - - // some validation of the bundle.l10n.json format - for (const key in bundleJson) { - if ( - typeof bundleJson[key] !== 'string' && - (typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment)) - ) { - callback(new Error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format.`)); - return; - } - } - - callback(undefined, file); - })) - .pipe(jsonMerge({ - fileName: `extensions/${extensionFolderName}/bundle.l10n.json`, - jsonSpace: '', - concatArrays: true - })); -} - + const prefix = prefixWithBuildFolder ? '.build/' : ''; + return gulp + .src([ + // For source code of extensions + `${prefix}extensions/${extensionFolderName}/{src,client,server}/**/*.{ts,tsx}`, + // // For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper) + `${prefix}extensions/${extensionFolderName}/**/node_modules/{@vscode,vscode-*}/**/*.{js,jsx}`, + // // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle + `${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`, + ]) + .pipe(map(function (data, callback) { + const file = data as File; + if (!file.isBuffer()) { + // Not a buffer so we drop it + callback(); + return; + } + const extension = path.extname(file.relative); + if (extension !== '.json') { + const contents = file.contents.toString('utf8'); + getL10nJson([{ contents, extension }]) + .then((json) => { + callback(undefined, new File({ + path: `extensions/${extensionFolderName}/bundle.l10n.json`, + contents: Buffer.from(JSON.stringify(json), 'utf8') + })); + }) + .catch((err) => { + callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); + }); + // signal pause? + return false; + } + // for bundle.l10n.jsons + let bundleJson; + try { + bundleJson = JSON.parse(file.contents.toString('utf8')); + } + catch (err) { + callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); + return; + } + // some validation of the bundle.l10n.json format + for (const key in bundleJson) { + if (typeof bundleJson[key] !== 'string' && + (typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment))) { + callback(new Error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format.`)); + return; + } + } + callback(undefined, file); + })) + .pipe(jsonMerge({ + fileName: `extensions/${extensionFolderName}/bundle.l10n.json`, + jsonSpace: '', + concatArrays: true + })); +} export const EXTERNAL_EXTENSIONS = [ - 'ms-vscode.js-debug', - 'ms-vscode.js-debug-companion', - 'ms-vscode.vscode-js-profile-table', + 'ms-vscode.js-debug', + 'ms-vscode.js-debug-companion', + 'ms-vscode.vscode-js-profile-table', ]; - export function createXlfFilesForExtensions(): ThroughStream { - let counter: number = 0; - let folderStreamEnded: boolean = false; - let folderStreamEndEmitted: boolean = false; - return through(function (this: ThroughStream, extensionFolder: File) { - const folderStream = this; - const stat = fs.statSync(extensionFolder.path); - if (!stat.isDirectory()) { - return; - } - const extensionFolderName = path.basename(extensionFolder.path); - if (extensionFolderName === 'node_modules') { - return; - } - // Get extension id and use that as the id - const manifest = fs.readFileSync(path.join(extensionFolder.path, 'package.json'), 'utf-8'); - const manifestJson = JSON.parse(manifest); - const extensionId = manifestJson.publisher + '.' + manifestJson.name; - - counter++; - let _l10nMap: Map; - function getL10nMap() { - if (!_l10nMap) { - _l10nMap = new Map(); - } - return _l10nMap; - } - merge( - gulp.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), - createL10nBundleForExtension(extensionFolderName, EXTERNAL_EXTENSIONS.includes(extensionId)) - ).pipe(through(function (file: File) { - if (file.isBuffer()) { - const buffer: Buffer = file.contents as Buffer; - const basename = path.basename(file.path); - if (basename === 'package.nls.json') { - const json: l10nJsonFormat = JSON.parse(buffer.toString('utf8')); - getL10nMap().set(`extensions/${extensionId}/package`, json); - } else if (basename === 'nls.metadata.json') { - const json: BundledExtensionFormat = JSON.parse(buffer.toString('utf8')); - const relPath = path.relative(`.build/extensions/${extensionFolderName}`, path.dirname(file.path)); - for (const file in json) { - const fileContent = json[file]; - const info: l10nJsonFormat = Object.create(null); - for (let i = 0; i < fileContent.messages.length; i++) { - const message = fileContent.messages[i]; - const { key, comment } = LocalizeInfo.is(fileContent.keys[i]) - ? fileContent.keys[i] as LocalizeInfo - : { key: fileContent.keys[i] as string, comment: undefined }; - - info[key] = comment ? { message, comment } : message; - } - getL10nMap().set(`extensions/${extensionId}/${relPath}/${file}`, info); - } - } else if (basename === 'bundle.l10n.json') { - const json: l10nJsonFormat = JSON.parse(buffer.toString('utf8')); - getL10nMap().set(`extensions/${extensionId}/bundle`, json); - } else { - this.emit('error', new Error(`${file.path} is not a valid extension nls file`)); - return; - } - } - }, function () { - if (_l10nMap?.size > 0) { - const xlfFile = new File({ - path: path.join(extensionsProject, extensionId + '.xlf'), - contents: Buffer.from(getL10nXlf(_l10nMap), 'utf8') - }); - folderStream.queue(xlfFile); - } - this.queue(null); - counter--; - if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) { - folderStreamEndEmitted = true; - folderStream.queue(null); - } - })); - }, function () { - folderStreamEnded = true; - if (counter === 0) { - folderStreamEndEmitted = true; - this.queue(null); - } - }); -} - + let counter: number = 0; + let folderStreamEnded: boolean = false; + let folderStreamEndEmitted: boolean = false; + return through(function (this: ThroughStream, extensionFolder: File) { + const folderStream = this; + const stat = fs.statSync(extensionFolder.path); + if (!stat.isDirectory()) { + return; + } + const extensionFolderName = path.basename(extensionFolder.path); + if (extensionFolderName === 'node_modules') { + return; + } + // Get extension id and use that as the id + const manifest = fs.readFileSync(path.join(extensionFolder.path, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const extensionId = manifestJson.publisher + '.' + manifestJson.name; + counter++; + let _l10nMap: Map; + function getL10nMap() { + if (!_l10nMap) { + _l10nMap = new Map(); + } + return _l10nMap; + } + merge(gulp.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), createL10nBundleForExtension(extensionFolderName, EXTERNAL_EXTENSIONS.includes(extensionId))).pipe(through(function (file: File) { + if (file.isBuffer()) { + const buffer: Buffer = file.contents as Buffer; + const basename = path.basename(file.path); + if (basename === 'package.nls.json') { + const json: l10nJsonFormat = JSON.parse(buffer.toString('utf8')); + getL10nMap().set(`extensions/${extensionId}/package`, json); + } + else if (basename === 'nls.metadata.json') { + const json: BundledExtensionFormat = JSON.parse(buffer.toString('utf8')); + const relPath = path.relative(`.build/extensions/${extensionFolderName}`, path.dirname(file.path)); + for (const file in json) { + const fileContent = json[file]; + const info: l10nJsonFormat = Object.create(null); + for (let i = 0; i < fileContent.messages.length; i++) { + const message = fileContent.messages[i]; + const { key, comment } = LocalizeInfo.is(fileContent.keys[i]) + ? fileContent.keys[i] as LocalizeInfo + : { key: fileContent.keys[i] as string, comment: undefined }; + info[key] = comment ? { message, comment } : message; + } + getL10nMap().set(`extensions/${extensionId}/${relPath}/${file}`, info); + } + } + else if (basename === 'bundle.l10n.json') { + const json: l10nJsonFormat = JSON.parse(buffer.toString('utf8')); + getL10nMap().set(`extensions/${extensionId}/bundle`, json); + } + else { + this.emit('error', new Error(`${file.path} is not a valid extension nls file`)); + return; + } + } + }, function () { + if (_l10nMap?.size > 0) { + const xlfFile = new File({ + path: path.join(extensionsProject, extensionId + '.xlf'), + contents: Buffer.from(getL10nXlf(_l10nMap), 'utf8') + }); + folderStream.queue(xlfFile); + } + this.queue(null); + counter--; + if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) { + folderStreamEndEmitted = true; + folderStream.queue(null); + } + })); + }, function () { + folderStreamEnded = true; + if (counter === 0) { + folderStreamEndEmitted = true; + this.queue(null); + } + }); +} export function createXlfFilesForIsl(): ThroughStream { - return through(function (this: ThroughStream, file: File) { - let projectName: string, - resourceFile: string; - if (path.basename(file.path) === 'messages.en.isl') { - projectName = setupProject; - resourceFile = 'messages.xlf'; - } else { - throw new Error(`Unknown input file ${file.path}`); - } - - const xlf = new XLF(projectName), - keys: string[] = [], - messages: string[] = []; - - const model = new TextModel(file.contents.toString()); - let inMessageSection = false; - model.lines.forEach(line => { - if (line.length === 0) { - return; - } - const firstChar = line.charAt(0); - switch (firstChar) { - case ';': - // Comment line; - return; - case '[': - inMessageSection = '[Messages]' === line || '[CustomMessages]' === line; - return; - } - if (!inMessageSection) { - return; - } - const sections: string[] = line.split('='); - if (sections.length !== 2) { - throw new Error(`Badly formatted message found: ${line}`); - } else { - const key = sections[0]; - const value = sections[1]; - if (key.length > 0 && value.length > 0) { - keys.push(key); - messages.push(value); - } - } - }); - - const originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/'); - xlf.addFile(originalPath, keys, messages); - - // Emit only upon all ISL files combined into single XLF instance - const newFilePath = path.join(projectName, resourceFile); - const xlfFile = new File({ path: newFilePath, contents: Buffer.from(xlf.toString(), 'utf-8') }); - this.queue(xlfFile); - }); -} - + return through(function (this: ThroughStream, file: File) { + let projectName: string, resourceFile: string; + if (path.basename(file.path) === 'messages.en.isl') { + projectName = setupProject; + resourceFile = 'messages.xlf'; + } + else { + throw new Error(`Unknown input file ${file.path}`); + } + const xlf = new XLF(projectName), keys: string[] = [], messages: string[] = []; + const model = new TextModel(file.contents.toString()); + let inMessageSection = false; + model.lines.forEach(line => { + if (line.length === 0) { + return; + } + const firstChar = line.charAt(0); + switch (firstChar) { + case ';': + // Comment line; + return; + case '[': + inMessageSection = '[Messages]' === line || '[CustomMessages]' === line; + return; + } + if (!inMessageSection) { + return; + } + const sections: string[] = line.split('='); + if (sections.length !== 2) { + throw new Error(`Badly formatted message found: ${line}`); + } + else { + const key = sections[0]; + const value = sections[1]; + if (key.length > 0 && value.length > 0) { + keys.push(key); + messages.push(value); + } + } + }); + const originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/'); + xlf.addFile(originalPath, keys, messages); + // Emit only upon all ISL files combined into single XLF instance + const newFilePath = path.join(projectName, resourceFile); + const xlfFile = new File({ path: newFilePath, contents: Buffer.from(xlf.toString(), 'utf-8') }); + this.queue(xlfFile); + }); +} function createI18nFile(name: string, messages: any): File { - const result = Object.create(null); - result[''] = [ - '--------------------------------------------------------------------------------------------', - 'Copyright (c) Microsoft Corporation. All rights reserved.', - 'Licensed under the MIT License. See License.txt in the project root for license information.', - '--------------------------------------------------------------------------------------------', - 'Do not edit this file. It is machine generated.' - ]; - for (const key of Object.keys(messages)) { - result[key] = messages[key]; - } - - let content = JSON.stringify(result, null, '\t'); - if (process.platform === 'win32') { - content = content.replace(/\n/g, '\r\n'); - } - return new File({ - path: path.join(name + '.i18n.json'), - contents: Buffer.from(content, 'utf8') - }); -} - + const result = Object.create(null); + result[''] = [ + '--------------------------------------------------------------------------------------------', + 'Copyright (c) Microsoft Corporation. All rights reserved.', + 'Licensed under the MIT License. See License.txt in the project root for license information.', + '--------------------------------------------------------------------------------------------', + 'Do not edit this file. It is machine generated.' + ]; + for (const key of Object.keys(messages)) { + result[key] = messages[key]; + } + let content = JSON.stringify(result, null, '\t'); + if (process.platform === 'win32') { + content = content.replace(/\n/g, '\r\n'); + } + return new File({ + path: path.join(name + '.i18n.json'), + contents: Buffer.from(content, 'utf8') + }); +} interface I18nPack { - version: string; - contents: { - [path: string]: Record; - }; + version: string; + contents: { + [path: string]: Record; + }; } - const i18nPackVersion = '1.0.0'; - export interface TranslationPath { - id: string; - resourceName: string; + id: string; + resourceName: string; } - function getRecordFromL10nJsonFormat(l10nJsonFormat: l10nJsonFormat): Record { - const record: Record = {}; - for (const key of Object.keys(l10nJsonFormat).sort()) { - const value = l10nJsonFormat[key]; - record[key] = typeof value === 'string' ? value : value.message; - } - return record; -} - + const record: Record = {}; + for (const key of Object.keys(l10nJsonFormat).sort()) { + const value = l10nJsonFormat[key]; + record[key] = typeof value === 'string' ? value : value.message; + } + return record; +} export function prepareI18nPackFiles(resultingTranslationPaths: TranslationPath[]): NodeJS.ReadWriteStream { - const parsePromises: Promise[] = []; - const mainPack: I18nPack = { version: i18nPackVersion, contents: {} }; - const extensionsPacks: Record = {}; - const errors: any[] = []; - return through(function (this: ThroughStream, xlf: File) { - let project = path.basename(path.dirname(path.dirname(xlf.relative))); - // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline - const resource = path.basename(path.basename(xlf.relative, '.xlf'), '-new'); - if (EXTERNAL_EXTENSIONS.find(e => e === resource)) { - project = extensionsProject; - } - const contents = xlf.contents.toString(); - log(`Found ${project}: ${resource}`); - const parsePromise = getL10nFilesFromXlf(contents); - parsePromises.push(parsePromise); - parsePromise.then( - resolvedFiles => { - resolvedFiles.forEach(file => { - const path = file.name; - const firstSlash = path.indexOf('/'); - - if (project === extensionsProject) { - // resource will be the extension id - let extPack = extensionsPacks[resource]; - if (!extPack) { - extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} }; - } - // remove 'extensions/extensionId/' segment - const secondSlash = path.indexOf('/', firstSlash + 1); - extPack.contents[path.substring(secondSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); - } else { - mainPack.contents[path.substring(firstSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); - } - }); - } - ).catch(reason => { - errors.push(reason); - }); - }, function () { - Promise.all(parsePromises) - .then(() => { - if (errors.length > 0) { - throw errors; - } - const translatedMainFile = createI18nFile('./main', mainPack); - resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' }); - - this.queue(translatedMainFile); - for (const extensionId in extensionsPacks) { - const translatedExtFile = createI18nFile(`extensions/${extensionId}`, extensionsPacks[extensionId]); - this.queue(translatedExtFile); - - resultingTranslationPaths.push({ id: extensionId, resourceName: `extensions/${extensionId}.i18n.json` }); - } - this.queue(null); - }) - .catch((reason) => { - this.emit('error', reason); - }); - }); -} - + const parsePromises: Promise[] = []; + const mainPack: I18nPack = { version: i18nPackVersion, contents: {} }; + const extensionsPacks: Record = {}; + const errors: any[] = []; + return through(function (this: ThroughStream, xlf: File) { + let project = path.basename(path.dirname(path.dirname(xlf.relative))); + // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline + const resource = path.basename(path.basename(xlf.relative, '.xlf'), '-new'); + if (EXTERNAL_EXTENSIONS.find(e => e === resource)) { + project = extensionsProject; + } + const contents = xlf.contents.toString(); + log(`Found ${project}: ${resource}`); + const parsePromise = getL10nFilesFromXlf(contents); + parsePromises.push(parsePromise); + parsePromise.then(resolvedFiles => { + resolvedFiles.forEach(file => { + const path = file.name; + const firstSlash = path.indexOf('/'); + if (project === extensionsProject) { + // resource will be the extension id + let extPack = extensionsPacks[resource]; + if (!extPack) { + extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} }; + } + // remove 'extensions/extensionId/' segment + const secondSlash = path.indexOf('/', firstSlash + 1); + extPack.contents[path.substring(secondSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); + } + else { + mainPack.contents[path.substring(firstSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); + } + }); + }).catch(reason => { + errors.push(reason); + }); + }, function () { + Promise.all(parsePromises) + .then(() => { + if (errors.length > 0) { + throw errors; + } + const translatedMainFile = createI18nFile('./main', mainPack); + resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' }); + this.queue(translatedMainFile); + for (const extensionId in extensionsPacks) { + const translatedExtFile = createI18nFile(`extensions/${extensionId}`, extensionsPacks[extensionId]); + this.queue(translatedExtFile); + resultingTranslationPaths.push({ id: extensionId, resourceName: `extensions/${extensionId}.i18n.json` }); + } + this.queue(null); + }) + .catch((reason) => { + this.emit('error', reason); + }); + }); +} export function prepareIslFiles(language: Language, innoSetupConfig: InnoSetup): ThroughStream { - const parsePromises: Promise[] = []; - - return through(function (this: ThroughStream, xlf: File) { - const stream = this; - const parsePromise = XLF.parse(xlf.contents.toString()); - parsePromises.push(parsePromise); - parsePromise.then( - resolvedFiles => { - resolvedFiles.forEach(file => { - const translatedFile = createIslFile(file.name, file.messages, language, innoSetupConfig); - stream.queue(translatedFile); - }); - } - ).catch(reason => { - this.emit('error', reason); - }); - }, function () { - Promise.all(parsePromises) - .then(() => { this.queue(null); }) - .catch(reason => { - this.emit('error', reason); - }); - }); -} - + const parsePromises: Promise[] = []; + return through(function (this: ThroughStream, xlf: File) { + const stream = this; + const parsePromise = XLF.parse(xlf.contents.toString()); + parsePromises.push(parsePromise); + parsePromise.then(resolvedFiles => { + resolvedFiles.forEach(file => { + const translatedFile = createIslFile(file.name, file.messages, language, innoSetupConfig); + stream.queue(translatedFile); + }); + }).catch(reason => { + this.emit('error', reason); + }); + }, function () { + Promise.all(parsePromises) + .then(() => { this.queue(null); }) + .catch(reason => { + this.emit('error', reason); + }); + }); +} function createIslFile(name: string, messages: l10nJsonFormat, language: Language, innoSetup: InnoSetup): File { - const content: string[] = []; - let originalContent: TextModel; - if (path.basename(name) === 'Default') { - originalContent = new TextModel(fs.readFileSync(name + '.isl', 'utf8')); - } else { - originalContent = new TextModel(fs.readFileSync(name + '.en.isl', 'utf8')); - } - originalContent.lines.forEach(line => { - if (line.length > 0) { - const firstChar = line.charAt(0); - if (firstChar === '[' || firstChar === ';') { - content.push(line); - } else { - const sections: string[] = line.split('='); - const key = sections[0]; - let translated = line; - if (key) { - const translatedMessage = messages[key]; - if (translatedMessage) { - translated = `${key}=${translatedMessage}`; - } - } - - content.push(translated); - } - } - }); - - const basename = path.basename(name); - const filePath = `${basename}.${language.id}.isl`; - const encoded = iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage); - - return new File({ - path: filePath, - contents: Buffer.from(encoded), - }); -} - + const content: string[] = []; + let originalContent: TextModel; + if (path.basename(name) === 'Default') { + originalContent = new TextModel(fs.readFileSync(name + '.isl', 'utf8')); + } + else { + originalContent = new TextModel(fs.readFileSync(name + '.en.isl', 'utf8')); + } + originalContent.lines.forEach(line => { + if (line.length > 0) { + const firstChar = line.charAt(0); + if (firstChar === '[' || firstChar === ';') { + content.push(line); + } + else { + const sections: string[] = line.split('='); + const key = sections[0]; + let translated = line; + if (key) { + const translatedMessage = messages[key]; + if (translatedMessage) { + translated = `${key}=${translatedMessage}`; + } + } + content.push(translated); + } + } + }); + const basename = path.basename(name); + const filePath = `${basename}.${language.id}.isl`; + const encoded = iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage); + return new File({ + path: filePath, + contents: Buffer.from(encoded), + }); +} function encodeEntities(value: string): string { - const result: string[] = []; - for (let i = 0; i < value.length; i++) { - const ch = value[i]; - switch (ch) { - case '<': - result.push('<'); - break; - case '>': - result.push('>'); - break; - case '&': - result.push('&'); - break; - default: - result.push(ch); - } - } - return result.join(''); -} - + const result: string[] = []; + for (let i = 0; i < value.length; i++) { + const ch = value[i]; + switch (ch) { + case '<': + result.push('<'); + break; + case '>': + result.push('>'); + break; + case '&': + result.push('&'); + break; + default: + result.push(ch); + } + } + return result.join(''); +} function decodeEntities(value: string): string { - return value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + return value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); } diff --git a/build/lib/inlineMeta.ts b/build/lib/inlineMeta.ts index ef3987fc32ed1..a86e7db52d0ff 100644 --- a/build/lib/inlineMeta.ts +++ b/build/lib/inlineMeta.ts @@ -2,19 +2,15 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import { basename } from 'path'; import * as File from 'vinyl'; - export interface IInlineMetaContext { - readonly targetPaths: string[]; - readonly packageJsonFn: () => string; - readonly productJsonFn: () => string; + readonly targetPaths: string[]; + readonly packageJsonFn: () => string; + readonly productJsonFn: () => string; } - const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; - // TODO@bpasero in order to inline `product.json`, more work is // needed to ensure that we cover all cases where modifications // are done to the product configuration during build. There are @@ -22,39 +18,33 @@ const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; // - a `darwinUniversalAssetId` is added in`create-universal-app.ts` // - a `target` is added in `gulpfile.vscode.win32.js` // const productJsonMarkerId = 'BUILD_INSERT_PRODUCT_CONFIGURATION'; - export function inlineMeta(result: NodeJS.ReadWriteStream, ctx: IInlineMetaContext): NodeJS.ReadWriteStream { - return result.pipe(es.through(function (file: File) { - if (matchesFile(file, ctx)) { - let content = file.contents.toString(); - let markerFound = false; - - const packageMarker = `${packageJsonMarkerId}:"${packageJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) - if (content.includes(packageMarker)) { - content = content.replace(packageMarker, JSON.stringify(JSON.parse(ctx.packageJsonFn())).slice(1, -1) /* trim braces */); - markerFound = true; - } - - // const productMarker = `${productJsonMarkerId}:"${productJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) - // if (content.includes(productMarker)) { - // content = content.replace(productMarker, JSON.stringify(JSON.parse(ctx.productJsonFn())).slice(1, -1) /* trim braces */); - // markerFound = true; - // } - - if (markerFound) { - file.contents = Buffer.from(content); - } - } - - this.emit('data', file); - })); + return result.pipe(es.through(function (file: File) { + if (matchesFile(file, ctx)) { + let content = file.contents.toString(); + let markerFound = false; + const packageMarker = `${packageJsonMarkerId}:"${packageJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) + if (content.includes(packageMarker)) { + content = content.replace(packageMarker, JSON.stringify(JSON.parse(ctx.packageJsonFn())).slice(1, -1) /* trim braces */); + markerFound = true; + } + // const productMarker = `${productJsonMarkerId}:"${productJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) + // if (content.includes(productMarker)) { + // content = content.replace(productMarker, JSON.stringify(JSON.parse(ctx.productJsonFn())).slice(1, -1) /* trim braces */); + // markerFound = true; + // } + if (markerFound) { + file.contents = Buffer.from(content); + } + } + this.emit('data', file); + })); } - function matchesFile(file: File, ctx: IInlineMetaContext): boolean { - for (const targetPath of ctx.targetPaths) { - if (file.basename === basename(targetPath)) { // TODO would be nicer to figure out root relative path to not match on false positives - return true; - } - } - return false; + for (const targetPath of ctx.targetPaths) { + if (file.basename === basename(targetPath)) { // TODO would be nicer to figure out root relative path to not match on false positives + return true; + } + } + return false; } diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 3ecde09be21fc..833b528e6bad1 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -2,12 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as ts from 'typescript'; import { readFileSync, existsSync } from 'fs'; import { resolve, dirname, join } from 'path'; import { match } from 'minimatch'; - // // ############################################################################################# // @@ -20,414 +18,362 @@ import { match } from 'minimatch'; // // ############################################################################################# // - // Types we assume are present in all implementations of JS VMs (node.js, browsers) // Feel free to add more core types as you see needed if present in node.js and browsers const CORE_TYPES = [ - 'setTimeout', - 'clearTimeout', - 'setInterval', - 'clearInterval', - 'console', - 'Console', - 'Error', - 'ErrorConstructor', - 'String', - 'TextDecoder', - 'TextEncoder', - 'self', - 'queueMicrotask', - 'Array', - 'Uint8Array', - 'Uint16Array', - 'Uint32Array', - 'Int8Array', - 'Int16Array', - 'Int32Array', - 'Float32Array', - 'Float64Array', - 'Uint8ClampedArray', - 'BigUint64Array', - 'BigInt64Array', - 'btoa', - 'atob', - 'AbortController', - 'AbortSignal', - 'MessageChannel', - 'MessagePort', - 'URL', - 'URLSearchParams', - 'ReadonlyArray', - 'Event', - 'EventTarget', - 'BroadcastChannel', - 'performance', - 'Blob', - 'crypto', - 'File', - 'fetch', - 'RequestInit', - 'Headers', - 'Response', - '__global', - 'PerformanceMark', - 'PerformanceObserver', - 'ImportMeta', - - // webcrypto has been available since Node.js 19, but still live in dom.d.ts - 'Crypto', - 'SubtleCrypto' + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'console', + 'Console', + 'Error', + 'ErrorConstructor', + 'String', + 'TextDecoder', + 'TextEncoder', + 'self', + 'queueMicrotask', + 'Array', + 'Uint8Array', + 'Uint16Array', + 'Uint32Array', + 'Int8Array', + 'Int16Array', + 'Int32Array', + 'Float32Array', + 'Float64Array', + 'Uint8ClampedArray', + 'BigUint64Array', + 'BigInt64Array', + 'btoa', + 'atob', + 'AbortController', + 'AbortSignal', + 'MessageChannel', + 'MessagePort', + 'URL', + 'URLSearchParams', + 'ReadonlyArray', + 'Event', + 'EventTarget', + 'BroadcastChannel', + 'performance', + 'Blob', + 'crypto', + 'File', + 'fetch', + 'RequestInit', + 'Headers', + 'Response', + '__global', + 'PerformanceMark', + 'PerformanceObserver', + 'ImportMeta', + // webcrypto has been available since Node.js 19, but still live in dom.d.ts + 'Crypto', + 'SubtleCrypto' ]; - // Types that are defined in a common layer but are known to be only // available in native environments should not be allowed in browser const NATIVE_TYPES = [ - 'NativeParsedArgs', - 'INativeEnvironmentService', - 'AbstractNativeEnvironmentService', - 'INativeWindowConfiguration', - 'ICommonNativeHostService', - 'INativeHostService', - 'IMainProcessService' + 'NativeParsedArgs', + 'INativeEnvironmentService', + 'AbstractNativeEnvironmentService', + 'INativeWindowConfiguration', + 'ICommonNativeHostService', + 'INativeHostService', + 'IMainProcessService' ]; - const RULES: IRule[] = [ - - // Tests: skip - { - target: '**/vs/**/test/**', - skip: true // -> skip all test files - }, - - // Common: vs/base/common/platform.ts - { - target: '**/vs/base/common/platform.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to postMessage() and friends - 'MessageEvent', - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/base/common/async.ts - { - target: '**/vs/base/common/async.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to requestIdleCallback & cancelIdleCallback - 'requestIdleCallback', - 'cancelIdleCallback' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/base/common/performance.ts - { - target: '**/vs/base/common/performance.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to Performance - 'Performance', - 'PerformanceEntry', - 'PerformanceTiming' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/platform/environment/common/* - { - target: '**/vs/platform/environment/common/*.ts', - allowedTypes: CORE_TYPES, - disallowedTypes: [/* Ignore native types that are defined from here */], - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/platform/window/common/window.ts - { - target: '**/vs/platform/window/common/window.ts', - allowedTypes: CORE_TYPES, - disallowedTypes: [/* Ignore native types that are defined from here */], - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/platform/native/common/native.ts - { - target: '**/vs/platform/native/common/native.ts', - allowedTypes: CORE_TYPES, - disallowedTypes: [/* Ignore native types that are defined from here */], - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/platform/native/common/nativeHostService.ts - { - target: '**/vs/platform/native/common/nativeHostService.ts', - allowedTypes: CORE_TYPES, - disallowedTypes: [/* Ignore native types that are defined from here */], - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/workbench/api/common/extHostExtensionService.ts - { - target: '**/vs/workbench/api/common/extHostExtensionService.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to global - 'global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/base/parts/sandbox/electron-sandbox/preload.ts - { - target: '**/vs/base/parts/sandbox/electron-sandbox/preload.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to a very small subset of node.js - 'process', - 'NodeJS' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // Common - { - target: '**/vs/**/common/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Browser - { - target: '**/vs/**/browser/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - allowedDefinitions: [ - '@types/node/stream/consumers.d.ts' // node.js started to duplicate types from lib.dom.d.ts so we have to account for that - ], - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // Browser (editor contrib) - { - target: '**/src/vs/editor/contrib/**', - allowedTypes: CORE_TYPES, - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // node.js - { - target: '**/vs/**/node/**', - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - }, - - // Electron (sandbox) - { - target: '**/vs/**/electron-sandbox/**', - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - '@types/node' // no node.js - ] - }, - - // Electron (utility) - { - target: '**/vs/**/electron-utility/**', - allowedTypes: [ - ...CORE_TYPES, - - // --> types from electron.d.ts that duplicate from lib.dom.d.ts - 'Event', - 'Request' - ], - disallowedTypes: [ - 'ipcMain' // not allowed, use validatedIpcMain instead - ], - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - }, - - // Electron (main) - { - target: '**/vs/**/electron-main/**', - allowedTypes: [ - ...CORE_TYPES, - - // --> types from electron.d.ts that duplicate from lib.dom.d.ts - 'Event', - 'Request' - ], - disallowedTypes: [ - 'ipcMain' // not allowed, use validatedIpcMain instead - ], - disallowedDefinitions: [ - 'lib.dom.d.ts' // no DOM - ] - } + // Tests: skip + { + target: '**/vs/**/test/**', + skip: true // -> skip all test files + }, + // Common: vs/base/common/platform.ts + { + target: '**/vs/base/common/platform.ts', + allowedTypes: [ + ...CORE_TYPES, + // Safe access to postMessage() and friends + 'MessageEvent', + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/base/common/async.ts + { + target: '**/vs/base/common/async.ts', + allowedTypes: [ + ...CORE_TYPES, + // Safe access to requestIdleCallback & cancelIdleCallback + 'requestIdleCallback', + 'cancelIdleCallback' + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/base/common/performance.ts + { + target: '**/vs/base/common/performance.ts', + allowedTypes: [ + ...CORE_TYPES, + // Safe access to Performance + 'Performance', + 'PerformanceEntry', + 'PerformanceTiming' + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/platform/environment/common/* + { + target: '**/vs/platform/environment/common/*.ts', + allowedTypes: CORE_TYPES, + disallowedTypes: [ /* Ignore native types that are defined from here */], + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/platform/window/common/window.ts + { + target: '**/vs/platform/window/common/window.ts', + allowedTypes: CORE_TYPES, + disallowedTypes: [ /* Ignore native types that are defined from here */], + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/platform/native/common/native.ts + { + target: '**/vs/platform/native/common/native.ts', + allowedTypes: CORE_TYPES, + disallowedTypes: [ /* Ignore native types that are defined from here */], + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/platform/native/common/nativeHostService.ts + { + target: '**/vs/platform/native/common/nativeHostService.ts', + allowedTypes: CORE_TYPES, + disallowedTypes: [ /* Ignore native types that are defined from here */], + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/workbench/api/common/extHostExtensionService.ts + { + target: '**/vs/workbench/api/common/extHostExtensionService.ts', + allowedTypes: [ + ...CORE_TYPES, + // Safe access to global + 'global' + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/base/parts/sandbox/electron-sandbox/preload.ts + { + target: '**/vs/base/parts/sandbox/electron-sandbox/preload.ts', + allowedTypes: [ + ...CORE_TYPES, + // Safe access to a very small subset of node.js + 'process', + 'NodeJS' + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + '@types/node' // no node.js + ] + }, + // Common + { + target: '**/vs/**/common/**', + allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Browser + { + target: '**/vs/**/browser/**', + allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, + allowedDefinitions: [ + '@types/node/stream/consumers.d.ts' // node.js started to duplicate types from lib.dom.d.ts so we have to account for that + ], + disallowedDefinitions: [ + '@types/node' // no node.js + ] + }, + // Browser (editor contrib) + { + target: '**/src/vs/editor/contrib/**', + allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + '@types/node' // no node.js + ] + }, + // node.js + { + target: '**/vs/**/node/**', + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts' // no DOM + ] + }, + // Electron (sandbox) + { + target: '**/vs/**/electron-sandbox/**', + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + '@types/node' // no node.js + ] + }, + // Electron (utility) + { + target: '**/vs/**/electron-utility/**', + allowedTypes: [ + ...CORE_TYPES, + // --> types from electron.d.ts that duplicate from lib.dom.d.ts + 'Event', + 'Request' + ], + disallowedTypes: [ + 'ipcMain' // not allowed, use validatedIpcMain instead + ], + disallowedDefinitions: [ + 'lib.dom.d.ts' // no DOM + ] + }, + // Electron (main) + { + target: '**/vs/**/electron-main/**', + allowedTypes: [ + ...CORE_TYPES, + // --> types from electron.d.ts that duplicate from lib.dom.d.ts + 'Event', + 'Request' + ], + disallowedTypes: [ + 'ipcMain' // not allowed, use validatedIpcMain instead + ], + disallowedDefinitions: [ + 'lib.dom.d.ts' // no DOM + ] + } ]; - const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json'); - interface IRule { - target: string; - skip?: boolean; - allowedTypes?: string[]; - allowedDefinitions?: string[]; - disallowedDefinitions?: string[]; - disallowedTypes?: string[]; + target: string; + skip?: boolean; + allowedTypes?: string[]; + allowedDefinitions?: string[]; + disallowedDefinitions?: string[]; + disallowedTypes?: string[]; } - let hasErrors = false; - function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) { - checkNode(sourceFile); - - function checkNode(node: ts.Node): void { - if (node.kind !== ts.SyntaxKind.Identifier) { - return ts.forEachChild(node, checkNode); // recurse down - } - - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - - if (!symbol) { - return; - } - - let _parentSymbol: any = symbol; - - while (_parentSymbol.parent) { - _parentSymbol = _parentSymbol.parent; - } - - const parentSymbol = _parentSymbol as ts.Symbol; - const text = parentSymbol.getName(); - - if (rule.allowedTypes?.some(allowed => allowed === text)) { - return; // override - } - - if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - - hasErrors = true; - return; - } - - const declarations = symbol.declarations; - if (Array.isArray(declarations)) { - DeclarationLoop: for (const declaration of declarations) { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const parentSourceFile = parent.getSourceFile(); - if (parentSourceFile) { - const definitionFileName = parentSourceFile.fileName; - if (rule.allowedDefinitions) { - for (const allowedDefinition of rule.allowedDefinitions) { - if (definitionFileName.indexOf(allowedDefinition) >= 0) { - continue DeclarationLoop; - } - } - } - if (rule.disallowedDefinitions) { - for (const disallowedDefinition of rule.disallowedDefinitions) { - if (definitionFileName.indexOf(disallowedDefinition) >= 0) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - - hasErrors = true; - return; - } - } - } - } - } - } - } - } - } + checkNode(sourceFile); + function checkNode(node: ts.Node): void { + if (node.kind !== ts.SyntaxKind.Identifier) { + return ts.forEachChild(node, checkNode); // recurse down + } + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (!symbol) { + return; + } + let _parentSymbol: any = symbol; + while (_parentSymbol.parent) { + _parentSymbol = _parentSymbol.parent; + } + const parentSymbol = _parentSymbol as ts.Symbol; + const text = parentSymbol.getName(); + if (rule.allowedTypes?.some(allowed => allowed === text)) { + return; // override + } + if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); + hasErrors = true; + return; + } + const declarations = symbol.declarations; + if (Array.isArray(declarations)) { + DeclarationLoop: for (const declaration of declarations) { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const parentSourceFile = parent.getSourceFile(); + if (parentSourceFile) { + const definitionFileName = parentSourceFile.fileName; + if (rule.allowedDefinitions) { + for (const allowedDefinition of rule.allowedDefinitions) { + if (definitionFileName.indexOf(allowedDefinition) >= 0) { + continue DeclarationLoop; + } + } + } + if (rule.disallowedDefinitions) { + for (const disallowedDefinition of rule.disallowedDefinitions) { + if (definitionFileName.indexOf(disallowedDefinition) >= 0) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); + hasErrors = true; + return; + } + } + } + } + } + } + } + } + } } - function createProgram(tsconfigPath: string): ts.Program { - const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); - - const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; - const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true }); - - const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); - - return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); + const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); + const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; + const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true }); + const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); + return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); } - // // Create program and start checking // const program = createProgram(TS_CONFIG_PATH); - for (const sourceFile of program.getSourceFiles()) { - for (const rule of RULES) { - if (match([sourceFile.fileName], rule.target).length > 0) { - if (!rule.skip) { - checkFile(program, sourceFile, rule); - } - - break; - } - } + for (const rule of RULES) { + if (match([sourceFile.fileName], rule.target).length > 0) { + if (!rule.skip) { + checkFile(program, sourceFile, rule); + } + break; + } + } } - if (hasErrors) { - process.exit(1); + process.exit(1); } diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index f291bd63f6b9f..cca11e4abd840 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as v8 from 'node:v8'; import * as fs from 'fs'; import * as path from 'path'; @@ -13,377 +12,319 @@ import { pathToFileURL } from 'url'; import * as workerpool from 'workerpool'; import { StaticLanguageServiceHost } from './staticLanguageServiceHost'; const buildfile = require('../../buildfile'); - class ShortIdent { - - private static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', - 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', - 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw', - 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']); - - private static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$_'.split(''); - - private _value = 0; - - constructor( - private readonly prefix: string - ) { } - - next(isNameTaken?: (name: string) => boolean): string { - const candidate = this.prefix + ShortIdent.convert(this._value); - this._value++; - if (ShortIdent._keywords.has(candidate) || /^[_0-9]/.test(candidate) || isNameTaken?.(candidate)) { - // try again - return this.next(isNameTaken); - } - return candidate; - } - - private static convert(n: number): string { - const base = this._alphabet.length; - let result = ''; - do { - const rest = n % base; - result += this._alphabet[rest]; - n = (n / base) | 0; - } while (n > 0); - return result; - } + private static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', + 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', + 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw', + 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']); + private static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$_'.split(''); + private _value = 0; + constructor(private readonly prefix: string) { } + next(isNameTaken?: (name: string) => boolean): string { + const candidate = this.prefix + ShortIdent.convert(this._value); + this._value++; + if (ShortIdent._keywords.has(candidate) || /^[_0-9]/.test(candidate) || isNameTaken?.(candidate)) { + // try again + return this.next(isNameTaken); + } + return candidate; + } + private static convert(n: number): string { + const base = this._alphabet.length; + let result = ''; + do { + const rest = n % base; + result += this._alphabet[rest]; + n = (n / base) | 0; + } while (n > 0); + return result; + } } - const enum FieldType { - Public, - Protected, - Private + Public, + Protected, + Private } - class ClassData { - - fields = new Map(); - - private replacements: Map | undefined; - - parent: ClassData | undefined; - children: ClassData[] | undefined; - - constructor( - readonly fileName: string, - readonly node: ts.ClassDeclaration | ts.ClassExpression, - ) { - // analyse all fields (properties and methods). Find usages of all protected and - // private ones and keep track of all public ones (to prevent naming collisions) - - const candidates: (ts.NamedDeclaration)[] = []; - for (const member of node.members) { - if (ts.isMethodDeclaration(member)) { - // method `foo() {}` - candidates.push(member); - - } else if (ts.isPropertyDeclaration(member)) { - // property `foo = 234` - candidates.push(member); - - } else if (ts.isGetAccessor(member)) { - // getter: `get foo() { ... }` - candidates.push(member); - - } else if (ts.isSetAccessor(member)) { - // setter: `set foo() { ... }` - candidates.push(member); - - } else if (ts.isConstructorDeclaration(member)) { - // constructor-prop:`constructor(private foo) {}` - for (const param of member.parameters) { - if (hasModifier(param, ts.SyntaxKind.PrivateKeyword) - || hasModifier(param, ts.SyntaxKind.ProtectedKeyword) - || hasModifier(param, ts.SyntaxKind.PublicKeyword) - || hasModifier(param, ts.SyntaxKind.ReadonlyKeyword) - ) { - candidates.push(param); - } - } - } - } - for (const member of candidates) { - const ident = ClassData._getMemberName(member); - if (!ident) { - continue; - } - const type = ClassData._getFieldType(member); - this.fields.set(ident, { type, pos: member.name!.getStart() }); - } - } - - private static _getMemberName(node: ts.NamedDeclaration): string | undefined { - if (!node.name) { - return undefined; - } - const { name } = node; - let ident = name.getText(); - if (name.kind === ts.SyntaxKind.ComputedPropertyName) { - if (name.expression.kind !== ts.SyntaxKind.StringLiteral) { - // unsupported: [Symbol.foo] or [abc + 'field'] - return; - } - // ['foo'] - ident = name.expression.getText().slice(1, -1); - } - - return ident; - } - - private static _getFieldType(node: ts.Node): FieldType { - if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) { - return FieldType.Private; - } else if (hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) { - return FieldType.Protected; - } else { - return FieldType.Public; - } - } - - static _shouldMangle(type: FieldType): boolean { - return type === FieldType.Private - || type === FieldType.Protected - ; - } - - static makeImplicitPublicActuallyPublic(data: ClassData, reportViolation: (name: string, what: string, why: string) => void): void { - // TS-HACK - // A subtype can make an inherited protected field public. To prevent accidential - // mangling of public fields we mark the original (protected) fields as public... - for (const [name, info] of data.fields) { - if (info.type !== FieldType.Public) { - continue; - } - let parent: ClassData | undefined = data.parent; - while (parent) { - if (parent.fields.get(name)?.type === FieldType.Protected) { - const parentPos = parent.node.getSourceFile().getLineAndCharacterOfPosition(parent.fields.get(name)!.pos); - const infoPos = data.node.getSourceFile().getLineAndCharacterOfPosition(info.pos); - reportViolation(name, `'${name}' from ${parent.fileName}:${parentPos.line + 1}`, `${data.fileName}:${infoPos.line + 1}`); - - parent.fields.get(name)!.type = FieldType.Public; - } - parent = parent.parent; - } - } - } - - static fillInReplacement(data: ClassData) { - - if (data.replacements) { - // already done - return; - } - - // fill in parents first - if (data.parent) { - ClassData.fillInReplacement(data.parent); - } - - data.replacements = new Map(); - - const isNameTaken = (name: string) => { - // locally taken - if (data._isNameTaken(name)) { - return true; - } - - // parents - let parent: ClassData | undefined = data.parent; - while (parent) { - if (parent._isNameTaken(name)) { - return true; - } - parent = parent.parent; - } - - // children - if (data.children) { - const stack = [...data.children]; - while (stack.length) { - const node = stack.pop()!; - if (node._isNameTaken(name)) { - return true; - } - if (node.children) { - stack.push(...node.children); - } - } - } - - return false; - }; - const identPool = new ShortIdent(''); - - for (const [name, info] of data.fields) { - if (ClassData._shouldMangle(info.type)) { - const shortName = identPool.next(isNameTaken); - data.replacements.set(name, shortName); - } - } - } - - // a name is taken when a field that doesn't get mangled exists or - // when the name is already in use for replacement - private _isNameTaken(name: string) { - if (this.fields.has(name) && !ClassData._shouldMangle(this.fields.get(name)!.type)) { - // public field - return true; - } - if (this.replacements) { - for (const shortName of this.replacements.values()) { - if (shortName === name) { - // replaced already (happens wih super types) - return true; - } - } - } - - if (isNameTakenInFile(this.node, name)) { - return true; - } - - return false; - } - - lookupShortName(name: string): string { - let value = this.replacements!.get(name)!; - let parent = this.parent; - while (parent) { - if (parent.replacements!.has(name) && parent.fields.get(name)?.type === FieldType.Protected) { - value = parent.replacements!.get(name)! ?? value; - } - parent = parent.parent; - } - return value; - } - - // --- parent chaining - - addChild(child: ClassData) { - this.children ??= []; - this.children.push(child); - child.parent = this; - } + fields = new Map(); + private replacements: Map | undefined; + parent: ClassData | undefined; + children: ClassData[] | undefined; + constructor(readonly fileName: string, readonly node: ts.ClassDeclaration | ts.ClassExpression) { + // analyse all fields (properties and methods). Find usages of all protected and + // private ones and keep track of all public ones (to prevent naming collisions) + const candidates: (ts.NamedDeclaration)[] = []; + for (const member of node.members) { + if (ts.isMethodDeclaration(member)) { + // method `foo() {}` + candidates.push(member); + } + else if (ts.isPropertyDeclaration(member)) { + // property `foo = 234` + candidates.push(member); + } + else if (ts.isGetAccessor(member)) { + // getter: `get foo() { ... }` + candidates.push(member); + } + else if (ts.isSetAccessor(member)) { + // setter: `set foo() { ... }` + candidates.push(member); + } + else if (ts.isConstructorDeclaration(member)) { + // constructor-prop:`constructor(private foo) {}` + for (const param of member.parameters) { + if (hasModifier(param, ts.SyntaxKind.PrivateKeyword) + || hasModifier(param, ts.SyntaxKind.ProtectedKeyword) + || hasModifier(param, ts.SyntaxKind.PublicKeyword) + || hasModifier(param, ts.SyntaxKind.ReadonlyKeyword)) { + candidates.push(param); + } + } + } + } + for (const member of candidates) { + const ident = ClassData._getMemberName(member); + if (!ident) { + continue; + } + const type = ClassData._getFieldType(member); + this.fields.set(ident, { type, pos: member.name!.getStart() }); + } + } + private static _getMemberName(node: ts.NamedDeclaration): string | undefined { + if (!node.name) { + return undefined; + } + const { name } = node; + let ident = name.getText(); + if (name.kind === ts.SyntaxKind.ComputedPropertyName) { + if (name.expression.kind !== ts.SyntaxKind.StringLiteral) { + // unsupported: [Symbol.foo] or [abc + 'field'] + return; + } + // ['foo'] + ident = name.expression.getText().slice(1, -1); + } + return ident; + } + private static _getFieldType(node: ts.Node): FieldType { + if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) { + return FieldType.Private; + } + else if (hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) { + return FieldType.Protected; + } + else { + return FieldType.Public; + } + } + static _shouldMangle(type: FieldType): boolean { + return type === FieldType.Private + || type === FieldType.Protected; + } + static makeImplicitPublicActuallyPublic(data: ClassData, reportViolation: (name: string, what: string, why: string) => void): void { + // TS-HACK + // A subtype can make an inherited protected field public. To prevent accidential + // mangling of public fields we mark the original (protected) fields as public... + for (const [name, info] of data.fields) { + if (info.type !== FieldType.Public) { + continue; + } + let parent: ClassData | undefined = data.parent; + while (parent) { + if (parent.fields.get(name)?.type === FieldType.Protected) { + const parentPos = parent.node.getSourceFile().getLineAndCharacterOfPosition(parent.fields.get(name)!.pos); + const infoPos = data.node.getSourceFile().getLineAndCharacterOfPosition(info.pos); + reportViolation(name, `'${name}' from ${parent.fileName}:${parentPos.line + 1}`, `${data.fileName}:${infoPos.line + 1}`); + parent.fields.get(name)!.type = FieldType.Public; + } + parent = parent.parent; + } + } + } + static fillInReplacement(data: ClassData) { + if (data.replacements) { + // already done + return; + } + // fill in parents first + if (data.parent) { + ClassData.fillInReplacement(data.parent); + } + data.replacements = new Map(); + const isNameTaken = (name: string) => { + // locally taken + if (data._isNameTaken(name)) { + return true; + } + // parents + let parent: ClassData | undefined = data.parent; + while (parent) { + if (parent._isNameTaken(name)) { + return true; + } + parent = parent.parent; + } + // children + if (data.children) { + const stack = [...data.children]; + while (stack.length) { + const node = stack.pop()!; + if (node._isNameTaken(name)) { + return true; + } + if (node.children) { + stack.push(...node.children); + } + } + } + return false; + }; + const identPool = new ShortIdent(''); + for (const [name, info] of data.fields) { + if (ClassData._shouldMangle(info.type)) { + const shortName = identPool.next(isNameTaken); + data.replacements.set(name, shortName); + } + } + } + // a name is taken when a field that doesn't get mangled exists or + // when the name is already in use for replacement + private _isNameTaken(name: string) { + if (this.fields.has(name) && !ClassData._shouldMangle(this.fields.get(name)!.type)) { + // public field + return true; + } + if (this.replacements) { + for (const shortName of this.replacements.values()) { + if (shortName === name) { + // replaced already (happens wih super types) + return true; + } + } + } + if (isNameTakenInFile(this.node, name)) { + return true; + } + return false; + } + lookupShortName(name: string): string { + let value = this.replacements!.get(name)!; + let parent = this.parent; + while (parent) { + if (parent.replacements!.has(name) && parent.fields.get(name)?.type === FieldType.Protected) { + value = parent.replacements!.get(name)! ?? value; + } + parent = parent.parent; + } + return value; + } + // --- parent chaining + addChild(child: ClassData) { + this.children ??= []; + this.children.push(child); + child.parent = this; + } } - function isNameTakenInFile(node: ts.Node, name: string): boolean { - const identifiers = (node.getSourceFile()).identifiers; - if (identifiers instanceof Map) { - if (identifiers.has(name)) { - return true; - } - } - return false; + const identifiers = (node.getSourceFile()).identifiers; + if (identifiers instanceof Map) { + if (identifiers.has(name)) { + return true; + } + } + return false; } - const skippedExportMangledFiles = [ - // Build - 'css.build', - - // Monaco - 'editorCommon', - 'editorOptions', - 'editorZoom', - 'standaloneEditor', - 'standaloneEnums', - 'standaloneLanguages', - - // Generated - 'extensionsApiProposals', - - // Module passed around as type - 'pfs', - - // entry points - ...[ - buildfile.workerEditor, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerProfileAnalysis, - buildfile.workerOutputLinks, - buildfile.workerBackgroundTokenization, - buildfile.workbenchDesktop, - buildfile.workbenchWeb, - buildfile.code, - buildfile.codeWeb - ].flat().map(x => x.name), + // Build + 'css.build', + // Monaco + 'editorCommon', + 'editorOptions', + 'editorZoom', + 'standaloneEditor', + 'standaloneEnums', + 'standaloneLanguages', + // Generated + 'extensionsApiProposals', + // Module passed around as type + 'pfs', + // entry points + ...[ + buildfile.workerEditor, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.workbenchDesktop, + buildfile.workbenchWeb, + buildfile.code, + buildfile.codeWeb + ].flat().map(x => x.name), ]; - const skippedExportMangledProjects = [ - // Test projects - 'vscode-api-tests', - - // These projects use webpack to dynamically rewrite imports, which messes up our mangling - 'configuration-editing', - 'microsoft-authentication', - 'github-authentication', - 'html-language-features/server', + // Test projects + 'vscode-api-tests', + // These projects use webpack to dynamically rewrite imports, which messes up our mangling + 'configuration-editing', + 'microsoft-authentication', + 'github-authentication', + 'html-language-features/server', ]; - const skippedExportMangledSymbols = [ - // Don't mangle extension entry points - 'activate', - 'deactivate', + // Don't mangle extension entry points + 'activate', + 'deactivate', ]; - class DeclarationData { - - readonly replacementName: string; - - constructor( - readonly fileName: string, - readonly node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration, - fileIdents: ShortIdent, - ) { - // Todo: generate replacement names based on usage count, with more used names getting shorter identifiers - this.replacementName = fileIdents.next(); - } - - getLocations(service: ts.LanguageService): Iterable<{ fileName: string; offset: number }> { - if (ts.isVariableDeclaration(this.node)) { - // If the const aliases any types, we need to rename those too - const definitionResult = service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); - if (definitionResult?.definitions && definitionResult.definitions.length > 1) { - return definitionResult.definitions.map(x => ({ fileName: x.fileName, offset: x.textSpan.start })); - } - } - - return [{ - fileName: this.fileName, - offset: this.node.name!.getStart() - }]; - } - - shouldMangle(newName: string): boolean { - const currentName = this.node.name!.getText(); - if (currentName.startsWith('$') || skippedExportMangledSymbols.includes(currentName)) { - return false; - } - - // New name is longer the existing one :'( - if (newName.length >= currentName.length) { - return false; - } - - // Don't mangle functions we've explicitly opted out - if (this.node.getFullText().includes('@skipMangle')) { - return false; - } - - return true; - } + readonly replacementName: string; + constructor(readonly fileName: string, readonly node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration, fileIdents: ShortIdent) { + // Todo: generate replacement names based on usage count, with more used names getting shorter identifiers + this.replacementName = fileIdents.next(); + } + getLocations(service: ts.LanguageService): Iterable<{ + fileName: string; + offset: number; + }> { + if (ts.isVariableDeclaration(this.node)) { + // If the const aliases any types, we need to rename those too + const definitionResult = service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); + if (definitionResult?.definitions && definitionResult.definitions.length > 1) { + return definitionResult.definitions.map(x => ({ fileName: x.fileName, offset: x.textSpan.start })); + } + } + return [{ + fileName: this.fileName, + offset: this.node.name!.getStart() + }]; + } + shouldMangle(newName: string): boolean { + const currentName = this.node.name!.getText(); + if (currentName.startsWith('$') || skippedExportMangledSymbols.includes(currentName)) { + return false; + } + // New name is longer the existing one :'( + if (newName.length >= currentName.length) { + return false; + } + // Don't mangle functions we've explicitly opted out + if (this.node.getFullText().includes('@skipMangle')) { + return false; + } + return true; + } } - export interface MangleOutput { - out: string; - sourceMap?: string; + out: string; + sourceMap?: string; } - /** * TypeScript2TypeScript transformer that mangles all private and protected fields * @@ -394,382 +335,325 @@ export interface MangleOutput { * 5. Prepare and apply edits */ export class Mangler { - - private readonly allClassDataByKey = new Map(); - private readonly allExportedSymbols = new Set(); - - private readonly renameWorkerPool: workerpool.WorkerPool; - - constructor( - private readonly projectPath: string, - private readonly log: typeof console.log = () => { }, - private readonly config: { readonly manglePrivateFields: boolean; readonly mangleExports: boolean }, - ) { - - this.renameWorkerPool = workerpool.pool(path.join(__dirname, 'renameWorker.js'), { - maxWorkers: 1, - minWorkers: 'max' - }); - } - - async computeNewFileContents(strictImplicitPublicHandling?: Set): Promise> { - - const service = ts.createLanguageService(new StaticLanguageServiceHost(this.projectPath)); - - // STEP: - // - Find all classes and their field info. - // - Find exported symbols. - - const fileIdents = new ShortIdent('$'); - - const visit = (node: ts.Node): void => { - if (this.config.manglePrivateFields) { - if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { - const anchor = node.name ?? node; - const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`; - if (this.allClassDataByKey.has(key)) { - throw new Error('DUPE?'); - } - this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node)); - } - } - - if (this.config.mangleExports) { - // Find exported classes, functions, and vars - if ( - ( - // Exported class - ts.isClassDeclaration(node) - && hasModifier(node, ts.SyntaxKind.ExportKeyword) - && node.name - ) || ( - // Exported function - ts.isFunctionDeclaration(node) - && ts.isSourceFile(node.parent) - && hasModifier(node, ts.SyntaxKind.ExportKeyword) - && node.name && node.body // On named function and not on the overload - ) || ( - // Exported variable - ts.isVariableDeclaration(node) - && hasModifier(node.parent.parent, ts.SyntaxKind.ExportKeyword) // Variable statement is exported - && ts.isSourceFile(node.parent.parent.parent) - ) - - // Disabled for now because we need to figure out how to handle - // enums that are used in monaco or extHost interfaces. - /* || ( - // Exported enum - ts.isEnumDeclaration(node) - && ts.isSourceFile(node.parent) - && hasModifier(node, ts.SyntaxKind.ExportKeyword) - && !hasModifier(node, ts.SyntaxKind.ConstKeyword) // Don't bother mangling const enums because these are inlined - && node.name - */ - ) { - if (isInAmbientContext(node)) { - return; - } - - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, fileIdents)); - } - } - - ts.forEachChild(node, visit); - }; - - for (const file of service.getProgram()!.getSourceFiles()) { - if (!file.isDeclarationFile) { - ts.forEachChild(file, visit); - } - } - this.log(`Done collecting. Classes: ${this.allClassDataByKey.size}. Exported symbols: ${this.allExportedSymbols.size}`); - - - // STEP: connect sub and super-types - - const setupParents = (data: ClassData) => { - const extendsClause = data.node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword); - if (!extendsClause) { - // no EXTENDS-clause - return; - } - - const info = service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); - if (!info || info.length === 0) { - // throw new Error('SUPER type not found'); - return; - } - - if (info.length !== 1) { - // inherits from declared/library type - return; - } - - const [definition] = info; - const key = `${definition.fileName}|${definition.textSpan.start}`; - const parent = this.allClassDataByKey.get(key); - if (!parent) { - // throw new Error(`SUPER type not found: ${key}`); - return; - } - parent.addChild(data); - }; - for (const data of this.allClassDataByKey.values()) { - setupParents(data); - } - - // STEP: make implicit public (actually protected) field really public - const violations = new Map(); - let violationsCauseFailure = false; - for (const data of this.allClassDataByKey.values()) { - ClassData.makeImplicitPublicActuallyPublic(data, (name: string, what, why) => { - const arr = violations.get(what); - if (arr) { - arr.push(why); - } else { - violations.set(what, [why]); - } - - if (strictImplicitPublicHandling && !strictImplicitPublicHandling.has(name)) { - violationsCauseFailure = true; - } - }); - } - for (const [why, whys] of violations) { - this.log(`WARN: ${why} became PUBLIC because of: ${whys.join(' , ')}`); - } - if (violationsCauseFailure) { - const message = 'Protected fields have been made PUBLIC. This hurts minification and is therefore not allowed. Review the WARN messages further above'; - this.log(`ERROR: ${message}`); - throw new Error(message); - } - - // STEP: compute replacement names for each class - for (const data of this.allClassDataByKey.values()) { - ClassData.fillInReplacement(data); - } - this.log(`Done creating class replacements`); - - // STEP: prepare rename edits - this.log(`Starting prepare rename edits`); - - type Edit = { newText: string; offset: number; length: number }; - const editsByFile = new Map(); - - const appendEdit = (fileName: string, edit: Edit) => { - const edits = editsByFile.get(fileName); - if (!edits) { - editsByFile.set(fileName, [edit]); - } else { - edits.push(edit); - } - }; - const appendRename = (newText: string, loc: ts.RenameLocation) => { - appendEdit(loc.fileName, { - newText: (loc.prefixText || '') + newText + (loc.suffixText || ''), - offset: loc.textSpan.start, - length: loc.textSpan.length - }); - }; - - type RenameFn = (projectName: string, fileName: string, pos: number) => ts.RenameLocation[]; - - const renameResults: Array> = []; - - const queueRename = (fileName: string, pos: number, newName: string) => { - renameResults.push(Promise.resolve(this.renameWorkerPool.exec('findRenameLocations', [this.projectPath, fileName, pos])) - .then((locations) => ({ newName, locations }))); - }; - - for (const data of this.allClassDataByKey.values()) { - if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) { - continue; - } - - fields: for (const [name, info] of data.fields) { - if (!ClassData._shouldMangle(info.type)) { - continue fields; - } - - // TS-HACK: protected became public via 'some' child - // and because of that we might need to ignore this now - let parent = data.parent; - while (parent) { - if (parent.fields.get(name)?.type === FieldType.Public) { - continue fields; - } - parent = parent.parent; - } - - const newName = data.lookupShortName(name); - queueRename(data.fileName, info.pos, newName); - } - } - - for (const data of this.allExportedSymbols.values()) { - if (data.fileName.endsWith('.d.ts') - || skippedExportMangledProjects.some(proj => data.fileName.includes(proj)) - || skippedExportMangledFiles.some(file => data.fileName.endsWith(file + '.ts')) - ) { - continue; - } - - if (!data.shouldMangle(data.replacementName)) { - continue; - } - - const newText = data.replacementName; - for (const { fileName, offset } of data.getLocations(service)) { - queueRename(fileName, offset, newText); - } - } - - await Promise.all(renameResults).then((result) => { - for (const { newName, locations } of result) { - for (const loc of locations) { - appendRename(newName, loc); - } - } - }); - - await this.renameWorkerPool.terminate(); - - this.log(`Done preparing edits: ${editsByFile.size} files`); - - // STEP: apply all rename edits (per file) - const result = new Map(); - let savedBytes = 0; - - for (const item of service.getProgram()!.getSourceFiles()) { - - const { mapRoot, sourceRoot } = service.getProgram()!.getCompilerOptions(); - const projectDir = path.dirname(this.projectPath); - const sourceMapRoot = mapRoot ?? pathToFileURL(sourceRoot ?? projectDir).toString(); - - // source maps - let generator: SourceMapGenerator | undefined; - - let newFullText: string; - const edits = editsByFile.get(item.fileName); - if (!edits) { - // just copy - newFullText = item.getFullText(); - - } else { - // source map generator - const relativeFileName = normalize(path.relative(projectDir, item.fileName)); - const mappingsByLine = new Map(); - - // apply renames - edits.sort((a, b) => b.offset - a.offset); - const characters = item.getFullText().split(''); - - let lastEdit: Edit | undefined; - - for (const edit of edits) { - if (lastEdit && lastEdit.offset === edit.offset) { - // - if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) { - this.log('ERROR: Overlapping edit', item.fileName, edit.offset, edits); - throw new Error('OVERLAPPING edit'); - } else { - continue; - } - } - lastEdit = edit; - const mangledName = characters.splice(edit.offset, edit.length, edit.newText).join(''); - savedBytes += mangledName.length - edit.newText.length; - - // source maps - const pos = item.getLineAndCharacterOfPosition(edit.offset); - - - let mappings = mappingsByLine.get(pos.line); - if (!mappings) { - mappings = []; - mappingsByLine.set(pos.line, mappings); - } - mappings.unshift({ - source: relativeFileName, - original: { line: pos.line + 1, column: pos.character }, - generated: { line: pos.line + 1, column: pos.character }, - name: mangledName - }, { - source: relativeFileName, - original: { line: pos.line + 1, column: pos.character + edit.length }, - generated: { line: pos.line + 1, column: pos.character + edit.newText.length }, - }); - } - - // source map generation, make sure to get mappings per line correct - generator = new SourceMapGenerator({ file: path.basename(item.fileName), sourceRoot: sourceMapRoot }); - generator.setSourceContent(relativeFileName, item.getFullText()); - for (const [, mappings] of mappingsByLine) { - let lineDelta = 0; - for (const mapping of mappings) { - generator.addMapping({ - ...mapping, - generated: { line: mapping.generated.line, column: mapping.generated.column - lineDelta } - }); - lineDelta += mapping.original.column - mapping.generated.column; - } - } - - newFullText = characters.join(''); - } - result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); - } - - service.dispose(); - this.renameWorkerPool.terminate(); - - this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(v8.getHeapStatistics())}`); - return result; - } + private readonly allClassDataByKey = new Map(); + private readonly allExportedSymbols = new Set(); + private readonly renameWorkerPool: workerpool.WorkerPool; + constructor(private readonly projectPath: string, private readonly log: typeof console.log = () => { }, private readonly config: { + readonly manglePrivateFields: boolean; + readonly mangleExports: boolean; + }) { + this.renameWorkerPool = workerpool.pool(path.join(__dirname, 'renameWorker.js'), { + maxWorkers: 1, + minWorkers: 'max' + }); + } + async computeNewFileContents(strictImplicitPublicHandling?: Set): Promise> { + const service = ts.createLanguageService(new StaticLanguageServiceHost(this.projectPath)); + // STEP: + // - Find all classes and their field info. + // - Find exported symbols. + const fileIdents = new ShortIdent('$'); + const visit = (node: ts.Node): void => { + if (this.config.manglePrivateFields) { + if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { + const anchor = node.name ?? node; + const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`; + if (this.allClassDataByKey.has(key)) { + throw new Error('DUPE?'); + } + this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node)); + } + } + if (this.config.mangleExports) { + // Find exported classes, functions, and vars + if (( + // Exported class + ts.isClassDeclaration(node) + && hasModifier(node, ts.SyntaxKind.ExportKeyword) + && node.name) || ( + // Exported function + ts.isFunctionDeclaration(node) + && ts.isSourceFile(node.parent) + && hasModifier(node, ts.SyntaxKind.ExportKeyword) + && node.name && node.body // On named function and not on the overload + ) || ( + // Exported variable + ts.isVariableDeclaration(node) + && hasModifier(node.parent.parent, ts.SyntaxKind.ExportKeyword) // Variable statement is exported + && ts.isSourceFile(node.parent.parent.parent)) + // Disabled for now because we need to figure out how to handle + // enums that are used in monaco or extHost interfaces. + /* || ( + // Exported enum + ts.isEnumDeclaration(node) + && ts.isSourceFile(node.parent) + && hasModifier(node, ts.SyntaxKind.ExportKeyword) + && !hasModifier(node, ts.SyntaxKind.ConstKeyword) // Don't bother mangling const enums because these are inlined + && node.name + */ + ) { + if (isInAmbientContext(node)) { + return; + } + this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, fileIdents)); + } + } + ts.forEachChild(node, visit); + }; + for (const file of service.getProgram()!.getSourceFiles()) { + if (!file.isDeclarationFile) { + ts.forEachChild(file, visit); + } + } + this.log(`Done collecting. Classes: ${this.allClassDataByKey.size}. Exported symbols: ${this.allExportedSymbols.size}`); + // STEP: connect sub and super-types + const setupParents = (data: ClassData) => { + const extendsClause = data.node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword); + if (!extendsClause) { + // no EXTENDS-clause + return; + } + const info = service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); + if (!info || info.length === 0) { + // throw new Error('SUPER type not found'); + return; + } + if (info.length !== 1) { + // inherits from declared/library type + return; + } + const [definition] = info; + const key = `${definition.fileName}|${definition.textSpan.start}`; + const parent = this.allClassDataByKey.get(key); + if (!parent) { + // throw new Error(`SUPER type not found: ${key}`); + return; + } + parent.addChild(data); + }; + for (const data of this.allClassDataByKey.values()) { + setupParents(data); + } + // STEP: make implicit public (actually protected) field really public + const violations = new Map(); + let violationsCauseFailure = false; + for (const data of this.allClassDataByKey.values()) { + ClassData.makeImplicitPublicActuallyPublic(data, (name: string, what, why) => { + const arr = violations.get(what); + if (arr) { + arr.push(why); + } + else { + violations.set(what, [why]); + } + if (strictImplicitPublicHandling && !strictImplicitPublicHandling.has(name)) { + violationsCauseFailure = true; + } + }); + } + for (const [why, whys] of violations) { + this.log(`WARN: ${why} became PUBLIC because of: ${whys.join(' , ')}`); + } + if (violationsCauseFailure) { + const message = 'Protected fields have been made PUBLIC. This hurts minification and is therefore not allowed. Review the WARN messages further above'; + this.log(`ERROR: ${message}`); + throw new Error(message); + } + // STEP: compute replacement names for each class + for (const data of this.allClassDataByKey.values()) { + ClassData.fillInReplacement(data); + } + this.log(`Done creating class replacements`); + // STEP: prepare rename edits + this.log(`Starting prepare rename edits`); + type Edit = { + newText: string; + offset: number; + length: number; + }; + const editsByFile = new Map(); + const appendEdit = (fileName: string, edit: Edit) => { + const edits = editsByFile.get(fileName); + if (!edits) { + editsByFile.set(fileName, [edit]); + } + else { + edits.push(edit); + } + }; + const appendRename = (newText: string, loc: ts.RenameLocation) => { + appendEdit(loc.fileName, { + newText: (loc.prefixText || '') + newText + (loc.suffixText || ''), + offset: loc.textSpan.start, + length: loc.textSpan.length + }); + }; + type RenameFn = (projectName: string, fileName: string, pos: number) => ts.RenameLocation[]; + const renameResults: Array> = []; + const queueRename = (fileName: string, pos: number, newName: string) => { + renameResults.push(Promise.resolve(this.renameWorkerPool.exec('findRenameLocations', [this.projectPath, fileName, pos])) + .then((locations) => ({ newName, locations }))); + }; + for (const data of this.allClassDataByKey.values()) { + if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) { + continue; + } + fields: for (const [name, info] of data.fields) { + if (!ClassData._shouldMangle(info.type)) { + continue fields; + } + // TS-HACK: protected became public via 'some' child + // and because of that we might need to ignore this now + let parent = data.parent; + while (parent) { + if (parent.fields.get(name)?.type === FieldType.Public) { + continue fields; + } + parent = parent.parent; + } + const newName = data.lookupShortName(name); + queueRename(data.fileName, info.pos, newName); + } + } + for (const data of this.allExportedSymbols.values()) { + if (data.fileName.endsWith('.d.ts') + || skippedExportMangledProjects.some(proj => data.fileName.includes(proj)) + || skippedExportMangledFiles.some(file => data.fileName.endsWith(file + '.ts'))) { + continue; + } + if (!data.shouldMangle(data.replacementName)) { + continue; + } + const newText = data.replacementName; + for (const { fileName, offset } of data.getLocations(service)) { + queueRename(fileName, offset, newText); + } + } + await Promise.all(renameResults).then((result) => { + for (const { newName, locations } of result) { + for (const loc of locations) { + appendRename(newName, loc); + } + } + }); + await this.renameWorkerPool.terminate(); + this.log(`Done preparing edits: ${editsByFile.size} files`); + // STEP: apply all rename edits (per file) + const result = new Map(); + let savedBytes = 0; + for (const item of service.getProgram()!.getSourceFiles()) { + const { mapRoot, sourceRoot } = service.getProgram()!.getCompilerOptions(); + const projectDir = path.dirname(this.projectPath); + const sourceMapRoot = mapRoot ?? pathToFileURL(sourceRoot ?? projectDir).toString(); + // source maps + let generator: SourceMapGenerator | undefined; + let newFullText: string; + const edits = editsByFile.get(item.fileName); + if (!edits) { + // just copy + newFullText = item.getFullText(); + } + else { + // source map generator + const relativeFileName = normalize(path.relative(projectDir, item.fileName)); + const mappingsByLine = new Map(); + // apply renames + edits.sort((a, b) => b.offset - a.offset); + const characters = item.getFullText().split(''); + let lastEdit: Edit | undefined; + for (const edit of edits) { + if (lastEdit && lastEdit.offset === edit.offset) { + // + if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) { + this.log('ERROR: Overlapping edit', item.fileName, edit.offset, edits); + throw new Error('OVERLAPPING edit'); + } + else { + continue; + } + } + lastEdit = edit; + const mangledName = characters.splice(edit.offset, edit.length, edit.newText).join(''); + savedBytes += mangledName.length - edit.newText.length; + // source maps + const pos = item.getLineAndCharacterOfPosition(edit.offset); + let mappings = mappingsByLine.get(pos.line); + if (!mappings) { + mappings = []; + mappingsByLine.set(pos.line, mappings); + } + mappings.unshift({ + source: relativeFileName, + original: { line: pos.line + 1, column: pos.character }, + generated: { line: pos.line + 1, column: pos.character }, + name: mangledName + }, { + source: relativeFileName, + original: { line: pos.line + 1, column: pos.character + edit.length }, + generated: { line: pos.line + 1, column: pos.character + edit.newText.length }, + }); + } + // source map generation, make sure to get mappings per line correct + generator = new SourceMapGenerator({ file: path.basename(item.fileName), sourceRoot: sourceMapRoot }); + generator.setSourceContent(relativeFileName, item.getFullText()); + for (const [, mappings] of mappingsByLine) { + let lineDelta = 0; + for (const mapping of mappings) { + generator.addMapping({ + ...mapping, + generated: { line: mapping.generated.line, column: mapping.generated.column - lineDelta } + }); + lineDelta += mapping.original.column - mapping.generated.column; + } + } + newFullText = characters.join(''); + } + result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); + } + service.dispose(); + this.renameWorkerPool.terminate(); + this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(v8.getHeapStatistics())}`); + return result; + } } - // --- ast utils - function hasModifier(node: ts.Node, kind: ts.SyntaxKind) { - const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; - return Boolean(modifiers?.find(mode => mode.kind === kind)); + const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + return Boolean(modifiers?.find(mode => mode.kind === kind)); } - function isInAmbientContext(node: ts.Node): boolean { - for (let p = node.parent; p; p = p.parent) { - if (ts.isModuleDeclaration(p)) { - return true; - } - } - return false; + for (let p = node.parent; p; p = p.parent) { + if (ts.isModuleDeclaration(p)) { + return true; + } + } + return false; } - function normalize(path: string): string { - return path.replace(/\\/g, '/'); + return path.replace(/\\/g, '/'); } - async function _run() { - const root = path.join(__dirname, '..', '..', '..'); - const projectBase = path.join(root, 'src'); - const projectPath = path.join(projectBase, 'tsconfig.json'); - const newProjectBase = path.join(path.dirname(projectBase), path.basename(projectBase) + '2'); - - fs.cpSync(projectBase, newProjectBase, { recursive: true }); - - const mangler = new Mangler(projectPath, console.log, { - mangleExports: true, - manglePrivateFields: true, - }); - for (const [fileName, contents] of await mangler.computeNewFileContents(new Set(['saveState']))) { - const newFilePath = path.join(newProjectBase, path.relative(projectBase, fileName)); - await fs.promises.mkdir(path.dirname(newFilePath), { recursive: true }); - await fs.promises.writeFile(newFilePath, contents.out); - if (contents.sourceMap) { - await fs.promises.writeFile(newFilePath + '.map', contents.sourceMap); - } - } + const root = path.join(__dirname, '..', '..', '..'); + const projectBase = path.join(root, 'src'); + const projectPath = path.join(projectBase, 'tsconfig.json'); + const newProjectBase = path.join(path.dirname(projectBase), path.basename(projectBase) + '2'); + fs.cpSync(projectBase, newProjectBase, { recursive: true }); + const mangler = new Mangler(projectPath, console.log, { + mangleExports: true, + manglePrivateFields: true, + }); + for (const [fileName, contents] of await mangler.computeNewFileContents(new Set(['saveState']))) { + const newFilePath = path.join(newProjectBase, path.relative(projectBase, fileName)); + await fs.promises.mkdir(path.dirname(newFilePath), { recursive: true }); + await fs.promises.writeFile(newFilePath, contents.out); + if (contents.sourceMap) { + await fs.promises.writeFile(newFilePath + '.map', contents.sourceMap); + } + } } - if (__filename === argv[1]) { - _run(); + _run(); } diff --git a/build/lib/mangle/renameWorker.ts b/build/lib/mangle/renameWorker.ts index 29b34e8c51479..39a659a585142 100644 --- a/build/lib/mangle/renameWorker.ts +++ b/build/lib/mangle/renameWorker.ts @@ -2,27 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as ts from 'typescript'; import * as workerpool from 'workerpool'; import { StaticLanguageServiceHost } from './staticLanguageServiceHost'; - let service: ts.LanguageService | undefined; - -function findRenameLocations( - projectPath: string, - fileName: string, - position: number, -): readonly ts.RenameLocation[] { - if (!service) { - service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); - } - - return service.findRenameLocations(fileName, position, false, false, { - providePrefixAndSuffixTextForRename: true, - }) ?? []; +function findRenameLocations(projectPath: string, fileName: string, position: number): readonly ts.RenameLocation[] { + if (!service) { + service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); + } + return service.findRenameLocations(fileName, position, false, false, { + providePrefixAndSuffixTextForRename: true, + }) ?? []; } - workerpool.worker({ - findRenameLocations + findRenameLocations }); diff --git a/build/lib/mangle/staticLanguageServiceHost.ts b/build/lib/mangle/staticLanguageServiceHost.ts index c2793342ce34e..e489ce020f616 100644 --- a/build/lib/mangle/staticLanguageServiceHost.ts +++ b/build/lib/mangle/staticLanguageServiceHost.ts @@ -2,61 +2,57 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as ts from 'typescript'; import * as path from 'path'; - export class StaticLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _cmdLine: ts.ParsedCommandLine; - private readonly _scriptSnapshots: Map = new Map(); - - constructor(readonly projectPath: string) { - const existingOptions: Partial = {}; - const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); - if (parsed.error) { - throw parsed.error; - } - this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions); - if (this._cmdLine.errors.length > 0) { - throw parsed.error; - } - } - getCompilationSettings(): ts.CompilerOptions { - return this._cmdLine.options; - } - getScriptFileNames(): string[] { - return this._cmdLine.fileNames; - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { - let result: ts.IScriptSnapshot | undefined = this._scriptSnapshots.get(fileName); - if (result === undefined) { - const content = ts.sys.readFile(fileName); - if (content === undefined) { - return undefined; - } - result = ts.ScriptSnapshot.fromString(content); - this._scriptSnapshots.set(fileName, result); - } - return result; - } - getCurrentDirectory(): string { - return path.dirname(this.projectPath); - } - getDefaultLibFileName(options: ts.CompilerOptions): string { - return ts.getDefaultLibFilePath(options); - } - directoryExists = ts.sys.directoryExists; - getDirectories = ts.sys.getDirectories; - fileExists = ts.sys.fileExists; - readFile = ts.sys.readFile; - readDirectory = ts.sys.readDirectory; - // this is necessary to make source references work. - realpath = ts.sys.realpath; + private readonly _cmdLine: ts.ParsedCommandLine; + private readonly _scriptSnapshots: Map = new Map(); + constructor(readonly projectPath: string) { + const existingOptions: Partial = {}; + const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); + if (parsed.error) { + throw parsed.error; + } + this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions); + if (this._cmdLine.errors.length > 0) { + throw parsed.error; + } + } + getCompilationSettings(): ts.CompilerOptions { + return this._cmdLine.options; + } + getScriptFileNames(): string[] { + return this._cmdLine.fileNames; + } + getScriptVersion(_fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { + let result: ts.IScriptSnapshot | undefined = this._scriptSnapshots.get(fileName); + if (result === undefined) { + const content = ts.sys.readFile(fileName); + if (content === undefined) { + return undefined; + } + result = ts.ScriptSnapshot.fromString(content); + this._scriptSnapshots.set(fileName, result); + } + return result; + } + getCurrentDirectory(): string { + return path.dirname(this.projectPath); + } + getDefaultLibFileName(options: ts.CompilerOptions): string { + return ts.getDefaultLibFilePath(options); + } + directoryExists = ts.sys.directoryExists; + getDirectories = ts.sys.getDirectories; + fileExists = ts.sys.fileExists; + readFile = ts.sys.readFile; + readDirectory = ts.sys.readDirectory; + // this is necessary to make source references work. + realpath = ts.sys.realpath; } diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index 288bec0f858f6..931660ef19015 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -2,742 +2,650 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import type * as ts from 'typescript'; import * as path from 'path'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; - const dtsv = '3'; - const tsfmt = require('../../tsfmt.json'); - const SRC = path.join(__dirname, '../../src'); export const RECIPE_PATH = path.join(__dirname, '../monaco/monaco.d.ts.recipe'); const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); - function logErr(message: any, ...rest: any[]): void { - fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest); + fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest); } - type SourceFileGetter = (moduleId: string) => ts.SourceFile | null; - type TSTopLevelDeclaration = ts.InterfaceDeclaration | ts.EnumDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration | ts.ModuleDeclaration; type TSTopLevelDeclare = TSTopLevelDeclaration | ts.VariableStatement; - function isDeclaration(ts: typeof import('typescript'), a: TSTopLevelDeclare): a is TSTopLevelDeclaration { - return ( - a.kind === ts.SyntaxKind.InterfaceDeclaration - || a.kind === ts.SyntaxKind.EnumDeclaration - || a.kind === ts.SyntaxKind.ClassDeclaration - || a.kind === ts.SyntaxKind.TypeAliasDeclaration - || a.kind === ts.SyntaxKind.FunctionDeclaration - || a.kind === ts.SyntaxKind.ModuleDeclaration - ); + return (a.kind === ts.SyntaxKind.InterfaceDeclaration + || a.kind === ts.SyntaxKind.EnumDeclaration + || a.kind === ts.SyntaxKind.ClassDeclaration + || a.kind === ts.SyntaxKind.TypeAliasDeclaration + || a.kind === ts.SyntaxKind.FunctionDeclaration + || a.kind === ts.SyntaxKind.ModuleDeclaration); } - function visitTopLevelDeclarations(ts: typeof import('typescript'), sourceFile: ts.SourceFile, visitor: (node: TSTopLevelDeclare) => boolean): void { - let stop = false; - - const visit = (node: ts.Node): void => { - if (stop) { - return; - } - - switch (node.kind) { - case ts.SyntaxKind.InterfaceDeclaration: - case ts.SyntaxKind.EnumDeclaration: - case ts.SyntaxKind.ClassDeclaration: - case ts.SyntaxKind.VariableStatement: - case ts.SyntaxKind.TypeAliasDeclaration: - case ts.SyntaxKind.FunctionDeclaration: - case ts.SyntaxKind.ModuleDeclaration: - stop = visitor(node); - } - - if (stop) { - return; - } - ts.forEachChild(node, visit); - }; - - visit(sourceFile); + let stop = false; + const visit = (node: ts.Node): void => { + if (stop) { + return; + } + switch (node.kind) { + case ts.SyntaxKind.InterfaceDeclaration: + case ts.SyntaxKind.EnumDeclaration: + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.VariableStatement: + case ts.SyntaxKind.TypeAliasDeclaration: + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.ModuleDeclaration: + stop = visitor(node); + } + if (stop) { + return; + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); } - - function getAllTopLevelDeclarations(ts: typeof import('typescript'), sourceFile: ts.SourceFile): TSTopLevelDeclare[] { - const all: TSTopLevelDeclare[] = []; - visitTopLevelDeclarations(ts, sourceFile, (node) => { - if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) { - const interfaceDeclaration = node; - const triviaStart = interfaceDeclaration.pos; - const triviaEnd = interfaceDeclaration.name.pos; - const triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd }); - - if (triviaText.indexOf('@internal') === -1) { - all.push(node); - } - } else { - const nodeText = getNodeText(sourceFile, node); - if (nodeText.indexOf('@internal') === -1) { - all.push(node); - } - } - return false /*continue*/; - }); - return all; + const all: TSTopLevelDeclare[] = []; + visitTopLevelDeclarations(ts, sourceFile, (node) => { + if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) { + const interfaceDeclaration = node; + const triviaStart = interfaceDeclaration.pos; + const triviaEnd = interfaceDeclaration.name.pos; + const triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd }); + if (triviaText.indexOf('@internal') === -1) { + all.push(node); + } + } + else { + const nodeText = getNodeText(sourceFile, node); + if (nodeText.indexOf('@internal') === -1) { + all.push(node); + } + } + return false /*continue*/; + }); + return all; } - - function getTopLevelDeclaration(ts: typeof import('typescript'), sourceFile: ts.SourceFile, typeName: string): TSTopLevelDeclare | null { - let result: TSTopLevelDeclare | null = null; - visitTopLevelDeclarations(ts, sourceFile, (node) => { - if (isDeclaration(ts, node) && node.name) { - if (node.name.text === typeName) { - result = node; - return true /*stop*/; - } - return false /*continue*/; - } - // node is ts.VariableStatement - if (getNodeText(sourceFile, node).indexOf(typeName) >= 0) { - result = node; - return true /*stop*/; - } - return false /*continue*/; - }); - return result; + let result: TSTopLevelDeclare | null = null; + visitTopLevelDeclarations(ts, sourceFile, (node) => { + if (isDeclaration(ts, node) && node.name) { + if (node.name.text === typeName) { + result = node; + return true /*stop*/; + } + return false /*continue*/; + } + // node is ts.VariableStatement + if (getNodeText(sourceFile, node).indexOf(typeName) >= 0) { + result = node; + return true /*stop*/; + } + return false /*continue*/; + }); + return result; } - - -function getNodeText(sourceFile: ts.SourceFile, node: { pos: number; end: number }): string { - return sourceFile.getFullText().substring(node.pos, node.end); +function getNodeText(sourceFile: ts.SourceFile, node: { + pos: number; + end: number; +}): string { + return sourceFile.getFullText().substring(node.pos, node.end); } - function hasModifier(modifiers: readonly ts.ModifierLike[] | undefined, kind: ts.SyntaxKind): boolean { - if (modifiers) { - for (let i = 0; i < modifiers.length; i++) { - const mod = modifiers[i]; - if (mod.kind === kind) { - return true; - } - } - } - return false; + if (modifiers) { + for (let i = 0; i < modifiers.length; i++) { + const mod = modifiers[i]; + if (mod.kind === kind) { + return true; + } + } + } + return false; } - function isStatic(ts: typeof import('typescript'), member: ts.ClassElement | ts.TypeElement): boolean { - if (ts.canHaveModifiers(member)) { - return hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword); - } - return false; + if (ts.canHaveModifiers(member)) { + return hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword); + } + return false; } - function isDefaultExport(ts: typeof import('typescript'), declaration: ts.InterfaceDeclaration | ts.ClassDeclaration): boolean { - return ( - hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword) - && hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword) - ); + return (hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword) + && hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword)); } - function getMassagedTopLevelDeclarationText(ts: typeof import('typescript'), sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[], enums: IEnumEntry[]): string { - let result = getNodeText(sourceFile, declaration); - if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { - const interfaceDeclaration = declaration; - - const staticTypeName = ( - isDefaultExport(ts, interfaceDeclaration) - ? `${importName}.default` - : `${importName}.${declaration.name!.text}` - ); - - let instanceTypeName = staticTypeName; - const typeParametersCnt = (interfaceDeclaration.typeParameters ? interfaceDeclaration.typeParameters.length : 0); - if (typeParametersCnt > 0) { - const arr: string[] = []; - for (let i = 0; i < typeParametersCnt; i++) { - arr.push('any'); - } - instanceTypeName = `${instanceTypeName}<${arr.join(',')}>`; - } - - const members: ts.NodeArray = interfaceDeclaration.members; - members.forEach((member) => { - try { - const memberText = getNodeText(sourceFile, member); - if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) { - result = result.replace(memberText, ''); - } else { - const memberName = (member.name).text; - const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); - if (isStatic(ts, member)) { - usage.push(`a = ${staticTypeName}${memberAccess};`); - } else { - usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); - } - } - } catch (err) { - // life.. - } - }); - } - result = result.replace(/export default /g, 'export '); - result = result.replace(/export declare /g, 'export '); - result = result.replace(/declare /g, ''); - const lines = result.split(/\r\n|\r|\n/); - for (let i = 0; i < lines.length; i++) { - if (/\s*\*/.test(lines[i])) { - // very likely a comment - continue; - } - lines[i] = lines[i].replace(/"/g, '\''); - } - result = lines.join('\n'); - - if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { - result = result.replace(/const enum/, 'enum'); - enums.push({ - enumName: declaration.name.getText(sourceFile), - text: result - }); - } - - return result; + let result = getNodeText(sourceFile, declaration); + if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { + const interfaceDeclaration = declaration; + const staticTypeName = (isDefaultExport(ts, interfaceDeclaration) + ? `${importName}.default` + : `${importName}.${declaration.name!.text}`); + let instanceTypeName = staticTypeName; + const typeParametersCnt = (interfaceDeclaration.typeParameters ? interfaceDeclaration.typeParameters.length : 0); + if (typeParametersCnt > 0) { + const arr: string[] = []; + for (let i = 0; i < typeParametersCnt; i++) { + arr.push('any'); + } + instanceTypeName = `${instanceTypeName}<${arr.join(',')}>`; + } + const members: ts.NodeArray = interfaceDeclaration.members; + members.forEach((member) => { + try { + const memberText = getNodeText(sourceFile, member); + if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) { + result = result.replace(memberText, ''); + } + else { + const memberName = (member.name).text; + const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); + if (isStatic(ts, member)) { + usage.push(`a = ${staticTypeName}${memberAccess};`); + } + else { + usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); + } + } + } + catch (err) { + // life.. + } + }); + } + result = result.replace(/export default /g, 'export '); + result = result.replace(/export declare /g, 'export '); + result = result.replace(/declare /g, ''); + const lines = result.split(/\r\n|\r|\n/); + for (let i = 0; i < lines.length; i++) { + if (/\s*\*/.test(lines[i])) { + // very likely a comment + continue; + } + lines[i] = lines[i].replace(/"/g, '\''); + } + result = lines.join('\n'); + if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { + result = result.replace(/const enum/, 'enum'); + enums.push({ + enumName: declaration.name.getText(sourceFile), + text: result + }); + } + return result; } - function format(ts: typeof import('typescript'), text: string, endl: string): string { - const REALLY_FORMAT = false; - - text = preformat(text, endl); - if (!REALLY_FORMAT) { - return text; - } - - // Parse the source text - const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); - - // Get the formatting edits on the input sources - const edits = (ts).formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); - - // Apply the edits on the input code - return applyEdits(text, edits); - - function countParensCurly(text: string): number { - let cnt = 0; - for (let i = 0; i < text.length; i++) { - if (text.charAt(i) === '(' || text.charAt(i) === '{') { - cnt++; - } - if (text.charAt(i) === ')' || text.charAt(i) === '}') { - cnt--; - } - } - return cnt; - } - - function repeatStr(s: string, cnt: number): string { - let r = ''; - for (let i = 0; i < cnt; i++) { - r += s; - } - return r; - } - - function preformat(text: string, endl: string): string { - const lines = text.split(endl); - let inComment = false; - let inCommentDeltaIndent = 0; - let indent = 0; - for (let i = 0; i < lines.length; i++) { - let line = lines[i].replace(/\s$/, ''); - let repeat = false; - let lineIndent = 0; - do { - repeat = false; - if (line.substring(0, 4) === ' ') { - line = line.substring(4); - lineIndent++; - repeat = true; - } - if (line.charAt(0) === '\t') { - line = line.substring(1); - lineIndent++; - repeat = true; - } - } while (repeat); - - if (line.length === 0) { - continue; - } - - if (inComment) { - if (/\*\//.test(line)) { - inComment = false; - } - lines[i] = repeatStr('\t', lineIndent + inCommentDeltaIndent) + line; - continue; - } - - if (/\/\*/.test(line)) { - inComment = true; - inCommentDeltaIndent = indent - lineIndent; - lines[i] = repeatStr('\t', indent) + line; - continue; - } - - const cnt = countParensCurly(line); - let shouldUnindentAfter = false; - let shouldUnindentBefore = false; - if (cnt < 0) { - if (/[({]/.test(line)) { - shouldUnindentAfter = true; - } else { - shouldUnindentBefore = true; - } - } else if (cnt === 0) { - shouldUnindentBefore = /^\}/.test(line); - } - let shouldIndentAfter = false; - if (cnt > 0) { - shouldIndentAfter = true; - } else if (cnt === 0) { - shouldIndentAfter = /{$/.test(line); - } - - if (shouldUnindentBefore) { - indent--; - } - - lines[i] = repeatStr('\t', indent) + line; - - if (shouldUnindentAfter) { - indent--; - } - if (shouldIndentAfter) { - indent++; - } - } - return lines.join(endl); - } - - function getRuleProvider(options: ts.FormatCodeSettings) { - // Share this between multiple formatters using the same options. - // This represents the bulk of the space the formatter uses. - return (ts as any).formatting.getFormatContext(options); - } - - function applyEdits(text: string, edits: ts.TextChange[]): string { - // Apply edits in reverse on the existing text - let result = text; - for (let i = edits.length - 1; i >= 0; i--) { - const change = edits[i]; - const head = result.slice(0, change.span.start); - const tail = result.slice(change.span.start + change.span.length); - result = head + change.newText + tail; - } - return result; - } + const REALLY_FORMAT = false; + text = preformat(text, endl); + if (!REALLY_FORMAT) { + return text; + } + // Parse the source text + const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); + // Get the formatting edits on the input sources + const edits = (ts).formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); + // Apply the edits on the input code + return applyEdits(text, edits); + function countParensCurly(text: string): number { + let cnt = 0; + for (let i = 0; i < text.length; i++) { + if (text.charAt(i) === '(' || text.charAt(i) === '{') { + cnt++; + } + if (text.charAt(i) === ')' || text.charAt(i) === '}') { + cnt--; + } + } + return cnt; + } + function repeatStr(s: string, cnt: number): string { + let r = ''; + for (let i = 0; i < cnt; i++) { + r += s; + } + return r; + } + function preformat(text: string, endl: string): string { + const lines = text.split(endl); + let inComment = false; + let inCommentDeltaIndent = 0; + let indent = 0; + for (let i = 0; i < lines.length; i++) { + let line = lines[i].replace(/\s$/, ''); + let repeat = false; + let lineIndent = 0; + do { + repeat = false; + if (line.substring(0, 4) === ' ') { + line = line.substring(4); + lineIndent++; + repeat = true; + } + if (line.charAt(0) === '\t') { + line = line.substring(1); + lineIndent++; + repeat = true; + } + } while (repeat); + if (line.length === 0) { + continue; + } + if (inComment) { + if (/\*\//.test(line)) { + inComment = false; + } + lines[i] = repeatStr('\t', lineIndent + inCommentDeltaIndent) + line; + continue; + } + if (/\/\*/.test(line)) { + inComment = true; + inCommentDeltaIndent = indent - lineIndent; + lines[i] = repeatStr('\t', indent) + line; + continue; + } + const cnt = countParensCurly(line); + let shouldUnindentAfter = false; + let shouldUnindentBefore = false; + if (cnt < 0) { + if (/[({]/.test(line)) { + shouldUnindentAfter = true; + } + else { + shouldUnindentBefore = true; + } + } + else if (cnt === 0) { + shouldUnindentBefore = /^\}/.test(line); + } + let shouldIndentAfter = false; + if (cnt > 0) { + shouldIndentAfter = true; + } + else if (cnt === 0) { + shouldIndentAfter = /{$/.test(line); + } + if (shouldUnindentBefore) { + indent--; + } + lines[i] = repeatStr('\t', indent) + line; + if (shouldUnindentAfter) { + indent--; + } + if (shouldIndentAfter) { + indent++; + } + } + return lines.join(endl); + } + function getRuleProvider(options: ts.FormatCodeSettings) { + // Share this between multiple formatters using the same options. + // This represents the bulk of the space the formatter uses. + return (ts as any).formatting.getFormatContext(options); + } + function applyEdits(text: string, edits: ts.TextChange[]): string { + // Apply edits in reverse on the existing text + let result = text; + for (let i = edits.length - 1; i >= 0; i--) { + const change = edits[i]; + const head = result.slice(0, change.span.start); + const tail = result.slice(change.span.start + change.span.length); + result = head + change.newText + tail; + } + return result; + } } - -function createReplacerFromDirectives(directives: [RegExp, string][]): (str: string) => string { - return (str: string) => { - for (let i = 0; i < directives.length; i++) { - str = str.replace(directives[i][0], directives[i][1]); - } - return str; - }; +function createReplacerFromDirectives(directives: [ + RegExp, + string +][]): (str: string) => string { + return (str: string) => { + for (let i = 0; i < directives.length; i++) { + str = str.replace(directives[i][0], directives[i][1]); + } + return str; + }; } - function createReplacer(data: string): (str: string) => string { - data = data || ''; - const rawDirectives = data.split(';'); - const directives: [RegExp, string][] = []; - rawDirectives.forEach((rawDirective) => { - if (rawDirective.length === 0) { - return; - } - const pieces = rawDirective.split('=>'); - let findStr = pieces[0]; - const replaceStr = pieces[1]; - - findStr = findStr.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&'); - findStr = '\\b' + findStr + '\\b'; - directives.push([new RegExp(findStr, 'g'), replaceStr]); - }); - - return createReplacerFromDirectives(directives); + data = data || ''; + const rawDirectives = data.split(';'); + const directives: [ + RegExp, + string + ][] = []; + rawDirectives.forEach((rawDirective) => { + if (rawDirective.length === 0) { + return; + } + const pieces = rawDirective.split('=>'); + let findStr = pieces[0]; + const replaceStr = pieces[1]; + findStr = findStr.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&'); + findStr = '\\b' + findStr + '\\b'; + directives.push([new RegExp(findStr, 'g'), replaceStr]); + }); + return createReplacerFromDirectives(directives); } - interface ITempResult { - result: string; - usageContent: string; - enums: string; + result: string; + usageContent: string; + enums: string; } - interface IEnumEntry { - enumName: string; - text: string; + enumName: string; + text: string; } - function generateDeclarationFile(ts: typeof import('typescript'), recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null { - const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; - - const lines = recipe.split(endl); - const result: string[] = []; - - let usageCounter = 0; - const usageImports: string[] = []; - const usage: string[] = []; - - let failed = false; - - usage.push(`var a: any;`); - usage.push(`var b: any;`); - - const generateUsageImport = (moduleId: string) => { - const importName = 'm' + (++usageCounter); - usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`); - return importName; - }; - - const enums: IEnumEntry[] = []; - let version: string | null = null; - - lines.forEach(line => { - - if (failed) { - return; - } - - const m0 = line.match(/^\/\/dtsv=(\d+)$/); - if (m0) { - version = m0[1]; - } - - const m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/); - if (m1) { - const moduleId = m1[1]; - const sourceFile = sourceFileGetter(moduleId); - if (!sourceFile) { - logErr(`While handling ${line}`); - logErr(`Cannot find ${moduleId}`); - failed = true; - return; - } - - const importName = generateUsageImport(moduleId); - - const replacer = createReplacer(m1[2]); - - const typeNames = m1[3].split(/,/); - typeNames.forEach((typeName) => { - typeName = typeName.trim(); - if (typeName.length === 0) { - return; - } - const declaration = getTopLevelDeclaration(ts, sourceFile, typeName); - if (!declaration) { - logErr(`While handling ${line}`); - logErr(`Cannot find ${typeName}`); - failed = true; - return; - } - result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums))); - }); - return; - } - - const m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/); - if (m2) { - const moduleId = m2[1]; - const sourceFile = sourceFileGetter(moduleId); - if (!sourceFile) { - logErr(`While handling ${line}`); - logErr(`Cannot find ${moduleId}`); - failed = true; - return; - } - - const importName = generateUsageImport(moduleId); - - const replacer = createReplacer(m2[2]); - - const typeNames = m2[3].split(/,/); - const typesToExcludeMap: { [typeName: string]: boolean } = {}; - const typesToExcludeArr: string[] = []; - typeNames.forEach((typeName) => { - typeName = typeName.trim(); - if (typeName.length === 0) { - return; - } - typesToExcludeMap[typeName] = true; - typesToExcludeArr.push(typeName); - }); - - getAllTopLevelDeclarations(ts, sourceFile).forEach((declaration) => { - if (isDeclaration(ts, declaration) && declaration.name) { - if (typesToExcludeMap[declaration.name.text]) { - return; - } - } else { - // node is ts.VariableStatement - const nodeText = getNodeText(sourceFile, declaration); - for (let i = 0; i < typesToExcludeArr.length; i++) { - if (nodeText.indexOf(typesToExcludeArr[i]) >= 0) { - return; - } - } - } - result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums))); - }); - return; - } - - result.push(line); - }); - - if (failed) { - return null; - } - - if (version !== dtsv) { - if (!version) { - logErr(`gulp watch restart required. 'monaco.d.ts.recipe' is written before versioning was introduced.`); - } else { - logErr(`gulp watch restart required. 'monaco.d.ts.recipe' v${version} does not match runtime v${dtsv}.`); - } - return null; - } - - let resultTxt = result.join(endl); - resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri'); - resultTxt = resultTxt.replace(/\bEvent { - if (e1.enumName < e2.enumName) { - return -1; - } - if (e1.enumName > e2.enumName) { - return 1; - } - return 0; - }); - - let resultEnums = [ - '/*---------------------------------------------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' * Licensed under the MIT License. See License.txt in the project root for license information.', - ' *--------------------------------------------------------------------------------------------*/', - '', - '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', - '' - ].concat(enums.map(e => e.text)).join(endl); - resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); - resultEnums = format(ts, resultEnums, endl); - resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); - - return { - result: resultTxt, - usageContent: `${usageImports.join('\n')}\n\n${usage.join('\n')}`, - enums: resultEnums - }; + const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; + const lines = recipe.split(endl); + const result: string[] = []; + let usageCounter = 0; + const usageImports: string[] = []; + const usage: string[] = []; + let failed = false; + usage.push(`var a: any;`); + usage.push(`var b: any;`); + const generateUsageImport = (moduleId: string) => { + const importName = 'm' + (++usageCounter); + usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`); + return importName; + }; + const enums: IEnumEntry[] = []; + let version: string | null = null; + lines.forEach(line => { + if (failed) { + return; + } + const m0 = line.match(/^\/\/dtsv=(\d+)$/); + if (m0) { + version = m0[1]; + } + const m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/); + if (m1) { + const moduleId = m1[1]; + const sourceFile = sourceFileGetter(moduleId); + if (!sourceFile) { + logErr(`While handling ${line}`); + logErr(`Cannot find ${moduleId}`); + failed = true; + return; + } + const importName = generateUsageImport(moduleId); + const replacer = createReplacer(m1[2]); + const typeNames = m1[3].split(/,/); + typeNames.forEach((typeName) => { + typeName = typeName.trim(); + if (typeName.length === 0) { + return; + } + const declaration = getTopLevelDeclaration(ts, sourceFile, typeName); + if (!declaration) { + logErr(`While handling ${line}`); + logErr(`Cannot find ${typeName}`); + failed = true; + return; + } + result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums))); + }); + return; + } + const m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/); + if (m2) { + const moduleId = m2[1]; + const sourceFile = sourceFileGetter(moduleId); + if (!sourceFile) { + logErr(`While handling ${line}`); + logErr(`Cannot find ${moduleId}`); + failed = true; + return; + } + const importName = generateUsageImport(moduleId); + const replacer = createReplacer(m2[2]); + const typeNames = m2[3].split(/,/); + const typesToExcludeMap: { + [typeName: string]: boolean; + } = {}; + const typesToExcludeArr: string[] = []; + typeNames.forEach((typeName) => { + typeName = typeName.trim(); + if (typeName.length === 0) { + return; + } + typesToExcludeMap[typeName] = true; + typesToExcludeArr.push(typeName); + }); + getAllTopLevelDeclarations(ts, sourceFile).forEach((declaration) => { + if (isDeclaration(ts, declaration) && declaration.name) { + if (typesToExcludeMap[declaration.name.text]) { + return; + } + } + else { + // node is ts.VariableStatement + const nodeText = getNodeText(sourceFile, declaration); + for (let i = 0; i < typesToExcludeArr.length; i++) { + if (nodeText.indexOf(typesToExcludeArr[i]) >= 0) { + return; + } + } + } + result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums))); + }); + return; + } + result.push(line); + }); + if (failed) { + return null; + } + if (version !== dtsv) { + if (!version) { + logErr(`gulp watch restart required. 'monaco.d.ts.recipe' is written before versioning was introduced.`); + } + else { + logErr(`gulp watch restart required. 'monaco.d.ts.recipe' v${version} does not match runtime v${dtsv}.`); + } + return null; + } + let resultTxt = result.join(endl); + resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri'); + resultTxt = resultTxt.replace(/\bEvent { + if (e1.enumName < e2.enumName) { + return -1; + } + if (e1.enumName > e2.enumName) { + return 1; + } + return 0; + }); + let resultEnums = [ + '/*---------------------------------------------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' * Licensed under the MIT License. See License.txt in the project root for license information.', + ' *--------------------------------------------------------------------------------------------*/', + '', + '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', + '' + ].concat(enums.map(e => e.text)).join(endl); + resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); + resultEnums = format(ts, resultEnums, endl); + resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); + return { + result: resultTxt, + usageContent: `${usageImports.join('\n')}\n\n${usage.join('\n')}`, + enums: resultEnums + }; } - export interface IMonacoDeclarationResult { - content: string; - usageContent: string; - enums: string; - filePath: string; - isTheSame: boolean; + content: string; + usageContent: string; + enums: string; + filePath: string; + isTheSame: boolean; } - function _run(ts: typeof import('typescript'), sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null { - const recipe = fs.readFileSync(RECIPE_PATH).toString(); - const t = generateDeclarationFile(ts, recipe, sourceFileGetter); - if (!t) { - return null; - } - - const result = t.result; - const usageContent = t.usageContent; - const enums = t.enums; - - const currentContent = fs.readFileSync(DECLARATION_PATH).toString(); - const one = currentContent.replace(/\r\n/gm, '\n'); - const other = result.replace(/\r\n/gm, '\n'); - const isTheSame = (one === other); - - return { - content: result, - usageContent: usageContent, - enums: enums, - filePath: DECLARATION_PATH, - isTheSame - }; + const recipe = fs.readFileSync(RECIPE_PATH).toString(); + const t = generateDeclarationFile(ts, recipe, sourceFileGetter); + if (!t) { + return null; + } + const result = t.result; + const usageContent = t.usageContent; + const enums = t.enums; + const currentContent = fs.readFileSync(DECLARATION_PATH).toString(); + const one = currentContent.replace(/\r\n/gm, '\n'); + const other = result.replace(/\r\n/gm, '\n'); + const isTheSame = (one === other); + return { + content: result, + usageContent: usageContent, + enums: enums, + filePath: DECLARATION_PATH, + isTheSame + }; } - export class FSProvider { - public existsSync(filePath: string): boolean { - return fs.existsSync(filePath); - } - public statSync(filePath: string): fs.Stats { - return fs.statSync(filePath); - } - public readFileSync(_moduleId: string, filePath: string): Buffer { - return fs.readFileSync(filePath); - } + public existsSync(filePath: string): boolean { + return fs.existsSync(filePath); + } + public statSync(filePath: string): fs.Stats { + return fs.statSync(filePath); + } + public readFileSync(_moduleId: string, filePath: string): Buffer { + return fs.readFileSync(filePath); + } } - class CacheEntry { - constructor( - public readonly sourceFile: ts.SourceFile, - public readonly mtime: number - ) { } + constructor(public readonly sourceFile: ts.SourceFile, public readonly mtime: number) { } } - export class DeclarationResolver { - - public readonly ts: typeof import('typescript'); - private _sourceFileCache: { [moduleId: string]: CacheEntry | null }; - - constructor(private readonly _fsProvider: FSProvider) { - this.ts = require('typescript') as typeof import('typescript'); - this._sourceFileCache = Object.create(null); - } - - public invalidateCache(moduleId: string): void { - this._sourceFileCache[moduleId] = null; - } - - public getDeclarationSourceFile(moduleId: string): ts.SourceFile | null { - if (this._sourceFileCache[moduleId]) { - // Since we cannot trust file watching to invalidate the cache, check also the mtime - const fileName = this._getFileName(moduleId); - const mtime = this._fsProvider.statSync(fileName).mtime.getTime(); - if (this._sourceFileCache[moduleId]!.mtime !== mtime) { - this._sourceFileCache[moduleId] = null; - } - } - if (!this._sourceFileCache[moduleId]) { - this._sourceFileCache[moduleId] = this._getDeclarationSourceFile(moduleId); - } - return this._sourceFileCache[moduleId] ? this._sourceFileCache[moduleId]!.sourceFile : null; - } - - private _getFileName(moduleId: string): string { - if (/\.d\.ts$/.test(moduleId)) { - return path.join(SRC, moduleId); - } - return path.join(SRC, `${moduleId}.ts`); - } - - private _getDeclarationSourceFile(moduleId: string): CacheEntry | null { - const fileName = this._getFileName(moduleId); - if (!this._fsProvider.existsSync(fileName)) { - return null; - } - const mtime = this._fsProvider.statSync(fileName).mtime.getTime(); - if (/\.d\.ts$/.test(moduleId)) { - // const mtime = this._fsProvider.statFileSync() - const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - return new CacheEntry( - this.ts.createSourceFile(fileName, fileContents, this.ts.ScriptTarget.ES5), - mtime - ); - } - const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - const fileMap: IFileMap = { - 'file.ts': fileContents - }; - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {})); - const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; - return new CacheEntry( - this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), - mtime - ); - } + public readonly ts: typeof import('typescript'); + private _sourceFileCache: { + [moduleId: string]: CacheEntry | null; + }; + constructor(private readonly _fsProvider: FSProvider) { + this.ts = require('typescript') as typeof import('typescript'); + this._sourceFileCache = Object.create(null); + } + public invalidateCache(moduleId: string): void { + this._sourceFileCache[moduleId] = null; + } + public getDeclarationSourceFile(moduleId: string): ts.SourceFile | null { + if (this._sourceFileCache[moduleId]) { + // Since we cannot trust file watching to invalidate the cache, check also the mtime + const fileName = this._getFileName(moduleId); + const mtime = this._fsProvider.statSync(fileName).mtime.getTime(); + if (this._sourceFileCache[moduleId]!.mtime !== mtime) { + this._sourceFileCache[moduleId] = null; + } + } + if (!this._sourceFileCache[moduleId]) { + this._sourceFileCache[moduleId] = this._getDeclarationSourceFile(moduleId); + } + return this._sourceFileCache[moduleId] ? this._sourceFileCache[moduleId]!.sourceFile : null; + } + private _getFileName(moduleId: string): string { + if (/\.d\.ts$/.test(moduleId)) { + return path.join(SRC, moduleId); + } + return path.join(SRC, `${moduleId}.ts`); + } + private _getDeclarationSourceFile(moduleId: string): CacheEntry | null { + const fileName = this._getFileName(moduleId); + if (!this._fsProvider.existsSync(fileName)) { + return null; + } + const mtime = this._fsProvider.statSync(fileName).mtime.getTime(); + if (/\.d\.ts$/.test(moduleId)) { + // const mtime = this._fsProvider.statFileSync() + const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); + return new CacheEntry(this.ts.createSourceFile(fileName, fileContents, this.ts.ScriptTarget.ES5), mtime); + } + const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); + const fileMap: IFileMap = { + 'file.ts': fileContents + }; + const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {})); + const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; + return new CacheEntry(this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), mtime); + } } - export function run3(resolver: DeclarationResolver): IMonacoDeclarationResult | null { - const sourceFileGetter = (moduleId: string) => resolver.getDeclarationSourceFile(moduleId); - return _run(resolver.ts, sourceFileGetter); + const sourceFileGetter = (moduleId: string) => resolver.getDeclarationSourceFile(moduleId); + return _run(resolver.ts, sourceFileGetter); +} +interface ILibMap { + [libName: string]: string; +} +interface IFileMap { + [fileName: string]: string; } - - - - -interface ILibMap { [libName: string]: string } -interface IFileMap { [fileName: string]: string } - class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _ts: typeof import('typescript'); - private readonly _libs: ILibMap; - private readonly _files: IFileMap; - private readonly _compilerOptions: ts.CompilerOptions; - - constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - - // --- language service host --------------- - - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - getScriptFileNames(): string[] { - return ( - ([] as string[]) - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files)) - ); - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); - } else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName: string): ts.ScriptKind { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory(): string { - return ''; - } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return 'defaultLib:es5'; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path: string, _encoding?: string): string | undefined { - return this._files[path] || this._libs[path]; - } - fileExists(path: string): boolean { - return path in this._files || path in this._libs; - } + private readonly _ts: typeof import('typescript'); + private readonly _libs: ILibMap; + private readonly _files: IFileMap; + private readonly _compilerOptions: ts.CompilerOptions; + constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { + this._ts = ts; + this._libs = libs; + this._files = files; + this._compilerOptions = compilerOptions; + } + // --- language service host --------------- + getCompilationSettings(): ts.CompilerOptions { + return this._compilerOptions; + } + getScriptFileNames(): string[] { + return (([] as string[]) + .concat(Object.keys(this._libs)) + .concat(Object.keys(this._files))); + } + getScriptVersion(_fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + if (this._files.hasOwnProperty(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._files[fileName]); + } + else if (this._libs.hasOwnProperty(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); + } + else { + return this._ts.ScriptSnapshot.fromString(''); + } + } + getScriptKind(_fileName: string): ts.ScriptKind { + return this._ts.ScriptKind.TS; + } + getCurrentDirectory(): string { + return ''; + } + getDefaultLibFileName(_options: ts.CompilerOptions): string { + return 'defaultLib:es5'; + } + isDefaultLibFileName(fileName: string): boolean { + return fileName === this.getDefaultLibFileName(this._compilerOptions); + } + readFile(path: string, _encoding?: string): string | undefined { + return this._files[path] || this._libs[path]; + } + fileExists(path: string): boolean { + return path in this._files || path in this._libs; + } } - export function execute(): IMonacoDeclarationResult { - const r = run3(new DeclarationResolver(new FSProvider())); - if (!r) { - throw new Error(`monaco.d.ts generation error - Cannot continue`); - } - return r; + const r = run3(new DeclarationResolver(new FSProvider())); + if (!r) { + throw new Error(`monaco.d.ts generation error - Cannot continue`); + } + return r; } diff --git a/build/lib/nls.ts b/build/lib/nls.ts index cac832903a3b8..0db8814b72b73 100644 --- a/build/lib/nls.ts +++ b/build/lib/nls.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import type * as ts from 'typescript'; import * as lazy from 'lazy.js'; import { duplex, through } from 'event-stream'; @@ -10,518 +9,435 @@ import * as File from 'vinyl'; import * as sm from 'source-map'; import * as path from 'path'; import * as sort from 'gulp-sort'; - declare class FileSourceMap extends File { - public sourceMap: sm.RawSourceMap; + public sourceMap: sm.RawSourceMap; } - enum CollectStepResult { - Yes, - YesAndRecurse, - No, - NoAndRecurse + Yes, + YesAndRecurse, + No, + NoAndRecurse } - function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.Node) => CollectStepResult): ts.Node[] { - const result: ts.Node[] = []; - - function loop(node: ts.Node) { - const stepResult = fn(node); - - if (stepResult === CollectStepResult.Yes || stepResult === CollectStepResult.YesAndRecurse) { - result.push(node); - } - - if (stepResult === CollectStepResult.YesAndRecurse || stepResult === CollectStepResult.NoAndRecurse) { - ts.forEachChild(node, loop); - } - } - - loop(node); - return result; + const result: ts.Node[] = []; + function loop(node: ts.Node) { + const stepResult = fn(node); + if (stepResult === CollectStepResult.Yes || stepResult === CollectStepResult.YesAndRecurse) { + result.push(node); + } + if (stepResult === CollectStepResult.YesAndRecurse || stepResult === CollectStepResult.NoAndRecurse) { + ts.forEachChild(node, loop); + } + } + loop(node); + return result; } - function clone(object: T): T { - const result = {} as any as T; - for (const id in object) { - result[id] = object[id]; - } - return result; + const result = {} as any as T; + for (const id in object) { + result[id] = object[id]; + } + return result; } - /** * Returns a stream containing the patched JavaScript and source maps. */ -export function nls(options: { preserveEnglish: boolean }): NodeJS.ReadWriteStream { - let base: string; - const input = through(); - const output = input - .pipe(sort()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files - .pipe(through(function (f: FileSourceMap) { - if (!f.sourceMap) { - return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); - } - - let source = f.sourceMap.sources[0]; - if (!source) { - return this.emit('error', new Error(`File ${f.relative} does not have a source in the source map.`)); - } - - const root = f.sourceMap.sourceRoot; - if (root) { - source = path.join(root, source); - } - - const typescript = f.sourceMap.sourcesContent![0]; - if (!typescript) { - return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`)); - } - - base = f.base; - this.emit('data', _nls.patchFile(f, typescript, options)); - }, function () { - for (const file of [ - new File({ - contents: Buffer.from(JSON.stringify({ - keys: _nls.moduleToNLSKeys, - messages: _nls.moduleToNLSMessages, - }, null, '\t')), - base, - path: `${base}/nls.metadata.json` - }), - new File({ - contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)), - base, - path: `${base}/nls.messages.json` - }), - new File({ - contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)), - base, - path: `${base}/nls.keys.json` - }), - new File({ - contents: Buffer.from(`/*--------------------------------------------------------- +export function nls(options: { + preserveEnglish: boolean; +}): NodeJS.ReadWriteStream { + let base: string; + const input = through(); + const output = input + .pipe(sort()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files + .pipe(through(function (f: FileSourceMap) { + if (!f.sourceMap) { + return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); + } + let source = f.sourceMap.sources[0]; + if (!source) { + return this.emit('error', new Error(`File ${f.relative} does not have a source in the source map.`)); + } + const root = f.sourceMap.sourceRoot; + if (root) { + source = path.join(root, source); + } + const typescript = f.sourceMap.sourcesContent![0]; + if (!typescript) { + return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`)); + } + base = f.base; + this.emit('data', _nls.patchFile(f, typescript, options)); + }, function () { + for (const file of [ + new File({ + contents: Buffer.from(JSON.stringify({ + keys: _nls.moduleToNLSKeys, + messages: _nls.moduleToNLSMessages, + }, null, '\t')), + base, + path: `${base}/nls.metadata.json` + }), + new File({ + contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)), + base, + path: `${base}/nls.messages.json` + }), + new File({ + contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)), + base, + path: `${base}/nls.keys.json` + }), + new File({ + contents: Buffer.from(`/*--------------------------------------------------------- * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`), - base, - path: `${base}/nls.messages.js` - }) - ]) { - this.emit('data', file); - } - - this.emit('end'); - })); - - return duplex(input, output); + base, + path: `${base}/nls.messages.js` + }) + ]) { + this.emit('data', file); + } + this.emit('end'); + })); + return duplex(input, output); } - function isImportNode(ts: typeof import('typescript'), node: ts.Node): boolean { - return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; + return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; } - module _nls { - - export const moduleToNLSKeys: { [name: string /* module ID */]: ILocalizeKey[] /* keys */ } = {}; - export const moduleToNLSMessages: { [name: string /* module ID */]: string[] /* messages */ } = {}; - export const allNLSMessages: string[] = []; - export const allNLSModulesAndKeys: Array<[string /* module ID */, string[] /* keys */]> = []; - let allNLSMessagesIndex = 0; - - type ILocalizeKey = string | { key: string }; // key might contain metadata for translators and then is not just a string - - interface INlsPatchResult { - javascript: string; - sourcemap: sm.RawSourceMap; - nlsMessages?: string[]; - nlsKeys?: ILocalizeKey[]; - } - - interface ISpan { - start: ts.LineAndCharacter; - end: ts.LineAndCharacter; - } - - interface ILocalizeCall { - keySpan: ISpan; - key: string; - valueSpan: ISpan; - value: string; - } - - interface ILocalizeAnalysisResult { - localizeCalls: ILocalizeCall[]; - } - - interface IPatch { - span: ISpan; - content: string; - } - - function fileFrom(file: File, contents: string, path: string = file.path) { - return new File({ - contents: Buffer.from(contents), - base: file.base, - cwd: file.cwd, - path: path - }); - } - - function mappedPositionFrom(source: string, lc: ts.LineAndCharacter): sm.MappedPosition { - return { source, line: lc.line + 1, column: lc.character }; - } - - function lcFrom(position: sm.Position): ts.LineAndCharacter { - return { line: position.line - 1, character: position.column }; - } - - class SingleFileServiceHost implements ts.LanguageServiceHost { - - private file: ts.IScriptSnapshot; - private lib: ts.IScriptSnapshot; - - constructor(ts: typeof import('typescript'), private options: ts.CompilerOptions, private filename: string, contents: string) { - this.file = ts.ScriptSnapshot.fromString(contents); - this.lib = ts.ScriptSnapshot.fromString(''); - } - - getCompilationSettings = () => this.options; - getScriptFileNames = () => [this.filename]; - getScriptVersion = () => '1'; - getScriptSnapshot = (name: string) => name === this.filename ? this.file : this.lib; - getCurrentDirectory = () => ''; - getDefaultLibFileName = () => 'lib.d.ts'; - - readFile(path: string, _encoding?: string): string | undefined { - if (path === this.filename) { - return this.file.getText(0, this.file.getLength()); - } - return undefined; - } - fileExists(path: string): boolean { - return path === this.filename; - } - } - - function isCallExpressionWithinTextSpanCollectStep(ts: typeof import('typescript'), textSpan: ts.TextSpan, node: ts.Node): CollectStepResult { - if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) { - return CollectStepResult.No; - } - - return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse; - } - - function analyze( - ts: typeof import('typescript'), - contents: string, - functionName: 'localize' | 'localize2', - options: ts.CompilerOptions = {} - ): ILocalizeAnalysisResult { - const filename = 'file.ts'; - const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents); - const service = ts.createLanguageService(serviceHost); - const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true); - - // all imports - const imports = lazy(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse)); - - // import nls = require('vs/nls'); - const importEqualsDeclarations = imports - .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) - .map(n => n) - .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) - .filter(d => (d.moduleReference).expression.getText().endsWith(`/nls.js'`)); - - // import ... from 'vs/nls'; - const importDeclarations = imports - .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) - .map(n => n) - .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) - .filter(d => d.moduleSpecifier.getText().endsWith(`/nls.js'`)) - .filter(d => !!d.importClause && !!d.importClause.namedBindings); - - // `nls.localize(...)` calls - const nlsLocalizeCallExpressions = importDeclarations - .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) - .map(d => (d.importClause!.namedBindings).name) - .concat(importEqualsDeclarations.map(d => d.name)) - - // find read-only references to `nls` - .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) - .flatten() - .filter(r => !r.isWriteAccess) - - // find the deepest call expressions AST nodes that contain those references - .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) - .map(a => lazy(a).last()) - .filter(n => !!n) - .map(n => n) - - // only `localize` calls - .filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && (n.expression).name.getText() === functionName); - - // `localize` named imports - const allLocalizeImportDeclarations = importDeclarations - .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamedImports)) - .map(d => ([] as any[]).concat((d.importClause!.namedBindings!).elements)) - .flatten(); - - // `localize` read-only references - const localizeReferences = allLocalizeImportDeclarations - .filter(d => d.name.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) - .flatten() - .filter(r => !r.isWriteAccess); - - // custom named `localize` read-only references - const namedLocalizeReferences = allLocalizeImportDeclarations - .filter(d => d.propertyName && d.propertyName.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1)) - .flatten() - .filter(r => !r.isWriteAccess); - - // find the deepest call expressions AST nodes that contain those references - const localizeCallExpressions = localizeReferences - .concat(namedLocalizeReferences) - .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) - .map(a => lazy(a).last()) - .filter(n => !!n) - .map(n => n); - - // collect everything - const localizeCalls = nlsLocalizeCallExpressions - .concat(localizeCallExpressions) - .map(e => e.arguments) - .filter(a => a.length > 1) - .sort((a, b) => a[0].getStart() - b[0].getStart()) - .map(a => ({ - keySpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getEnd()) }, - key: a[0].getText(), - valueSpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getEnd()) }, - value: a[1].getText() - })); - - return { - localizeCalls: localizeCalls.toArray() - }; - } - - class TextModel { - - private lines: string[]; - private lineEndings: string[]; - - constructor(contents: string) { - const regex = /\r\n|\r|\n/g; - let index = 0; - let match: RegExpExecArray | null; - - this.lines = []; - this.lineEndings = []; - - while (match = regex.exec(contents)) { - this.lines.push(contents.substring(index, match.index)); - this.lineEndings.push(match[0]); - index = regex.lastIndex; - } - - if (contents.length > 0) { - this.lines.push(contents.substring(index, contents.length)); - this.lineEndings.push(''); - } - } - - public get(index: number): string { - return this.lines[index]; - } - - public set(index: number, line: string): void { - this.lines[index] = line; - } - - public get lineCount(): number { - return this.lines.length; - } - - /** - * Applies patch(es) to the model. - * Multiple patches must be ordered. - * Does not support patches spanning multiple lines. - */ - public apply(patch: IPatch): void { - const startLineNumber = patch.span.start.line; - const endLineNumber = patch.span.end.line; - - const startLine = this.lines[startLineNumber] || ''; - const endLine = this.lines[endLineNumber] || ''; - - this.lines[startLineNumber] = [ - startLine.substring(0, patch.span.start.character), - patch.content, - endLine.substring(patch.span.end.character) - ].join(''); - - for (let i = startLineNumber + 1; i <= endLineNumber; i++) { - this.lines[i] = ''; - } - } - - public toString(): string { - return lazy(this.lines).zip(this.lineEndings) - .flatten().toArray().join(''); - } - } - - function patchJavascript(patches: IPatch[], contents: string): string { - const model = new TextModel(contents); - - // patch the localize calls - lazy(patches).reverse().each(p => model.apply(p)); - - return model.toString(); - } - - function patchSourcemap(patches: IPatch[], rsm: sm.RawSourceMap, smc: sm.SourceMapConsumer): sm.RawSourceMap { - const smg = new sm.SourceMapGenerator({ - file: rsm.file, - sourceRoot: rsm.sourceRoot - }); - - patches = patches.reverse(); - let currentLine = -1; - let currentLineDiff = 0; - let source: string | null = null; - - smc.eachMapping(m => { - const patch = patches[patches.length - 1]; - const original = { line: m.originalLine, column: m.originalColumn }; - const generated = { line: m.generatedLine, column: m.generatedColumn }; - - if (currentLine !== generated.line) { - currentLineDiff = 0; - } - - currentLine = generated.line; - generated.column += currentLineDiff; - - if (patch && m.generatedLine - 1 === patch.span.end.line && m.generatedColumn === patch.span.end.character) { - const originalLength = patch.span.end.character - patch.span.start.character; - const modifiedLength = patch.content.length; - const lengthDiff = modifiedLength - originalLength; - currentLineDiff += lengthDiff; - generated.column += lengthDiff; - - patches.pop(); - } - - source = rsm.sourceRoot ? path.relative(rsm.sourceRoot, m.source) : m.source; - source = source.replace(/\\/g, '/'); - smg.addMapping({ source, name: m.name, original, generated }); - }, null, sm.SourceMapConsumer.GENERATED_ORDER); - - if (source) { - smg.setSourceContent(source, smc.sourceContentFor(source)); - } - - return JSON.parse(smg.toString()); - } - - function parseLocalizeKeyOrValue(sourceExpression: string) { - // sourceValue can be "foo", 'foo', `foo` or { .... } - // in its evalulated form - // we want to return either the string or the object - // eslint-disable-next-line no-eval - return eval(`(${sourceExpression})`); - } - - function patch(ts: typeof import('typescript'), typescript: string, javascript: string, sourcemap: sm.RawSourceMap, options: { preserveEnglish: boolean }): INlsPatchResult { - const { localizeCalls } = analyze(ts, typescript, 'localize'); - const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2'); - - if (localizeCalls.length === 0 && localize2Calls.length === 0) { - return { javascript, sourcemap }; - } - - const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key))); - const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value))); - const smc = new sm.SourceMapConsumer(sourcemap); - const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); - - // build patches - const toPatch = (c: { range: ISpan; content: string }): IPatch => { - const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); - const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); - return { span: { start, end }, content: c.content }; - }; - - const localizePatches = lazy(localizeCalls) - .map(lc => ( - options.preserveEnglish ? [ - { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(, "message") - ] : [ - { range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(, null) - { range: lc.valueSpan, content: 'null' } - ])) - .flatten() - .map(toPatch); - - const localize2Patches = lazy(localize2Calls) - .map(lc => ( - { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(, "message") - )) - .map(toPatch); - - // Sort patches by their start position - const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => { - if (a.span.start.line < b.span.start.line) { - return -1; - } else if (a.span.start.line > b.span.start.line) { - return 1; - } else if (a.span.start.character < b.span.start.character) { - return -1; - } else if (a.span.start.character > b.span.start.character) { - return 1; - } else { - return 0; - } - }); - - javascript = patchJavascript(patches, javascript); - - sourcemap = patchSourcemap(patches, sourcemap, smc); - - return { javascript, sourcemap, nlsKeys, nlsMessages }; - } - - export function patchFile(javascriptFile: File, typescript: string, options: { preserveEnglish: boolean }): File { - const ts = require('typescript') as typeof import('typescript'); - // hack? - const moduleId = javascriptFile.relative - .replace(/\.js$/, '') - .replace(/\\/g, '/'); - - const { javascript, sourcemap, nlsKeys, nlsMessages } = patch( - ts, - typescript, - javascriptFile.contents.toString(), - (javascriptFile).sourceMap, - options - ); - - const result = fileFrom(javascriptFile, javascript); - (result).sourceMap = sourcemap; - - if (nlsKeys) { - moduleToNLSKeys[moduleId] = nlsKeys; - allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]); - } - - if (nlsMessages) { - moduleToNLSMessages[moduleId] = nlsMessages; - allNLSMessages.push(...nlsMessages); - } - - return result; - } + export const moduleToNLSKeys: { + [name: string /* module ID */]: ILocalizeKey[]; /* keys */ + } = {}; + export const moduleToNLSMessages: { + [name: string /* module ID */]: string[]; /* messages */ + } = {}; + export const allNLSMessages: string[] = []; + export const allNLSModulesAndKeys: Array<[ + string /* module ID */, + string[] /* keys */ + ]> = []; + let allNLSMessagesIndex = 0; + type ILocalizeKey = string | { + key: string; + }; // key might contain metadata for translators and then is not just a string + interface INlsPatchResult { + javascript: string; + sourcemap: sm.RawSourceMap; + nlsMessages?: string[]; + nlsKeys?: ILocalizeKey[]; + } + interface ISpan { + start: ts.LineAndCharacter; + end: ts.LineAndCharacter; + } + interface ILocalizeCall { + keySpan: ISpan; + key: string; + valueSpan: ISpan; + value: string; + } + interface ILocalizeAnalysisResult { + localizeCalls: ILocalizeCall[]; + } + interface IPatch { + span: ISpan; + content: string; + } + function fileFrom(file: File, contents: string, path: string = file.path) { + return new File({ + contents: Buffer.from(contents), + base: file.base, + cwd: file.cwd, + path: path + }); + } + function mappedPositionFrom(source: string, lc: ts.LineAndCharacter): sm.MappedPosition { + return { source, line: lc.line + 1, column: lc.character }; + } + function lcFrom(position: sm.Position): ts.LineAndCharacter { + return { line: position.line - 1, character: position.column }; + } + class SingleFileServiceHost implements ts.LanguageServiceHost { + private file: ts.IScriptSnapshot; + private lib: ts.IScriptSnapshot; + constructor(ts: typeof import('typescript'), private options: ts.CompilerOptions, private filename: string, contents: string) { + this.file = ts.ScriptSnapshot.fromString(contents); + this.lib = ts.ScriptSnapshot.fromString(''); + } + getCompilationSettings = () => this.options; + getScriptFileNames = () => [this.filename]; + getScriptVersion = () => '1'; + getScriptSnapshot = (name: string) => name === this.filename ? this.file : this.lib; + getCurrentDirectory = () => ''; + getDefaultLibFileName = () => 'lib.d.ts'; + readFile(path: string, _encoding?: string): string | undefined { + if (path === this.filename) { + return this.file.getText(0, this.file.getLength()); + } + return undefined; + } + fileExists(path: string): boolean { + return path === this.filename; + } + } + function isCallExpressionWithinTextSpanCollectStep(ts: typeof import('typescript'), textSpan: ts.TextSpan, node: ts.Node): CollectStepResult { + if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) { + return CollectStepResult.No; + } + return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse; + } + function analyze(ts: typeof import('typescript'), contents: string, functionName: 'localize' | 'localize2', options: ts.CompilerOptions = {}): ILocalizeAnalysisResult { + const filename = 'file.ts'; + const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents); + const service = ts.createLanguageService(serviceHost); + const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true); + // all imports + const imports = lazy(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse)); + // import nls = require('vs/nls'); + const importEqualsDeclarations = imports + .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) + .map(n => n) + .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) + .filter(d => (d.moduleReference).expression.getText().endsWith(`/nls.js'`)); + // import ... from 'vs/nls'; + const importDeclarations = imports + .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) + .map(n => n) + .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) + .filter(d => d.moduleSpecifier.getText().endsWith(`/nls.js'`)) + .filter(d => !!d.importClause && !!d.importClause.namedBindings); + // `nls.localize(...)` calls + const nlsLocalizeCallExpressions = importDeclarations + .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) + .map(d => (d.importClause!.namedBindings).name) + .concat(importEqualsDeclarations.map(d => d.name)) + // find read-only references to `nls` + .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) + .flatten() + .filter(r => !r.isWriteAccess) + // find the deepest call expressions AST nodes that contain those references + .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) + .map(a => lazy(a).last()) + .filter(n => !!n) + .map(n => n) + // only `localize` calls + .filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && (n.expression).name.getText() === functionName); + // `localize` named imports + const allLocalizeImportDeclarations = importDeclarations + .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamedImports)) + .map(d => ([] as any[]).concat((d.importClause!.namedBindings!).elements)) + .flatten(); + // `localize` read-only references + const localizeReferences = allLocalizeImportDeclarations + .filter(d => d.name.getText() === functionName) + .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) + .flatten() + .filter(r => !r.isWriteAccess); + // custom named `localize` read-only references + const namedLocalizeReferences = allLocalizeImportDeclarations + .filter(d => d.propertyName && d.propertyName.getText() === functionName) + .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1)) + .flatten() + .filter(r => !r.isWriteAccess); + // find the deepest call expressions AST nodes that contain those references + const localizeCallExpressions = localizeReferences + .concat(namedLocalizeReferences) + .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) + .map(a => lazy(a).last()) + .filter(n => !!n) + .map(n => n); + // collect everything + const localizeCalls = nlsLocalizeCallExpressions + .concat(localizeCallExpressions) + .map(e => e.arguments) + .filter(a => a.length > 1) + .sort((a, b) => a[0].getStart() - b[0].getStart()) + .map(a => ({ + keySpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getEnd()) }, + key: a[0].getText(), + valueSpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getEnd()) }, + value: a[1].getText() + })); + return { + localizeCalls: localizeCalls.toArray() + }; + } + class TextModel { + private lines: string[]; + private lineEndings: string[]; + constructor(contents: string) { + const regex = /\r\n|\r|\n/g; + let index = 0; + let match: RegExpExecArray | null; + this.lines = []; + this.lineEndings = []; + while (match = regex.exec(contents)) { + this.lines.push(contents.substring(index, match.index)); + this.lineEndings.push(match[0]); + index = regex.lastIndex; + } + if (contents.length > 0) { + this.lines.push(contents.substring(index, contents.length)); + this.lineEndings.push(''); + } + } + public get(index: number): string { + return this.lines[index]; + } + public set(index: number, line: string): void { + this.lines[index] = line; + } + public get lineCount(): number { + return this.lines.length; + } + /** + * Applies patch(es) to the model. + * Multiple patches must be ordered. + * Does not support patches spanning multiple lines. + */ + public apply(patch: IPatch): void { + const startLineNumber = patch.span.start.line; + const endLineNumber = patch.span.end.line; + const startLine = this.lines[startLineNumber] || ''; + const endLine = this.lines[endLineNumber] || ''; + this.lines[startLineNumber] = [ + startLine.substring(0, patch.span.start.character), + patch.content, + endLine.substring(patch.span.end.character) + ].join(''); + for (let i = startLineNumber + 1; i <= endLineNumber; i++) { + this.lines[i] = ''; + } + } + public toString(): string { + return lazy(this.lines).zip(this.lineEndings) + .flatten().toArray().join(''); + } + } + function patchJavascript(patches: IPatch[], contents: string): string { + const model = new TextModel(contents); + // patch the localize calls + lazy(patches).reverse().each(p => model.apply(p)); + return model.toString(); + } + function patchSourcemap(patches: IPatch[], rsm: sm.RawSourceMap, smc: sm.SourceMapConsumer): sm.RawSourceMap { + const smg = new sm.SourceMapGenerator({ + file: rsm.file, + sourceRoot: rsm.sourceRoot + }); + patches = patches.reverse(); + let currentLine = -1; + let currentLineDiff = 0; + let source: string | null = null; + smc.eachMapping(m => { + const patch = patches[patches.length - 1]; + const original = { line: m.originalLine, column: m.originalColumn }; + const generated = { line: m.generatedLine, column: m.generatedColumn }; + if (currentLine !== generated.line) { + currentLineDiff = 0; + } + currentLine = generated.line; + generated.column += currentLineDiff; + if (patch && m.generatedLine - 1 === patch.span.end.line && m.generatedColumn === patch.span.end.character) { + const originalLength = patch.span.end.character - patch.span.start.character; + const modifiedLength = patch.content.length; + const lengthDiff = modifiedLength - originalLength; + currentLineDiff += lengthDiff; + generated.column += lengthDiff; + patches.pop(); + } + source = rsm.sourceRoot ? path.relative(rsm.sourceRoot, m.source) : m.source; + source = source.replace(/\\/g, '/'); + smg.addMapping({ source, name: m.name, original, generated }); + }, null, sm.SourceMapConsumer.GENERATED_ORDER); + if (source) { + smg.setSourceContent(source, smc.sourceContentFor(source)); + } + return JSON.parse(smg.toString()); + } + function parseLocalizeKeyOrValue(sourceExpression: string) { + // sourceValue can be "foo", 'foo', `foo` or { .... } + // in its evalulated form + // we want to return either the string or the object + // eslint-disable-next-line no-eval + return eval(`(${sourceExpression})`); + } + function patch(ts: typeof import('typescript'), typescript: string, javascript: string, sourcemap: sm.RawSourceMap, options: { + preserveEnglish: boolean; + }): INlsPatchResult { + const { localizeCalls } = analyze(ts, typescript, 'localize'); + const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2'); + if (localizeCalls.length === 0 && localize2Calls.length === 0) { + return { javascript, sourcemap }; + } + const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key))); + const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value))); + const smc = new sm.SourceMapConsumer(sourcemap); + const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); + // build patches + const toPatch = (c: { + range: ISpan; + content: string; + }): IPatch => { + const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); + const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); + return { span: { start, end }, content: c.content }; + }; + const localizePatches = lazy(localizeCalls) + .map(lc => (options.preserveEnglish ? [ + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(, "message") + ] : [ + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(, null) + { range: lc.valueSpan, content: 'null' } + ])) + .flatten() + .map(toPatch); + const localize2Patches = lazy(localize2Calls) + .map(lc => ({ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(, "message") + )) + .map(toPatch); + // Sort patches by their start position + const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => { + if (a.span.start.line < b.span.start.line) { + return -1; + } + else if (a.span.start.line > b.span.start.line) { + return 1; + } + else if (a.span.start.character < b.span.start.character) { + return -1; + } + else if (a.span.start.character > b.span.start.character) { + return 1; + } + else { + return 0; + } + }); + javascript = patchJavascript(patches, javascript); + sourcemap = patchSourcemap(patches, sourcemap, smc); + return { javascript, sourcemap, nlsKeys, nlsMessages }; + } + export function patchFile(javascriptFile: File, typescript: string, options: { + preserveEnglish: boolean; + }): File { + const ts = require('typescript') as typeof import('typescript'); + // hack? + const moduleId = javascriptFile.relative + .replace(/\.js$/, '') + .replace(/\\/g, '/'); + const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), (javascriptFile).sourceMap, options); + const result = fileFrom(javascriptFile, javascript); + (result).sourceMap = sourcemap; + if (nlsKeys) { + moduleToNLSKeys[moduleId] = nlsKeys; + allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]); + } + if (nlsMessages) { + moduleToNLSMessages[moduleId] = nlsMessages; + allNLSMessages.push(...nlsMessages); + } + return result; + } } diff --git a/build/lib/node.ts b/build/lib/node.ts index 4beb13ae91bf4..c04395ad59da1 100644 --- a/build/lib/node.ts +++ b/build/lib/node.ts @@ -2,19 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as fs from 'fs'; - const root = path.dirname(path.dirname(__dirname)); const npmrcPath = path.join(root, 'remote', '.npmrc'); const npmrc = fs.readFileSync(npmrcPath, 'utf8'); const version = /^target="(.*)"$/m.exec(npmrc)![1]; - const platform = process.platform; const arch = process.arch; - const node = platform === 'win32' ? 'node.exe' : 'node'; const nodePath = path.join(root, '.build', 'node', `v${version}`, `${platform}-${arch}`, node); - console.log(nodePath); diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 40efe87d6749e..50c99f67b253b 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as gulp from 'gulp'; import * as filter from 'gulp-filter'; @@ -16,262 +15,213 @@ import * as esbuild from 'esbuild'; import * as sourcemaps from 'gulp-sourcemaps'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; - const REPO_ROOT_PATH = path.join(__dirname, '../..'); - export interface IBundleESMTaskOpts { - /** - * The folder to read files from. - */ - src: string; - /** - * The entry points to bundle. - */ - entryPoints: Array; - /** - * Other resources to consider (svg, etc.) - */ - resources?: string[]; - /** - * File contents interceptor for a given path. - */ - fileContentMapper?: (path: string) => ((contents: string) => Promise | string) | undefined; - /** - * Allows to skip the removal of TS boilerplate. Use this when - * the entry point is small and the overhead of removing the - * boilerplate makes the file larger in the end. - */ - skipTSBoilerplateRemoval?: (entryPointName: string) => boolean; + /** + * The folder to read files from. + */ + src: string; + /** + * The entry points to bundle. + */ + entryPoints: Array; + /** + * Other resources to consider (svg, etc.) + */ + resources?: string[]; + /** + * File contents interceptor for a given path. + */ + fileContentMapper?: (path: string) => ((contents: string) => Promise | string) | undefined; + /** + * Allows to skip the removal of TS boilerplate. Use this when + * the entry point is small and the overhead of removing the + * boilerplate makes the file larger in the end. + */ + skipTSBoilerplateRemoval?: (entryPointName: string) => boolean; } - const DEFAULT_FILE_HEADER = [ - '/*!--------------------------------------------------------', - ' * Copyright (C) Microsoft Corporation. All rights reserved.', - ' *--------------------------------------------------------*/' + '/*!--------------------------------------------------------', + ' * Copyright (C) Microsoft Corporation. All rights reserved.', + ' *--------------------------------------------------------*/' ].join('\n'); - function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { - const resourcesStream = es.through(); // this stream will contain the resources - const bundlesStream = es.through(); // this stream will contain the bundled files - - const entryPoints = opts.entryPoints.map(entryPoint => { - if (typeof entryPoint === 'string') { - return { name: path.parse(entryPoint).name }; - } - - return entryPoint; - }); - - const allMentionedModules = new Set(); - for (const entryPoint of entryPoints) { - allMentionedModules.add(entryPoint.name); - entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules); - entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules); - } - - const bundleAsync = async () => { - const files: VinylFile[] = []; - const tasks: Promise[] = []; - - for (const entryPoint of entryPoints) { - fancyLog(`Bundled entry point: ${ansiColors.yellow(entryPoint.name)}...`); - - // support for 'dest' via esbuild#in/out - const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name; - - // banner contents - const banner = { - js: DEFAULT_FILE_HEADER, - css: DEFAULT_FILE_HEADER - }; - - // TS Boilerplate - if (!opts.skipTSBoilerplateRemoval?.(entryPoint.name)) { - const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js'); - banner.js += await fs.promises.readFile(tslibPath, 'utf-8'); - } - - const contentsMapper: esbuild.Plugin = { - name: 'contents-mapper', - setup(build) { - build.onLoad({ filter: /\.js$/ }, async ({ path }) => { - const contents = await fs.promises.readFile(path, 'utf-8'); - - // TS Boilerplate - let newContents: string; - if (!opts.skipTSBoilerplateRemoval?.(entryPoint.name)) { - newContents = bundle.removeAllTSBoilerplate(contents); - } else { - newContents = contents; - } - - // File Content Mapper - const mapper = opts.fileContentMapper?.(path); - if (mapper) { - newContents = await mapper(newContents); - } - - return { contents: newContents }; - }); - } - }; - - const externalOverride: esbuild.Plugin = { - name: 'external-override', - setup(build) { - // We inline selected modules that are we depend on on startup without - // a conditional `await import(...)` by hooking into the resolution. - build.onResolve({ filter: /^minimist$/ }, () => { - return { path: path.join(REPO_ROOT_PATH, 'node_modules', 'minimist', 'index.js'), external: false }; - }); - }, - }; - - const task = esbuild.build({ - bundle: true, - external: entryPoint.exclude, - packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages - platform: 'neutral', // makes esm - format: 'esm', - sourcemap: 'external', - plugins: [contentsMapper, externalOverride], - target: ['es2022'], - loader: { - '.ttf': 'file', - '.svg': 'file', - '.png': 'file', - '.sh': 'file', - }, - assetNames: 'media/[name]', // moves media assets into a sub-folder "media" - banner: entryPoint.name === 'vs/workbench/workbench.web.main' ? undefined : banner, // TODO@esm remove line when we stop supporting web-amd-esm-bridge - entryPoints: [ - { - in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`), - out: dest, - } - ], - outdir: path.join(REPO_ROOT_PATH, opts.src), - write: false, // enables res.outputFiles - metafile: true, // enables res.metafile - // minify: NOT enabled because we have a separate minify task that takes care of the TSLib banner as well - }).then(res => { - for (const file of res.outputFiles) { - let sourceMapFile: esbuild.OutputFile | undefined = undefined; - if (file.path.endsWith('.js')) { - sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`); - } - - const fileProps = { - contents: Buffer.from(file.contents), - sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps - path: file.path, - base: path.join(REPO_ROOT_PATH, opts.src) - }; - files.push(new VinylFile(fileProps)); - } - }); - - tasks.push(task); - } - - await Promise.all(tasks); - return { files }; - }; - - bundleAsync().then((output) => { - - // bundle output (JS, CSS, SVG...) - es.readArray(output.files).pipe(bundlesStream); - - // forward all resources - gulp.src(opts.resources ?? [], { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream); - }); - - const result = es.merge( - bundlesStream, - resourcesStream - ); - - return result - .pipe(sourcemaps.write('./', { - sourceRoot: undefined, - addComment: true, - includeContent: true - })); + const resourcesStream = es.through(); // this stream will contain the resources + const bundlesStream = es.through(); // this stream will contain the bundled files + const entryPoints = opts.entryPoints.map(entryPoint => { + if (typeof entryPoint === 'string') { + return { name: path.parse(entryPoint).name }; + } + return entryPoint; + }); + const allMentionedModules = new Set(); + for (const entryPoint of entryPoints) { + allMentionedModules.add(entryPoint.name); + entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules); + entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules); + } + const bundleAsync = async () => { + const files: VinylFile[] = []; + const tasks: Promise[] = []; + for (const entryPoint of entryPoints) { + fancyLog(`Bundled entry point: ${ansiColors.yellow(entryPoint.name)}...`); + // support for 'dest' via esbuild#in/out + const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name; + // banner contents + const banner = { + js: DEFAULT_FILE_HEADER, + css: DEFAULT_FILE_HEADER + }; + // TS Boilerplate + if (!opts.skipTSBoilerplateRemoval?.(entryPoint.name)) { + const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js'); + banner.js += await fs.promises.readFile(tslibPath, 'utf-8'); + } + const contentsMapper: esbuild.Plugin = { + name: 'contents-mapper', + setup(build) { + build.onLoad({ filter: /\.js$/ }, async ({ path }) => { + const contents = await fs.promises.readFile(path, 'utf-8'); + // TS Boilerplate + let newContents: string; + if (!opts.skipTSBoilerplateRemoval?.(entryPoint.name)) { + newContents = bundle.removeAllTSBoilerplate(contents); + } + else { + newContents = contents; + } + // File Content Mapper + const mapper = opts.fileContentMapper?.(path); + if (mapper) { + newContents = await mapper(newContents); + } + return { contents: newContents }; + }); + } + }; + const externalOverride: esbuild.Plugin = { + name: 'external-override', + setup(build) { + // We inline selected modules that are we depend on on startup without + // a conditional `await import(...)` by hooking into the resolution. + build.onResolve({ filter: /^minimist$/ }, () => { + return { path: path.join(REPO_ROOT_PATH, 'node_modules', 'minimist', 'index.js'), external: false }; + }); + }, + }; + const task = esbuild.build({ + bundle: true, + external: entryPoint.exclude, + packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages + platform: 'neutral', // makes esm + format: 'esm', + sourcemap: 'external', + plugins: [contentsMapper, externalOverride], + target: ['es2022'], + loader: { + '.ttf': 'file', + '.svg': 'file', + '.png': 'file', + '.sh': 'file', + }, + assetNames: 'media/[name]', // moves media assets into a sub-folder "media" + banner: entryPoint.name === 'vs/workbench/workbench.web.main' ? undefined : banner, // TODO@esm remove line when we stop supporting web-amd-esm-bridge + entryPoints: [ + { + in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`), + out: dest, + } + ], + outdir: path.join(REPO_ROOT_PATH, opts.src), + write: false, // enables res.outputFiles + metafile: true, // enables res.metafile + // minify: NOT enabled because we have a separate minify task that takes care of the TSLib banner as well + }).then(res => { + for (const file of res.outputFiles) { + let sourceMapFile: esbuild.OutputFile | undefined = undefined; + if (file.path.endsWith('.js')) { + sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`); + } + const fileProps = { + contents: Buffer.from(file.contents), + sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps + path: file.path, + base: path.join(REPO_ROOT_PATH, opts.src) + }; + files.push(new VinylFile(fileProps)); + } + }); + tasks.push(task); + } + await Promise.all(tasks); + return { files }; + }; + bundleAsync().then((output) => { + // bundle output (JS, CSS, SVG...) + es.readArray(output.files).pipe(bundlesStream); + // forward all resources + gulp.src(opts.resources ?? [], { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream); + }); + const result = es.merge(bundlesStream, resourcesStream); + return result + .pipe(sourcemaps.write('./', { + sourceRoot: undefined, + addComment: true, + includeContent: true + })); } - export interface IBundleESMTaskOpts { - /** - * Destination folder for the bundled files. - */ - out: string; - /** - * Bundle ESM modules (using esbuild). - */ - esm: IBundleESMTaskOpts; + /** + * Destination folder for the bundled files. + */ + out: string; + /** + * Bundle ESM modules (using esbuild). + */ + esm: IBundleESMTaskOpts; } - export function bundleTask(opts: IBundleESMTaskOpts): () => NodeJS.ReadWriteStream { - return function () { - return bundleESMTask(opts.esm).pipe(gulp.dest(opts.out)); - }; + return function () { + return bundleESMTask(opts.esm).pipe(gulp.dest(opts.out)); + }; } - export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void { - const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; - - return cb => { - const cssnano = require('cssnano') as typeof import('cssnano'); - const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); - - const jsFilter = filter('**/*.js', { restore: true }); - const cssFilter = filter('**/*.css', { restore: true }); - const svgFilter = filter('**/*.svg', { restore: true }); - - pump( - gulp.src([src + '/**', '!' + src + '/**/*.map']), - jsFilter, - sourcemaps.init({ loadMaps: true }), - es.map((f: any, cb) => { - esbuild.build({ - entryPoints: [f.path], - minify: true, - sourcemap: 'external', - outdir: '.', - packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages - platform: 'neutral', // makes esm - target: ['es2022'], - write: false - }).then(res => { - const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!; - const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!; - - const contents = Buffer.from(jsFile.contents); - const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); - if (unicodeMatch) { - cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); - } else { - f.contents = contents; - f.sourceMap = JSON.parse(sourceMapFile.text); - - cb(undefined, f); - } - }, cb); - }), - jsFilter.restore, - cssFilter, - gulpPostcss([cssnano({ preset: 'default' })]), - cssFilter.restore, - svgFilter, - svgmin(), - svgFilter.restore, - sourcemaps.write('./', { - sourceMappingURL, - sourceRoot: undefined, - includeContent: true, - addComment: true - } as any), - gulp.dest(src + '-min'), - (err: any) => cb(err)); - }; + const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; + return cb => { + const cssnano = require('cssnano') as typeof import('cssnano'); + const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); + const jsFilter = filter('**/*.js', { restore: true }); + const cssFilter = filter('**/*.css', { restore: true }); + const svgFilter = filter('**/*.svg', { restore: true }); + pump(gulp.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, sourcemaps.init({ loadMaps: true }), es.map((f: any, cb) => { + esbuild.build({ + entryPoints: [f.path], + minify: true, + sourcemap: 'external', + outdir: '.', + packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages + platform: 'neutral', // makes esm + target: ['es2022'], + write: false + }).then(res => { + const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!; + const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!; + const contents = Buffer.from(jsFile.contents); + const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); + if (unicodeMatch) { + cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); + } + else { + f.contents = contents; + f.sourceMap = JSON.parse(sourceMapFile.text); + cb(undefined, f); + } + }, cb); + }), jsFilter.restore, cssFilter, gulpPostcss([cssnano({ preset: 'default' })]), cssFilter.restore, svgFilter, svgmin(), svgFilter.restore, sourcemaps.write('./', { + sourceMappingURL, + sourceRoot: undefined, + includeContent: true, + addComment: true + } as any), gulp.dest(src + '-min'), (err: any) => cb(err)); + }; } diff --git a/build/lib/policies.ts b/build/lib/policies.ts index 68f6989f27a7d..814042140fbd6 100644 --- a/build/lib/policies.ts +++ b/build/lib/policies.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { spawn } from 'child_process'; import { promises as fs } from 'fs'; import * as path from 'path'; @@ -12,462 +11,307 @@ import * as Parser from 'tree-sitter'; const { typescript } = require('tree-sitter-typescript'); const product = require('../../product.json'); const packageJson = require('../../package.json'); - -type NlsString = { value: string; nlsKey: string }; - +type NlsString = { + value: string; + nlsKey: string; +}; function isNlsString(value: string | NlsString | undefined): value is NlsString { - return value ? typeof value !== 'string' : false; + return value ? typeof value !== 'string' : false; } - function isStringArray(value: (string | NlsString)[]): value is string[] { - return !value.some(s => isNlsString(s)); + return !value.some(s => isNlsString(s)); } - function isNlsStringArray(value: (string | NlsString)[]): value is NlsString[] { - return value.every(s => isNlsString(s)); + return value.every(s => isNlsString(s)); } - interface Category { - readonly moduleName: string; - readonly name: NlsString; + readonly moduleName: string; + readonly name: NlsString; } - enum PolicyType { - StringEnum + StringEnum } - interface Policy { - readonly category: Category; - readonly minimumVersion: string; - renderADMX(regKey: string): string[]; - renderADMLStrings(translations?: LanguageTranslations): string[]; - renderADMLPresentation(): string; + readonly category: Category; + readonly minimumVersion: string; + renderADMX(regKey: string): string[]; + renderADMLStrings(translations?: LanguageTranslations): string[]; + renderADMLPresentation(): string; } - function renderADMLString(prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { - let value: string | undefined; - - if (translations) { - const moduleTranslations = translations[moduleName]; - - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - - if (!value) { - value = nlsString.value; - } - - return `${value}`; -} - + let value: string | undefined; + if (translations) { + const moduleTranslations = translations[moduleName]; + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + if (!value) { + value = nlsString.value; + } + return `${value}`; +} abstract class BasePolicy implements Policy { - constructor( - protected policyType: PolicyType, - protected name: string, - readonly category: Category, - readonly minimumVersion: string, - protected description: NlsString, - protected moduleName: string, - ) { } - - protected renderADMLString(nlsString: NlsString, translations?: LanguageTranslations): string { - return renderADMLString(this.name, this.moduleName, nlsString, translations); - } - - renderADMX(regKey: string) { - return [ - ``, - ` `, - ` `, - ` `, - ...this.renderADMXElements(), - ` `, - `` - ]; - } - - protected abstract renderADMXElements(): string[]; - - renderADMLStrings(translations?: LanguageTranslations) { - return [ - `${this.name}`, - this.renderADMLString(this.description, translations) - ]; - } - - renderADMLPresentation(): string { - return `${this.renderADMLPresentationContents()}`; - } - - protected abstract renderADMLPresentationContents(): string; -} - + constructor(protected policyType: PolicyType, protected name: string, readonly category: Category, readonly minimumVersion: string, protected description: NlsString, protected moduleName: string) { } + protected renderADMLString(nlsString: NlsString, translations?: LanguageTranslations): string { + return renderADMLString(this.name, this.moduleName, nlsString, translations); + } + renderADMX(regKey: string) { + return [ + ``, + ` `, + ` `, + ` `, + ...this.renderADMXElements(), + ` `, + `` + ]; + } + protected abstract renderADMXElements(): string[]; + renderADMLStrings(translations?: LanguageTranslations) { + return [ + `${this.name}`, + this.renderADMLString(this.description, translations) + ]; + } + renderADMLPresentation(): string { + return `${this.renderADMLPresentationContents()}`; + } + protected abstract renderADMLPresentationContents(): string; +} class BooleanPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): BooleanPolicy | undefined { - const type = getStringProperty(settingNode, 'type'); - - if (type !== 'boolean') { - return undefined; - } - - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - ``, - ` `, - `` - ]; - } - - renderADMLPresentationContents() { - return `${this.name}`; - } -} - + static from(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string, settingNode: Parser.SyntaxNode): BooleanPolicy | undefined { + const type = getStringProperty(settingNode, 'type'); + if (type !== 'boolean') { + return undefined; + } + return new BooleanPolicy(name, category, minimumVersion, description, moduleName); + } + private constructor(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + protected renderADMXElements(): string[] { + return [ + ``, + ` `, + `` + ]; + } + renderADMLPresentationContents() { + return `${this.name}`; + } +} class IntPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): IntPolicy | undefined { - const type = getStringProperty(settingNode, 'type'); - - if (type !== 'number') { - return undefined; - } - - const defaultValue = getIntProperty(settingNode, 'default'); - - if (typeof defaultValue === 'undefined') { - throw new Error(`Missing required 'default' property.`); - } - - return new IntPolicy(name, category, minimumVersion, description, moduleName, defaultValue); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - protected readonly defaultValue: number, - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - `` - // `` - ]; - } - - renderADMLPresentationContents() { - return `${this.name}`; - } -} - + static from(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string, settingNode: Parser.SyntaxNode): IntPolicy | undefined { + const type = getStringProperty(settingNode, 'type'); + if (type !== 'number') { + return undefined; + } + const defaultValue = getIntProperty(settingNode, 'default'); + if (typeof defaultValue === 'undefined') { + throw new Error(`Missing required 'default' property.`); + } + return new IntPolicy(name, category, minimumVersion, description, moduleName, defaultValue); + } + private constructor(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string, protected readonly defaultValue: number) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + protected renderADMXElements(): string[] { + return [ + `` + // `` + ]; + } + renderADMLPresentationContents() { + return `${this.name}`; + } +} class StringPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringPolicy | undefined { - const type = getStringProperty(settingNode, 'type'); - - if (type !== 'string') { - return undefined; - } - - return new StringPolicy(name, category, minimumVersion, description, moduleName); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [``]; - } - - renderADMLPresentationContents() { - return ``; - } -} - + static from(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string, settingNode: Parser.SyntaxNode): StringPolicy | undefined { + const type = getStringProperty(settingNode, 'type'); + if (type !== 'string') { + return undefined; + } + return new StringPolicy(name, category, minimumVersion, description, moduleName); + } + private constructor(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + protected renderADMXElements(): string[] { + return [``]; + } + renderADMLPresentationContents() { + return ``; + } +} class StringEnumPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringEnumPolicy | undefined { - const type = getStringProperty(settingNode, 'type'); - - if (type !== 'string') { - return undefined; - } - - const enum_ = getStringArrayProperty(settingNode, 'enum'); - - if (!enum_) { - return undefined; - } - - if (!isStringArray(enum_)) { - throw new Error(`Property 'enum' should not be localized.`); - } - - const enumDescriptions = getStringArrayProperty(settingNode, 'enumDescriptions'); - - if (!enumDescriptions) { - throw new Error(`Missing required 'enumDescriptions' property.`); - } else if (!isNlsStringArray(enumDescriptions)) { - throw new Error(`Property 'enumDescriptions' should be localized.`); - } - - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - protected enum_: string[], - protected enumDescriptions: NlsString[], - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - ``, - ...this.enum_.map((value, index) => ` ${value}`), - `` - ]; - } - - renderADMLStrings(translations?: LanguageTranslations) { - return [ - ...super.renderADMLStrings(translations), - ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) - ]; - } - - renderADMLPresentationContents() { - return ``; - } -} - + static from(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string, settingNode: Parser.SyntaxNode): StringEnumPolicy | undefined { + const type = getStringProperty(settingNode, 'type'); + if (type !== 'string') { + return undefined; + } + const enum_ = getStringArrayProperty(settingNode, 'enum'); + if (!enum_) { + return undefined; + } + if (!isStringArray(enum_)) { + throw new Error(`Property 'enum' should not be localized.`); + } + const enumDescriptions = getStringArrayProperty(settingNode, 'enumDescriptions'); + if (!enumDescriptions) { + throw new Error(`Missing required 'enumDescriptions' property.`); + } + else if (!isNlsStringArray(enumDescriptions)) { + throw new Error(`Property 'enumDescriptions' should be localized.`); + } + return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); + } + private constructor(name: string, category: Category, minimumVersion: string, description: NlsString, moduleName: string, protected enum_: string[], protected enumDescriptions: NlsString[]) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + } + protected renderADMXElements(): string[] { + return [ + ``, + ...this.enum_.map((value, index) => ` ${value}`), + `` + ]; + } + renderADMLStrings(translations?: LanguageTranslations) { + return [ + ...super.renderADMLStrings(translations), + ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) + ]; + } + renderADMLPresentationContents() { + return ``; + } +} interface QType { - Q: string; - value(matches: Parser.QueryMatch[]): T | undefined; + Q: string; + value(matches: Parser.QueryMatch[]): T | undefined; } - const IntQ: QType = { - Q: `(number) @value`, - - value(matches: Parser.QueryMatch[]): number | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - return parseInt(value); - } + Q: `(number) @value`, + value(matches: Parser.QueryMatch[]): number | undefined { + const match = matches[0]; + if (!match) { + return undefined; + } + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + return parseInt(value); + } }; - const StringQ: QType = { - Q: `[ + Q: `[ (string (string_fragment) @value) (call_expression function: (identifier) @localizeFn arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) (#eq? @localizeFn localize)) ]`, - - value(matches: Parser.QueryMatch[]): string | NlsString | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - - if (nlsKey) { - return { value, nlsKey }; - } else { - return value; - } - } + value(matches: Parser.QueryMatch[]): string | NlsString | undefined { + const match = matches[0]; + if (!match) { + return undefined; + } + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; + if (nlsKey) { + return { value, nlsKey }; + } + else { + return value; + } + } }; - const StringArrayQ: QType<(string | NlsString)[]> = { - Q: `(array ${StringQ.Q})`, - - value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { - if (matches.length === 0) { - return undefined; - } - - return matches.map(match => { - return StringQ.value([match]) as string | NlsString; - }); - } + Q: `(array ${StringQ.Q})`, + value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { + if (matches.length === 0) { + return undefined; + } + return matches.map(match => { + return StringQ.value([match]) as string | NlsString; + }); + } }; - function getProperty(qtype: QType, node: Parser.SyntaxNode, key: string): T | undefined { - const query = new Parser.Query( - typescript, - `( + const query = new Parser.Query(typescript, `( (pair key: [(property_identifier)(string)] @key value: ${qtype.Q} ) (#eq? @key ${key}) - )` - ); - - return qtype.value(query.matches(node)); + )`); + return qtype.value(query.matches(node)); } - function getIntProperty(node: Parser.SyntaxNode, key: string): number | undefined { - return getProperty(IntQ, node, key); + return getProperty(IntQ, node, key); } - function getStringProperty(node: Parser.SyntaxNode, key: string): string | NlsString | undefined { - return getProperty(StringQ, node, key); + return getProperty(StringQ, node, key); } - function getStringArrayProperty(node: Parser.SyntaxNode, key: string): (string | NlsString)[] | undefined { - return getProperty(StringArrayQ, node, key); + return getProperty(StringArrayQ, node, key); } - // TODO: add more policy types const PolicyTypes = [ - BooleanPolicy, - IntPolicy, - StringEnumPolicy, - StringPolicy, + BooleanPolicy, + IntPolicy, + StringEnumPolicy, + StringPolicy, ]; - -function getPolicy( - moduleName: string, - configurationNode: Parser.SyntaxNode, - settingNode: Parser.SyntaxNode, - policyNode: Parser.SyntaxNode, - categories: Map -): Policy { - const name = getStringProperty(policyNode, 'name'); - - if (!name) { - throw new Error(`Missing required 'name' property.`); - } else if (isNlsString(name)) { - throw new Error(`Property 'name' should be a literal string.`); - } - - const categoryName = getStringProperty(configurationNode, 'title'); - - if (!categoryName) { - throw new Error(`Missing required 'title' property.`); - } else if (!isNlsString(categoryName)) { - throw new Error(`Property 'title' should be localized.`); - } - - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - - const minimumVersion = getStringProperty(policyNode, 'minimumVersion'); - - if (!minimumVersion) { - throw new Error(`Missing required 'minimumVersion' property.`); - } else if (isNlsString(minimumVersion)) { - throw new Error(`Property 'minimumVersion' should be a literal string.`); - } - - const description = getStringProperty(settingNode, 'description'); - - if (!description) { - throw new Error(`Missing required 'description' property.`); - } if (!isNlsString(description)) { - throw new Error(`Property 'description' should be localized.`); - } - - let result: Policy | undefined; - - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - - if (!result) { - throw new Error(`Failed to parse policy '${name}'.`); - } - - return result; -} - +function getPolicy(moduleName: string, configurationNode: Parser.SyntaxNode, settingNode: Parser.SyntaxNode, policyNode: Parser.SyntaxNode, categories: Map): Policy { + const name = getStringProperty(policyNode, 'name'); + if (!name) { + throw new Error(`Missing required 'name' property.`); + } + else if (isNlsString(name)) { + throw new Error(`Property 'name' should be a literal string.`); + } + const categoryName = getStringProperty(configurationNode, 'title'); + if (!categoryName) { + throw new Error(`Missing required 'title' property.`); + } + else if (!isNlsString(categoryName)) { + throw new Error(`Property 'title' should be localized.`); + } + const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; + let category = categories.get(categoryKey); + if (!category) { + category = { moduleName, name: categoryName }; + categories.set(categoryKey, category); + } + const minimumVersion = getStringProperty(policyNode, 'minimumVersion'); + if (!minimumVersion) { + throw new Error(`Missing required 'minimumVersion' property.`); + } + else if (isNlsString(minimumVersion)) { + throw new Error(`Property 'minimumVersion' should be a literal string.`); + } + const description = getStringProperty(settingNode, 'description'); + if (!description) { + throw new Error(`Missing required 'description' property.`); + } + if (!isNlsString(description)) { + throw new Error(`Property 'description' should be localized.`); + } + let result: Policy | undefined; + for (const policyType of PolicyTypes) { + if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { + break; + } + } + if (!result) { + throw new Error(`Failed to parse policy '${name}'.`); + } + return result; +} function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { - const query = new Parser.Query(typescript, ` + const query = new Parser.Query(typescript, ` ( (call_expression function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) @@ -484,32 +328,27 @@ function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { ) ) `); - - const categories = new Map(); - - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} - + const categories = new Map(); + return query.matches(node).map(m => { + const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; + const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; + const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; + return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); + }); +} async function getFiles(root: string): Promise { - return new Promise((c, e) => { - const result: string[] = []; - const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = byline(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} - + return new Promise((c, e) => { + const result: string[] = []; + const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); + const stream = byline(rg.stdout.setEncoding('utf8')); + stream.on('data', path => result.push(path)); + stream.on('error', err => e(err)); + stream.on('end', () => c(result)); + }); +} function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { - versions = versions.map(v => v.replace(/\./g, '_')); - - return ` + versions = versions.map(v => v.replace(/\./g, '_')); + return ` @@ -530,9 +369,8 @@ function renderADMX(regKey: string, versions: string[], categories: Category[], `; } - function renderADML(appName: string, versions: string[], categories: Category[], policies: Policy[], translations?: LanguageTranslations) { - return ` + return ` @@ -550,172 +388,163 @@ function renderADML(appName: string, versions: string[], categories: Category[], `; } - function renderGP(policies: Policy[], translations: Translations) { - const appName = product.nameLong; - const regKey = product.win32RegValueName; - - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...new Set(policies.map(p => p.category))]; - - return { - admx: renderADMX(regKey, versions, categories, policies), - adml: [ - { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => - ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) - ] - }; -} - + const appName = product.nameLong; + const regKey = product.win32RegValueName; + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...new Set(policies.map(p => p.category))]; + return { + admx: renderADMX(regKey, versions, categories, policies), + adml: [ + { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) + ] + }; +} const Languages = { - 'fr': 'fr-fr', - 'it': 'it-it', - 'de': 'de-de', - 'es': 'es-es', - 'ru': 'ru-ru', - 'zh-hans': 'zh-cn', - 'zh-hant': 'zh-tw', - 'ja': 'ja-jp', - 'ko': 'ko-kr', - 'cs': 'cs-cz', - 'pt-br': 'pt-br', - 'tr': 'tr-tr', - 'pl': 'pl-pl', + 'fr': 'fr-fr', + 'it': 'it-it', + 'de': 'de-de', + 'es': 'es-es', + 'ru': 'ru-ru', + 'zh-hans': 'zh-cn', + 'zh-hant': 'zh-tw', + 'ja': 'ja-jp', + 'ko': 'ko-kr', + 'cs': 'cs-cz', + 'pt-br': 'pt-br', + 'tr': 'tr-tr', + 'pl': 'pl-pl', +}; +type LanguageTranslations = { + [moduleName: string]: { + [nlsKey: string]: string; + }; }; - -type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } }; -type Translations = { languageId: string; languageTranslations: LanguageTranslations }[]; - -type Version = [number, number, number]; - +type Translations = { + languageId: string; + languageTranslations: LanguageTranslations; +}[]; +type Version = [ + number, + number, + number +]; async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { - const resource = { - publisher: 'ms-ceintl', - name: `vscode-language-pack-${languageId}`, - version: `${version[0]}.${version[1]}.${version[2]}`, - path: 'extension/translations/main.i18n.json' - }; - - const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key as keyof typeof resource]); - const res = await fetch(url); - - if (res.status !== 200) { - throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); - } - - const { contents: result } = await res.json() as { contents: LanguageTranslations }; - return result; -} - + const resource = { + publisher: 'ms-ceintl', + name: `vscode-language-pack-${languageId}`, + version: `${version[0]}.${version[1]}.${version[2]}`, + path: 'extension/translations/main.i18n.json' + }; + const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key as keyof typeof resource]); + const res = await fetch(url); + if (res.status !== 200) { + throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); + } + const { contents: result } = await res.json() as { + contents: LanguageTranslations; + }; + return result; +} function parseVersion(version: string): Version { - const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; - return [parseInt(major), parseInt(minor), parseInt(patch)]; + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; + return [parseInt(major), parseInt(minor), parseInt(patch)]; } - function compareVersions(a: Version, b: Version): number { - if (a[0] !== b[0]) { return a[0] - b[0]; } - if (a[1] !== b[1]) { return a[1] - b[1]; } - return a[2] - b[2]; + if (a[0] !== b[0]) { + return a[0] - b[0]; + } + if (a[1] !== b[1]) { + return a[1] - b[1]; + } + return a[2] - b[2]; } - async function queryVersions(serviceUrl: string, languageId: string): Promise { - const res = await fetch(`${serviceUrl}/extensionquery`, { - method: 'POST', - headers: { - 'Accept': 'application/json;api-version=3.0-preview.1', - 'Content-Type': 'application/json', - 'User-Agent': 'VS Code Build', - }, - body: JSON.stringify({ - filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], - flags: 0x1 - }) - }); - - if (res.status !== 200) { - throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); - } - - const result = await res.json() as { results: [{ extensions: { versions: { version: string }[] }[] }] }; - return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); -} - + const res = await fetch(`${serviceUrl}/extensionquery`, { + method: 'POST', + headers: { + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Content-Type': 'application/json', + 'User-Agent': 'VS Code Build', + }, + body: JSON.stringify({ + filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], + flags: 0x1 + }) + }); + if (res.status !== 200) { + throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); + } + const result = await res.json() as { + results: [ + { + extensions: { + versions: { + version: string; + }[]; + }[]; + } + ]; + }; + return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); +} async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: string, languageId: string, version: Version) { - const versions = await queryVersions(extensionGalleryServiceUrl, languageId); - const nextMinor: Version = [version[0], version[1] + 1, 0]; - const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); - const latestCompatibleVersion = compatibleVersions.at(-1)!; // order is newest to oldest - - if (!latestCompatibleVersion) { - throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); - } - - return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); -} - + const versions = await queryVersions(extensionGalleryServiceUrl, languageId); + const nextMinor: Version = [version[0], version[1] + 1, 0]; + const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); + const latestCompatibleVersion = compatibleVersions.at(-1)!; // order is newest to oldest + if (!latestCompatibleVersion) { + throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); + } + return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); +} async function parsePolicies(): Promise { - const parser = new Parser(); - parser.setLanguage(typescript); - - const files = await getFiles(process.cwd()); - const base = path.join(process.cwd(), 'src'); - const policies = []; - - for (const file of files) { - const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); - } - - return policies; -} - + const parser = new Parser(); + parser.setLanguage(typescript); + const files = await getFiles(process.cwd()); + const base = path.join(process.cwd(), 'src'); + const policies = []; + for (const file of files) { + const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); + const contents = await fs.readFile(file, { encoding: 'utf8' }); + const tree = parser.parse(contents); + policies.push(...getPolicies(moduleName, tree.rootNode)); + } + return policies; +} async function getTranslations(): Promise { - const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; - - if (!extensionGalleryServiceUrl) { - console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); - return []; - } - - const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; - - if (!resourceUrlTemplate) { - console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); - return []; - } - - const version = parseVersion(packageJson.version); - const languageIds = Object.keys(Languages); - - return await Promise.all(languageIds.map( - languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) - .then(languageTranslations => ({ languageId, languageTranslations })) - )); -} - + const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; + if (!extensionGalleryServiceUrl) { + console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); + return []; + } + const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; + if (!resourceUrlTemplate) { + console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); + return []; + } + const version = parseVersion(packageJson.version); + const languageIds = Object.keys(Languages); + return await Promise.all(languageIds.map(languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) + .then(languageTranslations => ({ languageId, languageTranslations })))); +} async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const { admx, adml } = await renderGP(policies, translations); - - const root = '.build/policies/win32'; - await fs.rm(root, { recursive: true, force: true }); - await fs.mkdir(root, { recursive: true }); - - await fs.writeFile(path.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); - - for (const { languageId, contents } of adml) { - const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]); - await fs.mkdir(languagePath, { recursive: true }); - await fs.writeFile(path.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); - } -} - + const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); + const { admx, adml } = await renderGP(policies, translations); + const root = '.build/policies/win32'; + await fs.rm(root, { recursive: true, force: true }); + await fs.mkdir(root, { recursive: true }); + await fs.writeFile(path.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); + for (const { languageId, contents } of adml) { + const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]); + await fs.mkdir(languagePath, { recursive: true }); + await fs.writeFile(path.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); + } +} if (require.main === module) { - main().catch(err => { - console.error(err); - process.exit(1); - }); + main().catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/lib/postcss.ts b/build/lib/postcss.ts index cf3121e221e72..53fdfaf78d378 100644 --- a/build/lib/postcss.ts +++ b/build/lib/postcss.ts @@ -5,32 +5,29 @@ import * as postcss from 'postcss'; import * as File from 'vinyl'; import * as es from 'event-stream'; - export function gulpPostcss(plugins: postcss.AcceptedPlugin[], handleError?: (err: Error) => void) { - const instance = postcss(plugins); - - return es.map((file: File, callback: (error?: any, file?: any) => void) => { - if (file.isNull()) { - return callback(null, file); - } - - if (file.isStream()) { - return callback(new Error('Streaming not supported')); - } - - instance - .process(file.contents.toString(), { from: file.path }) - .then((result) => { - file.contents = Buffer.from(result.css); - callback(null, file); - }) - .catch((error) => { - if (handleError) { - handleError(error); - callback(); - } else { - callback(error); - } - }); - }); + const instance = postcss(plugins); + return es.map((file: File, callback: (error?: any, file?: any) => void) => { + if (file.isNull()) { + return callback(null, file); + } + if (file.isStream()) { + return callback(new Error('Streaming not supported')); + } + instance + .process(file.contents.toString(), { from: file.path }) + .then((result) => { + file.contents = Buffer.from(result.css); + callback(null, file); + }) + .catch((error) => { + if (handleError) { + handleError(error); + callback(); + } + else { + callback(error); + } + }); + }); } diff --git a/build/lib/preLaunch.ts b/build/lib/preLaunch.ts index e0ea274458a43..1818dcab941cb 100644 --- a/build/lib/preLaunch.ts +++ b/build/lib/preLaunch.ts @@ -2,62 +2,52 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - // @ts-check - import * as path from 'path'; import { spawn } from 'child_process'; import { promises as fs } from 'fs'; - const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const rootDir = path.resolve(__dirname, '..', '..'); - function runProcess(command: string, args: ReadonlyArray = []) { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { cwd: rootDir, stdio: 'inherit', env: process.env, shell: process.platform === 'win32' }); - child.on('exit', err => !err ? resolve() : process.exit(err ?? 1)); - child.on('error', reject); - }); + return new Promise((resolve, reject) => { + const child = spawn(command, args, { cwd: rootDir, stdio: 'inherit', env: process.env, shell: process.platform === 'win32' }); + child.on('exit', err => !err ? resolve() : process.exit(err ?? 1)); + child.on('error', reject); + }); } - async function exists(subdir: string) { - try { - await fs.stat(path.join(rootDir, subdir)); - return true; - } catch { - return false; - } + try { + await fs.stat(path.join(rootDir, subdir)); + return true; + } + catch { + return false; + } } - async function ensureNodeModules() { - if (!(await exists('node_modules'))) { - await runProcess(npm, ['ci']); - } + if (!(await exists('node_modules'))) { + await runProcess(npm, ['ci']); + } } - async function getElectron() { - await runProcess(npm, ['run', 'electron']); + await runProcess(npm, ['run', 'electron']); } - async function ensureCompiled() { - if (!(await exists('out'))) { - await runProcess(npm, ['run', 'compile']); - } + if (!(await exists('out'))) { + await runProcess(npm, ['run', 'compile']); + } } - async function main() { - await ensureNodeModules(); - await getElectron(); - await ensureCompiled(); - - // Can't require this until after dependencies are installed - const { getBuiltInExtensions } = require('./builtInExtensions'); - await getBuiltInExtensions(); + await ensureNodeModules(); + await getElectron(); + await ensureCompiled(); + // Can't require this until after dependencies are installed + const { getBuiltInExtensions } = require('./builtInExtensions'); + await getBuiltInExtensions(); } - if (require.main === module) { - main().catch(err => { - console.error(err); - process.exit(1); - }); + main().catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index 382e0c7854643..65d937da5359c 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -2,123 +2,100 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import * as fs from 'fs'; import * as path from 'path'; - class ErrorLog { - constructor(public id: string) { - } - allErrors: string[][] = []; - startTime: number | null = null; - count = 0; - - onStart(): void { - if (this.count++ > 0) { - return; - } - - this.startTime = new Date().getTime(); - fancyLog(`Starting ${ansiColors.green('compilation')}${this.id ? ansiColors.blue(` ${this.id}`) : ''}...`); - } - - onEnd(): void { - if (--this.count > 0) { - return; - } - - this.log(); - } - - log(): void { - const errors = this.allErrors.flat(); - const seen = new Set(); - - errors.map(err => { - if (!seen.has(err)) { - seen.add(err); - fancyLog(`${ansiColors.red('Error')}: ${err}`); - } - }); - - fancyLog(`Finished ${ansiColors.green('compilation')}${this.id ? ansiColors.blue(` ${this.id}`) : ''} with ${errors.length} errors after ${ansiColors.magenta((new Date().getTime() - this.startTime!) + ' ms')}`); - - const regex = /^([^(]+)\((\d+),(\d+)\): (.*)$/s; - const messages = errors - .map(err => regex.exec(err)) - .filter(match => !!match) - .map(x => x as string[]) - .map(([, path, line, column, message]) => ({ path, line: parseInt(line), column: parseInt(column), message })); - - try { - const logFileName = 'log' + (this.id ? `_${this.id}` : ''); - fs.writeFileSync(path.join(buildLogFolder, logFileName), JSON.stringify(messages)); - } catch (err) { - //noop - } - } - + constructor(public id: string) { + } + allErrors: string[][] = []; + startTime: number | null = null; + count = 0; + onStart(): void { + if (this.count++ > 0) { + return; + } + this.startTime = new Date().getTime(); + fancyLog(`Starting ${ansiColors.green('compilation')}${this.id ? ansiColors.blue(` ${this.id}`) : ''}...`); + } + onEnd(): void { + if (--this.count > 0) { + return; + } + this.log(); + } + log(): void { + const errors = this.allErrors.flat(); + const seen = new Set(); + errors.map(err => { + if (!seen.has(err)) { + seen.add(err); + fancyLog(`${ansiColors.red('Error')}: ${err}`); + } + }); + fancyLog(`Finished ${ansiColors.green('compilation')}${this.id ? ansiColors.blue(` ${this.id}`) : ''} with ${errors.length} errors after ${ansiColors.magenta((new Date().getTime() - this.startTime!) + ' ms')}`); + const regex = /^([^(]+)\((\d+),(\d+)\): (.*)$/s; + const messages = errors + .map(err => regex.exec(err)) + .filter(match => !!match) + .map(x => x as string[]) + .map(([, path, line, column, message]) => ({ path, line: parseInt(line), column: parseInt(column), message })); + try { + const logFileName = 'log' + (this.id ? `_${this.id}` : ''); + fs.writeFileSync(path.join(buildLogFolder, logFileName), JSON.stringify(messages)); + } + catch (err) { + //noop + } + } } - const errorLogsById = new Map(); function getErrorLog(id: string = '') { - let errorLog = errorLogsById.get(id); - if (!errorLog) { - errorLog = new ErrorLog(id); - errorLogsById.set(id, errorLog); - } - return errorLog; + let errorLog = errorLogsById.get(id); + if (!errorLog) { + errorLog = new ErrorLog(id); + errorLogsById.set(id, errorLog); + } + return errorLog; } - const buildLogFolder = path.join(path.dirname(path.dirname(__dirname)), '.build'); - try { - fs.mkdirSync(buildLogFolder); -} catch (err) { - // ignore + fs.mkdirSync(buildLogFolder); +} +catch (err) { + // ignore } - export interface IReporter { - (err: string): void; - hasErrors(): boolean; - end(emitError: boolean): NodeJS.ReadWriteStream; + (err: string): void; + hasErrors(): boolean; + end(emitError: boolean): NodeJS.ReadWriteStream; } - export function createReporter(id?: string): IReporter { - const errorLog = getErrorLog(id); - - const errors: string[] = []; - errorLog.allErrors.push(errors); - - const result = (err: string) => errors.push(err); - - result.hasErrors = () => errors.length > 0; - - result.end = (emitError: boolean): NodeJS.ReadWriteStream => { - errors.length = 0; - errorLog.onStart(); - - return es.through(undefined, function () { - errorLog.onEnd(); - - if (emitError && errors.length > 0) { - if (!(errors as any).__logged__) { - errorLog.log(); - } - - (errors as any).__logged__ = true; - - const err = new Error(`Found ${errors.length} errors`); - (err as any).__reporter__ = true; - this.emit('error', err); - } else { - this.emit('end'); - } - }); - }; - - return result; + const errorLog = getErrorLog(id); + const errors: string[] = []; + errorLog.allErrors.push(errors); + const result = (err: string) => errors.push(err); + result.hasErrors = () => errors.length > 0; + result.end = (emitError: boolean): NodeJS.ReadWriteStream => { + errors.length = 0; + errorLog.onStart(); + return es.through(undefined, function () { + errorLog.onEnd(); + if (emitError && errors.length > 0) { + if (!(errors as any).__logged__) { + errorLog.log(); + } + (errors as any).__logged__ = true; + const err = new Error(`Found ${errors.length} errors`); + (err as any).__reporter__ = true; + this.emit('error', err); + } + else { + this.emit('end'); + } + }); + }; + return result; } diff --git a/build/lib/snapshotLoader.ts b/build/lib/snapshotLoader.ts index c3d66dba7e124..158b6b390086e 100644 --- a/build/lib/snapshotLoader.ts +++ b/build/lib/snapshotLoader.ts @@ -2,47 +2,36 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - namespace snaps { - - const fs = require('fs'); - const path = require('path'); - const os = require('os'); - const cp = require('child_process'); - - const mksnapshot = path.join(__dirname, `../../node_modules/.bin/${process.platform === 'win32' ? 'mksnapshot.cmd' : 'mksnapshot'}`); - const product = require('../../product.json'); - const arch = (process.argv.join('').match(/--arch=(.*)/) || [])[1]; - - // - let loaderFilepath: string; - let startupBlobFilepath: string; - - switch (process.platform) { - case 'darwin': - loaderFilepath = `VSCode-darwin/${product.nameLong}.app/Contents/Resources/app/out/vs/loader.js`; - startupBlobFilepath = `VSCode-darwin/${product.nameLong}.app/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin`; - break; - - case 'win32': - case 'linux': - loaderFilepath = `VSCode-${process.platform}-${arch}/resources/app/out/vs/loader.js`; - startupBlobFilepath = `VSCode-${process.platform}-${arch}/snapshot_blob.bin`; - break; - - default: - throw new Error('Unknown platform'); - } - - loaderFilepath = path.join(__dirname, '../../../', loaderFilepath); - startupBlobFilepath = path.join(__dirname, '../../../', startupBlobFilepath); - - snapshotLoader(loaderFilepath, startupBlobFilepath); - - function snapshotLoader(loaderFilepath: string, startupBlobFilepath: string): void { - - const inputFile = fs.readFileSync(loaderFilepath); - const wrappedInputFile = ` + const fs = require('fs'); + const path = require('path'); + const os = require('os'); + const cp = require('child_process'); + const mksnapshot = path.join(__dirname, `../../node_modules/.bin/${process.platform === 'win32' ? 'mksnapshot.cmd' : 'mksnapshot'}`); + const product = require('../../product.json'); + const arch = (process.argv.join('').match(/--arch=(.*)/) || [])[1]; + // + let loaderFilepath: string; + let startupBlobFilepath: string; + switch (process.platform) { + case 'darwin': + loaderFilepath = `VSCode-darwin/${product.nameLong}.app/Contents/Resources/app/out/vs/loader.js`; + startupBlobFilepath = `VSCode-darwin/${product.nameLong}.app/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin`; + break; + case 'win32': + case 'linux': + loaderFilepath = `VSCode-${process.platform}-${arch}/resources/app/out/vs/loader.js`; + startupBlobFilepath = `VSCode-${process.platform}-${arch}/snapshot_blob.bin`; + break; + default: + throw new Error('Unknown platform'); + } + loaderFilepath = path.join(__dirname, '../../../', loaderFilepath); + startupBlobFilepath = path.join(__dirname, '../../../', startupBlobFilepath); + snapshotLoader(loaderFilepath, startupBlobFilepath); + function snapshotLoader(loaderFilepath: string, startupBlobFilepath: string): void { + const inputFile = fs.readFileSync(loaderFilepath); + const wrappedInputFile = ` var Monaco_Loader_Init; (function() { var doNotInitLoader = true; @@ -56,10 +45,9 @@ namespace snaps { } })(); `; - const wrappedInputFilepath = path.join(os.tmpdir(), 'wrapped-loader.js'); - console.log(wrappedInputFilepath); - fs.writeFileSync(wrappedInputFilepath, wrappedInputFile); - - cp.execFileSync(mksnapshot, [wrappedInputFilepath, `--startup_blob`, startupBlobFilepath]); - } + const wrappedInputFilepath = path.join(os.tmpdir(), 'wrapped-loader.js'); + console.log(wrappedInputFilepath); + fs.writeFileSync(wrappedInputFilepath, wrappedInputFile); + cp.execFileSync(mksnapshot, [wrappedInputFilepath, `--startup_blob`, startupBlobFilepath]); + } } diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 8736583fb0923..338a3ad498c6a 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -2,315 +2,282 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as tss from './treeshaking'; - const REPO_ROOT = path.join(__dirname, '../../'); const SRC_DIR = path.join(REPO_ROOT, 'src'); - -const dirCache: { [dir: string]: boolean } = {}; - +const dirCache: { + [dir: string]: boolean; +} = {}; function writeFile(filePath: string, contents: Buffer | string): void { - function ensureDirs(dirPath: string): void { - if (dirCache[dirPath]) { - return; - } - dirCache[dirPath] = true; - - ensureDirs(path.dirname(dirPath)); - if (fs.existsSync(dirPath)) { - return; - } - fs.mkdirSync(dirPath); - } - ensureDirs(path.dirname(filePath)); - fs.writeFileSync(filePath, contents); + function ensureDirs(dirPath: string): void { + if (dirCache[dirPath]) { + return; + } + dirCache[dirPath] = true; + ensureDirs(path.dirname(dirPath)); + if (fs.existsSync(dirPath)) { + return; + } + fs.mkdirSync(dirPath); + } + ensureDirs(path.dirname(filePath)); + fs.writeFileSync(filePath, contents); } - -export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string }): void { - const ts = require('typescript') as typeof import('typescript'); - - const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); - let compilerOptions: { [key: string]: any }; - if (tsConfig.extends) { - compilerOptions = Object.assign({}, require(path.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions); - delete tsConfig.extends; - } else { - compilerOptions = tsConfig.compilerOptions; - } - tsConfig.compilerOptions = compilerOptions; - - compilerOptions.noEmit = false; - compilerOptions.noUnusedLocals = false; - compilerOptions.preserveConstEnums = false; - compilerOptions.declaration = false; - compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; - - - options.compilerOptions = compilerOptions; - - console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); - - // Take the extra included .d.ts files from `tsconfig.monaco.json` - options.typings = (tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile)); - - // Add extra .d.ts files from `node_modules/@types/` - if (Array.isArray(options.compilerOptions?.types)) { - options.compilerOptions.types.forEach((type: string) => { - if (type === '@webgpu/types') { - options.typings.push(`../node_modules/${type}/dist/index.d.ts`); - } else { - options.typings.push(`../node_modules/@types/${type}/index.d.ts`); - } - }); - } - - const result = tss.shake(options); - for (const fileName in result) { - if (result.hasOwnProperty(fileName)) { - writeFile(path.join(options.destRoot, fileName), result[fileName]); - } - } - const copied: { [fileName: string]: boolean } = {}; - const copyFile = (fileName: string) => { - if (copied[fileName]) { - return; - } - copied[fileName] = true; - const srcPath = path.join(options.sourcesRoot, fileName); - const dstPath = path.join(options.destRoot, fileName); - writeFile(dstPath, fs.readFileSync(srcPath)); - }; - const writeOutputFile = (fileName: string, contents: string | Buffer) => { - writeFile(path.join(options.destRoot, fileName), contents); - }; - for (const fileName in result) { - if (result.hasOwnProperty(fileName)) { - const fileContents = result[fileName]; - const info = ts.preProcessFile(fileContents); - - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - - let importedFilePath = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) { - importedFilePath = path.join(path.dirname(fileName), importedFilePath); - } - - if (/\.css$/.test(importedFilePath)) { - transportCSS(importedFilePath, copyFile, writeOutputFile); - } else { - const pathToCopy = path.join(options.sourcesRoot, importedFilePath); - if (fs.existsSync(pathToCopy) && !fs.statSync(pathToCopy).isDirectory()) { - copyFile(importedFilePath); - } - } - } - } - } - - delete tsConfig.compilerOptions.moduleResolution; - writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); - - [ - 'vs/loader.js' - ].forEach(copyFile); +export function extractEditor(options: tss.ITreeShakingOptions & { + destRoot: string; +}): void { + const ts = require('typescript') as typeof import('typescript'); + const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); + let compilerOptions: { + [key: string]: any; + }; + if (tsConfig.extends) { + compilerOptions = Object.assign({}, require(path.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions); + delete tsConfig.extends; + } + else { + compilerOptions = tsConfig.compilerOptions; + } + tsConfig.compilerOptions = compilerOptions; + compilerOptions.noEmit = false; + compilerOptions.noUnusedLocals = false; + compilerOptions.preserveConstEnums = false; + compilerOptions.declaration = false; + compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; + options.compilerOptions = compilerOptions; + console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); + // Take the extra included .d.ts files from `tsconfig.monaco.json` + options.typings = (tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile)); + // Add extra .d.ts files from `node_modules/@types/` + if (Array.isArray(options.compilerOptions?.types)) { + options.compilerOptions.types.forEach((type: string) => { + if (type === '@webgpu/types') { + options.typings.push(`../node_modules/${type}/dist/index.d.ts`); + } + else { + options.typings.push(`../node_modules/@types/${type}/index.d.ts`); + } + }); + } + const result = tss.shake(options); + for (const fileName in result) { + if (result.hasOwnProperty(fileName)) { + writeFile(path.join(options.destRoot, fileName), result[fileName]); + } + } + const copied: { + [fileName: string]: boolean; + } = {}; + const copyFile = (fileName: string) => { + if (copied[fileName]) { + return; + } + copied[fileName] = true; + const srcPath = path.join(options.sourcesRoot, fileName); + const dstPath = path.join(options.destRoot, fileName); + writeFile(dstPath, fs.readFileSync(srcPath)); + }; + const writeOutputFile = (fileName: string, contents: string | Buffer) => { + writeFile(path.join(options.destRoot, fileName), contents); + }; + for (const fileName in result) { + if (result.hasOwnProperty(fileName)) { + const fileContents = result[fileName]; + const info = ts.preProcessFile(fileContents); + for (let i = info.importedFiles.length - 1; i >= 0; i--) { + const importedFileName = info.importedFiles[i].fileName; + let importedFilePath = importedFileName; + if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) { + importedFilePath = path.join(path.dirname(fileName), importedFilePath); + } + if (/\.css$/.test(importedFilePath)) { + transportCSS(importedFilePath, copyFile, writeOutputFile); + } + else { + const pathToCopy = path.join(options.sourcesRoot, importedFilePath); + if (fs.existsSync(pathToCopy) && !fs.statSync(pathToCopy).isDirectory()) { + copyFile(importedFilePath); + } + } + } + } + } + delete tsConfig.compilerOptions.moduleResolution; + writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + [ + 'vs/loader.js' + ].forEach(copyFile); } - export interface IOptions2 { - srcFolder: string; - outFolder: string; - outResourcesFolder: string; - ignores: string[]; - renames: { [filename: string]: string }; + srcFolder: string; + outFolder: string; + outResourcesFolder: string; + ignores: string[]; + renames: { + [filename: string]: string; + }; } - export function createESMSourcesAndResources2(options: IOptions2): void { - - const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder); - const OUT_FOLDER = path.join(REPO_ROOT, options.outFolder); - const OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder); - - const getDestAbsoluteFilePath = (file: string): string => { - const dest = options.renames[file.replace(/\\/g, '/')] || file; - if (dest === 'tsconfig.json') { - return path.join(OUT_FOLDER, `tsconfig.json`); - } - if (/\.ts$/.test(dest)) { - return path.join(OUT_FOLDER, dest); - } - return path.join(OUT_RESOURCES_FOLDER, dest); - }; - - const allFiles = walkDirRecursive(SRC_FOLDER); - for (const file of allFiles) { - - if (options.ignores.indexOf(file.replace(/\\/g, '/')) >= 0) { - continue; - } - - if (file === 'tsconfig.json') { - const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString()); - tsConfig.compilerOptions.module = 'es2022'; - tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/'); - write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); - continue; - } - - if (/\.ts$/.test(file) || /\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file) || /\.ttf$/.test(file)) { - // Transport the files directly - write(getDestAbsoluteFilePath(file), fs.readFileSync(path.join(SRC_FOLDER, file))); - continue; - } - - console.log(`UNKNOWN FILE: ${file}`); - } - - - function walkDirRecursive(dir: string): string[] { - if (dir.charAt(dir.length - 1) !== '/' || dir.charAt(dir.length - 1) !== '\\') { - dir += '/'; - } - const result: string[] = []; - _walkDirRecursive(dir, result, dir.length); - return result; - } - - function _walkDirRecursive(dir: string, result: string[], trimPos: number): void { - const files = fs.readdirSync(dir); - for (let i = 0; i < files.length; i++) { - const file = path.join(dir, files[i]); - if (fs.statSync(file).isDirectory()) { - _walkDirRecursive(file, result, trimPos); - } else { - result.push(file.substr(trimPos)); - } - } - } - - function write(absoluteFilePath: string, contents: string | Buffer): void { - if (/(\.ts$)|(\.js$)/.test(absoluteFilePath)) { - contents = toggleComments(contents.toString()); - } - writeFile(absoluteFilePath, contents); - - function toggleComments(fileContents: string): string { - const lines = fileContents.split(/\r\n|\r|\n/); - let mode = 0; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (mode === 0) { - if (/\/\/ ESM-comment-begin/.test(line)) { - mode = 1; - continue; - } - if (/\/\/ ESM-uncomment-begin/.test(line)) { - mode = 2; - continue; - } - continue; - } - - if (mode === 1) { - if (/\/\/ ESM-comment-end/.test(line)) { - mode = 0; - continue; - } - lines[i] = '// ' + line; - continue; - } - - if (mode === 2) { - if (/\/\/ ESM-uncomment-end/.test(line)) { - mode = 0; - continue; - } - lines[i] = line.replace(/^(\s*)\/\/ ?/, function (_, indent) { - return indent; - }); - } - } - - return lines.join('\n'); - } - } + const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder); + const OUT_FOLDER = path.join(REPO_ROOT, options.outFolder); + const OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder); + const getDestAbsoluteFilePath = (file: string): string => { + const dest = options.renames[file.replace(/\\/g, '/')] || file; + if (dest === 'tsconfig.json') { + return path.join(OUT_FOLDER, `tsconfig.json`); + } + if (/\.ts$/.test(dest)) { + return path.join(OUT_FOLDER, dest); + } + return path.join(OUT_RESOURCES_FOLDER, dest); + }; + const allFiles = walkDirRecursive(SRC_FOLDER); + for (const file of allFiles) { + if (options.ignores.indexOf(file.replace(/\\/g, '/')) >= 0) { + continue; + } + if (file === 'tsconfig.json') { + const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString()); + tsConfig.compilerOptions.module = 'es2022'; + tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/'); + write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); + continue; + } + if (/\.ts$/.test(file) || /\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file) || /\.ttf$/.test(file)) { + // Transport the files directly + write(getDestAbsoluteFilePath(file), fs.readFileSync(path.join(SRC_FOLDER, file))); + continue; + } + console.log(`UNKNOWN FILE: ${file}`); + } + function walkDirRecursive(dir: string): string[] { + if (dir.charAt(dir.length - 1) !== '/' || dir.charAt(dir.length - 1) !== '\\') { + dir += '/'; + } + const result: string[] = []; + _walkDirRecursive(dir, result, dir.length); + return result; + } + function _walkDirRecursive(dir: string, result: string[], trimPos: number): void { + const files = fs.readdirSync(dir); + for (let i = 0; i < files.length; i++) { + const file = path.join(dir, files[i]); + if (fs.statSync(file).isDirectory()) { + _walkDirRecursive(file, result, trimPos); + } + else { + result.push(file.substr(trimPos)); + } + } + } + function write(absoluteFilePath: string, contents: string | Buffer): void { + if (/(\.ts$)|(\.js$)/.test(absoluteFilePath)) { + contents = toggleComments(contents.toString()); + } + writeFile(absoluteFilePath, contents); + function toggleComments(fileContents: string): string { + const lines = fileContents.split(/\r\n|\r|\n/); + let mode = 0; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (mode === 0) { + if (/\/\/ ESM-comment-begin/.test(line)) { + mode = 1; + continue; + } + if (/\/\/ ESM-uncomment-begin/.test(line)) { + mode = 2; + continue; + } + continue; + } + if (mode === 1) { + if (/\/\/ ESM-comment-end/.test(line)) { + mode = 0; + continue; + } + lines[i] = '// ' + line; + continue; + } + if (mode === 2) { + if (/\/\/ ESM-uncomment-end/.test(line)) { + mode = 0; + continue; + } + lines[i] = line.replace(/^(\s*)\/\/ ?/, function (_, indent) { + return indent; + }); + } + } + return lines.join('\n'); + } + } } - function transportCSS(module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { - - if (!/\.css/.test(module)) { - return false; - } - - const filename = path.join(SRC_DIR, module); - const fileContents = fs.readFileSync(filename).toString(); - const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148 - - const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); - write(module, newContents); - return true; - - function _rewriteOrInlineUrls(contents: string, forceBase64: boolean): string { - return _replaceURL(contents, (url) => { - const fontMatch = url.match(/^(.*).ttf\?(.*)$/); - if (fontMatch) { - const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter - const fontPath = path.join(path.dirname(module), relativeFontPath); - enqueue(fontPath); - return relativeFontPath; - } - - const imagePath = path.join(path.dirname(module), url); - const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); - const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; - let DATA = ';base64,' + fileContents.toString('base64'); - - if (!forceBase64 && /\.svg$/.test(url)) { - // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris - const newText = fileContents.toString() - .replace(/"/g, '\'') - .replace(//g, '%3E') - .replace(/&/g, '%26') - .replace(/#/g, '%23') - .replace(/\s+/g, ' '); - const encodedData = ',' + newText; - if (encodedData.length < DATA.length) { - DATA = encodedData; - } - } - return '"data:' + MIME + DATA + '"'; - }); - } - - function _replaceURL(contents: string, replacer: (url: string) => string): string { - // Use ")" as the terminator as quotes are oftentimes not used at all - return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_: string, ...matches: string[]) => { - let url = matches[0]; - // Eliminate starting quotes (the initial whitespace is not captured) - if (url.charAt(0) === '"' || url.charAt(0) === '\'') { - url = url.substring(1); - } - // The ending whitespace is captured - while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { - url = url.substring(0, url.length - 1); - } - // Eliminate ending quotes - if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { - url = url.substring(0, url.length - 1); - } - - if (!_startsWith(url, 'data:') && !_startsWith(url, 'http://') && !_startsWith(url, 'https://')) { - url = replacer(url); - } - - return 'url(' + url + ')'; - }); - } - - function _startsWith(haystack: string, needle: string): boolean { - return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; - } + if (!/\.css/.test(module)) { + return false; + } + const filename = path.join(SRC_DIR, module); + const fileContents = fs.readFileSync(filename).toString(); + const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148 + const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); + write(module, newContents); + return true; + function _rewriteOrInlineUrls(contents: string, forceBase64: boolean): string { + return _replaceURL(contents, (url) => { + const fontMatch = url.match(/^(.*).ttf\?(.*)$/); + if (fontMatch) { + const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter + const fontPath = path.join(path.dirname(module), relativeFontPath); + enqueue(fontPath); + return relativeFontPath; + } + const imagePath = path.join(path.dirname(module), url); + const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); + const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; + let DATA = ';base64,' + fileContents.toString('base64'); + if (!forceBase64 && /\.svg$/.test(url)) { + // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris + const newText = fileContents.toString() + .replace(/"/g, '\'') + .replace(//g, '%3E') + .replace(/&/g, '%26') + .replace(/#/g, '%23') + .replace(/\s+/g, ' '); + const encodedData = ',' + newText; + if (encodedData.length < DATA.length) { + DATA = encodedData; + } + } + return '"data:' + MIME + DATA + '"'; + }); + } + function _replaceURL(contents: string, replacer: (url: string) => string): string { + // Use ")" as the terminator as quotes are oftentimes not used at all + return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_: string, ...matches: string[]) => { + let url = matches[0]; + // Eliminate starting quotes (the initial whitespace is not captured) + if (url.charAt(0) === '"' || url.charAt(0) === '\'') { + url = url.substring(1); + } + // The ending whitespace is captured + while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { + url = url.substring(0, url.length - 1); + } + // Eliminate ending quotes + if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { + url = url.substring(0, url.length - 1); + } + if (!_startsWith(url, 'data:') && !_startsWith(url, 'http://') && !_startsWith(url, 'https://')) { + url = replacer(url); + } + return 'url(' + url + ')'; + }); + } + function _startsWith(haystack: string, needle: string): boolean { + return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; + } } diff --git a/build/lib/stats.ts b/build/lib/stats.ts index fe4b22453b501..a326ebb7affa8 100644 --- a/build/lib/stats.ts +++ b/build/lib/stats.ts @@ -2,71 +2,65 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import * as File from 'vinyl'; - class Entry { - constructor(readonly name: string, public totalCount: number, public totalSize: number) { } - - toString(pretty?: boolean): string { - if (!pretty) { - if (this.totalCount === 1) { - return `${this.name}: ${this.totalSize} bytes`; - } else { - return `${this.name}: ${this.totalCount} files with ${this.totalSize} bytes`; - } - } else { - if (this.totalCount === 1) { - return `Stats for '${ansiColors.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; - - } else { - const count = this.totalCount < 100 - ? ansiColors.green(this.totalCount.toString()) - : ansiColors.red(this.totalCount.toString()); - - return `Stats for '${ansiColors.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; - } - } - } + constructor(readonly name: string, public totalCount: number, public totalSize: number) { } + toString(pretty?: boolean): string { + if (!pretty) { + if (this.totalCount === 1) { + return `${this.name}: ${this.totalSize} bytes`; + } + else { + return `${this.name}: ${this.totalCount} files with ${this.totalSize} bytes`; + } + } + else { + if (this.totalCount === 1) { + return `Stats for '${ansiColors.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; + } + else { + const count = this.totalCount < 100 + ? ansiColors.green(this.totalCount.toString()) + : ansiColors.red(this.totalCount.toString()); + return `Stats for '${ansiColors.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; + } + } + } } - const _entries = new Map(); - export function createStatsStream(group: string, log?: boolean): es.ThroughStream { - - const entry = new Entry(group, 0, 0); - _entries.set(entry.name, entry); - - return es.through(function (data) { - const file = data as File; - if (typeof file.path === 'string') { - entry.totalCount += 1; - if (Buffer.isBuffer(file.contents)) { - entry.totalSize += file.contents.length; - } else if (file.stat && typeof file.stat.size === 'number') { - entry.totalSize += file.stat.size; - } else { - // funky file... - } - } - this.emit('data', data); - }, function () { - if (log) { - if (entry.totalCount === 1) { - fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); - - } else { - const count = entry.totalCount < 100 - ? ansiColors.green(entry.totalCount.toString()) - : ansiColors.red(entry.totalCount.toString()); - - fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); - } - } - - this.emit('end'); - }); + const entry = new Entry(group, 0, 0); + _entries.set(entry.name, entry); + return es.through(function (data) { + const file = data as File; + if (typeof file.path === 'string') { + entry.totalCount += 1; + if (Buffer.isBuffer(file.contents)) { + entry.totalSize += file.contents.length; + } + else if (file.stat && typeof file.stat.size === 'number') { + entry.totalSize += file.stat.size; + } + else { + // funky file... + } + } + this.emit('data', data); + }, function () { + if (log) { + if (entry.totalCount === 1) { + fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); + } + else { + const count = entry.totalCount < 100 + ? ansiColors.green(entry.totalCount.toString()) + : ansiColors.red(entry.totalCount.toString()); + fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); + } + } + this.emit('end'); + }); } diff --git a/build/lib/stylelint/validateVariableNames.ts b/build/lib/stylelint/validateVariableNames.ts index 6d9fa8a7cef5a..db81db7c4876c 100644 --- a/build/lib/stylelint/validateVariableNames.ts +++ b/build/lib/stylelint/validateVariableNames.ts @@ -2,39 +2,32 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { readFileSync } from 'fs'; import path = require('path'); - const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; - let knownVariables: Set | undefined; function getKnownVariableNames() { - if (!knownVariables) { - const knownVariablesFileContent = readFileSync(path.join(__dirname, './vscode-known-variables.json'), 'utf8').toString(); - const knownVariablesInfo = JSON.parse(knownVariablesFileContent); - knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others] as string[]); - } - return knownVariables; + if (!knownVariables) { + const knownVariablesFileContent = readFileSync(path.join(__dirname, './vscode-known-variables.json'), 'utf8').toString(); + const knownVariablesInfo = JSON.parse(knownVariablesFileContent); + knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others] as string[]); + } + return knownVariables; } - const iconVariable = /^--vscode-icon-.+-(content|font-family)$/; - export interface IValidator { - (value: string, report: (message: string) => void): void; + (value: string, report: (message: string) => void): void; } - export function getVariableNameValidator(): IValidator { - const allVariables = getKnownVariableNames(); - return (value: string, report: (unknwnVariable: string) => void) => { - RE_VAR_PROP.lastIndex = 0; // reset lastIndex just to be sure - let match; - while (match = RE_VAR_PROP.exec(value)) { - const variableName = match[1]; - if (variableName && !allVariables.has(variableName) && !iconVariable.test(variableName)) { - report(variableName); - } - } - }; + const allVariables = getKnownVariableNames(); + return (value: string, report: (unknwnVariable: string) => void) => { + RE_VAR_PROP.lastIndex = 0; // reset lastIndex just to be sure + let match; + while (match = RE_VAR_PROP.exec(value)) { + const variableName = match[1]; + if (variableName && !allVariables.has(variableName) && !iconVariable.test(variableName)) { + report(variableName); + } + } + }; } - diff --git a/build/lib/task.ts b/build/lib/task.ts index 7d2a4dee0169d..363b379363e3e 100644 --- a/build/lib/task.ts +++ b/build/lib/task.ts @@ -2,122 +2,105 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; - export interface BaseTask { - displayName?: string; - taskName?: string; - _tasks?: Task[]; + displayName?: string; + taskName?: string; + _tasks?: Task[]; } export interface PromiseTask extends BaseTask { - (): Promise; + (): Promise; } export interface StreamTask extends BaseTask { - (): NodeJS.ReadWriteStream; + (): NodeJS.ReadWriteStream; } export interface CallbackTask extends BaseTask { - (cb?: (err?: any) => void): void; + (cb?: (err?: any) => void): void; } - export type Task = PromiseTask | StreamTask | CallbackTask; - function _isPromise(p: Promise | NodeJS.ReadWriteStream): p is Promise { - if (typeof (p).then === 'function') { - return true; - } - return false; + if (typeof (p).then === 'function') { + return true; + } + return false; } - function _renderTime(time: number): string { - return `${Math.round(time)} ms`; + return `${Math.round(time)} ms`; } - async function _execute(task: Task): Promise { - const name = task.taskName || task.displayName || ``; - if (!task._tasks) { - fancyLog('Starting', ansiColors.cyan(name), '...'); - } - const startTime = process.hrtime(); - await _doExecute(task); - const elapsedArr = process.hrtime(startTime); - const elapsedNanoseconds = (elapsedArr[0] * 1e9 + elapsedArr[1]); - if (!task._tasks) { - fancyLog(`Finished`, ansiColors.cyan(name), 'after', ansiColors.magenta(_renderTime(elapsedNanoseconds / 1e6))); - } + const name = task.taskName || task.displayName || ``; + if (!task._tasks) { + fancyLog('Starting', ansiColors.cyan(name), '...'); + } + const startTime = process.hrtime(); + await _doExecute(task); + const elapsedArr = process.hrtime(startTime); + const elapsedNanoseconds = (elapsedArr[0] * 1e9 + elapsedArr[1]); + if (!task._tasks) { + fancyLog(`Finished`, ansiColors.cyan(name), 'after', ansiColors.magenta(_renderTime(elapsedNanoseconds / 1e6))); + } } - async function _doExecute(task: Task): Promise { - // Always invoke as if it were a callback task - return new Promise((resolve, reject) => { - if (task.length === 1) { - // this is a callback task - task((err) => { - if (err) { - return reject(err); - } - resolve(); - }); - return; - } - - const taskResult = task(); - - if (typeof taskResult === 'undefined') { - // this is a sync task - resolve(); - return; - } - - if (_isPromise(taskResult)) { - // this is a promise returning task - taskResult.then(resolve, reject); - return; - } - - // this is a stream returning task - taskResult.on('end', _ => resolve()); - taskResult.on('error', err => reject(err)); - }); + // Always invoke as if it were a callback task + return new Promise((resolve, reject) => { + if (task.length === 1) { + // this is a callback task + task((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + return; + } + const taskResult = task(); + if (typeof taskResult === 'undefined') { + // this is a sync task + resolve(); + return; + } + if (_isPromise(taskResult)) { + // this is a promise returning task + taskResult.then(resolve, reject); + return; + } + // this is a stream returning task + taskResult.on('end', _ => resolve()); + taskResult.on('error', err => reject(err)); + }); } - export function series(...tasks: Task[]): PromiseTask { - const result = async () => { - for (let i = 0; i < tasks.length; i++) { - await _execute(tasks[i]); - } - }; - result._tasks = tasks; - return result; + const result = async () => { + for (let i = 0; i < tasks.length; i++) { + await _execute(tasks[i]); + } + }; + result._tasks = tasks; + return result; } - export function parallel(...tasks: Task[]): PromiseTask { - const result = async () => { - await Promise.all(tasks.map(t => _execute(t))); - }; - result._tasks = tasks; - return result; + const result = async () => { + await Promise.all(tasks.map(t => _execute(t))); + }; + result._tasks = tasks; + return result; } - export function define(name: string, task: Task): Task { - if (task._tasks) { - // This is a composite task - const lastTask = task._tasks[task._tasks.length - 1]; - - if (lastTask._tasks || lastTask.taskName) { - // This is a composite task without a real task function - // => generate a fake task function - return define(name, series(task, () => Promise.resolve())); - } - - lastTask.taskName = name; - task.displayName = name; - return task; - } - - // This is a simple task - task.taskName = name; - task.displayName = name; - return task; + if (task._tasks) { + // This is a composite task + const lastTask = task._tasks[task._tasks.length - 1]; + if (lastTask._tasks || lastTask.taskName) { + // This is a composite task without a real task function + // => generate a fake task function + return define(name, series(task, () => Promise.resolve())); + } + lastTask.taskName = name; + task.displayName = name; + return task; + } + // This is a simple task + task.taskName = name; + task.displayName = name; + return task; } diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index cd17c5f027842..ee04a32975619 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -2,1068 +2,951 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import type * as ts from 'typescript'; - const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); - export const enum ShakeLevel { - Files = 0, - InnerFile = 1, - ClassMembers = 2 + Files = 0, + InnerFile = 1, + ClassMembers = 2 } - export function toStringShakeLevel(shakeLevel: ShakeLevel): string { - switch (shakeLevel) { - case ShakeLevel.Files: - return 'Files (0)'; - case ShakeLevel.InnerFile: - return 'InnerFile (1)'; - case ShakeLevel.ClassMembers: - return 'ClassMembers (2)'; - } + switch (shakeLevel) { + case ShakeLevel.Files: + return 'Files (0)'; + case ShakeLevel.InnerFile: + return 'InnerFile (1)'; + case ShakeLevel.ClassMembers: + return 'ClassMembers (2)'; + } } - export interface ITreeShakingOptions { - /** - * The full path to the root where sources are. - */ - sourcesRoot: string; - /** - * Module ids. - * e.g. `vs/editor/editor.main` or `index` - */ - entryPoints: string[]; - /** - * Inline usages. - */ - inlineEntryPoints: string[]; - /** - * Other .d.ts files - */ - typings: string[]; - /** - * TypeScript compiler options. - */ - compilerOptions?: any; - /** - * The shake level to perform. - */ - shakeLevel: ShakeLevel; - /** - * regex pattern to ignore certain imports e.g. `.css` imports - */ - importIgnorePattern: RegExp; - - redirects: { [module: string]: string }; + /** + * The full path to the root where sources are. + */ + sourcesRoot: string; + /** + * Module ids. + * e.g. `vs/editor/editor.main` or `index` + */ + entryPoints: string[]; + /** + * Inline usages. + */ + inlineEntryPoints: string[]; + /** + * Other .d.ts files + */ + typings: string[]; + /** + * TypeScript compiler options. + */ + compilerOptions?: any; + /** + * The shake level to perform. + */ + shakeLevel: ShakeLevel; + /** + * regex pattern to ignore certain imports e.g. `.css` imports + */ + importIgnorePattern: RegExp; + redirects: { + [module: string]: string; + }; } - export interface ITreeShakingResult { - [file: string]: string; + [file: string]: string; } - function printDiagnostics(options: ITreeShakingOptions, diagnostics: ReadonlyArray): void { - for (const diag of diagnostics) { - let result = ''; - if (diag.file) { - result += `${path.join(options.sourcesRoot, diag.file.fileName)}`; - } - if (diag.file && diag.start) { - const location = diag.file.getLineAndCharacterOfPosition(diag.start); - result += `:${location.line + 1}:${location.character}`; - } - result += ` - ` + JSON.stringify(diag.messageText); - console.log(result); - } + for (const diag of diagnostics) { + let result = ''; + if (diag.file) { + result += `${path.join(options.sourcesRoot, diag.file.fileName)}`; + } + if (diag.file && diag.start) { + const location = diag.file.getLineAndCharacterOfPosition(diag.start); + result += `:${location.line + 1}:${location.character}`; + } + result += ` - ` + JSON.stringify(diag.messageText); + console.log(result); + } } - export function shake(options: ITreeShakingOptions): ITreeShakingResult { - const ts = require('typescript') as typeof import('typescript'); - const languageService = createTypeScriptLanguageService(ts, options); - const program = languageService.getProgram()!; - - const globalDiagnostics = program.getGlobalDiagnostics(); - if (globalDiagnostics.length > 0) { - printDiagnostics(options, globalDiagnostics); - throw new Error(`Compilation Errors encountered.`); - } - - const syntacticDiagnostics = program.getSyntacticDiagnostics(); - if (syntacticDiagnostics.length > 0) { - printDiagnostics(options, syntacticDiagnostics); - throw new Error(`Compilation Errors encountered.`); - } - - const semanticDiagnostics = program.getSemanticDiagnostics(); - if (semanticDiagnostics.length > 0) { - printDiagnostics(options, semanticDiagnostics); - throw new Error(`Compilation Errors encountered.`); - } - - markNodes(ts, languageService, options); - - return generateResult(ts, languageService, options.shakeLevel); + const ts = require('typescript') as typeof import('typescript'); + const languageService = createTypeScriptLanguageService(ts, options); + const program = languageService.getProgram()!; + const globalDiagnostics = program.getGlobalDiagnostics(); + if (globalDiagnostics.length > 0) { + printDiagnostics(options, globalDiagnostics); + throw new Error(`Compilation Errors encountered.`); + } + const syntacticDiagnostics = program.getSyntacticDiagnostics(); + if (syntacticDiagnostics.length > 0) { + printDiagnostics(options, syntacticDiagnostics); + throw new Error(`Compilation Errors encountered.`); + } + const semanticDiagnostics = program.getSemanticDiagnostics(); + if (semanticDiagnostics.length > 0) { + printDiagnostics(options, semanticDiagnostics); + throw new Error(`Compilation Errors encountered.`); + } + markNodes(ts, languageService, options); + return generateResult(ts, languageService, options.shakeLevel); } - //#region Discovery, LanguageService & Setup function createTypeScriptLanguageService(ts: typeof import('typescript'), options: ITreeShakingOptions): ts.LanguageService { - // Discover referenced files - const FILES = discoverAndReadFiles(ts, options); - - // Add fake usage files - options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint; - }); - - // Add additional typings - options.typings.forEach((typing) => { - const filePath = path.join(options.sourcesRoot, typing); - FILES[typing] = fs.readFileSync(filePath).toString(); - }); - - // Resolve libs - const RESOLVED_LIBS = processLibFiles(ts, options); - - const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - - const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions); - return ts.createLanguageService(host); + // Discover referenced files + const FILES = discoverAndReadFiles(ts, options); + // Add fake usage files + options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { + FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint; + }); + // Add additional typings + options.typings.forEach((typing) => { + const filePath = path.join(options.sourcesRoot, typing); + FILES[typing] = fs.readFileSync(filePath).toString(); + }); + // Resolve libs + const RESOLVED_LIBS = processLibFiles(ts, options); + const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; + const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions); + return ts.createLanguageService(host); } - /** * Read imports and follow them until all files have been handled */ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): IFileMap { - const FILES: IFileMap = {}; - - const in_queue: { [module: string]: boolean } = Object.create(null); - const queue: string[] = []; - - const enqueue = (moduleId: string) => { - // To make the treeshaker work on windows... - moduleId = moduleId.replace(/\\/g, '/'); - if (in_queue[moduleId]) { - return; - } - in_queue[moduleId] = true; - queue.push(moduleId); - }; - - options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); - - while (queue.length > 0) { - const moduleId = queue.shift()!; - let redirectedModuleId: string = moduleId; - if (options.redirects[moduleId]) { - redirectedModuleId = options.redirects[moduleId]; - } - - const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); - if (fs.existsSync(dts_filename)) { - const dts_filecontents = fs.readFileSync(dts_filename).toString(); - FILES[`${moduleId}.d.ts`] = dts_filecontents; - continue; - } - - - const js_filename = path.join(options.sourcesRoot, redirectedModuleId + '.js'); - if (fs.existsSync(js_filename)) { - // This is an import for a .js file, so ignore it... - continue; - } - - const ts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.ts'); - - const ts_filecontents = fs.readFileSync(ts_filename).toString(); - const info = ts.preProcessFile(ts_filecontents); - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - - if (options.importIgnorePattern.test(importedFileName)) { - // Ignore *.css imports - continue; - } - - let importedModuleId = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { - importedModuleId = path.join(path.dirname(moduleId), importedModuleId); - if (importedModuleId.endsWith('.js')) { // ESM: code imports require to be relative and have a '.js' file extension - importedModuleId = importedModuleId.substr(0, importedModuleId.length - 3); - } - } - enqueue(importedModuleId); - } - - FILES[`${moduleId}.ts`] = ts_filecontents; - } - - return FILES; + const FILES: IFileMap = {}; + const in_queue: { + [module: string]: boolean; + } = Object.create(null); + const queue: string[] = []; + const enqueue = (moduleId: string) => { + // To make the treeshaker work on windows... + moduleId = moduleId.replace(/\\/g, '/'); + if (in_queue[moduleId]) { + return; + } + in_queue[moduleId] = true; + queue.push(moduleId); + }; + options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); + while (queue.length > 0) { + const moduleId = queue.shift()!; + let redirectedModuleId: string = moduleId; + if (options.redirects[moduleId]) { + redirectedModuleId = options.redirects[moduleId]; + } + const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); + if (fs.existsSync(dts_filename)) { + const dts_filecontents = fs.readFileSync(dts_filename).toString(); + FILES[`${moduleId}.d.ts`] = dts_filecontents; + continue; + } + const js_filename = path.join(options.sourcesRoot, redirectedModuleId + '.js'); + if (fs.existsSync(js_filename)) { + // This is an import for a .js file, so ignore it... + continue; + } + const ts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.ts'); + const ts_filecontents = fs.readFileSync(ts_filename).toString(); + const info = ts.preProcessFile(ts_filecontents); + for (let i = info.importedFiles.length - 1; i >= 0; i--) { + const importedFileName = info.importedFiles[i].fileName; + if (options.importIgnorePattern.test(importedFileName)) { + // Ignore *.css imports + continue; + } + let importedModuleId = importedFileName; + if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { + importedModuleId = path.join(path.dirname(moduleId), importedModuleId); + if (importedModuleId.endsWith('.js')) { // ESM: code imports require to be relative and have a '.js' file extension + importedModuleId = importedModuleId.substr(0, importedModuleId.length - 3); + } + } + enqueue(importedModuleId); + } + FILES[`${moduleId}.ts`] = ts_filecontents; + } + return FILES; } - /** * Read lib files and follow lib references */ function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): ILibMap { - - const stack: string[] = [...options.compilerOptions.lib]; - const result: ILibMap = {}; - - while (stack.length > 0) { - const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; - const key = `defaultLib:${filename}`; - if (!result[key]) { - // add this file - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - const sourceText = fs.readFileSync(filepath).toString(); - result[key] = sourceText; - - // precess dependencies and "recurse" - const info = ts.preProcessFile(sourceText); - for (const ref of info.libReferenceDirectives) { - stack.push(ref.fileName); - } - } - } - - return result; + const stack: string[] = [...options.compilerOptions.lib]; + const result: ILibMap = {}; + while (stack.length > 0) { + const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; + const key = `defaultLib:${filename}`; + if (!result[key]) { + // add this file + const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + const sourceText = fs.readFileSync(filepath).toString(); + result[key] = sourceText; + // precess dependencies and "recurse" + const info = ts.preProcessFile(sourceText); + for (const ref of info.libReferenceDirectives) { + stack.push(ref.fileName); + } + } + } + return result; +} +interface ILibMap { + [libName: string]: string; +} +interface IFileMap { + [fileName: string]: string; } - -interface ILibMap { [libName: string]: string } -interface IFileMap { [fileName: string]: string } - /** * A TypeScript language service host */ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _ts: typeof import('typescript'); - private readonly _libs: ILibMap; - private readonly _files: IFileMap; - private readonly _compilerOptions: ts.CompilerOptions; - - constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - - // --- language service host --------------- - - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - getScriptFileNames(): string[] { - return ( - ([] as string[]) - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files)) - ); - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); - } else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName: string): ts.ScriptKind { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory(): string { - return ''; - } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return 'defaultLib:lib.d.ts'; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path: string, _encoding?: string): string | undefined { - return this._files[path] || this._libs[path]; - } - fileExists(path: string): boolean { - return path in this._files || path in this._libs; - } + private readonly _ts: typeof import('typescript'); + private readonly _libs: ILibMap; + private readonly _files: IFileMap; + private readonly _compilerOptions: ts.CompilerOptions; + constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { + this._ts = ts; + this._libs = libs; + this._files = files; + this._compilerOptions = compilerOptions; + } + // --- language service host --------------- + getCompilationSettings(): ts.CompilerOptions { + return this._compilerOptions; + } + getScriptFileNames(): string[] { + return (([] as string[]) + .concat(Object.keys(this._libs)) + .concat(Object.keys(this._files))); + } + getScriptVersion(_fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + if (this._files.hasOwnProperty(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._files[fileName]); + } + else if (this._libs.hasOwnProperty(fileName)) { + return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); + } + else { + return this._ts.ScriptSnapshot.fromString(''); + } + } + getScriptKind(_fileName: string): ts.ScriptKind { + return this._ts.ScriptKind.TS; + } + getCurrentDirectory(): string { + return ''; + } + getDefaultLibFileName(_options: ts.CompilerOptions): string { + return 'defaultLib:lib.d.ts'; + } + isDefaultLibFileName(fileName: string): boolean { + return fileName === this.getDefaultLibFileName(this._compilerOptions); + } + readFile(path: string, _encoding?: string): string | undefined { + return this._files[path] || this._libs[path]; + } + fileExists(path: string): boolean { + return path in this._files || path in this._libs; + } } //#endregion - //#region Tree Shaking - const enum NodeColor { - White = 0, - Gray = 1, - Black = 2 + White = 0, + Gray = 1, + Black = 2 } - function getColor(node: ts.Node): NodeColor { - return (node).$$$color || NodeColor.White; + return (node).$$$color || NodeColor.White; } function setColor(node: ts.Node, color: NodeColor): void { - (node).$$$color = color; + (node).$$$color = color; } function markNeededSourceFile(node: ts.SourceFile): void { - (node).$$$neededSourceFile = true; + (node).$$$neededSourceFile = true; } function isNeededSourceFile(node: ts.SourceFile): boolean { - return Boolean((node).$$$neededSourceFile); + return Boolean((node).$$$neededSourceFile); } function nodeOrParentIsBlack(node: ts.Node): boolean { - while (node) { - const color = getColor(node); - if (color === NodeColor.Black) { - return true; - } - node = node.parent; - } - return false; + while (node) { + const color = getColor(node); + if (color === NodeColor.Black) { + return true; + } + node = node.parent; + } + return false; } function nodeOrChildIsBlack(node: ts.Node): boolean { - if (getColor(node) === NodeColor.Black) { - return true; - } - for (const child of node.getChildren()) { - if (nodeOrChildIsBlack(child)) { - return true; - } - } - return false; + if (getColor(node) === NodeColor.Black) { + return true; + } + for (const child of node.getChildren()) { + if (nodeOrChildIsBlack(child)) { + return true; + } + } + return false; } - -function isSymbolWithDeclarations(symbol: ts.Symbol | undefined | null): symbol is ts.Symbol & { declarations: ts.Declaration[] } { - return !!(symbol && symbol.declarations); +function isSymbolWithDeclarations(symbol: ts.Symbol | undefined | null): symbol is ts.Symbol & { + declarations: ts.Declaration[]; +} { + return !!(symbol && symbol.declarations); } - function isVariableStatementWithSideEffects(ts: typeof import('typescript'), node: ts.Node): boolean { - if (!ts.isVariableStatement(node)) { - return false; - } - let hasSideEffects = false; - const visitNode = (node: ts.Node) => { - if (hasSideEffects) { - // no need to go on - return; - } - if (ts.isCallExpression(node) || ts.isNewExpression(node)) { - // TODO: assuming `createDecorator` and `refineServiceDecorator` calls are side-effect free - const isSideEffectFree = /(createDecorator|refineServiceDecorator)/.test(node.expression.getText()); - if (!isSideEffectFree) { - hasSideEffects = true; - } - } - node.forEachChild(visitNode); - }; - node.forEachChild(visitNode); - return hasSideEffects; + if (!ts.isVariableStatement(node)) { + return false; + } + let hasSideEffects = false; + const visitNode = (node: ts.Node) => { + if (hasSideEffects) { + // no need to go on + return; + } + if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + // TODO: assuming `createDecorator` and `refineServiceDecorator` calls are side-effect free + const isSideEffectFree = /(createDecorator|refineServiceDecorator)/.test(node.expression.getText()); + if (!isSideEffectFree) { + hasSideEffects = true; + } + } + node.forEachChild(visitNode); + }; + node.forEachChild(visitNode); + return hasSideEffects; } - function isStaticMemberWithSideEffects(ts: typeof import('typescript'), node: ts.ClassElement | ts.TypeElement): boolean { - if (!ts.isPropertyDeclaration(node)) { - return false; - } - if (!node.modifiers) { - return false; - } - if (!node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) { - return false; - } - let hasSideEffects = false; - const visitNode = (node: ts.Node) => { - if (hasSideEffects) { - // no need to go on - return; - } - if (ts.isCallExpression(node) || ts.isNewExpression(node)) { - hasSideEffects = true; - } - node.forEachChild(visitNode); - }; - node.forEachChild(visitNode); - return hasSideEffects; + if (!ts.isPropertyDeclaration(node)) { + return false; + } + if (!node.modifiers) { + return false; + } + if (!node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) { + return false; + } + let hasSideEffects = false; + const visitNode = (node: ts.Node) => { + if (hasSideEffects) { + // no need to go on + return; + } + if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + hasSideEffects = true; + } + node.forEachChild(visitNode); + }; + node.forEachChild(visitNode); + return hasSideEffects; } - function markNodes(ts: typeof import('typescript'), languageService: ts.LanguageService, options: ITreeShakingOptions) { - const program = languageService.getProgram(); - if (!program) { - throw new Error('Could not get program from language service'); - } - - if (options.shakeLevel === ShakeLevel.Files) { - // Mark all source files Black - program.getSourceFiles().forEach((sourceFile) => { - setColor(sourceFile, NodeColor.Black); - }); - return; - } - - const black_queue: ts.Node[] = []; - const gray_queue: ts.Node[] = []; - const export_import_queue: ts.Node[] = []; - const sourceFilesLoaded: { [fileName: string]: boolean } = {}; - - function enqueueTopLevelModuleStatements(sourceFile: ts.SourceFile): void { - - sourceFile.forEachChild((node: ts.Node) => { - - if (ts.isImportDeclaration(node)) { - if (!node.importClause && ts.isStringLiteral(node.moduleSpecifier)) { - setColor(node, NodeColor.Black); - enqueueImport(node, node.moduleSpecifier.text); - } - return; - } - - if (ts.isExportDeclaration(node)) { - if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { - // export * from "foo"; - setColor(node, NodeColor.Black); - enqueueImport(node, node.moduleSpecifier.text); - } - if (node.exportClause && ts.isNamedExports(node.exportClause)) { - for (const exportSpecifier of node.exportClause.elements) { - export_import_queue.push(exportSpecifier); - } - } - return; - } - - if (isVariableStatementWithSideEffects(ts, node)) { - enqueue_black(node); - } - - if ( - ts.isExpressionStatement(node) - || ts.isIfStatement(node) - || ts.isIterationStatement(node, true) - || ts.isExportAssignment(node) - ) { - enqueue_black(node); - } - - if (ts.isImportEqualsDeclaration(node)) { - if (/export/.test(node.getFullText(sourceFile))) { - // e.g. "export import Severity = BaseSeverity;" - enqueue_black(node); - } - } - - }); - } - - /** - * Return the parent of `node` which is an ImportDeclaration - */ - function findParentImportDeclaration(node: ts.Declaration): ts.ImportDeclaration | null { - let _node: ts.Node = node; - do { - if (ts.isImportDeclaration(_node)) { - return _node; - } - _node = _node.parent; - } while (_node); - return null; - } - - function enqueue_gray(node: ts.Node): void { - if (nodeOrParentIsBlack(node) || getColor(node) === NodeColor.Gray) { - return; - } - setColor(node, NodeColor.Gray); - gray_queue.push(node); - } - - function enqueue_black(node: ts.Node): void { - const previousColor = getColor(node); - - if (previousColor === NodeColor.Black) { - return; - } - - if (previousColor === NodeColor.Gray) { - // remove from gray queue - gray_queue.splice(gray_queue.indexOf(node), 1); - setColor(node, NodeColor.White); - - // add to black queue - enqueue_black(node); - - // move from one queue to the other - // black_queue.push(node); - // setColor(node, NodeColor.Black); - return; - } - - if (nodeOrParentIsBlack(node)) { - return; - } - - const fileName = node.getSourceFile().fileName; - if (/^defaultLib:/.test(fileName) || /\.d\.ts$/.test(fileName)) { - setColor(node, NodeColor.Black); - return; - } - - const sourceFile = node.getSourceFile(); - if (!sourceFilesLoaded[sourceFile.fileName]) { - sourceFilesLoaded[sourceFile.fileName] = true; - enqueueTopLevelModuleStatements(sourceFile); - } - - if (ts.isSourceFile(node)) { - return; - } - - setColor(node, NodeColor.Black); - black_queue.push(node); - - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { - const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); - if (references) { - for (let i = 0, len = references.length; i < len; i++) { - const reference = references[i]; - const referenceSourceFile = program!.getSourceFile(reference.fileName); - if (!referenceSourceFile) { - continue; - } - - const referenceNode = getTokenAtPosition(ts, referenceSourceFile, reference.textSpan.start, false, false); - if ( - ts.isMethodDeclaration(referenceNode.parent) - || ts.isPropertyDeclaration(referenceNode.parent) - || ts.isGetAccessor(referenceNode.parent) - || ts.isSetAccessor(referenceNode.parent) - ) { - enqueue_gray(referenceNode.parent); - } - } - } - } - } - - function enqueueFile(filename: string): void { - const sourceFile = program!.getSourceFile(filename); - if (!sourceFile) { - console.warn(`Cannot find source file ${filename}`); - return; - } - // This source file should survive even if it is empty - markNeededSourceFile(sourceFile); - enqueue_black(sourceFile); - } - - function enqueueImport(node: ts.Node, importText: string): void { - if (options.importIgnorePattern.test(importText)) { - // this import should be ignored - return; - } - - const nodeSourceFile = node.getSourceFile(); - let fullPath: string; - if (/(^\.\/)|(^\.\.\/)/.test(importText)) { - if (importText.endsWith('.js')) { // ESM: code imports require to be relative and to have a '.js' file extension - importText = importText.substr(0, importText.length - 3); - } - fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts'; - } else { - fullPath = importText + '.ts'; - } - enqueueFile(fullPath); - } - - options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); - // Add fake usage files - options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`)); - - let step = 0; - - const checker = program.getTypeChecker(); - while (black_queue.length > 0 || gray_queue.length > 0) { - ++step; - let node: ts.Node; - - if (step % 100 === 0) { - console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); - } - - if (black_queue.length === 0) { - for (let i = 0; i < gray_queue.length; i++) { - const node = gray_queue[i]; - const nodeParent = node.parent; - if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { - gray_queue.splice(i, 1); - black_queue.push(node); - setColor(node, NodeColor.Black); - i--; - } - } - } - - if (black_queue.length > 0) { - node = black_queue.shift()!; - } else { - // only gray nodes remaining... - break; - } - const nodeSourceFile = node.getSourceFile(); - - const loop = (node: ts.Node) => { - const symbols = getRealNodeSymbol(ts, checker, node); - for (const { symbol, symbolImportNode } of symbols) { - if (symbolImportNode) { - setColor(symbolImportNode, NodeColor.Black); - const importDeclarationNode = findParentImportDeclaration(symbolImportNode); - if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { - enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); - } - } - - if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { - const declaration = symbol.declarations[i]; - if (ts.isSourceFile(declaration)) { - // Do not enqueue full source files - // (they can be the declaration of a module import) - continue; - } - - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { - enqueue_black(declaration.name!); - - for (let j = 0; j < declaration.members.length; j++) { - const member = declaration.members[j]; - const memberName = member.name ? member.name.getText() : null; - if ( - ts.isConstructorDeclaration(member) - || ts.isConstructSignatureDeclaration(member) - || ts.isIndexSignatureDeclaration(member) - || ts.isCallSignatureDeclaration(member) - || memberName === '[Symbol.iterator]' - || memberName === '[Symbol.toStringTag]' - || memberName === 'toJSON' - || memberName === 'toString' - || memberName === 'dispose'// TODO: keeping all `dispose` methods - || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... - ) { - enqueue_black(member); - } - - if (isStaticMemberWithSideEffects(ts, member)) { - enqueue_black(member); - } - } - - // queue the heritage clauses - if (declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - enqueue_black(heritageClause); - } - } - } else { - enqueue_black(declaration); - } - } - } - } - node.forEachChild(loop); - }; - node.forEachChild(loop); - } - - while (export_import_queue.length > 0) { - const node = export_import_queue.shift()!; - if (nodeOrParentIsBlack(node)) { - continue; - } - const symbol: ts.Symbol | undefined = (node).symbol; - if (!symbol) { - continue; - } - const aliased = checker.getAliasedSymbol(symbol); - if (aliased.declarations && aliased.declarations.length > 0) { - if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { - setColor(node, NodeColor.Black); - } - } - } + const program = languageService.getProgram(); + if (!program) { + throw new Error('Could not get program from language service'); + } + if (options.shakeLevel === ShakeLevel.Files) { + // Mark all source files Black + program.getSourceFiles().forEach((sourceFile) => { + setColor(sourceFile, NodeColor.Black); + }); + return; + } + const black_queue: ts.Node[] = []; + const gray_queue: ts.Node[] = []; + const export_import_queue: ts.Node[] = []; + const sourceFilesLoaded: { + [fileName: string]: boolean; + } = {}; + function enqueueTopLevelModuleStatements(sourceFile: ts.SourceFile): void { + sourceFile.forEachChild((node: ts.Node) => { + if (ts.isImportDeclaration(node)) { + if (!node.importClause && ts.isStringLiteral(node.moduleSpecifier)) { + setColor(node, NodeColor.Black); + enqueueImport(node, node.moduleSpecifier.text); + } + return; + } + if (ts.isExportDeclaration(node)) { + if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + // export * from "foo"; + setColor(node, NodeColor.Black); + enqueueImport(node, node.moduleSpecifier.text); + } + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + for (const exportSpecifier of node.exportClause.elements) { + export_import_queue.push(exportSpecifier); + } + } + return; + } + if (isVariableStatementWithSideEffects(ts, node)) { + enqueue_black(node); + } + if (ts.isExpressionStatement(node) + || ts.isIfStatement(node) + || ts.isIterationStatement(node, true) + || ts.isExportAssignment(node)) { + enqueue_black(node); + } + if (ts.isImportEqualsDeclaration(node)) { + if (/export/.test(node.getFullText(sourceFile))) { + // e.g. "export import Severity = BaseSeverity;" + enqueue_black(node); + } + } + }); + } + /** + * Return the parent of `node` which is an ImportDeclaration + */ + function findParentImportDeclaration(node: ts.Declaration): ts.ImportDeclaration | null { + let _node: ts.Node = node; + do { + if (ts.isImportDeclaration(_node)) { + return _node; + } + _node = _node.parent; + } while (_node); + return null; + } + function enqueue_gray(node: ts.Node): void { + if (nodeOrParentIsBlack(node) || getColor(node) === NodeColor.Gray) { + return; + } + setColor(node, NodeColor.Gray); + gray_queue.push(node); + } + function enqueue_black(node: ts.Node): void { + const previousColor = getColor(node); + if (previousColor === NodeColor.Black) { + return; + } + if (previousColor === NodeColor.Gray) { + // remove from gray queue + gray_queue.splice(gray_queue.indexOf(node), 1); + setColor(node, NodeColor.White); + // add to black queue + enqueue_black(node); + // move from one queue to the other + // black_queue.push(node); + // setColor(node, NodeColor.Black); + return; + } + if (nodeOrParentIsBlack(node)) { + return; + } + const fileName = node.getSourceFile().fileName; + if (/^defaultLib:/.test(fileName) || /\.d\.ts$/.test(fileName)) { + setColor(node, NodeColor.Black); + return; + } + const sourceFile = node.getSourceFile(); + if (!sourceFilesLoaded[sourceFile.fileName]) { + sourceFilesLoaded[sourceFile.fileName] = true; + enqueueTopLevelModuleStatements(sourceFile); + } + if (ts.isSourceFile(node)) { + return; + } + setColor(node, NodeColor.Black); + black_queue.push(node); + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); + if (references) { + for (let i = 0, len = references.length; i < len; i++) { + const reference = references[i]; + const referenceSourceFile = program!.getSourceFile(reference.fileName); + if (!referenceSourceFile) { + continue; + } + const referenceNode = getTokenAtPosition(ts, referenceSourceFile, reference.textSpan.start, false, false); + if (ts.isMethodDeclaration(referenceNode.parent) + || ts.isPropertyDeclaration(referenceNode.parent) + || ts.isGetAccessor(referenceNode.parent) + || ts.isSetAccessor(referenceNode.parent)) { + enqueue_gray(referenceNode.parent); + } + } + } + } + } + function enqueueFile(filename: string): void { + const sourceFile = program!.getSourceFile(filename); + if (!sourceFile) { + console.warn(`Cannot find source file ${filename}`); + return; + } + // This source file should survive even if it is empty + markNeededSourceFile(sourceFile); + enqueue_black(sourceFile); + } + function enqueueImport(node: ts.Node, importText: string): void { + if (options.importIgnorePattern.test(importText)) { + // this import should be ignored + return; + } + const nodeSourceFile = node.getSourceFile(); + let fullPath: string; + if (/(^\.\/)|(^\.\.\/)/.test(importText)) { + if (importText.endsWith('.js')) { // ESM: code imports require to be relative and to have a '.js' file extension + importText = importText.substr(0, importText.length - 3); + } + fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts'; + } + else { + fullPath = importText + '.ts'; + } + enqueueFile(fullPath); + } + options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); + // Add fake usage files + options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`)); + let step = 0; + const checker = program.getTypeChecker(); + while (black_queue.length > 0 || gray_queue.length > 0) { + ++step; + let node: ts.Node; + if (step % 100 === 0) { + console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); + } + if (black_queue.length === 0) { + for (let i = 0; i < gray_queue.length; i++) { + const node = gray_queue[i]; + const nodeParent = node.parent; + if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { + gray_queue.splice(i, 1); + black_queue.push(node); + setColor(node, NodeColor.Black); + i--; + } + } + } + if (black_queue.length > 0) { + node = black_queue.shift()!; + } + else { + // only gray nodes remaining... + break; + } + const nodeSourceFile = node.getSourceFile(); + const loop = (node: ts.Node) => { + const symbols = getRealNodeSymbol(ts, checker, node); + for (const { symbol, symbolImportNode } of symbols) { + if (symbolImportNode) { + setColor(symbolImportNode, NodeColor.Black); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } + } + if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { + const declaration = symbol.declarations[i]; + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; + } + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { + enqueue_black(declaration.name!); + for (let j = 0; j < declaration.members.length; j++) { + const member = declaration.members[j]; + const memberName = member.name ? member.name.getText() : null; + if (ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' + || memberName === '[Symbol.toStringTag]' + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose' // TODO: keeping all `dispose` methods + || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... + ) { + enqueue_black(member); + } + if (isStaticMemberWithSideEffects(ts, member)) { + enqueue_black(member); + } + } + // queue the heritage clauses + if (declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + enqueue_black(heritageClause); + } + } + } + else { + enqueue_black(declaration); + } + } + } + } + node.forEachChild(loop); + }; + node.forEachChild(loop); + } + while (export_import_queue.length > 0) { + const node = export_import_queue.shift()!; + if (nodeOrParentIsBlack(node)) { + continue; + } + const symbol: ts.Symbol | undefined = (node).symbol; + if (!symbol) { + continue; + } + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations && aliased.declarations.length > 0) { + if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { + setColor(node, NodeColor.Black); + } + } + } } - -function nodeIsInItsOwnDeclaration(nodeSourceFile: ts.SourceFile, node: ts.Node, symbol: ts.Symbol & { declarations: ts.Declaration[] }): boolean { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { - const declaration = symbol.declarations[i]; - const declarationSourceFile = declaration.getSourceFile(); - - if (nodeSourceFile === declarationSourceFile) { - if (declaration.pos <= node.pos && node.end <= declaration.end) { - return true; - } - } - } - - return false; +function nodeIsInItsOwnDeclaration(nodeSourceFile: ts.SourceFile, node: ts.Node, symbol: ts.Symbol & { + declarations: ts.Declaration[]; +}): boolean { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { + const declaration = symbol.declarations[i]; + const declarationSourceFile = declaration.getSourceFile(); + if (nodeSourceFile === declarationSourceFile) { + if (declaration.pos <= node.pos && node.end <= declaration.end) { + return true; + } + } + } + return false; } - function generateResult(ts: typeof import('typescript'), languageService: ts.LanguageService, shakeLevel: ShakeLevel): ITreeShakingResult { - const program = languageService.getProgram(); - if (!program) { - throw new Error('Could not get program from language service'); - } - - const result: ITreeShakingResult = {}; - const writeFile = (filePath: string, contents: string): void => { - result[filePath] = contents; - }; - - program.getSourceFiles().forEach((sourceFile) => { - const fileName = sourceFile.fileName; - if (/^defaultLib:/.test(fileName)) { - return; - } - const destination = fileName; - if (/\.d\.ts$/.test(fileName)) { - if (nodeOrChildIsBlack(sourceFile)) { - writeFile(destination, sourceFile.text); - } - return; - } - - const text = sourceFile.text; - let result = ''; - - function keep(node: ts.Node): void { - result += text.substring(node.pos, node.end); - } - function write(data: string): void { - result += data; - } - - function writeMarkedNodes(node: ts.Node): void { - if (getColor(node) === NodeColor.Black) { - return keep(node); - } - - // Always keep certain top-level statements - if (ts.isSourceFile(node.parent)) { - if (ts.isExpressionStatement(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'use strict') { - return keep(node); - } - - if (ts.isVariableStatement(node) && nodeOrChildIsBlack(node)) { - return keep(node); - } - } - - // Keep the entire import in import * as X cases - if (ts.isImportDeclaration(node)) { - if (node.importClause && node.importClause.namedBindings) { - if (ts.isNamespaceImport(node.importClause.namedBindings)) { - if (getColor(node.importClause.namedBindings) === NodeColor.Black) { - return keep(node); - } - } else { - const survivingImports: string[] = []; - for (const importNode of node.importClause.namedBindings.elements) { - if (getColor(importNode) === NodeColor.Black) { - survivingImports.push(importNode.getFullText(sourceFile)); - } - } - const leadingTriviaWidth = node.getLeadingTriviaWidth(); - const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); - if (survivingImports.length > 0) { - if (node.importClause && node.importClause.name && getColor(node.importClause) === NodeColor.Black) { - return write(`${leadingTrivia}import ${node.importClause.name.text}, {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - return write(`${leadingTrivia}import {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); - } else { - if (node.importClause && node.importClause.name && getColor(node.importClause) === NodeColor.Black) { - return write(`${leadingTrivia}import ${node.importClause.name.text} from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - } - } - } else { - if (node.importClause && getColor(node.importClause) === NodeColor.Black) { - return keep(node); - } - } - } - - if (ts.isExportDeclaration(node)) { - if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { - const survivingExports: string[] = []; - for (const exportSpecifier of node.exportClause.elements) { - if (getColor(exportSpecifier) === NodeColor.Black) { - survivingExports.push(exportSpecifier.getFullText(sourceFile)); - } - } - const leadingTriviaWidth = node.getLeadingTriviaWidth(); - const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); - if (survivingExports.length > 0) { - return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - } - } - - if (shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { - let toWrite = node.getFullText(); - for (let i = node.members.length - 1; i >= 0; i--) { - const member = node.members[i]; - if (getColor(member) === NodeColor.Black || !member.name) { - // keep method - continue; - } - - const pos = member.pos - node.pos; - const end = member.end - node.pos; - toWrite = toWrite.substring(0, pos) + toWrite.substring(end); - } - return write(toWrite); - } - - if (ts.isFunctionDeclaration(node)) { - // Do not go inside functions if they haven't been marked - return; - } - - node.forEachChild(writeMarkedNodes); - } - - if (getColor(sourceFile) !== NodeColor.Black) { - if (!nodeOrChildIsBlack(sourceFile)) { - // none of the elements are reachable - if (isNeededSourceFile(sourceFile)) { - // this source file must be written, even if nothing is used from it - // because there is an import somewhere for it. - // However, TS complains with empty files with the error "x" is not a module, - // so we will export a dummy variable - result = 'export const __dummy = 0;'; - } else { - // don't write this file at all! - return; - } - } else { - sourceFile.forEachChild(writeMarkedNodes); - result += sourceFile.endOfFileToken.getFullText(sourceFile); - } - } else { - result = text; - } - - writeFile(destination, result); - }); - - return result; + const program = languageService.getProgram(); + if (!program) { + throw new Error('Could not get program from language service'); + } + const result: ITreeShakingResult = {}; + const writeFile = (filePath: string, contents: string): void => { + result[filePath] = contents; + }; + program.getSourceFiles().forEach((sourceFile) => { + const fileName = sourceFile.fileName; + if (/^defaultLib:/.test(fileName)) { + return; + } + const destination = fileName; + if (/\.d\.ts$/.test(fileName)) { + if (nodeOrChildIsBlack(sourceFile)) { + writeFile(destination, sourceFile.text); + } + return; + } + const text = sourceFile.text; + let result = ''; + function keep(node: ts.Node): void { + result += text.substring(node.pos, node.end); + } + function write(data: string): void { + result += data; + } + function writeMarkedNodes(node: ts.Node): void { + if (getColor(node) === NodeColor.Black) { + return keep(node); + } + // Always keep certain top-level statements + if (ts.isSourceFile(node.parent)) { + if (ts.isExpressionStatement(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'use strict') { + return keep(node); + } + if (ts.isVariableStatement(node) && nodeOrChildIsBlack(node)) { + return keep(node); + } + } + // Keep the entire import in import * as X cases + if (ts.isImportDeclaration(node)) { + if (node.importClause && node.importClause.namedBindings) { + if (ts.isNamespaceImport(node.importClause.namedBindings)) { + if (getColor(node.importClause.namedBindings) === NodeColor.Black) { + return keep(node); + } + } + else { + const survivingImports: string[] = []; + for (const importNode of node.importClause.namedBindings.elements) { + if (getColor(importNode) === NodeColor.Black) { + survivingImports.push(importNode.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingImports.length > 0) { + if (node.importClause && node.importClause.name && getColor(node.importClause) === NodeColor.Black) { + return write(`${leadingTrivia}import ${node.importClause.name.text}, {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + return write(`${leadingTrivia}import {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + else { + if (node.importClause && node.importClause.name && getColor(node.importClause) === NodeColor.Black) { + return write(`${leadingTrivia}import ${node.importClause.name.text} from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } + } + else { + if (node.importClause && getColor(node.importClause) === NodeColor.Black) { + return keep(node); + } + } + } + if (ts.isExportDeclaration(node)) { + if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { + const survivingExports: string[] = []; + for (const exportSpecifier of node.exportClause.elements) { + if (getColor(exportSpecifier) === NodeColor.Black) { + survivingExports.push(exportSpecifier.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingExports.length > 0) { + return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } + if (shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { + let toWrite = node.getFullText(); + for (let i = node.members.length - 1; i >= 0; i--) { + const member = node.members[i]; + if (getColor(member) === NodeColor.Black || !member.name) { + // keep method + continue; + } + const pos = member.pos - node.pos; + const end = member.end - node.pos; + toWrite = toWrite.substring(0, pos) + toWrite.substring(end); + } + return write(toWrite); + } + if (ts.isFunctionDeclaration(node)) { + // Do not go inside functions if they haven't been marked + return; + } + node.forEachChild(writeMarkedNodes); + } + if (getColor(sourceFile) !== NodeColor.Black) { + if (!nodeOrChildIsBlack(sourceFile)) { + // none of the elements are reachable + if (isNeededSourceFile(sourceFile)) { + // this source file must be written, even if nothing is used from it + // because there is an import somewhere for it. + // However, TS complains with empty files with the error "x" is not a module, + // so we will export a dummy variable + result = 'export const __dummy = 0;'; + } + else { + // don't write this file at all! + return; + } + } + else { + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); + } + } + else { + result = text; + } + writeFile(destination, result); + }); + return result; } - //#endregion - //#region Utils - function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts: typeof import('typescript'), program: ts.Program, checker: ts.TypeChecker, declaration: ts.ClassDeclaration | ts.InterfaceDeclaration): boolean { - if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - for (const type of heritageClause.types) { - const symbol = findSymbolFromHeritageType(ts, checker, type); - if (symbol) { - const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]); - if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) { - return true; - } - } - } - } - } - return false; + if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + for (const type of heritageClause.types) { + const symbol = findSymbolFromHeritageType(ts, checker, type); + if (symbol) { + const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]); + if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) { + return true; + } + } + } + } + } + return false; } - function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts.TypeChecker, type: ts.ExpressionWithTypeArguments | ts.Expression | ts.PrivateIdentifier): ts.Symbol | null { - if (ts.isExpressionWithTypeArguments(type)) { - return findSymbolFromHeritageType(ts, checker, type.expression); - } - if (ts.isIdentifier(type)) { - const tmp = getRealNodeSymbol(ts, checker, type); - return (tmp.length > 0 ? tmp[0].symbol : null); - } - if (ts.isPropertyAccessExpression(type)) { - return findSymbolFromHeritageType(ts, checker, type.name); - } - return null; + if (ts.isExpressionWithTypeArguments(type)) { + return findSymbolFromHeritageType(ts, checker, type.expression); + } + if (ts.isIdentifier(type)) { + const tmp = getRealNodeSymbol(ts, checker, type); + return (tmp.length > 0 ? tmp[0].symbol : null); + } + if (ts.isPropertyAccessExpression(type)) { + return findSymbolFromHeritageType(ts, checker, type.name); + } + return null; } - class SymbolImportTuple { - constructor( - public readonly symbol: ts.Symbol | null, - public readonly symbolImportNode: ts.Declaration | null - ) { } + constructor(public readonly symbol: ts.Symbol | null, public readonly symbolImportNode: ts.Declaration | null) { } } - /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): SymbolImportTuple[] { - - // Use some TypeScript internals to avoid code duplication - type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; - const getPropertySymbolsFromContextualType: (node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean) => ReadonlyArray = (ts).getPropertySymbolsFromContextualType; - const getContainingObjectLiteralElement: (node: ts.Node) => ObjectLiteralElementWithName | undefined = (ts).getContainingObjectLiteralElement; - const getNameFromPropertyName: (name: ts.PropertyName) => string | undefined = (ts).getNameFromPropertyName; - - // Go to the original declaration for cases: - // - // (1) when the aliased symbol was declared in the location(parent). - // (2) when the aliased symbol is originating from an import. - // - function shouldSkipAlias(node: ts.Node, declaration: ts.Node): boolean { - if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { - return false; - } - if (node.parent === declaration) { - return true; - } - switch (declaration.kind) { - case ts.SyntaxKind.ImportClause: - case ts.SyntaxKind.ImportEqualsDeclaration: - return true; - case ts.SyntaxKind.ImportSpecifier: - return declaration.parent.kind === ts.SyntaxKind.NamedImports; - default: - return false; - } - } - - if (!ts.isShorthandPropertyAssignment(node)) { - if (node.getChildCount() !== 0) { - return []; - } - } - - const { parent } = node; - - let symbol = ( - ts.isShorthandPropertyAssignment(node) - ? checker.getShorthandAssignmentValueSymbol(node) - : checker.getSymbolAtLocation(node) - ); - - let importNode: ts.Declaration | null = null; - // If this is an alias, and the request came at the declaration location - // get the aliased symbol instead. This allows for goto def on an import e.g. - // import {A, B} from "mod"; - // to jump to the implementation directly. - if (symbol && symbol.flags & ts.SymbolFlags.Alias && symbol.declarations && shouldSkipAlias(node, symbol.declarations[0])) { - const aliased = checker.getAliasedSymbol(symbol); - if (aliased.declarations) { - // We should mark the import as visited - importNode = symbol.declarations[0]; - symbol = aliased; - } - } - - if (symbol) { - // Because name in short-hand property assignment has two different meanings: property name and property value, - // using go-to-definition at such position should go to the variable declaration of the property value rather than - // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition - // is performed at the location of property access, we would like to go to definition of the property in the short-hand - // assignment. This case and others are handled by the following code. - if (node.parent.kind === ts.SyntaxKind.ShorthandPropertyAssignment) { - symbol = checker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); - } - - // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the - // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern - // and return the property declaration for the referenced property. - // For example: - // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" - // - // function bar(onfulfilled: (value: T) => void) { //....} - // interface Test { - // pr/*destination*/op1: number - // } - // bar(({pr/*goto*/op1})=>{}); - if (ts.isPropertyName(node) && ts.isBindingElement(parent) && ts.isObjectBindingPattern(parent.parent) && - (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); - const type = checker.getTypeAtLocation(parent.parent); - if (name && type) { - if (type.isUnion()) { - return generateMultipleSymbols(type, name, importNode); - } else { - const prop = type.getProperty(name); - if (prop) { - symbol = prop; - } - } - } - } - - // If the current location we want to find its definition is in an object literal, try to get the contextual type for the - // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. - // For example - // interface Props{ - // /*first*/prop1: number - // prop2: boolean - // } - // function Foo(arg: Props) {} - // Foo( { pr/*1*/op1: 10, prop2: false }) - const element = getContainingObjectLiteralElement(node); - if (element) { - const contextualType = element && checker.getContextualType(element.parent); - if (contextualType) { - const propertySymbols = getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); - if (propertySymbols) { - symbol = propertySymbols[0]; - } - } - } - } - - if (symbol && symbol.declarations) { - return [new SymbolImportTuple(symbol, importNode)]; - } - - return []; - - function generateMultipleSymbols(type: ts.UnionType, name: string, importNode: ts.Declaration | null): SymbolImportTuple[] { - const result: SymbolImportTuple[] = []; - for (const t of type.types) { - const prop = t.getProperty(name); - if (prop && prop.declarations) { - result.push(new SymbolImportTuple(prop, importNode)); - } - } - return result; - } + // Use some TypeScript internals to avoid code duplication + type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { + name: ts.PropertyName; + parent: ts.ObjectLiteralExpression | ts.JsxAttributes; + }; + const getPropertySymbolsFromContextualType: (node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean) => ReadonlyArray = (ts).getPropertySymbolsFromContextualType; + const getContainingObjectLiteralElement: (node: ts.Node) => ObjectLiteralElementWithName | undefined = (ts).getContainingObjectLiteralElement; + const getNameFromPropertyName: (name: ts.PropertyName) => string | undefined = (ts).getNameFromPropertyName; + // Go to the original declaration for cases: + // + // (1) when the aliased symbol was declared in the location(parent). + // (2) when the aliased symbol is originating from an import. + // + function shouldSkipAlias(node: ts.Node, declaration: ts.Node): boolean { + if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { + return false; + } + if (node.parent === declaration) { + return true; + } + switch (declaration.kind) { + case ts.SyntaxKind.ImportClause: + case ts.SyntaxKind.ImportEqualsDeclaration: + return true; + case ts.SyntaxKind.ImportSpecifier: + return declaration.parent.kind === ts.SyntaxKind.NamedImports; + default: + return false; + } + } + if (!ts.isShorthandPropertyAssignment(node)) { + if (node.getChildCount() !== 0) { + return []; + } + } + const { parent } = node; + let symbol = (ts.isShorthandPropertyAssignment(node) + ? checker.getShorthandAssignmentValueSymbol(node) + : checker.getSymbolAtLocation(node)); + let importNode: ts.Declaration | null = null; + // If this is an alias, and the request came at the declaration location + // get the aliased symbol instead. This allows for goto def on an import e.g. + // import {A, B} from "mod"; + // to jump to the implementation directly. + if (symbol && symbol.flags & ts.SymbolFlags.Alias && symbol.declarations && shouldSkipAlias(node, symbol.declarations[0])) { + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations) { + // We should mark the import as visited + importNode = symbol.declarations[0]; + symbol = aliased; + } + } + if (symbol) { + // Because name in short-hand property assignment has two different meanings: property name and property value, + // using go-to-definition at such position should go to the variable declaration of the property value rather than + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition + // is performed at the location of property access, we would like to go to definition of the property in the short-hand + // assignment. This case and others are handled by the following code. + if (node.parent.kind === ts.SyntaxKind.ShorthandPropertyAssignment) { + symbol = checker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); + } + // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the + // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern + // and return the property declaration for the referenced property. + // For example: + // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" + // + // function bar(onfulfilled: (value: T) => void) { //....} + // interface Test { + // pr/*destination*/op1: number + // } + // bar(({pr/*goto*/op1})=>{}); + if (ts.isPropertyName(node) && ts.isBindingElement(parent) && ts.isObjectBindingPattern(parent.parent) && + (node === (parent.propertyName || parent.name))) { + const name = getNameFromPropertyName(node); + const type = checker.getTypeAtLocation(parent.parent); + if (name && type) { + if (type.isUnion()) { + return generateMultipleSymbols(type, name, importNode); + } + else { + const prop = type.getProperty(name); + if (prop) { + symbol = prop; + } + } + } + } + // If the current location we want to find its definition is in an object literal, try to get the contextual type for the + // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. + // For example + // interface Props{ + // /*first*/prop1: number + // prop2: boolean + // } + // function Foo(arg: Props) {} + // Foo( { pr/*1*/op1: 10, prop2: false }) + const element = getContainingObjectLiteralElement(node); + if (element) { + const contextualType = element && checker.getContextualType(element.parent); + if (contextualType) { + const propertySymbols = getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); + if (propertySymbols) { + symbol = propertySymbols[0]; + } + } + } + } + if (symbol && symbol.declarations) { + return [new SymbolImportTuple(symbol, importNode)]; + } + return []; + function generateMultipleSymbols(type: ts.UnionType, name: string, importNode: ts.Declaration | null): SymbolImportTuple[] { + const result: SymbolImportTuple[] = []; + for (const t of type.types) { + const prop = t.getProperty(name); + if (prop && prop.declarations) { + result.push(new SymbolImportTuple(prop, importNode)); + } + } + return result; + } } - /** Get the token whose text contains the position */ function getTokenAtPosition(ts: typeof import('typescript'), sourceFile: ts.SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeEndPosition: boolean): ts.Node { - let current: ts.Node = sourceFile; - outer: while (true) { - // find the child that contains 'position' - for (const child of current.getChildren()) { - const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); - if (start > position) { - // If this child begins after position, then all subsequent children will as well. - break; - } - - const end = child.getEnd(); - if (position < end || (position === end && (child.kind === ts.SyntaxKind.EndOfFileToken || includeEndPosition))) { - current = child; - continue outer; - } - } - - return current; - } + let current: ts.Node = sourceFile; + outer: while (true) { + // find the child that contains 'position' + for (const child of current.getChildren()) { + const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); + if (start > position) { + // If this child begins after position, then all subsequent children will as well. + break; + } + const end = child.getEnd(); + if (position < end || (position === end && (child.kind === ts.SyntaxKind.EndOfFileToken || includeEndPosition))) { + current = child; + continue outer; + } + } + return current; + } } - //#endregion diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 70c71591a6eb8..c79eb6f3806fd 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; @@ -11,678 +10,591 @@ import * as colors from 'ansi-colors'; import * as ts from 'typescript'; import * as Vinyl from 'vinyl'; import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; - export interface IConfiguration { - logFn: (topic: string, message: string) => void; - _emitWithoutBasePath?: boolean; + logFn: (topic: string, message: string) => void; + _emitWithoutBasePath?: boolean; } - export interface CancellationToken { - isCancellationRequested(): boolean; + isCancellationRequested(): boolean; } - export namespace CancellationToken { - export const None: CancellationToken = { - isCancellationRequested() { return false; } - }; + export const None: CancellationToken = { + isCancellationRequested() { return false; } + }; } - export interface ITypeScriptBuilder { - build(out: (file: Vinyl) => void, onError: (err: ts.Diagnostic) => void, token?: CancellationToken): Promise; - file(file: Vinyl): void; - languageService: ts.LanguageService; + build(out: (file: Vinyl) => void, onError: (err: ts.Diagnostic) => void, token?: CancellationToken): Promise; + file(file: Vinyl): void; + languageService: ts.LanguageService; } - function normalize(path: string): string { - return path.replace(/\\/g, '/'); + return path.replace(/\\/g, '/'); } - export function createTypeScriptBuilder(config: IConfiguration, projectFile: string, cmd: ts.ParsedCommandLine): ITypeScriptBuilder { - - const _log = config.logFn; - - const host = new LanguageServiceHost(cmd, projectFile, _log); - const service = ts.createLanguageService(host, ts.createDocumentRegistry()); - const lastBuildVersion: { [path: string]: string } = Object.create(null); - const lastDtsHash: { [path: string]: string } = Object.create(null); - const userWantsDeclarations = cmd.options.declaration; - let oldErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null); - let headUsed = process.memoryUsage().heapUsed; - let emitSourceMapsInStream = true; - - // always emit declaraction files - host.getCompilationSettings().declaration = true; - - function file(file: Vinyl): void { - // support gulp-sourcemaps - if ((file).sourceMap) { - emitSourceMapsInStream = false; - } - - if (!file.contents) { - host.removeScriptSnapshot(file.path); - } else { - host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file)); - } - } - - function baseFor(snapshot: ScriptSnapshot): string { - if (snapshot instanceof VinylScriptSnapshot) { - return cmd.options.outDir || snapshot.getBase(); - } else { - return ''; - } - } - - function isExternalModule(sourceFile: ts.SourceFile): boolean { - return (sourceFile).externalModuleIndicator - || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); - } - - function build(out: (file: Vinyl) => void, onError: (err: any) => void, token = CancellationToken.None): Promise { - - function checkSyntaxSoon(fileName: string): Promise { - return new Promise(resolve => { - process.nextTick(function () { - if (!host.getScriptSnapshot(fileName, false)) { - resolve([]); // no script, no problems - } else { - resolve(service.getSyntacticDiagnostics(fileName)); - } - }); - }); - } - - function checkSemanticsSoon(fileName: string): Promise { - return new Promise(resolve => { - process.nextTick(function () { - if (!host.getScriptSnapshot(fileName, false)) { - resolve([]); // no script, no problems - } else { - resolve(service.getSemanticDiagnostics(fileName)); - } - }); - }); - } - - function emitSoon(fileName: string): Promise<{ fileName: string; signature?: string; files: Vinyl[] }> { - - return new Promise(resolve => { - process.nextTick(function () { - - if (/\.d\.ts$/.test(fileName)) { - // if it's already a d.ts file just emit it signature - const snapshot = host.getScriptSnapshot(fileName); - const signature = crypto.createHash('sha256') - .update(snapshot.getText(0, snapshot.getLength())) - .digest('base64'); - - return resolve({ - fileName, - signature, - files: [] - }); - } - - const output = service.getEmitOutput(fileName); - const files: Vinyl[] = []; - let signature: string | undefined; - - for (const file of output.outputFiles) { - if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) { - continue; - } - - if (/\.d\.ts$/.test(file.name)) { - signature = crypto.createHash('sha256') - .update(file.text) - .digest('base64'); - - if (!userWantsDeclarations) { - // don't leak .d.ts files if users don't want them - continue; - } - } - - const vinyl = new Vinyl({ - path: file.name, - contents: Buffer.from(file.text), - base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined - }); - - if (!emitSourceMapsInStream && /\.js$/.test(file.name)) { - const sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0]; - - if (sourcemapFile) { - const extname = path.extname(vinyl.relative); - const basename = path.basename(vinyl.relative, extname); - const dirname = path.dirname(vinyl.relative); - const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; - - let sourceMap = JSON.parse(sourcemapFile.text); - sourceMap.sources[0] = tsname.replace(/\\/g, '/'); - - // check for an "input source" map and combine them - // in step 1 we extract all line edit from the input source map, and - // in step 2 we apply the line edits to the typescript source map - const snapshot = host.getScriptSnapshot(fileName); - if (snapshot instanceof VinylScriptSnapshot && snapshot.sourceMap) { - const inputSMC = new SourceMapConsumer(snapshot.sourceMap); - const tsSMC = new SourceMapConsumer(sourceMap); - let didChange = false; - const smg = new SourceMapGenerator({ - file: sourceMap.file, - sourceRoot: sourceMap.sourceRoot - }); - - // step 1 - const lineEdits = new Map(); - inputSMC.eachMapping(m => { - if (m.originalLine === m.generatedLine) { - // same line mapping - let array = lineEdits.get(m.originalLine); - if (!array) { - array = []; - lineEdits.set(m.originalLine, array); - } - array.push([m.originalColumn, m.generatedColumn]); - } else { - // NOT SUPPORTED - } - }); - - // step 2 - tsSMC.eachMapping(m => { - didChange = true; - const edits = lineEdits.get(m.originalLine); - let originalColumnDelta = 0; - if (edits) { - for (const [from, to] of edits) { - if (to >= m.originalColumn) { - break; - } - originalColumnDelta = from - to; - } - } - smg.addMapping({ - source: m.source, - name: m.name, - generated: { line: m.generatedLine, column: m.generatedColumn }, - original: { line: m.originalLine, column: m.originalColumn + originalColumnDelta } - }); - }); - - if (didChange) { - - [tsSMC, inputSMC].forEach((consumer) => { - (consumer).sources.forEach((sourceFile: any) => { - (smg)._sources.add(sourceFile); - const sourceContent = consumer.sourceContentFor(sourceFile); - if (sourceContent !== null) { - smg.setSourceContent(sourceFile, sourceContent); - } - }); - }); - - sourceMap = JSON.parse(smg.toString()); - - // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; - // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { - // await fs.promises.writeFile(filename, smg.toString()); - // await fs.promises.writeFile('/Users/jrieken/Code/vscode/src2/' + vinyl.relative, vinyl.contents); - // }); - } - } - - (vinyl).sourceMap = sourceMap; - } - } - - files.push(vinyl); - } - - resolve({ - fileName, - signature, - files - }); - }); - }); - } - - const newErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null); - const t1 = Date.now(); - - const toBeEmitted: string[] = []; - const toBeCheckedSyntactically: string[] = []; - const toBeCheckedSemantically: string[] = []; - const filesWithChangedSignature: string[] = []; - const dependentFiles: string[] = []; - const newLastBuildVersion = new Map(); - - for (const fileName of host.getScriptFileNames()) { - if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) { - - toBeEmitted.push(fileName); - toBeCheckedSyntactically.push(fileName); - toBeCheckedSemantically.push(fileName); - } - } - - return new Promise(resolve => { - - const semanticCheckInfo = new Map(); - const seenAsDependentFile = new Set(); - - function workOnNext() { - - let promise: Promise | undefined; - // let fileName: string; - - // someone told us to stop this - if (token.isCancellationRequested()) { - _log('[CANCEL]', '>>This compile run was cancelled<<'); - newLastBuildVersion.clear(); - resolve(); - return; - } - - // (1st) emit code - else if (toBeEmitted.length) { - const fileName = toBeEmitted.pop()!; - promise = emitSoon(fileName).then(value => { - - for (const file of value.files) { - _log('[emit code]', file.path); - out(file); - } - - // remember when this was build - newLastBuildVersion.set(fileName, host.getScriptVersion(fileName)); - - // remeber the signature - if (value.signature && lastDtsHash[fileName] !== value.signature) { - lastDtsHash[fileName] = value.signature; - filesWithChangedSignature.push(fileName); - } - }).catch(e => { - // can't just skip this or make a result up.. - host.error(`ERROR emitting ${fileName}`); - host.error(e); - }); - } - - // (2nd) check syntax - else if (toBeCheckedSyntactically.length) { - const fileName = toBeCheckedSyntactically.pop()!; - _log('[check syntax]', fileName); - promise = checkSyntaxSoon(fileName).then(diagnostics => { - delete oldErrors[fileName]; - if (diagnostics.length > 0) { - diagnostics.forEach(d => onError(d)); - newErrors[fileName] = diagnostics; - - // stop the world when there are syntax errors - toBeCheckedSyntactically.length = 0; - toBeCheckedSemantically.length = 0; - filesWithChangedSignature.length = 0; - } - }); - } - - // (3rd) check semantics - else if (toBeCheckedSemantically.length) { - - let fileName = toBeCheckedSemantically.pop(); - while (fileName && semanticCheckInfo.has(fileName)) { - fileName = toBeCheckedSemantically.pop()!; - } - - if (fileName) { - _log('[check semantics]', fileName); - promise = checkSemanticsSoon(fileName).then(diagnostics => { - delete oldErrors[fileName!]; - semanticCheckInfo.set(fileName!, diagnostics.length); - if (diagnostics.length > 0) { - diagnostics.forEach(d => onError(d)); - newErrors[fileName!] = diagnostics; - } - }); - } - } - - // (4th) check dependents - else if (filesWithChangedSignature.length) { - while (filesWithChangedSignature.length) { - const fileName = filesWithChangedSignature.pop()!; - - if (!isExternalModule(service.getProgram()!.getSourceFile(fileName)!)) { - _log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet'); - toBeCheckedSemantically.push(...host.getScriptFileNames()); - filesWithChangedSignature.length = 0; - dependentFiles.length = 0; - break; - } - - host.collectDependents(fileName, dependentFiles); - } - } - - // (5th) dependents contd - else if (dependentFiles.length) { - let fileName = dependentFiles.pop(); - while (fileName && seenAsDependentFile.has(fileName)) { - fileName = dependentFiles.pop(); - } - if (fileName) { - seenAsDependentFile.add(fileName); - const value = semanticCheckInfo.get(fileName); - if (value === 0) { - // already validated successfully -> look at dependents next - host.collectDependents(fileName, dependentFiles); - - } else if (typeof value === 'undefined') { - // first validate -> look at dependents next - dependentFiles.push(fileName); - toBeCheckedSemantically.push(fileName); - } - } - } - - // (last) done - else { - resolve(); - return; - } - - if (!promise) { - promise = Promise.resolve(); - } - - promise.then(function () { - // change to change - process.nextTick(workOnNext); - }).catch(err => { - console.error(err); - }); - } - - workOnNext(); - - }).then(() => { - // store the build versions to not rebuilt the next time - newLastBuildVersion.forEach((value, key) => { - lastBuildVersion[key] = value; - }); - - // print old errors and keep them - utils.collections.forEach(oldErrors, entry => { - entry.value.forEach(diag => onError(diag)); - newErrors[entry.key] = entry.value; - }); - oldErrors = newErrors; - - // print stats - const headNow = process.memoryUsage().heapUsed; - const MB = 1024 * 1024; - _log( - '[tsb]', - `time: ${colors.yellow((Date.now() - t1) + 'ms')} + \nmem: ${colors.cyan(Math.ceil(headNow / MB) + 'MB')} ${colors.bgcyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}` - ); - headUsed = headNow; - }); - } - - return { - file, - build, - languageService: service - }; + const _log = config.logFn; + const host = new LanguageServiceHost(cmd, projectFile, _log); + const service = ts.createLanguageService(host, ts.createDocumentRegistry()); + const lastBuildVersion: { + [path: string]: string; + } = Object.create(null); + const lastDtsHash: { + [path: string]: string; + } = Object.create(null); + const userWantsDeclarations = cmd.options.declaration; + let oldErrors: { + [path: string]: ts.Diagnostic[]; + } = Object.create(null); + let headUsed = process.memoryUsage().heapUsed; + let emitSourceMapsInStream = true; + // always emit declaraction files + host.getCompilationSettings().declaration = true; + function file(file: Vinyl): void { + // support gulp-sourcemaps + if ((file).sourceMap) { + emitSourceMapsInStream = false; + } + if (!file.contents) { + host.removeScriptSnapshot(file.path); + } + else { + host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file)); + } + } + function baseFor(snapshot: ScriptSnapshot): string { + if (snapshot instanceof VinylScriptSnapshot) { + return cmd.options.outDir || snapshot.getBase(); + } + else { + return ''; + } + } + function isExternalModule(sourceFile: ts.SourceFile): boolean { + return (sourceFile).externalModuleIndicator + || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); + } + function build(out: (file: Vinyl) => void, onError: (err: any) => void, token = CancellationToken.None): Promise { + function checkSyntaxSoon(fileName: string): Promise { + return new Promise(resolve => { + process.nextTick(function () { + if (!host.getScriptSnapshot(fileName, false)) { + resolve([]); // no script, no problems + } + else { + resolve(service.getSyntacticDiagnostics(fileName)); + } + }); + }); + } + function checkSemanticsSoon(fileName: string): Promise { + return new Promise(resolve => { + process.nextTick(function () { + if (!host.getScriptSnapshot(fileName, false)) { + resolve([]); // no script, no problems + } + else { + resolve(service.getSemanticDiagnostics(fileName)); + } + }); + }); + } + function emitSoon(fileName: string): Promise<{ + fileName: string; + signature?: string; + files: Vinyl[]; + }> { + return new Promise(resolve => { + process.nextTick(function () { + if (/\.d\.ts$/.test(fileName)) { + // if it's already a d.ts file just emit it signature + const snapshot = host.getScriptSnapshot(fileName); + const signature = crypto.createHash('sha256') + .update(snapshot.getText(0, snapshot.getLength())) + .digest('base64'); + return resolve({ + fileName, + signature, + files: [] + }); + } + const output = service.getEmitOutput(fileName); + const files: Vinyl[] = []; + let signature: string | undefined; + for (const file of output.outputFiles) { + if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) { + continue; + } + if (/\.d\.ts$/.test(file.name)) { + signature = crypto.createHash('sha256') + .update(file.text) + .digest('base64'); + if (!userWantsDeclarations) { + // don't leak .d.ts files if users don't want them + continue; + } + } + const vinyl = new Vinyl({ + path: file.name, + contents: Buffer.from(file.text), + base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined + }); + if (!emitSourceMapsInStream && /\.js$/.test(file.name)) { + const sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0]; + if (sourcemapFile) { + const extname = path.extname(vinyl.relative); + const basename = path.basename(vinyl.relative, extname); + const dirname = path.dirname(vinyl.relative); + const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; + let sourceMap = JSON.parse(sourcemapFile.text); + sourceMap.sources[0] = tsname.replace(/\\/g, '/'); + // check for an "input source" map and combine them + // in step 1 we extract all line edit from the input source map, and + // in step 2 we apply the line edits to the typescript source map + const snapshot = host.getScriptSnapshot(fileName); + if (snapshot instanceof VinylScriptSnapshot && snapshot.sourceMap) { + const inputSMC = new SourceMapConsumer(snapshot.sourceMap); + const tsSMC = new SourceMapConsumer(sourceMap); + let didChange = false; + const smg = new SourceMapGenerator({ + file: sourceMap.file, + sourceRoot: sourceMap.sourceRoot + }); + // step 1 + const lineEdits = new Map(); + inputSMC.eachMapping(m => { + if (m.originalLine === m.generatedLine) { + // same line mapping + let array = lineEdits.get(m.originalLine); + if (!array) { + array = []; + lineEdits.set(m.originalLine, array); + } + array.push([m.originalColumn, m.generatedColumn]); + } + else { + // NOT SUPPORTED + } + }); + // step 2 + tsSMC.eachMapping(m => { + didChange = true; + const edits = lineEdits.get(m.originalLine); + let originalColumnDelta = 0; + if (edits) { + for (const [from, to] of edits) { + if (to >= m.originalColumn) { + break; + } + originalColumnDelta = from - to; + } + } + smg.addMapping({ + source: m.source, + name: m.name, + generated: { line: m.generatedLine, column: m.generatedColumn }, + original: { line: m.originalLine, column: m.originalColumn + originalColumnDelta } + }); + }); + if (didChange) { + [tsSMC, inputSMC].forEach((consumer) => { + (consumer).sources.forEach((sourceFile: any) => { + (smg)._sources.add(sourceFile); + const sourceContent = consumer.sourceContentFor(sourceFile); + if (sourceContent !== null) { + smg.setSourceContent(sourceFile, sourceContent); + } + }); + }); + sourceMap = JSON.parse(smg.toString()); + // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; + // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { + // await fs.promises.writeFile(filename, smg.toString()); + // await fs.promises.writeFile('/Users/jrieken/Code/vscode/src2/' + vinyl.relative, vinyl.contents); + // }); + } + } + (vinyl).sourceMap = sourceMap; + } + } + files.push(vinyl); + } + resolve({ + fileName, + signature, + files + }); + }); + }); + } + const newErrors: { + [path: string]: ts.Diagnostic[]; + } = Object.create(null); + const t1 = Date.now(); + const toBeEmitted: string[] = []; + const toBeCheckedSyntactically: string[] = []; + const toBeCheckedSemantically: string[] = []; + const filesWithChangedSignature: string[] = []; + const dependentFiles: string[] = []; + const newLastBuildVersion = new Map(); + for (const fileName of host.getScriptFileNames()) { + if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) { + toBeEmitted.push(fileName); + toBeCheckedSyntactically.push(fileName); + toBeCheckedSemantically.push(fileName); + } + } + return new Promise(resolve => { + const semanticCheckInfo = new Map(); + const seenAsDependentFile = new Set(); + function workOnNext() { + let promise: Promise | undefined; + // let fileName: string; + // someone told us to stop this + if (token.isCancellationRequested()) { + _log('[CANCEL]', '>>This compile run was cancelled<<'); + newLastBuildVersion.clear(); + resolve(); + return; + } + // (1st) emit code + else if (toBeEmitted.length) { + const fileName = toBeEmitted.pop()!; + promise = emitSoon(fileName).then(value => { + for (const file of value.files) { + _log('[emit code]', file.path); + out(file); + } + // remember when this was build + newLastBuildVersion.set(fileName, host.getScriptVersion(fileName)); + // remeber the signature + if (value.signature && lastDtsHash[fileName] !== value.signature) { + lastDtsHash[fileName] = value.signature; + filesWithChangedSignature.push(fileName); + } + }).catch(e => { + // can't just skip this or make a result up.. + host.error(`ERROR emitting ${fileName}`); + host.error(e); + }); + } + // (2nd) check syntax + else if (toBeCheckedSyntactically.length) { + const fileName = toBeCheckedSyntactically.pop()!; + _log('[check syntax]', fileName); + promise = checkSyntaxSoon(fileName).then(diagnostics => { + delete oldErrors[fileName]; + if (diagnostics.length > 0) { + diagnostics.forEach(d => onError(d)); + newErrors[fileName] = diagnostics; + // stop the world when there are syntax errors + toBeCheckedSyntactically.length = 0; + toBeCheckedSemantically.length = 0; + filesWithChangedSignature.length = 0; + } + }); + } + // (3rd) check semantics + else if (toBeCheckedSemantically.length) { + let fileName = toBeCheckedSemantically.pop(); + while (fileName && semanticCheckInfo.has(fileName)) { + fileName = toBeCheckedSemantically.pop()!; + } + if (fileName) { + _log('[check semantics]', fileName); + promise = checkSemanticsSoon(fileName).then(diagnostics => { + delete oldErrors[fileName!]; + semanticCheckInfo.set(fileName!, diagnostics.length); + if (diagnostics.length > 0) { + diagnostics.forEach(d => onError(d)); + newErrors[fileName!] = diagnostics; + } + }); + } + } + // (4th) check dependents + else if (filesWithChangedSignature.length) { + while (filesWithChangedSignature.length) { + const fileName = filesWithChangedSignature.pop()!; + if (!isExternalModule(service.getProgram()!.getSourceFile(fileName)!)) { + _log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet'); + toBeCheckedSemantically.push(...host.getScriptFileNames()); + filesWithChangedSignature.length = 0; + dependentFiles.length = 0; + break; + } + host.collectDependents(fileName, dependentFiles); + } + } + // (5th) dependents contd + else if (dependentFiles.length) { + let fileName = dependentFiles.pop(); + while (fileName && seenAsDependentFile.has(fileName)) { + fileName = dependentFiles.pop(); + } + if (fileName) { + seenAsDependentFile.add(fileName); + const value = semanticCheckInfo.get(fileName); + if (value === 0) { + // already validated successfully -> look at dependents next + host.collectDependents(fileName, dependentFiles); + } + else if (typeof value === 'undefined') { + // first validate -> look at dependents next + dependentFiles.push(fileName); + toBeCheckedSemantically.push(fileName); + } + } + } + // (last) done + else { + resolve(); + return; + } + if (!promise) { + promise = Promise.resolve(); + } + promise.then(function () { + // change to change + process.nextTick(workOnNext); + }).catch(err => { + console.error(err); + }); + } + workOnNext(); + }).then(() => { + // store the build versions to not rebuilt the next time + newLastBuildVersion.forEach((value, key) => { + lastBuildVersion[key] = value; + }); + // print old errors and keep them + utils.collections.forEach(oldErrors, entry => { + entry.value.forEach(diag => onError(diag)); + newErrors[entry.key] = entry.value; + }); + oldErrors = newErrors; + // print stats + const headNow = process.memoryUsage().heapUsed; + const MB = 1024 * 1024; + _log('[tsb]', `time: ${colors.yellow((Date.now() - t1) + 'ms')} + \nmem: ${colors.cyan(Math.ceil(headNow / MB) + 'MB')} ${colors.bgcyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}`); + headUsed = headNow; + }); + } + return { + file, + build, + languageService: service + }; } - class ScriptSnapshot implements ts.IScriptSnapshot { - - private readonly _text: string; - private readonly _mtime: Date; - - constructor(text: string, mtime: Date) { - this._text = text; - this._mtime = mtime; - } - - getVersion(): string { - return this._mtime.toUTCString(); - } - - getText(start: number, end: number): string { - return this._text.substring(start, end); - } - - getLength(): number { - return this._text.length; - } - - getChangeRange(_oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange | undefined { - return undefined; - } + private readonly _text: string; + private readonly _mtime: Date; + constructor(text: string, mtime: Date) { + this._text = text; + this._mtime = mtime; + } + getVersion(): string { + return this._mtime.toUTCString(); + } + getText(start: number, end: number): string { + return this._text.substring(start, end); + } + getLength(): number { + return this._text.length; + } + getChangeRange(_oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange | undefined { + return undefined; + } } - class VinylScriptSnapshot extends ScriptSnapshot { - - private readonly _base: string; - - readonly sourceMap?: RawSourceMap; - - constructor(file: Vinyl & { sourceMap?: RawSourceMap }) { - super(file.contents!.toString(), file.stat!.mtime); - this._base = file.base; - this.sourceMap = file.sourceMap; - } - - getBase(): string { - return this._base; - } + private readonly _base: string; + readonly sourceMap?: RawSourceMap; + constructor(file: Vinyl & { + sourceMap?: RawSourceMap; + }) { + super(file.contents!.toString(), file.stat!.mtime); + this._base = file.base; + this.sourceMap = file.sourceMap; + } + getBase(): string { + return this._base; + } } - class LanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _snapshots: { [path: string]: ScriptSnapshot }; - private readonly _filesInProject: Set; - private readonly _filesAdded: Set; - private readonly _dependencies: utils.graph.Graph; - private readonly _dependenciesRecomputeList: string[]; - private readonly _fileNameToDeclaredModule: { [path: string]: string[] }; - - private _projectVersion: number; - - constructor( - private readonly _cmdLine: ts.ParsedCommandLine, - private readonly _projectPath: string, - private readonly _log: (topic: string, message: string) => void - ) { - this._snapshots = Object.create(null); - this._filesInProject = new Set(_cmdLine.fileNames); - this._filesAdded = new Set(); - this._dependencies = new utils.graph.Graph(s => s); - this._dependenciesRecomputeList = []; - this._fileNameToDeclaredModule = Object.create(null); - - this._projectVersion = 1; - } - - log(_s: string): void { - // console.log(s); - } - - trace(_s: string): void { - // console.log(s); - } - - error(s: string): void { - console.error(s); - } - - getCompilationSettings(): ts.CompilerOptions { - return this._cmdLine.options; - } - - getProjectVersion(): string { - return String(this._projectVersion); - } - - getScriptFileNames(): string[] { - const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path)); - return res; - } - - getScriptVersion(filename: string): string { - filename = normalize(filename); - const result = this._snapshots[filename]; - if (result) { - return result.getVersion(); - } - return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2); - } - - getScriptSnapshot(filename: string, resolve: boolean = true): ScriptSnapshot { - filename = normalize(filename); - let result = this._snapshots[filename]; - if (!result && resolve) { - try { - result = new VinylScriptSnapshot(new Vinyl({ - path: filename, - contents: fs.readFileSync(filename), - base: this.getCompilationSettings().outDir, - stat: fs.statSync(filename) - })); - this.addScriptSnapshot(filename, result); - } catch (e) { - // ignore - } - } - return result; - } - - private static _declareModule = /declare\s+module\s+('|")(.+)\1/g; - - addScriptSnapshot(filename: string, snapshot: ScriptSnapshot): ScriptSnapshot { - this._projectVersion++; - filename = normalize(filename); - const old = this._snapshots[filename]; - if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) { - // ^^^^^^^^^^^^^^^^^^^^^^^^^^ - // not very proper! - this._filesAdded.add(filename); - } - if (!old || old.getVersion() !== snapshot.getVersion()) { - this._dependenciesRecomputeList.push(filename); - const node = this._dependencies.lookup(filename); - if (node) { - node.outgoing = Object.create(null); - } - - // (cheap) check for declare module - LanguageServiceHost._declareModule.lastIndex = 0; - let match: RegExpExecArray | null | undefined; - while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) { - let declaredModules = this._fileNameToDeclaredModule[filename]; - if (!declaredModules) { - this._fileNameToDeclaredModule[filename] = declaredModules = []; - } - declaredModules.push(match[2]); - } - } - this._snapshots[filename] = snapshot; - return old; - } - - removeScriptSnapshot(filename: string): boolean { - this._filesInProject.delete(filename); - this._filesAdded.delete(filename); - this._projectVersion++; - filename = normalize(filename); - delete this._fileNameToDeclaredModule[filename]; - return delete this._snapshots[filename]; - } - - getCurrentDirectory(): string { - return path.dirname(this._projectPath); - } - - getDefaultLibFileName(options: ts.CompilerOptions): string { - return ts.getDefaultLibFilePath(options); - } - - readonly directoryExists = ts.sys.directoryExists; - readonly getDirectories = ts.sys.getDirectories; - readonly fileExists = ts.sys.fileExists; - readonly readFile = ts.sys.readFile; - readonly readDirectory = ts.sys.readDirectory; - - // ---- dependency management - - collectDependents(filename: string, target: string[]): void { - while (this._dependenciesRecomputeList.length) { - this._processFile(this._dependenciesRecomputeList.pop()!); - } - filename = normalize(filename); - const node = this._dependencies.lookup(filename); - if (node) { - utils.collections.forEach(node.incoming, entry => target.push(entry.key)); - } - } - - _processFile(filename: string): void { - if (filename.match(/.*\.d\.ts$/)) { - return; - } - filename = normalize(filename); - const snapshot = this.getScriptSnapshot(filename); - if (!snapshot) { - this._log('processFile', `Missing snapshot for: ${filename}`); - return; - } - const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); - - // (1) ///-references - info.referencedFiles.forEach(ref => { - const resolvedPath = path.resolve(path.dirname(filename), ref.fileName); - const normalizedPath = normalize(resolvedPath); - - this._dependencies.inertEdge(filename, normalizedPath); - }); - - // (2) import-require statements - info.importedFiles.forEach(ref => { - const stopDirname = normalize(this.getCurrentDirectory()); - let dirname = filename; - let found = false; - - while (!found && dirname.indexOf(stopDirname) === 0) { - dirname = path.dirname(dirname); - let resolvedPath = path.resolve(dirname, ref.fileName); - if (resolvedPath.endsWith('.js')) { - resolvedPath = resolvedPath.slice(0, -3); - } - const normalizedPath = normalize(resolvedPath); - - if (this.getScriptSnapshot(normalizedPath + '.ts')) { - this._dependencies.inertEdge(filename, normalizedPath + '.ts'); - found = true; - - } else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) { - this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); - found = true; - } - } - - if (!found) { - for (const key in this._fileNameToDeclaredModule) { - if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) { - this._dependencies.inertEdge(filename, key); - } - } - } - }); - } + private readonly _snapshots: { + [path: string]: ScriptSnapshot; + }; + private readonly _filesInProject: Set; + private readonly _filesAdded: Set; + private readonly _dependencies: utils.graph.Graph; + private readonly _dependenciesRecomputeList: string[]; + private readonly _fileNameToDeclaredModule: { + [path: string]: string[]; + }; + private _projectVersion: number; + constructor(private readonly _cmdLine: ts.ParsedCommandLine, private readonly _projectPath: string, private readonly _log: (topic: string, message: string) => void) { + this._snapshots = Object.create(null); + this._filesInProject = new Set(_cmdLine.fileNames); + this._filesAdded = new Set(); + this._dependencies = new utils.graph.Graph(s => s); + this._dependenciesRecomputeList = []; + this._fileNameToDeclaredModule = Object.create(null); + this._projectVersion = 1; + } + log(_s: string): void { + // console.log(s); + } + trace(_s: string): void { + // console.log(s); + } + error(s: string): void { + console.error(s); + } + getCompilationSettings(): ts.CompilerOptions { + return this._cmdLine.options; + } + getProjectVersion(): string { + return String(this._projectVersion); + } + getScriptFileNames(): string[] { + const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path)); + return res; + } + getScriptVersion(filename: string): string { + filename = normalize(filename); + const result = this._snapshots[filename]; + if (result) { + return result.getVersion(); + } + return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2); + } + getScriptSnapshot(filename: string, resolve: boolean = true): ScriptSnapshot { + filename = normalize(filename); + let result = this._snapshots[filename]; + if (!result && resolve) { + try { + result = new VinylScriptSnapshot(new Vinyl({ + path: filename, + contents: fs.readFileSync(filename), + base: this.getCompilationSettings().outDir, + stat: fs.statSync(filename) + })); + this.addScriptSnapshot(filename, result); + } + catch (e) { + // ignore + } + } + return result; + } + private static _declareModule = /declare\s+module\s+('|")(.+)\1/g; + addScriptSnapshot(filename: string, snapshot: ScriptSnapshot): ScriptSnapshot { + this._projectVersion++; + filename = normalize(filename); + const old = this._snapshots[filename]; + if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ + // not very proper! + this._filesAdded.add(filename); + } + if (!old || old.getVersion() !== snapshot.getVersion()) { + this._dependenciesRecomputeList.push(filename); + const node = this._dependencies.lookup(filename); + if (node) { + node.outgoing = Object.create(null); + } + // (cheap) check for declare module + LanguageServiceHost._declareModule.lastIndex = 0; + let match: RegExpExecArray | null | undefined; + while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) { + let declaredModules = this._fileNameToDeclaredModule[filename]; + if (!declaredModules) { + this._fileNameToDeclaredModule[filename] = declaredModules = []; + } + declaredModules.push(match[2]); + } + } + this._snapshots[filename] = snapshot; + return old; + } + removeScriptSnapshot(filename: string): boolean { + this._filesInProject.delete(filename); + this._filesAdded.delete(filename); + this._projectVersion++; + filename = normalize(filename); + delete this._fileNameToDeclaredModule[filename]; + return delete this._snapshots[filename]; + } + getCurrentDirectory(): string { + return path.dirname(this._projectPath); + } + getDefaultLibFileName(options: ts.CompilerOptions): string { + return ts.getDefaultLibFilePath(options); + } + readonly directoryExists = ts.sys.directoryExists; + readonly getDirectories = ts.sys.getDirectories; + readonly fileExists = ts.sys.fileExists; + readonly readFile = ts.sys.readFile; + readonly readDirectory = ts.sys.readDirectory; + // ---- dependency management + collectDependents(filename: string, target: string[]): void { + while (this._dependenciesRecomputeList.length) { + this._processFile(this._dependenciesRecomputeList.pop()!); + } + filename = normalize(filename); + const node = this._dependencies.lookup(filename); + if (node) { + utils.collections.forEach(node.incoming, entry => target.push(entry.key)); + } + } + _processFile(filename: string): void { + if (filename.match(/.*\.d\.ts$/)) { + return; + } + filename = normalize(filename); + const snapshot = this.getScriptSnapshot(filename); + if (!snapshot) { + this._log('processFile', `Missing snapshot for: ${filename}`); + return; + } + const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); + // (1) ///-references + info.referencedFiles.forEach(ref => { + const resolvedPath = path.resolve(path.dirname(filename), ref.fileName); + const normalizedPath = normalize(resolvedPath); + this._dependencies.inertEdge(filename, normalizedPath); + }); + // (2) import-require statements + info.importedFiles.forEach(ref => { + const stopDirname = normalize(this.getCurrentDirectory()); + let dirname = filename; + let found = false; + while (!found && dirname.indexOf(stopDirname) === 0) { + dirname = path.dirname(dirname); + let resolvedPath = path.resolve(dirname, ref.fileName); + if (resolvedPath.endsWith('.js')) { + resolvedPath = resolvedPath.slice(0, -3); + } + const normalizedPath = normalize(resolvedPath); + if (this.getScriptSnapshot(normalizedPath + '.ts')) { + this._dependencies.inertEdge(filename, normalizedPath + '.ts'); + found = true; + } + else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) { + this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); + found = true; + } + } + if (!found) { + for (const key in this._fileNameToDeclaredModule) { + if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) { + this._dependencies.inertEdge(filename, key); + } + } + } + }); + } } diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 53c752d2655ab..ca7a027beb781 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as Vinyl from 'vinyl'; import * as through from 'through'; import * as builder from './builder'; @@ -14,155 +13,140 @@ import { readFileSync, statSync } from 'fs'; import * as log from 'fancy-log'; import { ESBuildTranspiler, ITranspiler, TscTranspiler } from './transpiler'; import colors = require('ansi-colors'); - export interface IncrementalCompiler { - (token?: any): Readable & Writable; - src(opts?: { cwd?: string; base?: string }): Readable; + (token?: any): Readable & Writable; + src(opts?: { + cwd?: string; + base?: string; + }): Readable; } - class EmptyDuplex extends Duplex { - _write(_chunk: any, _encoding: string, callback: (err?: Error) => void): void { callback(); } - _read() { this.push(null); } + _write(_chunk: any, _encoding: string, callback: (err?: Error) => void): void { callback(); } + _read() { this.push(null); } } - function createNullCompiler(): IncrementalCompiler { - const result: IncrementalCompiler = function () { return new EmptyDuplex(); }; - result.src = () => new EmptyDuplex(); - return result; + const result: IncrementalCompiler = function () { return new EmptyDuplex(); }; + result.src = () => new EmptyDuplex(); + return result; } - const _defaultOnError = (err: string) => console.log(JSON.stringify(err, null, 4)); - -export function create( - projectPath: string, - existingOptions: Partial, - config: { verbose?: boolean; transpileOnly?: boolean; transpileOnlyIncludesDts?: boolean; transpileWithSwc?: boolean }, - onError: (message: string) => void = _defaultOnError -): IncrementalCompiler { - - function printDiagnostic(diag: ts.Diagnostic | Error): void { - - if (diag instanceof Error) { - onError(diag.message); - } else if (!diag.file || !diag.start) { - onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n')); - } else { - const lineAndCh = diag.file.getLineAndCharacterOfPosition(diag.start); - onError(strings.format('{0}({1},{2}): {3}', - diag.file.fileName, - lineAndCh.line + 1, - lineAndCh.character + 1, - ts.flattenDiagnosticMessageText(diag.messageText, '\n')) - ); - } - } - - const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); - if (parsed.error) { - printDiagnostic(parsed.error); - return createNullCompiler(); - } - - const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname(projectPath), existingOptions); - if (cmdLine.errors.length > 0) { - cmdLine.errors.forEach(printDiagnostic); - return createNullCompiler(); - } - - function logFn(topic: string, message: string): void { - if (config.verbose) { - log(colors.cyan(topic), message); - } - } - - // FULL COMPILE stream doing transpile, syntax and semantic diagnostics - function createCompileStream(builder: builder.ITypeScriptBuilder, token?: builder.CancellationToken): Readable & Writable { - - return through(function (this: through.ThroughStream, file: Vinyl) { - // give the file to the compiler - if (file.isStream()) { - this.emit('error', 'no support for streams'); - return; - } - builder.file(file); - - }, function (this: { queue(a: any): void }) { - // start the compilation process - builder.build( - file => this.queue(file), - printDiagnostic, - token - ).catch(e => console.error(e)).then(() => this.queue(null)); - }); - } - - // TRANSPILE ONLY stream doing just TS to JS conversion - function createTranspileStream(transpiler: ITranspiler): Readable & Writable { - return through(function (this: through.ThroughStream & { queue(a: any): void }, file: Vinyl) { - // give the file to the compiler - if (file.isStream()) { - this.emit('error', 'no support for streams'); - return; - } - if (!file.contents) { - return; - } - if (!config.transpileOnlyIncludesDts && file.path.endsWith('.d.ts')) { - return; - } - - if (!transpiler.onOutfile) { - transpiler.onOutfile = file => this.queue(file); - } - - transpiler.transpile(file); - - }, function (this: { queue(a: any): void }) { - transpiler.join().then(() => { - this.queue(null); - transpiler.onOutfile = undefined; - }); - }); - } - - - let result: IncrementalCompiler; - if (config.transpileOnly) { - const transpiler = !config.transpileWithSwc - ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) - : new ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); - result = (() => createTranspileStream(transpiler)); - } else { - const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)); - } - - result.src = (opts?: { cwd?: string; base?: string }) => { - let _pos = 0; - const _fileNames = cmdLine.fileNames.slice(0); - return new class extends Readable { - constructor() { - super({ objectMode: true }); - } - _read() { - let more: boolean = true; - let path: string; - for (; more && _pos < _fileNames.length; _pos++) { - path = _fileNames[_pos]; - more = this.push(new Vinyl({ - path, - contents: readFileSync(path), - stat: statSync(path), - cwd: opts && opts.cwd, - base: opts && opts.base || dirname(projectPath) - })); - } - if (_pos >= _fileNames.length) { - this.push(null); - } - } - }; - }; - - return result; +export function create(projectPath: string, existingOptions: Partial, config: { + verbose?: boolean; + transpileOnly?: boolean; + transpileOnlyIncludesDts?: boolean; + transpileWithSwc?: boolean; +}, onError: (message: string) => void = _defaultOnError): IncrementalCompiler { + function printDiagnostic(diag: ts.Diagnostic | Error): void { + if (diag instanceof Error) { + onError(diag.message); + } + else if (!diag.file || !diag.start) { + onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n')); + } + else { + const lineAndCh = diag.file.getLineAndCharacterOfPosition(diag.start); + onError(strings.format('{0}({1},{2}): {3}', diag.file.fileName, lineAndCh.line + 1, lineAndCh.character + 1, ts.flattenDiagnosticMessageText(diag.messageText, '\n'))); + } + } + const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); + if (parsed.error) { + printDiagnostic(parsed.error); + return createNullCompiler(); + } + const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname(projectPath), existingOptions); + if (cmdLine.errors.length > 0) { + cmdLine.errors.forEach(printDiagnostic); + return createNullCompiler(); + } + function logFn(topic: string, message: string): void { + if (config.verbose) { + log(colors.cyan(topic), message); + } + } + // FULL COMPILE stream doing transpile, syntax and semantic diagnostics + function createCompileStream(builder: builder.ITypeScriptBuilder, token?: builder.CancellationToken): Readable & Writable { + return through(function (this: through.ThroughStream, file: Vinyl) { + // give the file to the compiler + if (file.isStream()) { + this.emit('error', 'no support for streams'); + return; + } + builder.file(file); + }, function (this: { + queue(a: any): void; + }) { + // start the compilation process + builder.build(file => this.queue(file), printDiagnostic, token).catch(e => console.error(e)).then(() => this.queue(null)); + }); + } + // TRANSPILE ONLY stream doing just TS to JS conversion + function createTranspileStream(transpiler: ITranspiler): Readable & Writable { + return through(function (this: through.ThroughStream & { + queue(a: any): void; + }, file: Vinyl) { + // give the file to the compiler + if (file.isStream()) { + this.emit('error', 'no support for streams'); + return; + } + if (!file.contents) { + return; + } + if (!config.transpileOnlyIncludesDts && file.path.endsWith('.d.ts')) { + return; + } + if (!transpiler.onOutfile) { + transpiler.onOutfile = file => this.queue(file); + } + transpiler.transpile(file); + }, function (this: { + queue(a: any): void; + }) { + transpiler.join().then(() => { + this.queue(null); + transpiler.onOutfile = undefined; + }); + }); + } + let result: IncrementalCompiler; + if (config.transpileOnly) { + const transpiler = !config.transpileWithSwc + ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) + : new ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); + result = (() => createTranspileStream(transpiler)); + } + else { + const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); + result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)); + } + result.src = (opts?: { + cwd?: string; + base?: string; + }) => { + let _pos = 0; + const _fileNames = cmdLine.fileNames.slice(0); + return new class extends Readable { + constructor() { + super({ objectMode: true }); + } + _read() { + let more: boolean = true; + let path: string; + for (; more && _pos < _fileNames.length; _pos++) { + path = _fileNames[_pos]; + more = this.push(new Vinyl({ + path, + contents: readFileSync(path), + stat: statSync(path), + cwd: opts && opts.cwd, + base: opts && opts.base || dirname(projectPath) + })); + } + if (_pos >= _fileNames.length) { + this.push(null); + } + } + }; + }; + return result; } diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index ae841dcf88b74..2a2ec3a5bd338 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -2,380 +2,313 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as esbuild from 'esbuild'; import * as ts from 'typescript'; import * as threads from 'node:worker_threads'; import * as Vinyl from 'vinyl'; import { cpus } from 'node:os'; - interface TranspileReq { - readonly tsSrcs: string[]; - readonly options: ts.TranspileOptions; + readonly tsSrcs: string[]; + readonly options: ts.TranspileOptions; } - interface TranspileRes { - readonly jsSrcs: string[]; - readonly diagnostics: ts.Diagnostic[][]; + readonly jsSrcs: string[]; + readonly diagnostics: ts.Diagnostic[][]; } - -function transpile(tsSrc: string, options: ts.TranspileOptions): { jsSrc: string; diag: ts.Diagnostic[] } { - - const isAmd = /\n(import|export)/m.test(tsSrc); - if (!isAmd && options.compilerOptions?.module === ts.ModuleKind.AMD) { - // enforce NONE module-system for not-amd cases - options = { ...options, ...{ compilerOptions: { ...options.compilerOptions, module: ts.ModuleKind.None } } }; - } - const out = ts.transpileModule(tsSrc, options); - return { - jsSrc: out.outputText, - diag: out.diagnostics ?? [] - }; +function transpile(tsSrc: string, options: ts.TranspileOptions): { + jsSrc: string; + diag: ts.Diagnostic[]; +} { + const isAmd = /\n(import|export)/m.test(tsSrc); + if (!isAmd && options.compilerOptions?.module === ts.ModuleKind.AMD) { + // enforce NONE module-system for not-amd cases + options = { ...options, ...{ compilerOptions: { ...options.compilerOptions, module: ts.ModuleKind.None } } }; + } + const out = ts.transpileModule(tsSrc, options); + return { + jsSrc: out.outputText, + diag: out.diagnostics ?? [] + }; } - if (!threads.isMainThread) { - // WORKER - threads.parentPort?.addListener('message', (req: TranspileReq) => { - const res: TranspileRes = { - jsSrcs: [], - diagnostics: [] - }; - for (const tsSrc of req.tsSrcs) { - const out = transpile(tsSrc, req.options); - res.jsSrcs.push(out.jsSrc); - res.diagnostics.push(out.diag); - } - threads.parentPort!.postMessage(res); - }); + // WORKER + threads.parentPort?.addListener('message', (req: TranspileReq) => { + const res: TranspileRes = { + jsSrcs: [], + diagnostics: [] + }; + for (const tsSrc of req.tsSrcs) { + const out = transpile(tsSrc, req.options); + res.jsSrcs.push(out.jsSrc); + res.diagnostics.push(out.diag); + } + threads.parentPort!.postMessage(res); + }); } - class OutputFileNameOracle { - - readonly getOutputFileName: (name: string) => string; - - constructor(cmdLine: ts.ParsedCommandLine, configFilePath: string) { - // very complicated logic to re-use TS internal functions to know the output path - // given a TS input path and its config - type InternalTsApi = typeof ts & { - normalizePath(path: string): string; - getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; - }; - this.getOutputFileName = (file) => { - try { - - // windows: path-sep normalizing - file = (ts).normalizePath(file); - - if (!cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - cmdLine.options.configFilePath = configFilePath; - } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - cmdLine.fileNames.push(file); - } - const outfile = (ts).getOutputFileNames(cmdLine, file, true)[0]; - if (isDts) { - cmdLine.fileNames.pop(); - } - return outfile; - - } catch (err) { - console.error(file, cmdLine.fileNames); - console.error(err); - throw err; - } - }; - } + readonly getOutputFileName: (name: string) => string; + constructor(cmdLine: ts.ParsedCommandLine, configFilePath: string) { + // very complicated logic to re-use TS internal functions to know the output path + // given a TS input path and its config + type InternalTsApi = typeof ts & { + normalizePath(path: string): string; + getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; + }; + this.getOutputFileName = (file) => { + try { + // windows: path-sep normalizing + file = (ts).normalizePath(file); + if (!cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + cmdLine.fileNames.push(file); + } + const outfile = (ts).getOutputFileNames(cmdLine, file, true)[0]; + if (isDts) { + cmdLine.fileNames.pop(); + } + return outfile; + } + catch (err) { + console.error(file, cmdLine.fileNames); + console.error(err); + throw err; + } + }; + } } - class TranspileWorker { - - private static pool = 1; - - readonly id = TranspileWorker.pool++; - - private _worker = new threads.Worker(__filename); - private _pending?: [resolve: Function, reject: Function, file: Vinyl[], options: ts.TranspileOptions, t1: number]; - private _durations: number[] = []; - - constructor(outFileFn: (fileName: string) => string) { - - this._worker.addListener('message', (res: TranspileRes) => { - if (!this._pending) { - console.error('RECEIVING data WITHOUT request'); - return; - } - - const [resolve, reject, files, options, t1] = this._pending; - - const outFiles: Vinyl[] = []; - const diag: ts.Diagnostic[] = []; - - for (let i = 0; i < res.jsSrcs.length; i++) { - // inputs and outputs are aligned across the arrays - const file = files[i]; - const jsSrc = res.jsSrcs[i]; - const diag = res.diagnostics[i]; - - if (diag.length > 0) { - diag.push(...diag); - continue; - } - const enum SuffixTypes { - Dts = 5, - Ts = 3, - Unknown = 0 - } - const suffixLen = file.path.endsWith('.d.ts') ? SuffixTypes.Dts - : file.path.endsWith('.ts') ? SuffixTypes.Ts - : SuffixTypes.Unknown; - - // check if output of a DTS-files isn't just "empty" and iff so - // skip this file - if (suffixLen === SuffixTypes.Dts && _isDefaultEmpty(jsSrc)) { - continue; - } - - const outBase = options.compilerOptions?.outDir ?? file.base; - const outPath = outFileFn(file.path); - - outFiles.push(new Vinyl({ - path: outPath, - base: outBase, - contents: Buffer.from(jsSrc), - })); - } - - this._pending = undefined; - this._durations.push(Date.now() - t1); - - if (diag.length > 0) { - reject(diag); - } else { - resolve(outFiles); - } - }); - } - - terminate() { - // console.log(`Worker#${this.id} ENDS after ${this._durations.length} jobs (total: ${this._durations.reduce((p, c) => p + c, 0)}, avg: ${this._durations.reduce((p, c) => p + c, 0) / this._durations.length})`); - this._worker.terminate(); - } - - get isBusy() { - return this._pending !== undefined; - } - - next(files: Vinyl[], options: ts.TranspileOptions) { - if (this._pending !== undefined) { - throw new Error('BUSY'); - } - return new Promise((resolve, reject) => { - this._pending = [resolve, reject, files, options, Date.now()]; - const req: TranspileReq = { - options, - tsSrcs: files.map(file => String(file.contents)) - }; - this._worker.postMessage(req); - }); - } + private static pool = 1; + readonly id = TranspileWorker.pool++; + private _worker = new threads.Worker(__filename); + private _pending?: [ + resolve: Function, + reject: Function, + file: Vinyl[], + options: ts.TranspileOptions, + t1: number + ]; + private _durations: number[] = []; + constructor(outFileFn: (fileName: string) => string) { + this._worker.addListener('message', (res: TranspileRes) => { + if (!this._pending) { + console.error('RECEIVING data WITHOUT request'); + return; + } + const [resolve, reject, files, options, t1] = this._pending; + const outFiles: Vinyl[] = []; + const diag: ts.Diagnostic[] = []; + for (let i = 0; i < res.jsSrcs.length; i++) { + // inputs and outputs are aligned across the arrays + const file = files[i]; + const jsSrc = res.jsSrcs[i]; + const diag = res.diagnostics[i]; + if (diag.length > 0) { + diag.push(...diag); + continue; + } + const enum SuffixTypes { + Dts = 5, + Ts = 3, + Unknown = 0 + } + const suffixLen = file.path.endsWith('.d.ts') ? SuffixTypes.Dts + : file.path.endsWith('.ts') ? SuffixTypes.Ts + : SuffixTypes.Unknown; + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (suffixLen === SuffixTypes.Dts && _isDefaultEmpty(jsSrc)) { + continue; + } + const outBase = options.compilerOptions?.outDir ?? file.base; + const outPath = outFileFn(file.path); + outFiles.push(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(jsSrc), + })); + } + this._pending = undefined; + this._durations.push(Date.now() - t1); + if (diag.length > 0) { + reject(diag); + } + else { + resolve(outFiles); + } + }); + } + terminate() { + // console.log(`Worker#${this.id} ENDS after ${this._durations.length} jobs (total: ${this._durations.reduce((p, c) => p + c, 0)}, avg: ${this._durations.reduce((p, c) => p + c, 0) / this._durations.length})`); + this._worker.terminate(); + } + get isBusy() { + return this._pending !== undefined; + } + next(files: Vinyl[], options: ts.TranspileOptions) { + if (this._pending !== undefined) { + throw new Error('BUSY'); + } + return new Promise((resolve, reject) => { + this._pending = [resolve, reject, files, options, Date.now()]; + const req: TranspileReq = { + options, + tsSrcs: files.map(file => String(file.contents)) + }; + this._worker.postMessage(req); + }); + } } - export interface ITranspiler { - onOutfile?: (file: Vinyl) => void; - join(): Promise; - transpile(file: Vinyl): void; + onOutfile?: (file: Vinyl) => void; + join(): Promise; + transpile(file: Vinyl): void; } - export class TscTranspiler implements ITranspiler { - - static P = Math.floor(cpus().length * .5); - - private readonly _outputFileNames: OutputFileNameOracle; - - - public onOutfile?: (file: Vinyl) => void; - - private _workerPool: TranspileWorker[] = []; - private _queue: Vinyl[] = []; - private _allJobs: Promise[] = []; - - constructor( - logFn: (topic: string, message: string) => void, - private readonly _onError: (err: any) => void, - configFilePath: string, - private readonly _cmdLine: ts.ParsedCommandLine - ) { - logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); - this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); - } - - async join() { - // wait for all penindg jobs - this._consumeQueue(); - await Promise.allSettled(this._allJobs); - this._allJobs.length = 0; - - // terminate all worker - this._workerPool.forEach(w => w.terminate()); - this._workerPool.length = 0; - } - - - transpile(file: Vinyl) { - - if (this._cmdLine.options.noEmit) { - // not doing ANYTHING here - return; - } - - const newLen = this._queue.push(file); - if (newLen > TscTranspiler.P ** 2) { - this._consumeQueue(); - } - } - - private _consumeQueue(): void { - - if (this._queue.length === 0) { - // no work... - return; - } - - // kinda LAZYily create workers - if (this._workerPool.length === 0) { - for (let i = 0; i < TscTranspiler.P; i++) { - this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); - } - } - - const freeWorker = this._workerPool.filter(w => !w.isBusy); - if (freeWorker.length === 0) { - // OK, they will pick up work themselves - return; - } - - for (const worker of freeWorker) { - if (this._queue.length === 0) { - break; - } - - const job = new Promise(resolve => { - - const consume = () => { - const files = this._queue.splice(0, TscTranspiler.P); - if (files.length === 0) { - // DONE - resolve(undefined); - return; - } - // work on the NEXT file - // const [inFile, outFn] = req; - worker.next(files, { compilerOptions: this._cmdLine.options }).then(outFiles => { - if (this.onOutfile) { - outFiles.map(this.onOutfile, this); - } - consume(); - }).catch(err => { - this._onError(err); - }); - }; - - consume(); - }); - - this._allJobs.push(job); - } - } + static P = Math.floor(cpus().length * .5); + private readonly _outputFileNames: OutputFileNameOracle; + public onOutfile?: (file: Vinyl) => void; + private _workerPool: TranspileWorker[] = []; + private _queue: Vinyl[] = []; + private _allJobs: Promise[] = []; + constructor(logFn: (topic: string, message: string) => void, private readonly _onError: (err: any) => void, configFilePath: string, private readonly _cmdLine: ts.ParsedCommandLine) { + logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + } + async join() { + // wait for all penindg jobs + this._consumeQueue(); + await Promise.allSettled(this._allJobs); + this._allJobs.length = 0; + // terminate all worker + this._workerPool.forEach(w => w.terminate()); + this._workerPool.length = 0; + } + transpile(file: Vinyl) { + if (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + const newLen = this._queue.push(file); + if (newLen > TscTranspiler.P ** 2) { + this._consumeQueue(); + } + } + private _consumeQueue(): void { + if (this._queue.length === 0) { + // no work... + return; + } + // kinda LAZYily create workers + if (this._workerPool.length === 0) { + for (let i = 0; i < TscTranspiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); + } + } + const freeWorker = this._workerPool.filter(w => !w.isBusy); + if (freeWorker.length === 0) { + // OK, they will pick up work themselves + return; + } + for (const worker of freeWorker) { + if (this._queue.length === 0) { + break; + } + const job = new Promise(resolve => { + const consume = () => { + const files = this._queue.splice(0, TscTranspiler.P); + if (files.length === 0) { + // DONE + resolve(undefined); + return; + } + // work on the NEXT file + // const [inFile, outFn] = req; + worker.next(files, { compilerOptions: this._cmdLine.options }).then(outFiles => { + if (this.onOutfile) { + outFiles.map(this.onOutfile, this); + } + consume(); + }).catch(err => { + this._onError(err); + }); + }; + consume(); + }); + this._allJobs.push(job); + } + } } - export class ESBuildTranspiler implements ITranspiler { - - private readonly _outputFileNames: OutputFileNameOracle; - private _jobs: Promise[] = []; - - onOutfile?: ((file: Vinyl) => void) | undefined; - - private readonly _transformOpts: esbuild.TransformOptions; - - constructor( - private readonly _logFn: (topic: string, message: string) => void, - private readonly _onError: (err: any) => void, - configFilePath: string, - private readonly _cmdLine: ts.ParsedCommandLine - ) { - _logFn('Transpile', `will use ESBuild to transpile source files`); - this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); - - const isExtension = configFilePath.includes('extensions'); - - this._transformOpts = { - target: ['es2022'], - format: isExtension ? 'cjs' : 'esm', - platform: isExtension ? 'node' : undefined, - loader: 'ts', - sourcemap: 'inline', - tsconfigRaw: JSON.stringify({ - compilerOptions: { - ...this._cmdLine.options, - ...{ - module: isExtension ? ts.ModuleKind.CommonJS : undefined - } satisfies ts.CompilerOptions - } - }), - supported: { - 'class-static-blocks': false, // SEE https://github.com/evanw/esbuild/issues/3823, - 'dynamic-import': !isExtension, // see https://github.com/evanw/esbuild/issues/1281 - 'class-field': !isExtension - } - }; - } - - async join(): Promise { - const jobs = this._jobs.slice(); - this._jobs.length = 0; - await Promise.allSettled(jobs); - } - - transpile(file: Vinyl): void { - if (!(file.contents instanceof Buffer)) { - throw Error('file.contents must be a Buffer'); - } - const t1 = Date.now(); - this._jobs.push(esbuild.transform(file.contents, { - ...this._transformOpts, - sourcefile: file.path, - }).then(result => { - - // check if output of a DTS-files isn't just "empty" and iff so - // skip this file - if (file.path.endsWith('.d.ts') && _isDefaultEmpty(result.code)) { - return; - } - - const outBase = this._cmdLine.options.outDir ?? file.base; - const outPath = this._outputFileNames.getOutputFileName(file.path); - - this.onOutfile!(new Vinyl({ - path: outPath, - base: outBase, - contents: Buffer.from(result.code), - })); - - this._logFn('Transpile', `esbuild took ${Date.now() - t1}ms for ${file.path}`); - - }).catch(err => { - this._onError(err); - })); - } + private readonly _outputFileNames: OutputFileNameOracle; + private _jobs: Promise[] = []; + onOutfile?: ((file: Vinyl) => void) | undefined; + private readonly _transformOpts: esbuild.TransformOptions; + constructor(private readonly _logFn: (topic: string, message: string) => void, private readonly _onError: (err: any) => void, configFilePath: string, private readonly _cmdLine: ts.ParsedCommandLine) { + _logFn('Transpile', `will use ESBuild to transpile source files`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + const isExtension = configFilePath.includes('extensions'); + this._transformOpts = { + target: ['es2022'], + format: isExtension ? 'cjs' : 'esm', + platform: isExtension ? 'node' : undefined, + loader: 'ts', + sourcemap: 'inline', + tsconfigRaw: JSON.stringify({ + compilerOptions: { + ...this._cmdLine.options, + ...{ + module: isExtension ? ts.ModuleKind.CommonJS : undefined + } satisfies ts.CompilerOptions + } + }), + supported: { + 'class-static-blocks': false, // SEE https://github.com/evanw/esbuild/issues/3823, + 'dynamic-import': !isExtension, // see https://github.com/evanw/esbuild/issues/1281 + 'class-field': !isExtension + } + }; + } + async join(): Promise { + const jobs = this._jobs.slice(); + this._jobs.length = 0; + await Promise.allSettled(jobs); + } + transpile(file: Vinyl): void { + if (!(file.contents instanceof Buffer)) { + throw Error('file.contents must be a Buffer'); + } + const t1 = Date.now(); + this._jobs.push(esbuild.transform(file.contents, { + ...this._transformOpts, + sourcefile: file.path, + }).then(result => { + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (file.path.endsWith('.d.ts') && _isDefaultEmpty(result.code)) { + return; + } + const outBase = this._cmdLine.options.outDir ?? file.base; + const outPath = this._outputFileNames.getOutputFileName(file.path); + this.onOutfile!(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(result.code), + })); + this._logFn('Transpile', `esbuild took ${Date.now() - t1}ms for ${file.path}`); + }).catch(err => { + this._onError(err); + })); + } } - function _isDefaultEmpty(src: string): boolean { - return src - .replace('"use strict";', '') - .replace(/\/\/# sourceMappingURL.*^/, '') - .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') - .trim().length === 0; + return src + .replace('"use strict";', '') + .replace(/\/\/# sourceMappingURL.*^/, '') + .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') + .trim().length === 0; } diff --git a/build/lib/tsb/utils.ts b/build/lib/tsb/utils.ts index 3b003e3a6e1de..a86a5c4c003f7 100644 --- a/build/lib/tsb/utils.ts +++ b/build/lib/tsb/utils.ts @@ -2,32 +2,38 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export module collections { - const hasOwnProperty = Object.prototype.hasOwnProperty; - - export function lookup(collection: { [keys: string]: T }, key: string): T | null { + export function lookup(collection: { + [keys: string]: T; + }, key: string): T | null { if (hasOwnProperty.call(collection, key)) { return collection[key]; } return null; } - - export function insert(collection: { [keys: string]: T }, key: string, value: T): void { + export function insert(collection: { + [keys: string]: T; + }, key: string, value: T): void { collection[key] = value; } - - export function lookupOrInsert(collection: { [keys: string]: T }, key: string, value: T): T { + export function lookupOrInsert(collection: { + [keys: string]: T; + }, key: string, value: T): T { if (hasOwnProperty.call(collection, key)) { return collection[key]; - } else { + } + else { collection[key] = value; return value; } } - - export function forEach(collection: { [keys: string]: T }, callback: (entry: { key: string; value: T }) => void): void { + export function forEach(collection: { + [keys: string]: T; + }, callback: (entry: { + key: string; + value: T; + }) => void): void { for (const key in collection) { if (hasOwnProperty.call(collection, key)) { callback({ @@ -37,21 +43,18 @@ export module collections { } } } - - export function contains(collection: { [keys: string]: any }, key: string): boolean { + export function contains(collection: { + [keys: string]: any; + }, key: string): boolean { return hasOwnProperty.call(collection, key); } } - export module strings { - /** * The empty string. The one and only. */ export const empty = ''; - export const eolUnix = '\r\n'; - export function format(value: string, ...rest: any[]): string { return value.replace(/({\d+})/g, function (match) { const index = Number(match.substring(1, match.length - 1)); @@ -59,15 +62,16 @@ export module strings { }); } } - export module graph { - export interface Node { data: T; - incoming: { [key: string]: Node }; - outgoing: { [key: string]: Node }; + incoming: { + [key: string]: Node; + }; + outgoing: { + [key: string]: Node; + }; } - export function newNode(data: T): Node { return { data: data, @@ -75,15 +79,13 @@ export module graph { outgoing: {} }; } - export class Graph { - - private _nodes: { [key: string]: Node } = {}; - + private _nodes: { + [key: string]: Node; + } = {}; constructor(private _hashFn: (element: T) => string) { // empty } - traverse(start: T, inwards: boolean, callback: (data: T) => void): void { const startNode = this.lookup(start); if (!startNode) { @@ -91,8 +93,9 @@ export module graph { } this._traverse(startNode, inwards, {}, callback); } - - private _traverse(node: Node, inwards: boolean, seen: { [key: string]: boolean }, callback: (data: T) => void): void { + private _traverse(node: Node, inwards: boolean, seen: { + [key: string]: boolean; + }, callback: (data: T) => void): void { const key = this._hashFn(node.data); if (collections.contains(seen, key)) { return; @@ -102,15 +105,12 @@ export module graph { const nodes = inwards ? node.outgoing : node.incoming; collections.forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback)); } - inertEdge(from: T, to: T): void { const fromNode = this.lookupOrInsertNode(from); const toNode = this.lookupOrInsertNode(to); - fromNode.outgoing[this._hashFn(to)] = toNode; toNode.incoming[this._hashFn(from)] = fromNode; } - removeNode(data: T): void { const key = this._hashFn(data); delete this._nodes[key]; @@ -119,22 +119,17 @@ export module graph { delete entry.value.incoming[key]; }); } - lookupOrInsertNode(data: T): Node { const key = this._hashFn(data); let node = collections.lookup(this._nodes, key); - if (!node) { node = newNode(data); this._nodes[key] = node; } - return node; } - lookup(data: T): Node | null { return collections.lookup(this._nodes, this._hashFn(data)); } } - } diff --git a/build/lib/util.ts b/build/lib/util.ts index 08921834676de..c5585327b02ff 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as es from 'event-stream'; import _debounce = require('debounce'); import * as _filter from 'gulp-filter'; @@ -15,368 +14,291 @@ import { ThroughStream } from 'through'; import * as sm from 'source-map'; import { pathToFileURL } from 'url'; import * as ternaryStream from 'ternary-stream'; - const root = path.dirname(path.dirname(__dirname)); - export interface ICancellationToken { - isCancellationRequested(): boolean; + isCancellationRequested(): boolean; } - const NoCancellationToken: ICancellationToken = { isCancellationRequested: () => false }; - export interface IStreamProvider { - (cancellationToken?: ICancellationToken): NodeJS.ReadWriteStream; + (cancellationToken?: ICancellationToken): NodeJS.ReadWriteStream; } - export function incremental(streamProvider: IStreamProvider, initial: NodeJS.ReadWriteStream, supportsCancellation?: boolean): NodeJS.ReadWriteStream { - const input = es.through(); - const output = es.through(); - let state = 'idle'; - let buffer = Object.create(null); - - const token: ICancellationToken | undefined = !supportsCancellation ? undefined : { isCancellationRequested: () => Object.keys(buffer).length > 0 }; - - const run = (input: NodeJS.ReadWriteStream, isCancellable: boolean) => { - state = 'running'; - - const stream = !supportsCancellation ? streamProvider() : streamProvider(isCancellable ? token : NoCancellationToken); - - input - .pipe(stream) - .pipe(es.through(undefined, () => { - state = 'idle'; - eventuallyRun(); - })) - .pipe(output); - }; - - if (initial) { - run(initial, false); - } - - const eventuallyRun = _debounce(() => { - const paths = Object.keys(buffer); - - if (paths.length === 0) { - return; - } - - const data = paths.map(path => buffer[path]); - buffer = Object.create(null); - run(es.readArray(data), true); - }, 500); - - input.on('data', (f: any) => { - buffer[f.path] = f; - - if (state === 'idle') { - eventuallyRun(); - } - }); - - return es.duplex(input, output); + const input = es.through(); + const output = es.through(); + let state = 'idle'; + let buffer = Object.create(null); + const token: ICancellationToken | undefined = !supportsCancellation ? undefined : { isCancellationRequested: () => Object.keys(buffer).length > 0 }; + const run = (input: NodeJS.ReadWriteStream, isCancellable: boolean) => { + state = 'running'; + const stream = !supportsCancellation ? streamProvider() : streamProvider(isCancellable ? token : NoCancellationToken); + input + .pipe(stream) + .pipe(es.through(undefined, () => { + state = 'idle'; + eventuallyRun(); + })) + .pipe(output); + }; + if (initial) { + run(initial, false); + } + const eventuallyRun = _debounce(() => { + const paths = Object.keys(buffer); + if (paths.length === 0) { + return; + } + const data = paths.map(path => buffer[path]); + buffer = Object.create(null); + run(es.readArray(data), true); + }, 500); + input.on('data', (f: any) => { + buffer[f.path] = f; + if (state === 'idle') { + eventuallyRun(); + } + }); + return es.duplex(input, output); } - export function debounce(task: () => NodeJS.ReadWriteStream, duration = 500): NodeJS.ReadWriteStream { - const input = es.through(); - const output = es.through(); - let state = 'idle'; - - const run = () => { - state = 'running'; - - task() - .pipe(es.through(undefined, () => { - const shouldRunAgain = state === 'stale'; - state = 'idle'; - - if (shouldRunAgain) { - eventuallyRun(); - } - })) - .pipe(output); - }; - - run(); - - const eventuallyRun = _debounce(() => run(), duration); - - input.on('data', () => { - if (state === 'idle') { - eventuallyRun(); - } else { - state = 'stale'; - } - }); - - return es.duplex(input, output); + const input = es.through(); + const output = es.through(); + let state = 'idle'; + const run = () => { + state = 'running'; + task() + .pipe(es.through(undefined, () => { + const shouldRunAgain = state === 'stale'; + state = 'idle'; + if (shouldRunAgain) { + eventuallyRun(); + } + })) + .pipe(output); + }; + run(); + const eventuallyRun = _debounce(() => run(), duration); + input.on('data', () => { + if (state === 'idle') { + eventuallyRun(); + } + else { + state = 'stale'; + } + }); + return es.duplex(input, output); } - export function fixWin32DirectoryPermissions(): NodeJS.ReadWriteStream { - if (!/win32/.test(process.platform)) { - return es.through(); - } - - return es.mapSync(f => { - if (f.stat && f.stat.isDirectory && f.stat.isDirectory()) { - f.stat.mode = 16877; - } - - return f; - }); + if (!/win32/.test(process.platform)) { + return es.through(); + } + return es.mapSync(f => { + if (f.stat && f.stat.isDirectory && f.stat.isDirectory()) { + f.stat.mode = 16877; + } + return f; + }); } - export function setExecutableBit(pattern?: string | string[]): NodeJS.ReadWriteStream { - const setBit = es.mapSync(f => { - if (!f.stat) { - f.stat = { isFile() { return true; } } as any; - } - f.stat.mode = /* 100755 */ 33261; - return f; - }); - - if (!pattern) { - return setBit; - } - - const input = es.through(); - const filter = _filter(pattern, { restore: true }); - const output = input - .pipe(filter) - .pipe(setBit) - .pipe(filter.restore); - - return es.duplex(input, output); + const setBit = es.mapSync(f => { + if (!f.stat) { + f.stat = { isFile() { return true; } } as any; + } + f.stat.mode = /* 100755 */ 33261; + return f; + }); + if (!pattern) { + return setBit; + } + const input = es.through(); + const filter = _filter(pattern, { restore: true }); + const output = input + .pipe(filter) + .pipe(setBit) + .pipe(filter.restore); + return es.duplex(input, output); } - export function toFileUri(filePath: string): string { - const match = filePath.match(/^([a-z])\:(.*)$/i); - - if (match) { - filePath = '/' + match[1].toUpperCase() + ':' + match[2]; - } - - return 'file://' + filePath.replace(/\\/g, '/'); + const match = filePath.match(/^([a-z])\:(.*)$/i); + if (match) { + filePath = '/' + match[1].toUpperCase() + ':' + match[2]; + } + return 'file://' + filePath.replace(/\\/g, '/'); } - export function skipDirectories(): NodeJS.ReadWriteStream { - return es.mapSync(f => { - if (!f.isDirectory()) { - return f; - } - }); + return es.mapSync(f => { + if (!f.isDirectory()) { + return f; + } + }); } - export function cleanNodeModules(rulePath: string): NodeJS.ReadWriteStream { - const rules = fs.readFileSync(rulePath, 'utf8') - .split(/\r?\n/g) - .map(line => line.trim()) - .filter(line => line && !/^#/.test(line)); - - const excludes = rules.filter(line => !/^!/.test(line)).map(line => `!**/node_modules/${line}`); - const includes = rules.filter(line => /^!/.test(line)).map(line => `**/node_modules/${line.substr(1)}`); - - const input = es.through(); - const output = es.merge( - input.pipe(_filter(['**', ...excludes])), - input.pipe(_filter(includes)) - ); - - return es.duplex(input, output); + const rules = fs.readFileSync(rulePath, 'utf8') + .split(/\r?\n/g) + .map(line => line.trim()) + .filter(line => line && !/^#/.test(line)); + const excludes = rules.filter(line => !/^!/.test(line)).map(line => `!**/node_modules/${line}`); + const includes = rules.filter(line => /^!/.test(line)).map(line => `**/node_modules/${line.substr(1)}`); + const input = es.through(); + const output = es.merge(input.pipe(_filter(['**', ...excludes])), input.pipe(_filter(includes))); + return es.duplex(input, output); } - declare class FileSourceMap extends VinylFile { - public sourceMap: sm.RawSourceMap; + public sourceMap: sm.RawSourceMap; } - export function loadSourcemaps(): NodeJS.ReadWriteStream { - const input = es.through(); - - const output = input - .pipe(es.map((f, cb): FileSourceMap | undefined => { - if (f.sourceMap) { - cb(undefined, f); - return; - } - - if (!f.contents) { - cb(undefined, f); - return; - } - - const contents = (f.contents).toString('utf8'); - - const reg = /\/\/# sourceMappingURL=(.*)$/g; - let lastMatch: RegExpExecArray | null = null; - let match: RegExpExecArray | null = null; - - while (match = reg.exec(contents)) { - lastMatch = match; - } - - if (!lastMatch) { - f.sourceMap = { - version: '3', - names: [], - mappings: '', - sources: [f.relative.replace(/\\/g, '/')], - sourcesContent: [contents] - }; - - cb(undefined, f); - return; - } - - f.contents = Buffer.from(contents.replace(/\/\/# sourceMappingURL=(.*)$/g, ''), 'utf8'); - - fs.readFile(path.join(path.dirname(f.path), lastMatch[1]), 'utf8', (err, contents) => { - if (err) { return cb(err); } - - f.sourceMap = JSON.parse(contents); - cb(undefined, f); - }); - })); - - return es.duplex(input, output); + const input = es.through(); + const output = input + .pipe(es.map((f, cb): FileSourceMap | undefined => { + if (f.sourceMap) { + cb(undefined, f); + return; + } + if (!f.contents) { + cb(undefined, f); + return; + } + const contents = (f.contents).toString('utf8'); + const reg = /\/\/# sourceMappingURL=(.*)$/g; + let lastMatch: RegExpExecArray | null = null; + let match: RegExpExecArray | null = null; + while (match = reg.exec(contents)) { + lastMatch = match; + } + if (!lastMatch) { + f.sourceMap = { + version: '3', + names: [], + mappings: '', + sources: [f.relative.replace(/\\/g, '/')], + sourcesContent: [contents] + }; + cb(undefined, f); + return; + } + f.contents = Buffer.from(contents.replace(/\/\/# sourceMappingURL=(.*)$/g, ''), 'utf8'); + fs.readFile(path.join(path.dirname(f.path), lastMatch[1]), 'utf8', (err, contents) => { + if (err) { + return cb(err); + } + f.sourceMap = JSON.parse(contents); + cb(undefined, f); + }); + })); + return es.duplex(input, output); } - export function stripSourceMappingURL(): NodeJS.ReadWriteStream { - const input = es.through(); - - const output = input - .pipe(es.mapSync(f => { - const contents = (f.contents).toString('utf8'); - f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8'); - return f; - })); - - return es.duplex(input, output); + const input = es.through(); + const output = input + .pipe(es.mapSync(f => { + const contents = (f.contents).toString('utf8'); + f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8'); + return f; + })); + return es.duplex(input, output); } - /** Splits items in the stream based on the predicate, sending them to onTrue if true, or onFalse otherwise */ export function $if(test: boolean | ((f: VinylFile) => boolean), onTrue: NodeJS.ReadWriteStream, onFalse: NodeJS.ReadWriteStream = es.through()) { - if (typeof test === 'boolean') { - return test ? onTrue : onFalse; - } - - return ternaryStream(test, onTrue, onFalse); + if (typeof test === 'boolean') { + return test ? onTrue : onFalse; + } + return ternaryStream(test, onTrue, onFalse); } - /** Operator that appends the js files' original path a sourceURL, so debug locations map */ export function appendOwnPathSourceURL(): NodeJS.ReadWriteStream { - const input = es.through(); - - const output = input - .pipe(es.mapSync(f => { - if (!(f.contents instanceof Buffer)) { - throw new Error(`contents of ${f.path} are not a buffer`); - } - - f.contents = Buffer.concat([f.contents, Buffer.from(`\n//# sourceURL=${pathToFileURL(f.path)}`)]); - return f; - })); - - return es.duplex(input, output); + const input = es.through(); + const output = input + .pipe(es.mapSync(f => { + if (!(f.contents instanceof Buffer)) { + throw new Error(`contents of ${f.path} are not a buffer`); + } + f.contents = Buffer.concat([f.contents, Buffer.from(`\n//# sourceURL=${pathToFileURL(f.path)}`)]); + return f; + })); + return es.duplex(input, output); } - export function rewriteSourceMappingURL(sourceMappingURLBase: string): NodeJS.ReadWriteStream { - const input = es.through(); - - const output = input - .pipe(es.mapSync(f => { - const contents = (f.contents).toString('utf8'); - const str = `//# sourceMappingURL=${sourceMappingURLBase}/${path.dirname(f.relative).replace(/\\/g, '/')}/$1`; - f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, str)); - return f; - })); - - return es.duplex(input, output); + const input = es.through(); + const output = input + .pipe(es.mapSync(f => { + const contents = (f.contents).toString('utf8'); + const str = `//# sourceMappingURL=${sourceMappingURLBase}/${path.dirname(f.relative).replace(/\\/g, '/')}/$1`; + f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, str)); + return f; + })); + return es.duplex(input, output); } - export function rimraf(dir: string): () => Promise { - const result = () => new Promise((c, e) => { - let retries = 0; - - const retry = () => { - _rimraf(dir, { maxBusyTries: 1 }, (err: any) => { - if (!err) { - return c(); - } - - if (err.code === 'ENOTEMPTY' && ++retries < 5) { - return setTimeout(() => retry(), 10); - } - - return e(err); - }); - }; - - retry(); - }); - - result.taskName = `clean-${path.basename(dir).toLowerCase()}`; - return result; + const result = () => new Promise((c, e) => { + let retries = 0; + const retry = () => { + _rimraf(dir, { maxBusyTries: 1 }, (err: any) => { + if (!err) { + return c(); + } + if (err.code === 'ENOTEMPTY' && ++retries < 5) { + return setTimeout(() => retry(), 10); + } + return e(err); + }); + }; + retry(); + }); + result.taskName = `clean-${path.basename(dir).toLowerCase()}`; + return result; } - function _rreaddir(dirPath: string, prepend: string, result: string[]): void { - const entries = fs.readdirSync(dirPath, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - _rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); - } else { - result.push(`${prepend}/${entry.name}`); - } - } + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + _rreaddir(path.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); + } + else { + result.push(`${prepend}/${entry.name}`); + } + } } - export function rreddir(dirPath: string): string[] { - const result: string[] = []; - _rreaddir(dirPath, '', result); - return result; + const result: string[] = []; + _rreaddir(dirPath, '', result); + return result; } - export function ensureDir(dirPath: string): void { - if (fs.existsSync(dirPath)) { - return; - } - ensureDir(path.dirname(dirPath)); - fs.mkdirSync(dirPath); + if (fs.existsSync(dirPath)) { + return; + } + ensureDir(path.dirname(dirPath)); + fs.mkdirSync(dirPath); } - export function rebase(count: number): NodeJS.ReadWriteStream { - return rename(f => { - const parts = f.dirname ? f.dirname.split(/[\/\\]/) : []; - f.dirname = parts.slice(count).join(path.sep); - }); + return rename(f => { + const parts = f.dirname ? f.dirname.split(/[\/\\]/) : []; + f.dirname = parts.slice(count).join(path.sep); + }); } - export interface FilterStream extends NodeJS.ReadWriteStream { - restore: ThroughStream; + restore: ThroughStream; } - export function filter(fn: (data: any) => boolean): FilterStream { - const result = es.through(function (data) { - if (fn(data)) { - this.emit('data', data); - } else { - result.restore.push(data); - } - }); - - result.restore = es.through(); - return result; + const result = es.through(function (data) { + if (fn(data)) { + this.emit('data', data); + } + else { + result.restore.push(data); + } + }); + result.restore = es.through(); + return result; } - export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise { - return new Promise((c, e) => { - stream.on('error', err => e(err)); - stream.on('end', () => c()); - }); + return new Promise((c, e) => { + stream.on('error', err => e(err)); + stream.on('end', () => c()); + }); } - export function getElectronVersion(): Record { - const npmrc = fs.readFileSync(path.join(root, '.npmrc'), 'utf8'); - const electronVersion = /^target="(.*)"$/m.exec(npmrc)![1]; - const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; - return { electronVersion, msBuildId }; + const npmrc = fs.readFileSync(path.join(root, '.npmrc'), 'utf8'); + const electronVersion = /^target="(.*)"$/m.exec(npmrc)![1]; + const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; + return { electronVersion, msBuildId }; } diff --git a/build/lib/watch/index.ts b/build/lib/watch/index.ts index ce4bdfd75edf1..18d1c29a313fa 100644 --- a/build/lib/watch/index.ts +++ b/build/lib/watch/index.ts @@ -2,9 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - const watch = process.platform === 'win32' ? require('./watch-win32') : require('vscode-gulp-watch'); - module.exports = function () { - return watch.apply(null, arguments); + return watch.apply(null, arguments); }; diff --git a/build/lib/watch/watch-win32.ts b/build/lib/watch/watch-win32.ts index afde6a79f2219..87f6ed501c66b 100644 --- a/build/lib/watch/watch-win32.ts +++ b/build/lib/watch/watch-win32.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as cp from 'child_process'; import * as fs from 'fs'; @@ -10,99 +9,99 @@ import * as File from 'vinyl'; import * as es from 'event-stream'; import * as filter from 'gulp-filter'; import { Stream } from 'stream'; - const watcherPath = path.join(__dirname, 'watcher.exe'); - function toChangeType(type: '0' | '1' | '2'): 'change' | 'add' | 'unlink' { - switch (type) { - case '0': return 'change'; - case '1': return 'add'; - default: return 'unlink'; - } + switch (type) { + case '0': return 'change'; + case '1': return 'add'; + default: return 'unlink'; + } } - function watch(root: string): Stream { - const result = es.through(); - let child: cp.ChildProcess | null = cp.spawn(watcherPath, [root]); - - child.stdout!.on('data', function (data) { - const lines: string[] = data.toString('utf8').split('\n'); - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - if (line.length === 0) { - continue; - } - - const changeType = <'0' | '1' | '2'>line[0]; - const changePath = line.substr(2); - - // filter as early as possible - if (/^\.git/.test(changePath) || /(^|\\)out($|\\)/.test(changePath)) { - continue; - } - - const changePathFull = path.join(root, changePath); - - const file = new File({ - path: changePathFull, - base: root - }); - (file).event = toChangeType(changeType); - result.emit('data', file); - } - }); - - child.stderr!.on('data', function (data) { - result.emit('error', data); - }); - - child.on('exit', function (code) { - result.emit('error', 'Watcher died with code ' + code); - child = null; - }); - - process.once('SIGTERM', function () { process.exit(0); }); - process.once('SIGTERM', function () { process.exit(0); }); - process.once('exit', function () { if (child) { child.kill(); } }); - - return result; + const result = es.through(); + let child: cp.ChildProcess | null = cp.spawn(watcherPath, [root]); + child.stdout!.on('data', function (data) { + const lines: string[] = data.toString('utf8').split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (line.length === 0) { + continue; + } + const changeType = <'0' | '1' | '2'>line[0]; + const changePath = line.substr(2); + // filter as early as possible + if (/^\.git/.test(changePath) || /(^|\\)out($|\\)/.test(changePath)) { + continue; + } + const changePathFull = path.join(root, changePath); + const file = new File({ + path: changePathFull, + base: root + }); + (file).event = toChangeType(changeType); + result.emit('data', file); + } + }); + child.stderr!.on('data', function (data) { + result.emit('error', data); + }); + child.on('exit', function (code) { + result.emit('error', 'Watcher died with code ' + code); + child = null; + }); + process.once('SIGTERM', function () { process.exit(0); }); + process.once('SIGTERM', function () { process.exit(0); }); + process.once('exit', function () { + if (child) { + child.kill(); + } + }); + return result; } - -const cache: { [cwd: string]: Stream } = Object.create(null); - -module.exports = function (pattern: string | string[] | filter.FileFunction, options?: { cwd?: string; base?: string; dot?: boolean }) { - options = options || {}; - - const cwd = path.normalize(options.cwd || process.cwd()); - let watcher = cache[cwd]; - - if (!watcher) { - watcher = cache[cwd] = watch(cwd); - } - - const rebase = !options.base ? es.through() : es.mapSync(function (f: File) { - f.base = options!.base!; - return f; - }); - - return watcher - .pipe(filter(['**', '!.git{,/**}'], { dot: options.dot })) // ignore all things git - .pipe(filter(pattern, { dot: options.dot })) - .pipe(es.map(function (file: File, cb) { - fs.stat(file.path, function (err, stat) { - if (err && err.code === 'ENOENT') { return cb(undefined, file); } - if (err) { return cb(); } - if (!stat.isFile()) { return cb(); } - - fs.readFile(file.path, function (err, contents) { - if (err && err.code === 'ENOENT') { return cb(undefined, file); } - if (err) { return cb(); } - - file.contents = contents; - file.stat = stat; - cb(undefined, file); - }); - }); - })) - .pipe(rebase); +const cache: { + [cwd: string]: Stream; +} = Object.create(null); +module.exports = function (pattern: string | string[] | filter.FileFunction, options?: { + cwd?: string; + base?: string; + dot?: boolean; +}) { + options = options || {}; + const cwd = path.normalize(options.cwd || process.cwd()); + let watcher = cache[cwd]; + if (!watcher) { + watcher = cache[cwd] = watch(cwd); + } + const rebase = !options.base ? es.through() : es.mapSync(function (f: File) { + f.base = options!.base!; + return f; + }); + return watcher + .pipe(filter(['**', '!.git{,/**}'], { dot: options.dot })) // ignore all things git + .pipe(filter(pattern, { dot: options.dot })) + .pipe(es.map(function (file: File, cb) { + fs.stat(file.path, function (err, stat) { + if (err && err.code === 'ENOENT') { + return cb(undefined, file); + } + if (err) { + return cb(); + } + if (!stat.isFile()) { + return cb(); + } + fs.readFile(file.path, function (err, contents) { + if (err && err.code === 'ENOENT') { + return cb(undefined, file); + } + if (err) { + return cb(); + } + file.contents = contents; + file.stat = stat; + cb(undefined, file); + }); + }); + })) + .pipe(rebase); }; diff --git a/build/linux/debian/calculate-deps.ts b/build/linux/debian/calculate-deps.ts index c44e241388bbe..4c1cf1c48b04d 100644 --- a/build/linux/debian/calculate-deps.ts +++ b/build/linux/debian/calculate-deps.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { spawnSync } from 'child_process'; import { constants, statSync } from 'fs'; import { tmpdir } from 'os'; @@ -10,94 +9,81 @@ import path = require('path'); import * as manifests from '../../../cgmanifest.json'; import { additionalDeps } from './dep-lists'; import { DebianArchString } from './types'; - export function generatePackageDeps(files: string[], arch: DebianArchString, chromiumSysroot: string, vscodeSysroot: string): Set[] { - const dependencies: Set[] = files.map(file => calculatePackageDeps(file, arch, chromiumSysroot, vscodeSysroot)); - const additionalDepsSet = new Set(additionalDeps); - dependencies.push(additionalDepsSet); - return dependencies; + const dependencies: Set[] = files.map(file => calculatePackageDeps(file, arch, chromiumSysroot, vscodeSysroot)); + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; } - // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. function calculatePackageDeps(binaryPath: string, arch: DebianArchString, chromiumSysroot: string, vscodeSysroot: string): Set { - try { - if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - - // Get the Chromium dpkg-shlibdeps file. - const chromiumManifest = manifests.registrations.filter(registration => { - return registration.component.type === 'git' && registration.component.git!.name === 'chromium'; - }); - const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; - const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; - const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); - if (result.status !== 0) { - throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); - } - const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; - switch (arch) { - case 'amd64': - cmd.push(`-l${chromiumSysroot}/usr/lib/x86_64-linux-gnu`, - `-l${chromiumSysroot}/lib/x86_64-linux-gnu`, - `-l${vscodeSysroot}/usr/lib/x86_64-linux-gnu`, - `-l${vscodeSysroot}/lib/x86_64-linux-gnu`); - break; - case 'armhf': - cmd.push(`-l${chromiumSysroot}/usr/lib/arm-linux-gnueabihf`, - `-l${chromiumSysroot}/lib/arm-linux-gnueabihf`, - `-l${vscodeSysroot}/usr/lib/arm-linux-gnueabihf`, - `-l${vscodeSysroot}/lib/arm-linux-gnueabihf`); - break; - case 'arm64': - cmd.push(`-l${chromiumSysroot}/usr/lib/aarch64-linux-gnu`, - `-l${chromiumSysroot}/lib/aarch64-linux-gnu`, - `-l${vscodeSysroot}/usr/lib/aarch64-linux-gnu`, - `-l${vscodeSysroot}/lib/aarch64-linux-gnu`); - break; - } - cmd.push(`-l${chromiumSysroot}/usr/lib`); - cmd.push(`-L${vscodeSysroot}/debian/libxkbfile1/DEBIAN/shlibs`); - cmd.push('-O', '-e', path.resolve(binaryPath)); - - const dpkgShlibdepsResult = spawnSync('perl', cmd, { cwd: chromiumSysroot }); - if (dpkgShlibdepsResult.status !== 0) { - throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); - } - - const shlibsDependsPrefix = 'shlibs:Depends='; - const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); - let depsStr = ''; - for (const line of requiresList) { - if (line.startsWith(shlibsDependsPrefix)) { - depsStr = line.substring(shlibsDependsPrefix.length); - } - } - // Refs https://chromium-review.googlesource.com/c/chromium/src/+/3572926 - // Chromium depends on libgcc_s, is from the package libgcc1. However, in - // Bullseye, the package was renamed to libgcc-s1. To avoid adding a dep - // on the newer package, this hack skips the dep. This is safe because - // libgcc-s1 is a dependency of libc6. This hack can be removed once - // support for Debian Buster and Ubuntu Bionic are dropped. - // - // libgdk-pixbuf package has been renamed from libgdk-pixbuf2.0-0 to - // libgdk-pixbuf-2.0-0 in recent distros. Since we only ship a single - // linux package we cannot declare a dependeny on it. We can safely - // exclude this dependency as GTK depends on it and we depend on GTK. - // - // Remove kerberos native module related dependencies as the versions - // computed from sysroot will not satisfy the minimum supported distros - // Refs https://github.com/microsoft/vscode/issues/188881. - // TODO(deepak1556): remove this workaround in favor of computing the - // versions from build container for native modules. - const filteredDeps = depsStr.split(', ').filter(dependency => { - return !dependency.startsWith('libgcc-s1') && - !dependency.startsWith('libgdk-pixbuf'); - }).sort(); - const requires = new Set(filteredDeps); - return requires; + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + // Get the Chromium dpkg-shlibdeps file. + const chromiumManifest = manifests.registrations.filter(registration => { + return registration.component.type === 'git' && registration.component.git!.name === 'chromium'; + }); + const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; + const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; + const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${chromiumSysroot}/usr/lib/x86_64-linux-gnu`, `-l${chromiumSysroot}/lib/x86_64-linux-gnu`, `-l${vscodeSysroot}/usr/lib/x86_64-linux-gnu`, `-l${vscodeSysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${chromiumSysroot}/usr/lib/arm-linux-gnueabihf`, `-l${chromiumSysroot}/lib/arm-linux-gnueabihf`, `-l${vscodeSysroot}/usr/lib/arm-linux-gnueabihf`, `-l${vscodeSysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${chromiumSysroot}/usr/lib/aarch64-linux-gnu`, `-l${chromiumSysroot}/lib/aarch64-linux-gnu`, `-l${vscodeSysroot}/usr/lib/aarch64-linux-gnu`, `-l${vscodeSysroot}/lib/aarch64-linux-gnu`); + break; + } + cmd.push(`-l${chromiumSysroot}/usr/lib`); + cmd.push(`-L${vscodeSysroot}/debian/libxkbfile1/DEBIAN/shlibs`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + const dpkgShlibdepsResult = spawnSync('perl', cmd, { cwd: chromiumSysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + // Refs https://chromium-review.googlesource.com/c/chromium/src/+/3572926 + // Chromium depends on libgcc_s, is from the package libgcc1. However, in + // Bullseye, the package was renamed to libgcc-s1. To avoid adding a dep + // on the newer package, this hack skips the dep. This is safe because + // libgcc-s1 is a dependency of libc6. This hack can be removed once + // support for Debian Buster and Ubuntu Bionic are dropped. + // + // libgdk-pixbuf package has been renamed from libgdk-pixbuf2.0-0 to + // libgdk-pixbuf-2.0-0 in recent distros. Since we only ship a single + // linux package we cannot declare a dependeny on it. We can safely + // exclude this dependency as GTK depends on it and we depend on GTK. + // + // Remove kerberos native module related dependencies as the versions + // computed from sysroot will not satisfy the minimum supported distros + // Refs https://github.com/microsoft/vscode/issues/188881. + // TODO(deepak1556): remove this workaround in favor of computing the + // versions from build container for native modules. + const filteredDeps = depsStr.split(', ').filter(dependency => { + return !dependency.startsWith('libgcc-s1') && + !dependency.startsWith('libgdk-pixbuf'); + }).sort(); + const requires = new Set(filteredDeps); + return requires; } diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts index e3d78d1139ad5..1c7068c5478d5 100644 --- a/build/linux/debian/dep-lists.ts +++ b/build/linux/debian/dep-lists.ts @@ -2,139 +2,136 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps // Additional dependencies not in the dpkg-shlibdeps output. export const additionalDeps = [ - 'ca-certificates', // Make sure users have SSL certificates. - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnss3 (>= 3.26)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports. - 'xdg-utils (>= 1.0.2)', // OS integration + 'ca-certificates', // Make sure users have SSL certificates. + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnss3 (>= 3.26)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports. + 'xdg-utils (>= 1.0.2)', // OS integration ]; - // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends // Dependencies that we can only recommend // for now since some of the older distros don't support them. export const recommendedDeps = [ - 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. + 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. ]; - export const referenceGeneratedDepsByArch = { - 'amd64': [ - 'ca-certificates', - 'libasound2 (>= 1.0.17)', - 'libatk-bridge2.0-0 (>= 2.5.3)', - 'libatk1.0-0 (>= 2.2.0)', - 'libatspi2.0-0 (>= 2.9.90)', - 'libc6 (>= 2.14)', - 'libc6 (>= 2.16)', - 'libc6 (>= 2.17)', - 'libc6 (>= 2.2.5)', - 'libc6 (>= 2.25)', - 'libc6 (>= 2.28)', - 'libcairo2 (>= 1.6.0)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'libdbus-1-3 (>= 1.9.14)', - 'libdrm2 (>= 2.4.75)', - 'libexpat1 (>= 2.1~beta3)', - 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', - 'libgtk-3-0 (>= 3.9.10)', - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnspr4 (>= 2:4.9-2~)', - 'libnss3 (>= 2:3.30)', - 'libnss3 (>= 3.26)', - 'libpango-1.0-0 (>= 1.14.0)', - 'libx11-6', - 'libx11-6 (>= 2:1.4.99.1)', - 'libxcb1 (>= 1.9.2)', - 'libxcomposite1 (>= 1:0.4.4-1)', - 'libxdamage1 (>= 1:1.1)', - 'libxext6', - 'libxfixes3', - 'libxkbcommon0 (>= 0.5.0)', - 'libxkbfile1 (>= 1:1.1.0)', - 'libxrandr2', - 'xdg-utils (>= 1.0.2)' - ], - 'armhf': [ - 'ca-certificates', - 'libasound2 (>= 1.0.17)', - 'libatk-bridge2.0-0 (>= 2.5.3)', - 'libatk1.0-0 (>= 2.2.0)', - 'libatspi2.0-0 (>= 2.9.90)', - 'libc6 (>= 2.16)', - 'libc6 (>= 2.17)', - 'libc6 (>= 2.25)', - 'libc6 (>= 2.28)', - 'libc6 (>= 2.4)', - 'libc6 (>= 2.9)', - 'libcairo2 (>= 1.6.0)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'libdbus-1-3 (>= 1.9.14)', - 'libdrm2 (>= 2.4.75)', - 'libexpat1 (>= 2.1~beta3)', - 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', - 'libgtk-3-0 (>= 3.9.10)', - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnspr4 (>= 2:4.9-2~)', - 'libnss3 (>= 2:3.30)', - 'libnss3 (>= 3.26)', - 'libpango-1.0-0 (>= 1.14.0)', - 'libstdc++6 (>= 4.1.1)', - 'libstdc++6 (>= 5)', - 'libstdc++6 (>= 5.2)', - 'libstdc++6 (>= 6)', - 'libx11-6', - 'libx11-6 (>= 2:1.4.99.1)', - 'libxcb1 (>= 1.9.2)', - 'libxcomposite1 (>= 1:0.4.4-1)', - 'libxdamage1 (>= 1:1.1)', - 'libxext6', - 'libxfixes3', - 'libxkbcommon0 (>= 0.5.0)', - 'libxkbfile1 (>= 1:1.1.0)', - 'libxrandr2', - 'xdg-utils (>= 1.0.2)' - ], - 'arm64': [ - 'ca-certificates', - 'libasound2 (>= 1.0.17)', - 'libatk-bridge2.0-0 (>= 2.5.3)', - 'libatk1.0-0 (>= 2.2.0)', - 'libatspi2.0-0 (>= 2.9.90)', - 'libc6 (>= 2.17)', - 'libc6 (>= 2.25)', - 'libc6 (>= 2.28)', - 'libcairo2 (>= 1.6.0)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'libdbus-1-3 (>= 1.9.14)', - 'libdrm2 (>= 2.4.75)', - 'libexpat1 (>= 2.1~beta3)', - 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', - 'libgtk-3-0 (>= 3.9.10)', - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnspr4 (>= 2:4.9-2~)', - 'libnss3 (>= 2:3.30)', - 'libnss3 (>= 3.26)', - 'libpango-1.0-0 (>= 1.14.0)', - 'libstdc++6 (>= 4.1.1)', - 'libstdc++6 (>= 5)', - 'libstdc++6 (>= 5.2)', - 'libstdc++6 (>= 6)', - 'libx11-6', - 'libx11-6 (>= 2:1.4.99.1)', - 'libxcb1 (>= 1.9.2)', - 'libxcomposite1 (>= 1:0.4.4-1)', - 'libxdamage1 (>= 1:1.1)', - 'libxext6', - 'libxfixes3', - 'libxkbcommon0 (>= 0.5.0)', - 'libxkbfile1 (>= 1:1.1.0)', - 'libxrandr2', - 'xdg-utils (>= 1.0.2)' - ] + 'amd64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.17)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.14)', + 'libc6 (>= 2.16)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.2.5)', + 'libc6 (>= 2.25)', + 'libc6 (>= 2.28)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.9.14)', + 'libdrm2 (>= 2.4.75)', + 'libexpat1 (>= 2.1~beta3)', + 'libgbm1 (>= 17.1.0~rc2)', + 'libglib2.0-0 (>= 2.37.3)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.30)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.5.0)', + 'libxkbfile1 (>= 1:1.1.0)', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'armhf': [ + 'ca-certificates', + 'libasound2 (>= 1.0.17)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.16)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.25)', + 'libc6 (>= 2.28)', + 'libc6 (>= 2.4)', + 'libc6 (>= 2.9)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.9.14)', + 'libdrm2 (>= 2.4.75)', + 'libexpat1 (>= 2.1~beta3)', + 'libgbm1 (>= 17.1.0~rc2)', + 'libglib2.0-0 (>= 2.37.3)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.30)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.5.0)', + 'libxkbfile1 (>= 1:1.1.0)', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'arm64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.17)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.25)', + 'libc6 (>= 2.28)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.9.14)', + 'libdrm2 (>= 2.4.75)', + 'libexpat1 (>= 2.1~beta3)', + 'libgbm1 (>= 17.1.0~rc2)', + 'libglib2.0-0 (>= 2.37.3)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.30)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.5.0)', + 'libxkbfile1 (>= 1:1.1.0)', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ] }; diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index 8ea43a523cf3a..530d937542f6b 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { spawnSync, execSync } from 'child_process'; import { tmpdir } from 'os'; import * as fs from 'fs'; @@ -11,225 +10,215 @@ import * as path from 'path'; import { createHash } from 'crypto'; import { DebianArchString } from './types'; import * as ansiColors from 'ansi-colors'; - // Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. const URL_PREFIX = 'https://msftelectronbuild.z5.web.core.windows.net'; const URL_PATH = 'sysroots/toolchain'; const REPO_ROOT = path.dirname(path.dirname(path.dirname(__dirname))); - const ghApiHeaders: Record = { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': 'VSCode Build', + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'VSCode Build', }; - if (process.env.GITHUB_TOKEN) { - ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); + ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); } - const ghDownloadHeaders = { - ...ghApiHeaders, - Accept: 'application/octet-stream', + ...ghApiHeaders, + Accept: 'application/octet-stream', }; - interface IFetchOptions { - assetName: string; - checksumSha256?: string; - dest: string; + assetName: string; + checksumSha256?: string; + dest: string; } - function getElectronVersion(): Record { - const npmrc = fs.readFileSync(path.join(REPO_ROOT, '.npmrc'), 'utf8'); - const electronVersion = /^target="(.*)"$/m.exec(npmrc)![1]; - const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; - return { electronVersion, msBuildId }; + const npmrc = fs.readFileSync(path.join(REPO_ROOT, '.npmrc'), 'utf8'); + const electronVersion = /^target="(.*)"$/m.exec(npmrc)![1]; + const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; + return { electronVersion, msBuildId }; } - function getSha(filename: fs.PathLike): string { - const hash = createHash('sha256'); - // Read file 1 MB at a time - const fd = fs.openSync(filename, 'r'); - const buffer = Buffer.alloc(1024 * 1024); - let position = 0; - let bytesRead = 0; - while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { - hash.update(buffer); - position += bytesRead; - } - hash.update(buffer.slice(0, bytesRead)); - return hash.digest('hex'); + const hash = createHash('sha256'); + // Read file 1 MB at a time + const fd = fs.openSync(filename, 'r'); + const buffer = Buffer.alloc(1024 * 1024); + let position = 0; + let bytesRead = 0; + while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { + hash.update(buffer); + position += bytesRead; + } + hash.update(buffer.slice(0, bytesRead)); + return hash.digest('hex'); } - function getVSCodeSysrootChecksum(expectedName: string) { - const checksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'vscode-sysroot.txt'), 'utf8'); - for (const line of checksums.split('\n')) { - const [checksum, name] = line.split(/\s+/); - if (name === expectedName) { - return checksum; - } - } - return undefined; + const checksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'vscode-sysroot.txt'), 'utf8'); + for (const line of checksums.split('\n')) { + const [checksum, name] = line.split(/\s+/); + if (name === expectedName) { + return checksum; + } + } + return undefined; } - /* * Do not use the fetch implementation from build/lib/fetch as it relies on vinyl streams * and vinyl-fs breaks the symlinks in the compiler toolchain sysroot. We use the native * tar implementation for that reason. */ async function fetchUrl(options: IFetchOptions, retries = 10, retryDelay = 1000): Promise { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 30 * 1000); - const version = '20240129-253798'; - try { - const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { - headers: ghApiHeaders, - signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ - }); - if (response.ok && (response.status >= 200 && response.status < 300)) { - console.log(`Fetch completed: Status ${response.status}.`); - const contents = Buffer.from(await response.arrayBuffer()); - const asset = JSON.parse(contents.toString()).assets.find((a: { name: string }) => a.name === options.assetName); - if (!asset) { - throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`); - } - console.log(`Found asset ${options.assetName} @ ${asset.url}.`); - const assetResponse = await fetch(asset.url, { - headers: ghDownloadHeaders - }); - if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) { - const assetContents = Buffer.from(await assetResponse.arrayBuffer()); - console.log(`Fetched response body buffer: ${ansiColors.magenta(`${(assetContents as Buffer).byteLength} bytes`)}`); - if (options.checksumSha256) { - const actualSHA256Checksum = createHash('sha256').update(assetContents).digest('hex'); - if (actualSHA256Checksum !== options.checksumSha256) { - throw new Error(`Checksum mismatch for ${ansiColors.cyan(asset.url)} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`); - } - } - console.log(`Verified SHA256 checksums match for ${ansiColors.cyan(asset.url)}`); - const tarCommand = `tar -xz -C ${options.dest}`; - execSync(tarCommand, { input: assetContents }); - console.log(`Fetch complete!`); - return; - } - throw new Error(`Request ${ansiColors.magenta(asset.url)} failed with status code: ${assetResponse.status}`); - } - throw new Error(`Request ${ansiColors.magenta('https://api.github.com')} failed with status code: ${response.status}`); - } finally { - clearTimeout(timeout); - } - } catch (e) { - if (retries > 0) { - console.log(`Fetching failed: ${e}`); - await new Promise(resolve => setTimeout(resolve, retryDelay)); - return fetchUrl(options, retries - 1, retryDelay); - } - throw e; - } + try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 30 * 1000); + const version = '20240129-253798'; + try { + const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { + headers: ghApiHeaders, + signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ + }); + if (response.ok && (response.status >= 200 && response.status < 300)) { + console.log(`Fetch completed: Status ${response.status}.`); + const contents = Buffer.from(await response.arrayBuffer()); + const asset = JSON.parse(contents.toString()).assets.find((a: { + name: string; + }) => a.name === options.assetName); + if (!asset) { + throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`); + } + console.log(`Found asset ${options.assetName} @ ${asset.url}.`); + const assetResponse = await fetch(asset.url, { + headers: ghDownloadHeaders + }); + if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) { + const assetContents = Buffer.from(await assetResponse.arrayBuffer()); + console.log(`Fetched response body buffer: ${ansiColors.magenta(`${(assetContents as Buffer).byteLength} bytes`)}`); + if (options.checksumSha256) { + const actualSHA256Checksum = createHash('sha256').update(assetContents).digest('hex'); + if (actualSHA256Checksum !== options.checksumSha256) { + throw new Error(`Checksum mismatch for ${ansiColors.cyan(asset.url)} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`); + } + } + console.log(`Verified SHA256 checksums match for ${ansiColors.cyan(asset.url)}`); + const tarCommand = `tar -xz -C ${options.dest}`; + execSync(tarCommand, { input: assetContents }); + console.log(`Fetch complete!`); + return; + } + throw new Error(`Request ${ansiColors.magenta(asset.url)} failed with status code: ${assetResponse.status}`); + } + throw new Error(`Request ${ansiColors.magenta('https://api.github.com')} failed with status code: ${response.status}`); + } + finally { + clearTimeout(timeout); + } + } + catch (e) { + if (retries > 0) { + console.log(`Fetching failed: ${e}`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + return fetchUrl(options, retries - 1, retryDelay); + } + throw e; + } } - type SysrootDictEntry = { - Sha256Sum: string; - SysrootDir: string; - Tarball: string; + Sha256Sum: string; + SysrootDir: string; + Tarball: string; }; - export async function getVSCodeSysroot(arch: DebianArchString): Promise { - let expectedName: string; - let triple: string; - const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28'; - switch (arch) { - case 'amd64': - expectedName = `x86_64-linux-gnu${prefix}.tar.gz`; - triple = 'x86_64-linux-gnu'; - break; - case 'arm64': - expectedName = `aarch64-linux-gnu${prefix}.tar.gz`; - triple = 'aarch64-linux-gnu'; - break; - case 'armhf': - expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`; - triple = 'arm-rpi-linux-gnueabihf'; - break; - } - console.log(`Fetching ${expectedName} for ${triple}`); - const checksumSha256 = getVSCodeSysrootChecksum(expectedName); - if (!checksumSha256) { - throw new Error(`Could not find checksum for ${expectedName}`); - } - const sysroot = process.env['VSCODE_SYSROOT_DIR'] ?? path.join(tmpdir(), `vscode-${arch}-sysroot`); - const stamp = path.join(sysroot, '.stamp'); - const result = `${sysroot}/${triple}/${triple}/sysroot`; - if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) { - return result; - } - console.log(`Installing ${arch} root image: ${sysroot}`); - fs.rmSync(sysroot, { recursive: true, force: true }); - fs.mkdirSync(sysroot); - await fetchUrl({ - checksumSha256, - assetName: expectedName, - dest: sysroot - }); - fs.writeFileSync(stamp, expectedName); - return result; + let expectedName: string; + let triple: string; + const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28'; + switch (arch) { + case 'amd64': + expectedName = `x86_64-linux-gnu${prefix}.tar.gz`; + triple = 'x86_64-linux-gnu'; + break; + case 'arm64': + expectedName = `aarch64-linux-gnu${prefix}.tar.gz`; + triple = 'aarch64-linux-gnu'; + break; + case 'armhf': + expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`; + triple = 'arm-rpi-linux-gnueabihf'; + break; + } + console.log(`Fetching ${expectedName} for ${triple}`); + const checksumSha256 = getVSCodeSysrootChecksum(expectedName); + if (!checksumSha256) { + throw new Error(`Could not find checksum for ${expectedName}`); + } + const sysroot = process.env['VSCODE_SYSROOT_DIR'] ?? path.join(tmpdir(), `vscode-${arch}-sysroot`); + const stamp = path.join(sysroot, '.stamp'); + const result = `${sysroot}/${triple}/${triple}/sysroot`; + if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === expectedName) { + return result; + } + console.log(`Installing ${arch} root image: ${sysroot}`); + fs.rmSync(sysroot, { recursive: true, force: true }); + fs.mkdirSync(sysroot); + await fetchUrl({ + checksumSha256, + assetName: expectedName, + dest: sysroot + }); + fs.writeFileSync(stamp, expectedName); + return result; } - export async function getChromiumSysroot(arch: DebianArchString): Promise { - const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${getElectronVersion().electronVersion}/script/sysroots.json`; - const sysrootDictLocation = `${tmpdir()}/sysroots.json`; - const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); - if (result.status !== 0) { - throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); - } - const sysrootInfo = require(sysrootDictLocation); - const sysrootArch = `bullseye_${arch}`; - const sysrootDict: SysrootDictEntry = sysrootInfo[sysrootArch]; - const tarballFilename = sysrootDict['Tarball']; - const tarballSha = sysrootDict['Sha256Sum']; - const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']); - const url = [URL_PREFIX, URL_PATH, tarballSha].join('/'); - const stamp = path.join(sysroot, '.stamp'); - if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { - return sysroot; - } - - console.log(`Installing Debian ${arch} root image: ${sysroot}`); - fs.rmSync(sysroot, { recursive: true, force: true }); - fs.mkdirSync(sysroot); - const tarball = path.join(sysroot, tarballFilename); - console.log(`Downloading ${url}`); - let downloadSuccess = false; - for (let i = 0; i < 3 && !downloadSuccess; i++) { - fs.writeFileSync(tarball, ''); - await new Promise((c) => { - https.get(url, (res) => { - res.on('data', (chunk) => { - fs.appendFileSync(tarball, chunk); - }); - res.on('end', () => { - downloadSuccess = true; - c(); - }); - }).on('error', (err) => { - console.error('Encountered an error during the download attempt: ' + err.message); - c(); - }); - }); - } - if (!downloadSuccess) { - fs.rmSync(tarball); - throw new Error('Failed to download ' + url); - } - const sha = getSha(tarball); - if (sha !== tarballSha) { - throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); - } - - const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]); - if (proc.status) { - throw new Error('Tarball extraction failed with code ' + proc.status); - } - fs.rmSync(tarball); - fs.writeFileSync(stamp, url); - return sysroot; + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${getElectronVersion().electronVersion}/script/sysroots.json`; + const sysrootDictLocation = `${tmpdir()}/sysroots.json`; + const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); + } + const sysrootInfo = require(sysrootDictLocation); + const sysrootArch = `bullseye_${arch}`; + const sysrootDict: SysrootDictEntry = sysrootInfo[sysrootArch]; + const tarballFilename = sysrootDict['Tarball']; + const tarballSha = sysrootDict['Sha256Sum']; + const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']); + const url = [URL_PREFIX, URL_PATH, tarballSha].join('/'); + const stamp = path.join(sysroot, '.stamp'); + if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { + return sysroot; + } + console.log(`Installing Debian ${arch} root image: ${sysroot}`); + fs.rmSync(sysroot, { recursive: true, force: true }); + fs.mkdirSync(sysroot); + const tarball = path.join(sysroot, tarballFilename); + console.log(`Downloading ${url}`); + let downloadSuccess = false; + for (let i = 0; i < 3 && !downloadSuccess; i++) { + fs.writeFileSync(tarball, ''); + await new Promise((c) => { + https.get(url, (res) => { + res.on('data', (chunk) => { + fs.appendFileSync(tarball, chunk); + }); + res.on('end', () => { + downloadSuccess = true; + c(); + }); + }).on('error', (err) => { + console.error('Encountered an error during the download attempt: ' + err.message); + c(); + }); + }); + } + if (!downloadSuccess) { + fs.rmSync(tarball); + throw new Error('Failed to download ' + url); + } + const sha = getSha(tarball); + if (sha !== tarballSha) { + throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); + } + const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]); + if (proc.status) { + throw new Error('Tarball extraction failed with code ' + proc.status); + } + fs.rmSync(tarball); + fs.writeFileSync(stamp, url); + return sysroot; } diff --git a/build/linux/debian/types.ts b/build/linux/debian/types.ts index e97485ef12887..e7b097c304f4d 100644 --- a/build/linux/debian/types.ts +++ b/build/linux/debian/types.ts @@ -2,9 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export type DebianArchString = 'amd64' | 'armhf' | 'arm64'; - export function isDebianArchString(s: string): s is DebianArchString { - return ['amd64', 'armhf', 'arm64'].includes(s); + return ['amd64', 'armhf', 'arm64'].includes(s); } diff --git a/build/linux/dependencies-generator.ts b/build/linux/dependencies-generator.ts index 3163aee545052..47f61863d0816 100644 --- a/build/linux/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -2,9 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; - import { spawnSync } from 'child_process'; import path = require('path'); import { getChromiumSysroot, getVSCodeSysroot } from './debian/install-sysroot'; @@ -15,7 +13,6 @@ import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-list import { DebianArchString, isDebianArchString } from './debian/types'; import { isRpmArchString, RpmArchString } from './rpm/types'; import product = require('../../product.json'); - // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. // If false, we warn about new dependencies if they show up @@ -24,95 +21,86 @@ import product = require('../../product.json'); // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; - // Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/128.0.6613.186:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'libvk_swiftshader.so', - 'libffmpeg.so' + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'libvk_swiftshader.so', + 'libffmpeg.so' ]; - export async function getDependencies(packageType: 'deb' | 'rpm', buildDir: string, applicationName: string, arch: string): Promise { - if (packageType === 'deb') { - if (!isDebianArchString(arch)) { - throw new Error('Invalid Debian arch string ' + arch); - } - } - if (packageType === 'rpm' && !isRpmArchString(arch)) { - throw new Error('Invalid RPM arch string ' + arch); - } - - // Get the files for which we want to find dependencies. - const canAsar = false; // TODO@esm ASAR disabled in ESM - const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); - const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); - if (findResult.status) { - console.error('Error finding files:'); - console.error(findResult.stderr.toString()); - return []; - } - - const appPath = path.join(buildDir, applicationName); - // Add the native modules - const files = findResult.stdout.toString().trimEnd().split('\n'); - // Add the tunnel binary. - files.push(path.join(buildDir, 'bin', product.tunnelApplicationName)); - // Add the main executable. - files.push(appPath); - // Add chrome sandbox and crashpad handler. - files.push(path.join(buildDir, 'chrome-sandbox')); - files.push(path.join(buildDir, 'chrome_crashpad_handler')); - - // Generate the dependencies. - let dependencies: Set[]; - if (packageType === 'deb') { - const chromiumSysroot = await getChromiumSysroot(arch as DebianArchString); - const vscodeSysroot = await getVSCodeSysroot(arch as DebianArchString); - dependencies = generatePackageDepsDebian(files, arch as DebianArchString, chromiumSysroot, vscodeSysroot); - } else { - dependencies = generatePackageDepsRpm(files); - } - - // Merge all the dependencies. - const mergedDependencies = mergePackageDeps(dependencies); - - // Exclude bundled dependencies and sort - const sortedDependencies: string[] = Array.from(mergedDependencies).filter(dependency => { - return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }).sort(); - - const referenceGeneratedDeps = packageType === 'deb' ? - debianGeneratedDeps[arch as DebianArchString] : - rpmGeneratedDeps[arch as RpmArchString]; - if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed.' - + '\nOld:\n' + referenceGeneratedDeps.join('\n') - + '\nNew:\n' + sortedDependencies.join('\n'); - if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { - throw new Error(failMessage); - } else { - console.warn(failMessage); - } - } - - return sortedDependencies; + if (packageType === 'deb') { + if (!isDebianArchString(arch)) { + throw new Error('Invalid Debian arch string ' + arch); + } + } + if (packageType === 'rpm' && !isRpmArchString(arch)) { + throw new Error('Invalid RPM arch string ' + arch); + } + // Get the files for which we want to find dependencies. + const canAsar = false; // TODO@esm ASAR disabled in ESM + const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); + const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); + if (findResult.status) { + console.error('Error finding files:'); + console.error(findResult.stderr.toString()); + return []; + } + const appPath = path.join(buildDir, applicationName); + // Add the native modules + const files = findResult.stdout.toString().trimEnd().split('\n'); + // Add the tunnel binary. + files.push(path.join(buildDir, 'bin', product.tunnelApplicationName)); + // Add the main executable. + files.push(appPath); + // Add chrome sandbox and crashpad handler. + files.push(path.join(buildDir, 'chrome-sandbox')); + files.push(path.join(buildDir, 'chrome_crashpad_handler')); + // Generate the dependencies. + let dependencies: Set[]; + if (packageType === 'deb') { + const chromiumSysroot = await getChromiumSysroot(arch as DebianArchString); + const vscodeSysroot = await getVSCodeSysroot(arch as DebianArchString); + dependencies = generatePackageDepsDebian(files, arch as DebianArchString, chromiumSysroot, vscodeSysroot); + } + else { + dependencies = generatePackageDepsRpm(files); + } + // Merge all the dependencies. + const mergedDependencies = mergePackageDeps(dependencies); + // Exclude bundled dependencies and sort + const sortedDependencies: string[] = Array.from(mergedDependencies).filter(dependency => { + return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); + }).sort(); + const referenceGeneratedDeps = packageType === 'deb' ? + debianGeneratedDeps[arch as DebianArchString] : + rpmGeneratedDeps[arch as RpmArchString]; + if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { + const failMessage = 'The dependencies list has changed.' + + '\nOld:\n' + referenceGeneratedDeps.join('\n') + + '\nNew:\n' + sortedDependencies.join('\n'); + if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { + throw new Error(failMessage); + } + else { + console.warn(failMessage); + } + } + return sortedDependencies; } - - // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps: Set[]): Set { - const requires = new Set(); - for (const depSet of inputDeps) { - for (const dep of depSet) { - const trimmedDependency = dep.trim(); - if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { - requires.add(trimmedDependency); - } - } - } - return requires; + const requires = new Set(); + for (const depSet of inputDeps) { + for (const dep of depSet) { + const trimmedDependency = dep.trim(); + if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { + requires.add(trimmedDependency); + } + } + } + return requires; } diff --git a/build/linux/libcxx-fetcher.ts b/build/linux/libcxx-fetcher.ts index 6abb67faa7624..f75e5e4fb9729 100644 --- a/build/linux/libcxx-fetcher.ts +++ b/build/linux/libcxx-fetcher.ts @@ -2,78 +2,64 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - // Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. - import * as fs from 'fs'; import * as path from 'path'; import * as debug from 'debug'; import * as extract from 'extract-zip'; import { downloadArtifact } from '@electron/get'; - const root = path.dirname(path.dirname(__dirname)); - const d = debug('libcxx-fetcher'); - export async function downloadLibcxxHeaders(outDir: string, electronVersion: string, lib_name: string): Promise { - if (await fs.existsSync(path.resolve(outDir, 'include'))) { - return; - } - if (!await fs.existsSync(outDir)) { - await fs.mkdirSync(outDir, { recursive: true }); - } - - d(`downloading ${lib_name}_headers`); - const headers = await downloadArtifact({ - version: electronVersion, - isGeneric: true, - artifactName: `${lib_name}_headers.zip`, - }); - - d(`unpacking ${lib_name}_headers from ${headers}`); - await extract(headers, { dir: outDir }); + if (await fs.existsSync(path.resolve(outDir, 'include'))) { + return; + } + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); + } + d(`downloading ${lib_name}_headers`); + const headers = await downloadArtifact({ + version: electronVersion, + isGeneric: true, + artifactName: `${lib_name}_headers.zip`, + }); + d(`unpacking ${lib_name}_headers from ${headers}`); + await extract(headers, { dir: outDir }); } - export async function downloadLibcxxObjects(outDir: string, electronVersion: string, targetArch: string = 'x64'): Promise { - if (await fs.existsSync(path.resolve(outDir, 'libc++.a'))) { - return; - } - if (!await fs.existsSync(outDir)) { - await fs.mkdirSync(outDir, { recursive: true }); - } - - d(`downloading libcxx-objects-linux-${targetArch}`); - const objects = await downloadArtifact({ - version: electronVersion, - platform: 'linux', - artifactName: 'libcxx-objects', - arch: targetArch, - }); - - d(`unpacking libcxx-objects from ${objects}`); - await extract(objects, { dir: outDir }); + if (await fs.existsSync(path.resolve(outDir, 'libc++.a'))) { + return; + } + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); + } + d(`downloading libcxx-objects-linux-${targetArch}`); + const objects = await downloadArtifact({ + version: electronVersion, + platform: 'linux', + artifactName: 'libcxx-objects', + arch: targetArch, + }); + d(`unpacking libcxx-objects from ${objects}`); + await extract(objects, { dir: outDir }); } - async function main(): Promise { - const libcxxObjectsDirPath = process.env['VSCODE_LIBCXX_OBJECTS_DIR']; - const libcxxHeadersDownloadDir = process.env['VSCODE_LIBCXX_HEADERS_DIR']; - const libcxxabiHeadersDownloadDir = process.env['VSCODE_LIBCXXABI_HEADERS_DIR']; - const arch = process.env['VSCODE_ARCH']; - const packageJSON = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); - const electronVersion = packageJSON.devDependencies.electron; - - if (!libcxxObjectsDirPath || !libcxxHeadersDownloadDir || !libcxxabiHeadersDownloadDir) { - throw new Error('Required build env not set'); - } - - await downloadLibcxxObjects(libcxxObjectsDirPath, electronVersion, arch); - await downloadLibcxxHeaders(libcxxHeadersDownloadDir, electronVersion, 'libcxx'); - await downloadLibcxxHeaders(libcxxabiHeadersDownloadDir, electronVersion, 'libcxxabi'); + const libcxxObjectsDirPath = process.env['VSCODE_LIBCXX_OBJECTS_DIR']; + const libcxxHeadersDownloadDir = process.env['VSCODE_LIBCXX_HEADERS_DIR']; + const libcxxabiHeadersDownloadDir = process.env['VSCODE_LIBCXXABI_HEADERS_DIR']; + const arch = process.env['VSCODE_ARCH']; + const packageJSON = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); + const electronVersion = packageJSON.devDependencies.electron; + if (!libcxxObjectsDirPath || !libcxxHeadersDownloadDir || !libcxxabiHeadersDownloadDir) { + throw new Error('Required build env not set'); + } + await downloadLibcxxObjects(libcxxObjectsDirPath, electronVersion, arch); + await downloadLibcxxHeaders(libcxxHeadersDownloadDir, electronVersion, 'libcxx'); + await downloadLibcxxHeaders(libcxxabiHeadersDownloadDir, electronVersion, 'libcxxabi'); } - if (require.main === module) { - main().catch(err => { - console.error(err); - process.exit(1); - }); + main().catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/linux/rpm/calculate-deps.ts b/build/linux/rpm/calculate-deps.ts index 4be2200c01830..b76db1be5861f 100644 --- a/build/linux/rpm/calculate-deps.ts +++ b/build/linux/rpm/calculate-deps.ts @@ -2,34 +2,30 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { spawnSync } from 'child_process'; import { constants, statSync } from 'fs'; import { additionalDeps } from './dep-lists'; - export function generatePackageDeps(files: string[]): Set[] { - const dependencies: Set[] = files.map(file => calculatePackageDeps(file)); - const additionalDepsSet = new Set(additionalDeps); - dependencies.push(additionalDepsSet); - return dependencies; + const dependencies: Set[] = files.map(file => calculatePackageDeps(file)); + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; } - // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. function calculatePackageDeps(binaryPath: string): Set { - try { - if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - - const findRequiresResult = spawnSync('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); - if (findRequiresResult.status !== 0) { - throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); - } - - const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); - return requires; + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + const findRequiresResult = spawnSync('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); + if (findRequiresResult.status !== 0) { + throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); + } + const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); + return requires; } diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 8761e40cb1ec8..7ce8c8a0b4ed6 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -2,310 +2,308 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/additional_deps // Additional dependencies not in the rpm find-requires output. export const additionalDeps = [ - 'ca-certificates', // Make sure users have SSL certificates. - 'libgtk-3.so.0()(64bit)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libssl3.so(NSS_3.28)(64bit)', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'libvulkan.so.1()(64bit)', - 'libcurl.so.4()(64bit)', - 'xdg-utils' // OS integration + 'ca-certificates', // Make sure users have SSL certificates. + 'libgtk-3.so.0()(64bit)', + 'libnss3.so(NSS_3.22)(64bit)', + 'libssl3.so(NSS_3.28)(64bit)', + 'rpmlib(FileDigests) <= 4.6.0-1', + 'libvulkan.so.1()(64bit)', + 'libcurl.so.4()(64bit)', + 'xdg-utils' // OS integration ]; - export const referenceGeneratedDepsByArch = { - 'x86_64': [ - 'ca-certificates', - 'ld-linux-x86-64.so.2()(64bit)', - 'ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)', - 'ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)', - 'libX11.so.6()(64bit)', - 'libXcomposite.so.1()(64bit)', - 'libXdamage.so.1()(64bit)', - 'libXext.so.6()(64bit)', - 'libXfixes.so.3()(64bit)', - 'libXrandr.so.2()(64bit)', - 'libasound.so.2()(64bit)', - 'libasound.so.2(ALSA_0.9)(64bit)', - 'libasound.so.2(ALSA_0.9.0rc4)(64bit)', - 'libatk-1.0.so.0()(64bit)', - 'libatk-bridge-2.0.so.0()(64bit)', - 'libatspi.so.0()(64bit)', - 'libc.so.6()(64bit)', - 'libc.so.6(GLIBC_2.10)(64bit)', - 'libc.so.6(GLIBC_2.11)(64bit)', - 'libc.so.6(GLIBC_2.12)(64bit)', - 'libc.so.6(GLIBC_2.14)(64bit)', - 'libc.so.6(GLIBC_2.15)(64bit)', - 'libc.so.6(GLIBC_2.16)(64bit)', - 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', - 'libc.so.6(GLIBC_2.2.5)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.28)(64bit)', - 'libc.so.6(GLIBC_2.3)(64bit)', - 'libc.so.6(GLIBC_2.3.2)(64bit)', - 'libc.so.6(GLIBC_2.3.3)(64bit)', - 'libc.so.6(GLIBC_2.3.4)(64bit)', - 'libc.so.6(GLIBC_2.4)(64bit)', - 'libc.so.6(GLIBC_2.5)(64bit)', - 'libc.so.6(GLIBC_2.6)(64bit)', - 'libc.so.6(GLIBC_2.7)(64bit)', - 'libc.so.6(GLIBC_2.8)(64bit)', - 'libc.so.6(GLIBC_2.9)(64bit)', - 'libcairo.so.2()(64bit)', - 'libcurl.so.4()(64bit)', - 'libdbus-1.so.3()(64bit)', - 'libdbus-1.so.3(LIBDBUS_1_3)(64bit)', - 'libdl.so.2()(64bit)', - 'libdl.so.2(GLIBC_2.2.5)(64bit)', - 'libdrm.so.2()(64bit)', - 'libexpat.so.1()(64bit)', - 'libgbm.so.1()(64bit)', - 'libgcc_s.so.1()(64bit)', - 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgcc_s.so.1(GCC_3.3)(64bit)', - 'libgcc_s.so.1(GCC_4.2.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', - 'libgio-2.0.so.0()(64bit)', - 'libglib-2.0.so.0()(64bit)', - 'libgobject-2.0.so.0()(64bit)', - 'libgtk-3.so.0()(64bit)', - 'libm.so.6()(64bit)', - 'libm.so.6(GLIBC_2.2.5)(64bit)', - 'libnspr4.so()(64bit)', - 'libnss3.so()(64bit)', - 'libnss3.so(NSS_3.11)(64bit)', - 'libnss3.so(NSS_3.12)(64bit)', - 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.2)(64bit)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libnss3.so(NSS_3.3)(64bit)', - 'libnss3.so(NSS_3.30)(64bit)', - 'libnss3.so(NSS_3.4)(64bit)', - 'libnss3.so(NSS_3.5)(64bit)', - 'libnss3.so(NSS_3.9.2)(64bit)', - 'libnssutil3.so()(64bit)', - 'libnssutil3.so(NSSUTIL_3.12.3)(64bit)', - 'libpango-1.0.so.0()(64bit)', - 'libpthread.so.0()(64bit)', - 'libpthread.so.0(GLIBC_2.12)(64bit)', - 'libpthread.so.0(GLIBC_2.2.5)(64bit)', - 'libpthread.so.0(GLIBC_2.3.2)(64bit)', - 'libpthread.so.0(GLIBC_2.3.3)(64bit)', - 'libpthread.so.0(GLIBC_2.3.4)(64bit)', - 'librt.so.1()(64bit)', - 'librt.so.1(GLIBC_2.2.5)(64bit)', - 'libsmime3.so()(64bit)', - 'libsmime3.so(NSS_3.10)(64bit)', - 'libsmime3.so(NSS_3.2)(64bit)', - 'libssl3.so(NSS_3.28)(64bit)', - 'libutil.so.1()(64bit)', - 'libutil.so.1(GLIBC_2.2.5)(64bit)', - 'libxcb.so.1()(64bit)', - 'libxkbcommon.so.0()(64bit)', - 'libxkbcommon.so.0(V_0.5.0)(64bit)', - 'libxkbfile.so.1()(64bit)', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'rtld(GNU_HASH)', - 'xdg-utils' - ], - 'armv7hl': [ - 'ca-certificates', - 'ld-linux-armhf.so.3', - 'ld-linux-armhf.so.3(GLIBC_2.4)', - 'libX11.so.6', - 'libXcomposite.so.1', - 'libXdamage.so.1', - 'libXext.so.6', - 'libXfixes.so.3', - 'libXrandr.so.2', - 'libasound.so.2', - 'libasound.so.2(ALSA_0.9)', - 'libasound.so.2(ALSA_0.9.0rc4)', - 'libatk-1.0.so.0', - 'libatk-bridge-2.0.so.0', - 'libatspi.so.0', - 'libc.so.6', - 'libc.so.6(GLIBC_2.10)', - 'libc.so.6(GLIBC_2.11)', - 'libc.so.6(GLIBC_2.12)', - 'libc.so.6(GLIBC_2.14)', - 'libc.so.6(GLIBC_2.15)', - 'libc.so.6(GLIBC_2.16)', - 'libc.so.6(GLIBC_2.17)', - 'libc.so.6(GLIBC_2.18)', - 'libc.so.6(GLIBC_2.25)', - 'libc.so.6(GLIBC_2.28)', - 'libc.so.6(GLIBC_2.4)', - 'libc.so.6(GLIBC_2.5)', - 'libc.so.6(GLIBC_2.6)', - 'libc.so.6(GLIBC_2.7)', - 'libc.so.6(GLIBC_2.8)', - 'libc.so.6(GLIBC_2.9)', - 'libcairo.so.2', - 'libcurl.so.4()(64bit)', - 'libdbus-1.so.3', - 'libdbus-1.so.3(LIBDBUS_1_3)', - 'libdl.so.2', - 'libdl.so.2(GLIBC_2.4)', - 'libdrm.so.2', - 'libexpat.so.1', - 'libgbm.so.1', - 'libgcc_s.so.1', - 'libgcc_s.so.1(GCC_3.0)', - 'libgcc_s.so.1(GCC_3.5)', - 'libgcc_s.so.1(GCC_4.3.0)', - 'libgdk_pixbuf-2.0.so.0', - 'libgio-2.0.so.0', - 'libglib-2.0.so.0', - 'libgobject-2.0.so.0', - 'libgtk-3.so.0', - 'libgtk-3.so.0()(64bit)', - 'libm.so.6', - 'libm.so.6(GLIBC_2.4)', - 'libnspr4.so', - 'libnss3.so', - 'libnss3.so(NSS_3.11)', - 'libnss3.so(NSS_3.12)', - 'libnss3.so(NSS_3.12.1)', - 'libnss3.so(NSS_3.2)', - 'libnss3.so(NSS_3.22)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libnss3.so(NSS_3.3)', - 'libnss3.so(NSS_3.30)', - 'libnss3.so(NSS_3.4)', - 'libnss3.so(NSS_3.5)', - 'libnss3.so(NSS_3.9.2)', - 'libnssutil3.so', - 'libnssutil3.so(NSSUTIL_3.12.3)', - 'libpango-1.0.so.0', - 'libpthread.so.0', - 'libpthread.so.0(GLIBC_2.12)', - 'libpthread.so.0(GLIBC_2.4)', - 'librt.so.1', - 'librt.so.1(GLIBC_2.4)', - 'libsmime3.so', - 'libsmime3.so(NSS_3.10)', - 'libsmime3.so(NSS_3.2)', - 'libssl3.so(NSS_3.28)(64bit)', - 'libstdc++.so.6', - 'libstdc++.so.6(CXXABI_1.3)', - 'libstdc++.so.6(CXXABI_1.3.5)', - 'libstdc++.so.6(CXXABI_1.3.8)', - 'libstdc++.so.6(CXXABI_1.3.9)', - 'libstdc++.so.6(CXXABI_ARM_1.3.3)', - 'libstdc++.so.6(GLIBCXX_3.4)', - 'libstdc++.so.6(GLIBCXX_3.4.11)', - 'libstdc++.so.6(GLIBCXX_3.4.14)', - 'libstdc++.so.6(GLIBCXX_3.4.15)', - 'libstdc++.so.6(GLIBCXX_3.4.18)', - 'libstdc++.so.6(GLIBCXX_3.4.19)', - 'libstdc++.so.6(GLIBCXX_3.4.20)', - 'libstdc++.so.6(GLIBCXX_3.4.21)', - 'libstdc++.so.6(GLIBCXX_3.4.22)', - 'libstdc++.so.6(GLIBCXX_3.4.5)', - 'libstdc++.so.6(GLIBCXX_3.4.9)', - 'libutil.so.1', - 'libutil.so.1(GLIBC_2.4)', - 'libxcb.so.1', - 'libxkbcommon.so.0', - 'libxkbcommon.so.0(V_0.5.0)', - 'libxkbfile.so.1', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'rtld(GNU_HASH)', - 'xdg-utils' - ], - 'aarch64': [ - 'ca-certificates', - 'ld-linux-aarch64.so.1()(64bit)', - 'ld-linux-aarch64.so.1(GLIBC_2.17)(64bit)', - 'libX11.so.6()(64bit)', - 'libXcomposite.so.1()(64bit)', - 'libXdamage.so.1()(64bit)', - 'libXext.so.6()(64bit)', - 'libXfixes.so.3()(64bit)', - 'libXrandr.so.2()(64bit)', - 'libasound.so.2()(64bit)', - 'libasound.so.2(ALSA_0.9)(64bit)', - 'libasound.so.2(ALSA_0.9.0rc4)(64bit)', - 'libatk-1.0.so.0()(64bit)', - 'libatk-bridge-2.0.so.0()(64bit)', - 'libatspi.so.0()(64bit)', - 'libc.so.6()(64bit)', - 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.28)(64bit)', - 'libcairo.so.2()(64bit)', - 'libcurl.so.4()(64bit)', - 'libdbus-1.so.3()(64bit)', - 'libdbus-1.so.3(LIBDBUS_1_3)(64bit)', - 'libdl.so.2()(64bit)', - 'libdl.so.2(GLIBC_2.17)(64bit)', - 'libdrm.so.2()(64bit)', - 'libexpat.so.1()(64bit)', - 'libgbm.so.1()(64bit)', - 'libgcc_s.so.1()(64bit)', - 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgcc_s.so.1(GCC_3.3)(64bit)', - 'libgcc_s.so.1(GCC_4.2.0)(64bit)', - 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', - 'libgio-2.0.so.0()(64bit)', - 'libglib-2.0.so.0()(64bit)', - 'libgobject-2.0.so.0()(64bit)', - 'libgtk-3.so.0()(64bit)', - 'libm.so.6()(64bit)', - 'libm.so.6(GLIBC_2.17)(64bit)', - 'libnspr4.so()(64bit)', - 'libnss3.so()(64bit)', - 'libnss3.so(NSS_3.11)(64bit)', - 'libnss3.so(NSS_3.12)(64bit)', - 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.2)(64bit)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libnss3.so(NSS_3.3)(64bit)', - 'libnss3.so(NSS_3.30)(64bit)', - 'libnss3.so(NSS_3.4)(64bit)', - 'libnss3.so(NSS_3.5)(64bit)', - 'libnss3.so(NSS_3.9.2)(64bit)', - 'libnssutil3.so()(64bit)', - 'libnssutil3.so(NSSUTIL_3.12.3)(64bit)', - 'libpango-1.0.so.0()(64bit)', - 'libpthread.so.0()(64bit)', - 'libpthread.so.0(GLIBC_2.17)(64bit)', - 'libsmime3.so()(64bit)', - 'libsmime3.so(NSS_3.10)(64bit)', - 'libsmime3.so(NSS_3.2)(64bit)', - 'libssl3.so(NSS_3.28)(64bit)', - 'libstdc++.so.6()(64bit)', - 'libstdc++.so.6(CXXABI_1.3)(64bit)', - 'libstdc++.so.6(CXXABI_1.3.5)(64bit)', - 'libstdc++.so.6(CXXABI_1.3.8)(64bit)', - 'libstdc++.so.6(CXXABI_1.3.9)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.11)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.14)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.15)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.18)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.19)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.20)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.21)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.22)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.5)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.9)(64bit)', - 'libutil.so.1()(64bit)', - 'libutil.so.1(GLIBC_2.17)(64bit)', - 'libxcb.so.1()(64bit)', - 'libxkbcommon.so.0()(64bit)', - 'libxkbcommon.so.0(V_0.5.0)(64bit)', - 'libxkbfile.so.1()(64bit)', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'rtld(GNU_HASH)', - 'xdg-utils' - ] + 'x86_64': [ + 'ca-certificates', + 'ld-linux-x86-64.so.2()(64bit)', + 'ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)', + 'ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)', + 'libX11.so.6()(64bit)', + 'libXcomposite.so.1()(64bit)', + 'libXdamage.so.1()(64bit)', + 'libXext.so.6()(64bit)', + 'libXfixes.so.3()(64bit)', + 'libXrandr.so.2()(64bit)', + 'libasound.so.2()(64bit)', + 'libasound.so.2(ALSA_0.9)(64bit)', + 'libasound.so.2(ALSA_0.9.0rc4)(64bit)', + 'libatk-1.0.so.0()(64bit)', + 'libatk-bridge-2.0.so.0()(64bit)', + 'libatspi.so.0()(64bit)', + 'libc.so.6()(64bit)', + 'libc.so.6(GLIBC_2.10)(64bit)', + 'libc.so.6(GLIBC_2.11)(64bit)', + 'libc.so.6(GLIBC_2.12)(64bit)', + 'libc.so.6(GLIBC_2.14)(64bit)', + 'libc.so.6(GLIBC_2.15)(64bit)', + 'libc.so.6(GLIBC_2.16)(64bit)', + 'libc.so.6(GLIBC_2.17)(64bit)', + 'libc.so.6(GLIBC_2.18)(64bit)', + 'libc.so.6(GLIBC_2.2.5)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', + 'libc.so.6(GLIBC_2.28)(64bit)', + 'libc.so.6(GLIBC_2.3)(64bit)', + 'libc.so.6(GLIBC_2.3.2)(64bit)', + 'libc.so.6(GLIBC_2.3.3)(64bit)', + 'libc.so.6(GLIBC_2.3.4)(64bit)', + 'libc.so.6(GLIBC_2.4)(64bit)', + 'libc.so.6(GLIBC_2.5)(64bit)', + 'libc.so.6(GLIBC_2.6)(64bit)', + 'libc.so.6(GLIBC_2.7)(64bit)', + 'libc.so.6(GLIBC_2.8)(64bit)', + 'libc.so.6(GLIBC_2.9)(64bit)', + 'libcairo.so.2()(64bit)', + 'libcurl.so.4()(64bit)', + 'libdbus-1.so.3()(64bit)', + 'libdbus-1.so.3(LIBDBUS_1_3)(64bit)', + 'libdl.so.2()(64bit)', + 'libdl.so.2(GLIBC_2.2.5)(64bit)', + 'libdrm.so.2()(64bit)', + 'libexpat.so.1()(64bit)', + 'libgbm.so.1()(64bit)', + 'libgcc_s.so.1()(64bit)', + 'libgcc_s.so.1(GCC_3.0)(64bit)', + 'libgcc_s.so.1(GCC_3.3)(64bit)', + 'libgcc_s.so.1(GCC_4.2.0)(64bit)', + 'libgdk_pixbuf-2.0.so.0()(64bit)', + 'libgio-2.0.so.0()(64bit)', + 'libglib-2.0.so.0()(64bit)', + 'libgobject-2.0.so.0()(64bit)', + 'libgtk-3.so.0()(64bit)', + 'libm.so.6()(64bit)', + 'libm.so.6(GLIBC_2.2.5)(64bit)', + 'libnspr4.so()(64bit)', + 'libnss3.so()(64bit)', + 'libnss3.so(NSS_3.11)(64bit)', + 'libnss3.so(NSS_3.12)(64bit)', + 'libnss3.so(NSS_3.12.1)(64bit)', + 'libnss3.so(NSS_3.2)(64bit)', + 'libnss3.so(NSS_3.22)(64bit)', + 'libnss3.so(NSS_3.3)(64bit)', + 'libnss3.so(NSS_3.30)(64bit)', + 'libnss3.so(NSS_3.4)(64bit)', + 'libnss3.so(NSS_3.5)(64bit)', + 'libnss3.so(NSS_3.9.2)(64bit)', + 'libnssutil3.so()(64bit)', + 'libnssutil3.so(NSSUTIL_3.12.3)(64bit)', + 'libpango-1.0.so.0()(64bit)', + 'libpthread.so.0()(64bit)', + 'libpthread.so.0(GLIBC_2.12)(64bit)', + 'libpthread.so.0(GLIBC_2.2.5)(64bit)', + 'libpthread.so.0(GLIBC_2.3.2)(64bit)', + 'libpthread.so.0(GLIBC_2.3.3)(64bit)', + 'libpthread.so.0(GLIBC_2.3.4)(64bit)', + 'librt.so.1()(64bit)', + 'librt.so.1(GLIBC_2.2.5)(64bit)', + 'libsmime3.so()(64bit)', + 'libsmime3.so(NSS_3.10)(64bit)', + 'libsmime3.so(NSS_3.2)(64bit)', + 'libssl3.so(NSS_3.28)(64bit)', + 'libutil.so.1()(64bit)', + 'libutil.so.1(GLIBC_2.2.5)(64bit)', + 'libxcb.so.1()(64bit)', + 'libxkbcommon.so.0()(64bit)', + 'libxkbcommon.so.0(V_0.5.0)(64bit)', + 'libxkbfile.so.1()(64bit)', + 'rpmlib(FileDigests) <= 4.6.0-1', + 'rtld(GNU_HASH)', + 'xdg-utils' + ], + 'armv7hl': [ + 'ca-certificates', + 'ld-linux-armhf.so.3', + 'ld-linux-armhf.so.3(GLIBC_2.4)', + 'libX11.so.6', + 'libXcomposite.so.1', + 'libXdamage.so.1', + 'libXext.so.6', + 'libXfixes.so.3', + 'libXrandr.so.2', + 'libasound.so.2', + 'libasound.so.2(ALSA_0.9)', + 'libasound.so.2(ALSA_0.9.0rc4)', + 'libatk-1.0.so.0', + 'libatk-bridge-2.0.so.0', + 'libatspi.so.0', + 'libc.so.6', + 'libc.so.6(GLIBC_2.10)', + 'libc.so.6(GLIBC_2.11)', + 'libc.so.6(GLIBC_2.12)', + 'libc.so.6(GLIBC_2.14)', + 'libc.so.6(GLIBC_2.15)', + 'libc.so.6(GLIBC_2.16)', + 'libc.so.6(GLIBC_2.17)', + 'libc.so.6(GLIBC_2.18)', + 'libc.so.6(GLIBC_2.25)', + 'libc.so.6(GLIBC_2.28)', + 'libc.so.6(GLIBC_2.4)', + 'libc.so.6(GLIBC_2.5)', + 'libc.so.6(GLIBC_2.6)', + 'libc.so.6(GLIBC_2.7)', + 'libc.so.6(GLIBC_2.8)', + 'libc.so.6(GLIBC_2.9)', + 'libcairo.so.2', + 'libcurl.so.4()(64bit)', + 'libdbus-1.so.3', + 'libdbus-1.so.3(LIBDBUS_1_3)', + 'libdl.so.2', + 'libdl.so.2(GLIBC_2.4)', + 'libdrm.so.2', + 'libexpat.so.1', + 'libgbm.so.1', + 'libgcc_s.so.1', + 'libgcc_s.so.1(GCC_3.0)', + 'libgcc_s.so.1(GCC_3.5)', + 'libgcc_s.so.1(GCC_4.3.0)', + 'libgdk_pixbuf-2.0.so.0', + 'libgio-2.0.so.0', + 'libglib-2.0.so.0', + 'libgobject-2.0.so.0', + 'libgtk-3.so.0', + 'libgtk-3.so.0()(64bit)', + 'libm.so.6', + 'libm.so.6(GLIBC_2.4)', + 'libnspr4.so', + 'libnss3.so', + 'libnss3.so(NSS_3.11)', + 'libnss3.so(NSS_3.12)', + 'libnss3.so(NSS_3.12.1)', + 'libnss3.so(NSS_3.2)', + 'libnss3.so(NSS_3.22)', + 'libnss3.so(NSS_3.22)(64bit)', + 'libnss3.so(NSS_3.3)', + 'libnss3.so(NSS_3.30)', + 'libnss3.so(NSS_3.4)', + 'libnss3.so(NSS_3.5)', + 'libnss3.so(NSS_3.9.2)', + 'libnssutil3.so', + 'libnssutil3.so(NSSUTIL_3.12.3)', + 'libpango-1.0.so.0', + 'libpthread.so.0', + 'libpthread.so.0(GLIBC_2.12)', + 'libpthread.so.0(GLIBC_2.4)', + 'librt.so.1', + 'librt.so.1(GLIBC_2.4)', + 'libsmime3.so', + 'libsmime3.so(NSS_3.10)', + 'libsmime3.so(NSS_3.2)', + 'libssl3.so(NSS_3.28)(64bit)', + 'libstdc++.so.6', + 'libstdc++.so.6(CXXABI_1.3)', + 'libstdc++.so.6(CXXABI_1.3.5)', + 'libstdc++.so.6(CXXABI_1.3.8)', + 'libstdc++.so.6(CXXABI_1.3.9)', + 'libstdc++.so.6(CXXABI_ARM_1.3.3)', + 'libstdc++.so.6(GLIBCXX_3.4)', + 'libstdc++.so.6(GLIBCXX_3.4.11)', + 'libstdc++.so.6(GLIBCXX_3.4.14)', + 'libstdc++.so.6(GLIBCXX_3.4.15)', + 'libstdc++.so.6(GLIBCXX_3.4.18)', + 'libstdc++.so.6(GLIBCXX_3.4.19)', + 'libstdc++.so.6(GLIBCXX_3.4.20)', + 'libstdc++.so.6(GLIBCXX_3.4.21)', + 'libstdc++.so.6(GLIBCXX_3.4.22)', + 'libstdc++.so.6(GLIBCXX_3.4.5)', + 'libstdc++.so.6(GLIBCXX_3.4.9)', + 'libutil.so.1', + 'libutil.so.1(GLIBC_2.4)', + 'libxcb.so.1', + 'libxkbcommon.so.0', + 'libxkbcommon.so.0(V_0.5.0)', + 'libxkbfile.so.1', + 'rpmlib(FileDigests) <= 4.6.0-1', + 'rtld(GNU_HASH)', + 'xdg-utils' + ], + 'aarch64': [ + 'ca-certificates', + 'ld-linux-aarch64.so.1()(64bit)', + 'ld-linux-aarch64.so.1(GLIBC_2.17)(64bit)', + 'libX11.so.6()(64bit)', + 'libXcomposite.so.1()(64bit)', + 'libXdamage.so.1()(64bit)', + 'libXext.so.6()(64bit)', + 'libXfixes.so.3()(64bit)', + 'libXrandr.so.2()(64bit)', + 'libasound.so.2()(64bit)', + 'libasound.so.2(ALSA_0.9)(64bit)', + 'libasound.so.2(ALSA_0.9.0rc4)(64bit)', + 'libatk-1.0.so.0()(64bit)', + 'libatk-bridge-2.0.so.0()(64bit)', + 'libatspi.so.0()(64bit)', + 'libc.so.6()(64bit)', + 'libc.so.6(GLIBC_2.17)(64bit)', + 'libc.so.6(GLIBC_2.18)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', + 'libc.so.6(GLIBC_2.28)(64bit)', + 'libcairo.so.2()(64bit)', + 'libcurl.so.4()(64bit)', + 'libdbus-1.so.3()(64bit)', + 'libdbus-1.so.3(LIBDBUS_1_3)(64bit)', + 'libdl.so.2()(64bit)', + 'libdl.so.2(GLIBC_2.17)(64bit)', + 'libdrm.so.2()(64bit)', + 'libexpat.so.1()(64bit)', + 'libgbm.so.1()(64bit)', + 'libgcc_s.so.1()(64bit)', + 'libgcc_s.so.1(GCC_3.0)(64bit)', + 'libgcc_s.so.1(GCC_3.3)(64bit)', + 'libgcc_s.so.1(GCC_4.2.0)(64bit)', + 'libgcc_s.so.1(GCC_4.5.0)(64bit)', + 'libgdk_pixbuf-2.0.so.0()(64bit)', + 'libgio-2.0.so.0()(64bit)', + 'libglib-2.0.so.0()(64bit)', + 'libgobject-2.0.so.0()(64bit)', + 'libgtk-3.so.0()(64bit)', + 'libm.so.6()(64bit)', + 'libm.so.6(GLIBC_2.17)(64bit)', + 'libnspr4.so()(64bit)', + 'libnss3.so()(64bit)', + 'libnss3.so(NSS_3.11)(64bit)', + 'libnss3.so(NSS_3.12)(64bit)', + 'libnss3.so(NSS_3.12.1)(64bit)', + 'libnss3.so(NSS_3.2)(64bit)', + 'libnss3.so(NSS_3.22)(64bit)', + 'libnss3.so(NSS_3.3)(64bit)', + 'libnss3.so(NSS_3.30)(64bit)', + 'libnss3.so(NSS_3.4)(64bit)', + 'libnss3.so(NSS_3.5)(64bit)', + 'libnss3.so(NSS_3.9.2)(64bit)', + 'libnssutil3.so()(64bit)', + 'libnssutil3.so(NSSUTIL_3.12.3)(64bit)', + 'libpango-1.0.so.0()(64bit)', + 'libpthread.so.0()(64bit)', + 'libpthread.so.0(GLIBC_2.17)(64bit)', + 'libsmime3.so()(64bit)', + 'libsmime3.so(NSS_3.10)(64bit)', + 'libsmime3.so(NSS_3.2)(64bit)', + 'libssl3.so(NSS_3.28)(64bit)', + 'libstdc++.so.6()(64bit)', + 'libstdc++.so.6(CXXABI_1.3)(64bit)', + 'libstdc++.so.6(CXXABI_1.3.5)(64bit)', + 'libstdc++.so.6(CXXABI_1.3.8)(64bit)', + 'libstdc++.so.6(CXXABI_1.3.9)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.11)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.14)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.15)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.18)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.19)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.20)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.21)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.22)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.5)(64bit)', + 'libstdc++.so.6(GLIBCXX_3.4.9)(64bit)', + 'libutil.so.1()(64bit)', + 'libutil.so.1(GLIBC_2.17)(64bit)', + 'libxcb.so.1()(64bit)', + 'libxkbcommon.so.0()(64bit)', + 'libxkbcommon.so.0(V_0.5.0)(64bit)', + 'libxkbfile.so.1()(64bit)', + 'rpmlib(FileDigests) <= 4.6.0-1', + 'rtld(GNU_HASH)', + 'xdg-utils' + ] }; diff --git a/build/linux/rpm/types.ts b/build/linux/rpm/types.ts index c6a01da1cf530..30c36350a6467 100644 --- a/build/linux/rpm/types.ts +++ b/build/linux/rpm/types.ts @@ -2,9 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export type RpmArchString = 'x86_64' | 'armv7hl' | 'aarch64'; - export function isRpmArchString(s: string): s is RpmArchString { - return ['x86_64', 'armv7hl', 'aarch64'].includes(s); + return ['x86_64', 'armv7hl', 'aarch64'].includes(s); } diff --git a/build/win32/explorer-appx-fetcher.ts b/build/win32/explorer-appx-fetcher.ts index 89fbb57c064c4..9b14957a742d6 100644 --- a/build/win32/explorer-appx-fetcher.ts +++ b/build/win32/explorer-appx-fetcher.ts @@ -2,62 +2,49 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; - import * as fs from 'fs'; import * as debug from 'debug'; import * as extract from 'extract-zip'; import * as path from 'path'; import { downloadArtifact } from '@electron/get'; - const root = path.dirname(path.dirname(__dirname)); - const d = debug('explorer-appx-fetcher'); - export async function downloadExplorerAppx(outDir: string, quality: string = 'stable', targetArch: string = 'x64'): Promise { - const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code'; - const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`; - - if (await fs.existsSync(path.resolve(outDir, 'resources.pri'))) { - return; - } - - if (!await fs.existsSync(outDir)) { - await fs.mkdirSync(outDir, { recursive: true }); - } - - d(`downloading ${fileName}`); - const artifact = await downloadArtifact({ - isGeneric: true, - version: '3.0.4', - artifactName: fileName, - unsafelyDisableChecksums: true, - mirrorOptions: { - mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', - customDir: '3.0.4', - customFilename: fileName - } - }); - - d(`unpacking from ${fileName}`); - await extract(artifact, { dir: fs.realpathSync(outDir) }); + const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code'; + const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`; + if (await fs.existsSync(path.resolve(outDir, 'resources.pri'))) { + return; + } + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); + } + d(`downloading ${fileName}`); + const artifact = await downloadArtifact({ + isGeneric: true, + version: '3.0.4', + artifactName: fileName, + unsafelyDisableChecksums: true, + mirrorOptions: { + mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', + customDir: '3.0.4', + customFilename: fileName + } + }); + d(`unpacking from ${fileName}`); + await extract(artifact, { dir: fs.realpathSync(outDir) }); } - async function main(outputDir?: string): Promise { - const arch = process.env['VSCODE_ARCH']; - - if (!outputDir) { - throw new Error('Required build env not set'); - } - - const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); - await downloadExplorerAppx(outputDir, (product as any).quality, arch); + const arch = process.env['VSCODE_ARCH']; + if (!outputDir) { + throw new Error('Required build env not set'); + } + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); + await downloadExplorerAppx(outputDir, (product as any).quality, arch); } - if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/extensions/configuration-editing/Source/browser/net.ts b/extensions/configuration-editing/Source/browser/net.ts index 39baea382fb7b..05b92172fc075 100644 --- a/extensions/configuration-editing/Source/browser/net.ts +++ b/extensions/configuration-editing/Source/browser/net.ts @@ -2,5 +2,4 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export const agent = undefined; diff --git a/extensions/configuration-editing/Source/configurationEditingMain.ts b/extensions/configuration-editing/Source/configurationEditingMain.ts index f791557a70562..070c710d91545 100644 --- a/extensions/configuration-editing/Source/configurationEditingMain.ts +++ b/extensions/configuration-editing/Source/configurationEditingMain.ts @@ -2,242 +2,211 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { getLocation, JSONPath, parse, visit, Location } from 'jsonc-parser'; import * as vscode from 'vscode'; import { SettingsDocument } from './settingsDocumentHelper'; import { provideInstalledExtensionProposals } from './extensionsProposals'; import './importExportProfiles'; - export function activate(context: vscode.ExtensionContext): void { - //settings.json suggestions - context.subscriptions.push(registerSettingsCompletions()); - - //extensions suggestions - context.subscriptions.push(...registerExtensionsCompletions()); - - // launch.json variable suggestions - context.subscriptions.push(registerVariableCompletions('**/launch.json')); - - // task.json variable suggestions - context.subscriptions.push(registerVariableCompletions('**/tasks.json')); - - // Workspace file launch/tasks variable completions - context.subscriptions.push(registerVariableCompletions('**/*.code-workspace')); - - // keybindings.json/package.json context key suggestions - context.subscriptions.push(registerContextKeyCompletions()); + //settings.json suggestions + context.subscriptions.push(registerSettingsCompletions()); + //extensions suggestions + context.subscriptions.push(...registerExtensionsCompletions()); + // launch.json variable suggestions + context.subscriptions.push(registerVariableCompletions('**/launch.json')); + // task.json variable suggestions + context.subscriptions.push(registerVariableCompletions('**/tasks.json')); + // Workspace file launch/tasks variable completions + context.subscriptions.push(registerVariableCompletions('**/*.code-workspace')); + // keybindings.json/package.json context key suggestions + context.subscriptions.push(registerContextKeyCompletions()); } - function registerSettingsCompletions(): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern: '**/settings.json' }, { - provideCompletionItems(document, position, token) { - return new SettingsDocument(document).provideCompletionItems(position, token); - } - }); + return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern: '**/settings.json' }, { + provideCompletionItems(document, position, token) { + return new SettingsDocument(document).provideCompletionItems(position, token); + } + }); } - function registerVariableCompletions(pattern: string): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, { - provideCompletionItems(document, position, _token) { - const location = getLocation(document.getText(), document.offsetAt(position)); - if (isCompletingInsidePropertyStringValue(document, location, position)) { - if (document.fileName.endsWith('.code-workspace') && !isLocationInsideTopLevelProperty(location, ['launch', 'tasks'])) { - return []; - } - - let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/); - if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) { - range = new vscode.Range(position, position); - } - - return [ - { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") }, - { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") }, - { label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") }, - { label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") }, - { label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") }, - { label: 'file', detail: vscode.l10n.t("The current opened file") }, - { label: 'cwd', detail: vscode.l10n.t("The task runner's current working directory on startup") }, - { label: 'lineNumber', detail: vscode.l10n.t("The current selected line number in the active file") }, - { label: 'selectedText', detail: vscode.l10n.t("The current selected text in the active file") }, - { label: 'fileDirname', detail: vscode.l10n.t("The current opened file's dirname") }, - { label: 'fileExtname', detail: vscode.l10n.t("The current opened file's extension") }, - { label: 'fileBasename', detail: vscode.l10n.t("The current opened file's basename") }, - { label: 'fileBasenameNoExtension', detail: vscode.l10n.t("The current opened file's basename with no file extension") }, - { label: 'defaultBuildTask', detail: vscode.l10n.t("The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") }, - { label: 'pathSeparator', detail: vscode.l10n.t("The character used by the operating system to separate components in file paths. Is also aliased to '/'.") }, - { label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an extension is installed."), param: 'publisher.extension' }, - ].map(variable => ({ - label: `\${${variable.label}}`, - range, - insertText: variable.param ? new vscode.SnippetString(`\${${variable.label}:`).appendPlaceholder(variable.param).appendText('}') : (`\${${variable.label}}`), - detail: variable.detail - })); - } - - return []; - } - }); + return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, { + provideCompletionItems(document, position, _token) { + const location = getLocation(document.getText(), document.offsetAt(position)); + if (isCompletingInsidePropertyStringValue(document, location, position)) { + if (document.fileName.endsWith('.code-workspace') && !isLocationInsideTopLevelProperty(location, ['launch', 'tasks'])) { + return []; + } + let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) { + range = new vscode.Range(position, position); + } + return [ + { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") }, + { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") }, + { label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") }, + { label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") }, + { label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") }, + { label: 'file', detail: vscode.l10n.t("The current opened file") }, + { label: 'cwd', detail: vscode.l10n.t("The task runner's current working directory on startup") }, + { label: 'lineNumber', detail: vscode.l10n.t("The current selected line number in the active file") }, + { label: 'selectedText', detail: vscode.l10n.t("The current selected text in the active file") }, + { label: 'fileDirname', detail: vscode.l10n.t("The current opened file's dirname") }, + { label: 'fileExtname', detail: vscode.l10n.t("The current opened file's extension") }, + { label: 'fileBasename', detail: vscode.l10n.t("The current opened file's basename") }, + { label: 'fileBasenameNoExtension', detail: vscode.l10n.t("The current opened file's basename with no file extension") }, + { label: 'defaultBuildTask', detail: vscode.l10n.t("The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") }, + { label: 'pathSeparator', detail: vscode.l10n.t("The character used by the operating system to separate components in file paths. Is also aliased to '/'.") }, + { label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an extension is installed."), param: 'publisher.extension' }, + ].map(variable => ({ + label: `\${${variable.label}}`, + range, + insertText: variable.param ? new vscode.SnippetString(`\${${variable.label}:`).appendPlaceholder(variable.param).appendText('}') : (`\${${variable.label}}`), + detail: variable.detail + })); + } + return []; + } + }); } - function isCompletingInsidePropertyStringValue(document: vscode.TextDocument, location: Location, pos: vscode.Position) { - if (location.isAtPropertyKey) { - return false; - } - const previousNode = location.previousNode; - if (previousNode && previousNode.type === 'string') { - const offset = document.offsetAt(pos); - return offset > previousNode.offset && offset < previousNode.offset + previousNode.length; - } - return false; + if (location.isAtPropertyKey) { + return false; + } + const previousNode = location.previousNode; + if (previousNode && previousNode.type === 'string') { + const offset = document.offsetAt(pos); + return offset > previousNode.offset && offset < previousNode.offset + previousNode.length; + } + return false; } - function isLocationInsideTopLevelProperty(location: Location, values: string[]) { - return values.includes(location.path[0] as string); + return values.includes(location.path[0] as string); } - interface IExtensionsContent { - recommendations: string[]; + recommendations: string[]; } - function registerExtensionsCompletions(): vscode.Disposable[] { - return [registerExtensionsCompletionsInExtensionsDocument(), registerExtensionsCompletionsInWorkspaceConfigurationDocument()]; + return [registerExtensionsCompletionsInExtensionsDocument(), registerExtensionsCompletionsInWorkspaceConfigurationDocument()]; } - function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider({ pattern: '**/extensions.json' }, { - provideCompletionItems(document, position, _token) { - const location = getLocation(document.getText(), document.offsetAt(position)); - if (location.path[0] === 'recommendations') { - const range = getReplaceRange(document, location, position); - const extensionsContent = parse(document.getText()); - return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false); - } - return []; - } - }); + return vscode.languages.registerCompletionItemProvider({ pattern: '**/extensions.json' }, { + provideCompletionItems(document, position, _token) { + const location = getLocation(document.getText(), document.offsetAt(position)); + if (location.path[0] === 'recommendations') { + const range = getReplaceRange(document, location, position); + const extensionsContent = parse(document.getText()); + return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false); + } + return []; + } + }); } - function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider({ pattern: '**/*.code-workspace' }, { - provideCompletionItems(document, position, _token) { - const location = getLocation(document.getText(), document.offsetAt(position)); - if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') { - const range = getReplaceRange(document, location, position); - const extensionsContent = parse(document.getText())['extensions']; - return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false); - } - return []; - } - }); + return vscode.languages.registerCompletionItemProvider({ pattern: '**/*.code-workspace' }, { + provideCompletionItems(document, position, _token) { + const location = getLocation(document.getText(), document.offsetAt(position)); + if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') { + const range = getReplaceRange(document, location, position); + const extensionsContent = parse(document.getText())['extensions']; + return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false); + } + return []; + } + }); } - function getReplaceRange(document: vscode.TextDocument, location: Location, position: vscode.Position) { - const node = location.previousNode; - if (node) { - const nodeStart = document.positionAt(node.offset), nodeEnd = document.positionAt(node.offset + node.length); - if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) { - return new vscode.Range(nodeStart, nodeEnd); - } - } - return new vscode.Range(position, position); + const node = location.previousNode; + if (node) { + const nodeStart = document.positionAt(node.offset), nodeEnd = document.positionAt(node.offset + node.length); + if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) { + return new vscode.Range(nodeStart, nodeEnd); + } + } + return new vscode.Range(position, position); } - vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { - provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { - const result: vscode.SymbolInformation[] = []; - let name: string = ''; - let lastProperty = ''; - let startOffset = 0; - let depthInObjects = 0; - - visit(document.getText(), { - onObjectProperty: (property, _offset, _length) => { - lastProperty = property; - }, - onLiteralValue: (value: any, _offset: number, _length: number) => { - if (lastProperty === 'name') { - name = value; - } - }, - onObjectBegin: (offset: number, _length: number) => { - depthInObjects++; - if (depthInObjects === 2) { - startOffset = offset; - } - }, - onObjectEnd: (offset: number, _length: number) => { - if (name && depthInObjects === 2) { - result.push(new vscode.SymbolInformation(name, vscode.SymbolKind.Object, new vscode.Range(document.positionAt(startOffset), document.positionAt(offset)))); - } - depthInObjects--; - }, - }); - - return result; - } + provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { + const result: vscode.SymbolInformation[] = []; + let name: string = ''; + let lastProperty = ''; + let startOffset = 0; + let depthInObjects = 0; + visit(document.getText(), { + onObjectProperty: (property, _offset, _length) => { + lastProperty = property; + }, + onLiteralValue: (value: any, _offset: number, _length: number) => { + if (lastProperty === 'name') { + name = value; + } + }, + onObjectBegin: (offset: number, _length: number) => { + depthInObjects++; + if (depthInObjects === 2) { + startOffset = offset; + } + }, + onObjectEnd: (offset: number, _length: number) => { + if (name && depthInObjects === 2) { + result.push(new vscode.SymbolInformation(name, vscode.SymbolKind.Object, new vscode.Range(document.positionAt(startOffset), document.positionAt(offset)))); + } + depthInObjects--; + }, + }); + return result; + } }, { label: 'Launch Targets' }); - function registerContextKeyCompletions(): vscode.Disposable { - type ContextKeyInfo = { key: string; type?: string; description?: string }; - - const paths = new Map([ - [{ language: 'jsonc', pattern: '**/keybindings.json' }, [ - ['*', 'when'] - ]], - [{ language: 'json', pattern: '**/package.json' }, [ - ['contributes', 'menus', '*', '*', 'when'], - ['contributes', 'views', '*', '*', 'when'], - ['contributes', 'viewsWelcome', '*', 'when'], - ['contributes', 'keybindings', '*', 'when'], - ['contributes', 'keybindings', 'when'], - ]] - ]); - - return vscode.languages.registerCompletionItemProvider( - [...paths.keys()], - { - async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) { - - const location = getLocation(document.getText(), document.offsetAt(position)); - - if (location.isAtPropertyKey) { - return; - } - - let isValidLocation = false; - for (const [key, value] of paths) { - if (vscode.languages.match(key, document)) { - if (value.some(location.matches.bind(location))) { - isValidLocation = true; - break; - } - } - } - - if (!isValidLocation || !isCompletingInsidePropertyStringValue(document, location, position)) { - return; - } - - const replacing = document.getWordRangeAtPosition(position, /[a-zA-Z.]+/) || new vscode.Range(position, position); - const inserting = replacing.with(undefined, position); - - const data = await vscode.commands.executeCommand('getContextKeyInfo'); - if (token.isCancellationRequested || !data) { - return; - } - - const result = new vscode.CompletionList(); - for (const item of data) { - const completion = new vscode.CompletionItem(item.key, vscode.CompletionItemKind.Constant); - completion.detail = item.type; - completion.range = { replacing, inserting }; - completion.documentation = item.description; - result.items.push(completion); - } - return result; - } - } - ); + type ContextKeyInfo = { + key: string; + type?: string; + description?: string; + }; + const paths = new Map([ + [{ language: 'jsonc', pattern: '**/keybindings.json' }, [ + ['*', 'when'] + ]], + [{ language: 'json', pattern: '**/package.json' }, [ + ['contributes', 'menus', '*', '*', 'when'], + ['contributes', 'views', '*', '*', 'when'], + ['contributes', 'viewsWelcome', '*', 'when'], + ['contributes', 'keybindings', '*', 'when'], + ['contributes', 'keybindings', 'when'], + ]] + ]); + return vscode.languages.registerCompletionItemProvider([...paths.keys()], { + async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) { + const location = getLocation(document.getText(), document.offsetAt(position)); + if (location.isAtPropertyKey) { + return; + } + let isValidLocation = false; + for (const [key, value] of paths) { + if (vscode.languages.match(key, document)) { + if (value.some(location.matches.bind(location))) { + isValidLocation = true; + break; + } + } + } + if (!isValidLocation || !isCompletingInsidePropertyStringValue(document, location, position)) { + return; + } + const replacing = document.getWordRangeAtPosition(position, /[a-zA-Z.]+/) || new vscode.Range(position, position); + const inserting = replacing.with(undefined, position); + const data = await vscode.commands.executeCommand('getContextKeyInfo'); + if (token.isCancellationRequested || !data) { + return; + } + const result = new vscode.CompletionList(); + for (const item of data) { + const completion = new vscode.CompletionItem(item.key, vscode.CompletionItemKind.Constant); + completion.detail = item.type; + completion.range = { replacing, inserting }; + completion.documentation = item.description; + result.items.push(completion); + } + return result; + } + }); } diff --git a/extensions/configuration-editing/Source/extensionsProposals.ts b/extensions/configuration-editing/Source/extensionsProposals.ts index 6438281dc0b00..0b79a0fff93f0 100644 --- a/extensions/configuration-editing/Source/extensionsProposals.ts +++ b/extensions/configuration-editing/Source/extensionsProposals.ts @@ -2,57 +2,54 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - - export async function provideInstalledExtensionProposals(existing: string[], additionalText: string, range: vscode.Range, includeBuiltinExtensions: boolean): Promise { - if (Array.isArray(existing)) { - const extensions = includeBuiltinExtensions ? vscode.extensions.all : vscode.extensions.all.filter(e => !(e.id.startsWith('vscode.') || e.id === 'Microsoft.vscode-markdown')); - const knownExtensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); - if (knownExtensionProposals.length) { - return knownExtensionProposals.map(e => { - const item = new vscode.CompletionItem(e.id); - const insertText = `"${e.id}"${additionalText}`; - item.kind = vscode.CompletionItemKind.Value; - item.insertText = insertText; - item.range = range; - item.filterText = insertText; - return item; - }); - } else { - const example = new vscode.CompletionItem(vscode.l10n.t("Example")); - example.insertText = '"vscode.csharp"'; - example.kind = vscode.CompletionItemKind.Value; - example.range = range; - return [example]; - } - } - return []; + if (Array.isArray(existing)) { + const extensions = includeBuiltinExtensions ? vscode.extensions.all : vscode.extensions.all.filter(e => !(e.id.startsWith('vscode.') || e.id === 'Microsoft.vscode-markdown')); + const knownExtensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); + if (knownExtensionProposals.length) { + return knownExtensionProposals.map(e => { + const item = new vscode.CompletionItem(e.id); + const insertText = `"${e.id}"${additionalText}`; + item.kind = vscode.CompletionItemKind.Value; + item.insertText = insertText; + item.range = range; + item.filterText = insertText; + return item; + }); + } + else { + const example = new vscode.CompletionItem(vscode.l10n.t("Example")); + example.insertText = '"vscode.csharp"'; + example.kind = vscode.CompletionItemKind.Value; + example.range = range; + return [example]; + } + } + return []; } - export async function provideWorkspaceTrustExtensionProposals(existing: string[], range: vscode.Range): Promise { - if (Array.isArray(existing)) { - const extensions = vscode.extensions.all.filter(e => e.packageJSON.main); - const extensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); - if (extensionProposals.length) { - return extensionProposals.map(e => { - const item = new vscode.CompletionItem(e.id); - const insertText = `"${e.id}": {\n\t"supported": false,\n\t"version": "${e.packageJSON.version}"\n}`; - item.kind = vscode.CompletionItemKind.Value; - item.insertText = insertText; - item.range = range; - item.filterText = insertText; - return item; - }); - } else { - const example = new vscode.CompletionItem(vscode.l10n.t("Example")); - example.insertText = '"vscode.csharp: {\n\t"supported": false,\n\t"version": "0.0.0"\n}`;"'; - example.kind = vscode.CompletionItemKind.Value; - example.range = range; - return [example]; - } - } - - return []; + if (Array.isArray(existing)) { + const extensions = vscode.extensions.all.filter(e => e.packageJSON.main); + const extensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); + if (extensionProposals.length) { + return extensionProposals.map(e => { + const item = new vscode.CompletionItem(e.id); + const insertText = `"${e.id}": {\n\t"supported": false,\n\t"version": "${e.packageJSON.version}"\n}`; + item.kind = vscode.CompletionItemKind.Value; + item.insertText = insertText; + item.range = range; + item.filterText = insertText; + return item; + }); + } + else { + const example = new vscode.CompletionItem(vscode.l10n.t("Example")); + example.insertText = '"vscode.csharp: {\n\t"supported": false,\n\t"version": "0.0.0"\n}`;"'; + example.kind = vscode.CompletionItemKind.Value; + example.range = range; + return [example]; + } + } + return []; } diff --git a/extensions/configuration-editing/Source/importExportProfiles.ts b/extensions/configuration-editing/Source/importExportProfiles.ts index 1e66c7a2b34f6..54219b8736b60 100644 --- a/extensions/configuration-editing/Source/importExportProfiles.ts +++ b/extensions/configuration-editing/Source/importExportProfiles.ts @@ -2,80 +2,73 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { Octokit } from '@octokit/rest'; import * as vscode from 'vscode'; import { basename } from 'path'; import { agent } from './node/net'; - class GitHubGistProfileContentHandler implements vscode.ProfileContentHandler { - - readonly name = vscode.l10n.t('GitHub'); - readonly description = vscode.l10n.t('gist'); - - private _octokit: Promise | undefined; - private getOctokit(): Promise { - if (!this._octokit) { - this._octokit = (async () => { - const session = await vscode.authentication.getSession('github', ['gist', 'user:email'], { createIfNone: true }); - const token = session.accessToken; - - const { Octokit } = await import('@octokit/rest'); - - return new Octokit({ - request: { agent }, - userAgent: 'GitHub VSCode', - auth: `token ${token}` - }); - })(); - } - return this._octokit; - } - - async saveProfile(name: string, content: string): Promise<{ readonly id: string; readonly link: vscode.Uri } | null> { - const octokit = await this.getOctokit(); - const result = await octokit.gists.create({ - public: false, - files: { - [name]: { - content - } - } - }); - if (result.data.id && result.data.html_url) { - const link = vscode.Uri.parse(result.data.html_url); - return { id: result.data.id, link }; - } - return null; - } - - private _public_octokit: Promise | undefined; - private getPublicOctokit(): Promise { - if (!this._public_octokit) { - this._public_octokit = (async () => { - const { Octokit } = await import('@octokit/rest'); - return new Octokit({ request: { agent }, userAgent: 'GitHub VSCode' }); - })(); - } - return this._public_octokit; - } - - async readProfile(id: string): Promise; - async readProfile(uri: vscode.Uri): Promise; - async readProfile(arg: string | vscode.Uri): Promise { - const gist_id = typeof arg === 'string' ? arg : basename(arg.path); - const octokit = await this.getPublicOctokit(); - try { - const gist = await octokit.gists.get({ gist_id }); - if (gist.data.files) { - return gist.data.files[Object.keys(gist.data.files)[0]]?.content ?? null; - } - } catch (error) { - // ignore - } - return null; - } - + readonly name = vscode.l10n.t('GitHub'); + readonly description = vscode.l10n.t('gist'); + private _octokit: Promise | undefined; + private getOctokit(): Promise { + if (!this._octokit) { + this._octokit = (async () => { + const session = await vscode.authentication.getSession('github', ['gist', 'user:email'], { createIfNone: true }); + const token = session.accessToken; + const { Octokit } = await import('@octokit/rest'); + return new Octokit({ + request: { agent }, + userAgent: 'GitHub VSCode', + auth: `token ${token}` + }); + })(); + } + return this._octokit; + } + async saveProfile(name: string, content: string): Promise<{ + readonly id: string; + readonly link: vscode.Uri; + } | null> { + const octokit = await this.getOctokit(); + const result = await octokit.gists.create({ + public: false, + files: { + [name]: { + content + } + } + }); + if (result.data.id && result.data.html_url) { + const link = vscode.Uri.parse(result.data.html_url); + return { id: result.data.id, link }; + } + return null; + } + private _public_octokit: Promise | undefined; + private getPublicOctokit(): Promise { + if (!this._public_octokit) { + this._public_octokit = (async () => { + const { Octokit } = await import('@octokit/rest'); + return new Octokit({ request: { agent }, userAgent: 'GitHub VSCode' }); + })(); + } + return this._public_octokit; + } + async readProfile(id: string): Promise; + async readProfile(uri: vscode.Uri): Promise; + async readProfile(arg: string | vscode.Uri): Promise { + const gist_id = typeof arg === 'string' ? arg : basename(arg.path); + const octokit = await this.getPublicOctokit(); + try { + const gist = await octokit.gists.get({ gist_id }); + if (gist.data.files) { + return gist.data.files[Object.keys(gist.data.files)[0]]?.content ?? null; + } + } + catch (error) { + // ignore + } + return null; + } } - vscode.window.registerProfileContentHandler('github', new GitHubGistProfileContentHandler()); diff --git a/extensions/configuration-editing/Source/node/net.ts b/extensions/configuration-editing/Source/node/net.ts index 4bbc6c69475cb..4734ad2e4ac8b 100644 --- a/extensions/configuration-editing/Source/node/net.ts +++ b/extensions/configuration-editing/Source/node/net.ts @@ -2,28 +2,26 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { Agent, globalAgent } from 'https'; import { URL } from 'url'; import { httpsOverHttp } from 'tunnel'; import { window } from 'vscode'; - export const agent = getAgent(); - /** * Return an https agent for the given proxy URL, or return the * global https agent if the URL was empty or invalid. */ function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent { - if (!url) { - return globalAgent; - } - try { - const { hostname, port, username, password } = new URL(url); - const auth = username && password && `${username}:${password}`; - return httpsOverHttp({ proxy: { host: hostname, port, proxyAuth: auth } }); - } catch (e) { - window.showErrorMessage(`HTTPS_PROXY environment variable ignored: ${e.message}`); - return globalAgent; - } + if (!url) { + return globalAgent; + } + try { + const { hostname, port, username, password } = new URL(url); + const auth = username && password && `${username}:${password}`; + return httpsOverHttp({ proxy: { host: hostname, port, proxyAuth: auth } }); + } + catch (e) { + window.showErrorMessage(`HTTPS_PROXY environment variable ignored: ${e.message}`); + return globalAgent; + } } diff --git a/extensions/configuration-editing/Source/settingsDocumentHelper.ts b/extensions/configuration-editing/Source/settingsDocumentHelper.ts index 6135df5315a5e..b102859d50cfe 100644 --- a/extensions/configuration-editing/Source/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/Source/settingsDocumentHelper.ts @@ -2,362 +2,316 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { getLocation, Location, parse } from 'jsonc-parser'; import { provideInstalledExtensionProposals } from './extensionsProposals'; - const OVERRIDE_IDENTIFIER_REGEX = /\[([^\[\]]*)\]/g; - export class SettingsDocument { - - constructor(private document: vscode.TextDocument) { } - - public async provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): Promise { - const location = getLocation(this.document.getText(), this.document.offsetAt(position)); - - // window.title - if (location.path[0] === 'window.title') { - return this.provideWindowTitleCompletionItems(location, position); - } - - // files.association - if (location.path[0] === 'files.associations') { - return this.provideFilesAssociationsCompletionItems(location, position); - } - - // files.exclude, search.exclude, explorer.autoRevealExclude - if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude' || location.path[0] === 'explorer.autoRevealExclude') { - return this.provideExcludeCompletionItems(location, position); - } - - // files.defaultLanguage - if (location.path[0] === 'files.defaultLanguage') { - return this.provideLanguageCompletionItems(location, position); - } - - // workbench.editor.label - if (location.path[0] === 'workbench.editor.label.patterns') { - return this.provideEditorLabelCompletionItems(location, position); - } - - // settingsSync.ignoredExtensions - if (location.path[0] === 'settingsSync.ignoredExtensions') { - let ignoredExtensions = []; - try { - ignoredExtensions = parse(this.document.getText())['settingsSync.ignoredExtensions']; - } catch (e) {/* ignore error */ } - const range = this.getReplaceRange(location, position); - return provideInstalledExtensionProposals(ignoredExtensions, '', range, true); - } - - // remote.extensionKind - if (location.path[0] === 'remote.extensionKind' && location.path.length === 2 && location.isAtPropertyKey) { - let alreadyConfigured: string[] = []; - try { - alreadyConfigured = Object.keys(parse(this.document.getText())['remote.extensionKind']); - } catch (e) {/* ignore error */ } - const range = this.getReplaceRange(location, position); - return provideInstalledExtensionProposals(alreadyConfigured, location.previousNode ? '' : `: [\n\t"ui"\n]`, range, true); - } - - // remote.portsAttributes - if (location.path[0] === 'remote.portsAttributes' && location.path.length === 2 && location.isAtPropertyKey) { - return this.providePortsAttributesCompletionItem(this.getReplaceRange(location, position)); - } - - return this.provideLanguageOverridesCompletionItems(location, position); - } - - private getReplaceRange(location: Location, position: vscode.Position) { - const node = location.previousNode; - if (node) { - const nodeStart = this.document.positionAt(node.offset), nodeEnd = this.document.positionAt(node.offset + node.length); - if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) { - return new vscode.Range(nodeStart, nodeEnd); - } - } - return new vscode.Range(position, position); - } - - private isCompletingPropertyValue(location: Location, pos: vscode.Position) { - if (location.isAtPropertyKey) { - return false; - } - const previousNode = location.previousNode; - if (previousNode) { - const offset = this.document.offsetAt(pos); - return offset >= previousNode.offset && offset <= previousNode.offset + previousNode.length; - } - return true; - } - - private async provideWindowTitleCompletionItems(location: Location, pos: vscode.Position): Promise { - const completions: vscode.CompletionItem[] = []; - - if (!this.isCompletingPropertyValue(location, pos)) { - return completions; - } - - let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/); - if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) { - range = new vscode.Range(pos, pos); - } - - const getText = (variable: string) => { - const text = '${' + variable + '}'; - return location.previousNode ? text : JSON.stringify(text); - }; - - - completions.push(this.newSimpleCompletionItem(getText('activeEditorShort'), range, vscode.l10n.t("the file name (e.g. myFile.txt)"))); - completions.push(this.newSimpleCompletionItem(getText('activeEditorMedium'), range, vscode.l10n.t("the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)"))); - completions.push(this.newSimpleCompletionItem(getText('activeEditorLong'), range, vscode.l10n.t("the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)"))); - completions.push(this.newSimpleCompletionItem(getText('activeFolderShort'), range, vscode.l10n.t("the name of the folder the file is contained in (e.g. myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('activeFolderMedium'), range, vscode.l10n.t("the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('activeFolderLong'), range, vscode.l10n.t("the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('rootName'), range, vscode.l10n.t("name of the workspace with optional remote name and workspace indicator if applicable (e.g. myFolder, myRemoteFolder [SSH] or myWorkspace (Workspace))"))); - completions.push(this.newSimpleCompletionItem(getText('rootNameShort'), range, vscode.l10n.t("shortened name of the workspace without suffixes (e.g. myFolder or myWorkspace)"))); - completions.push(this.newSimpleCompletionItem(getText('rootPath'), range, vscode.l10n.t("file path of the workspace (e.g. /Users/Development/myWorkspace)"))); - completions.push(this.newSimpleCompletionItem(getText('folderName'), range, vscode.l10n.t("name of the workspace folder the file is contained in (e.g. myFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, vscode.l10n.t("file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. VS Code"))); - completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); - completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); - completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); - completions.push(this.newSimpleCompletionItem(getText('activeRepositoryName'), range, vscode.l10n.t("the name of the active repository (e.g. vscode)"))); - completions.push(this.newSimpleCompletionItem(getText('activeRepositoryBranchName'), range, vscode.l10n.t("the name of the active branch in the active repository (e.g. main)"))); - - return completions; - } - - private async provideEditorLabelCompletionItems(location: Location, pos: vscode.Position): Promise { - const completions: vscode.CompletionItem[] = []; - - if (!this.isCompletingPropertyValue(location, pos)) { - return completions; - } - - let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/); - if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) { - range = new vscode.Range(pos, pos); - } - - const getText = (variable: string) => { - const text = '${' + variable + '}'; - return location.previousNode ? text : JSON.stringify(text); - }; - - - completions.push(this.newSimpleCompletionItem(getText('dirname'), range, vscode.l10n.t("The parent folder name of the editor (e.g. myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('dirname(1)'), range, vscode.l10n.t("The nth parent folder name of the editor"))); - completions.push(this.newSimpleCompletionItem(getText('filename'), range, vscode.l10n.t("The file name of the editor without its directory or extension (e.g. myFile)"))); - completions.push(this.newSimpleCompletionItem(getText('extname'), range, vscode.l10n.t("The file extension of the editor (e.g. txt)"))); - return completions; - } - - private async provideFilesAssociationsCompletionItems(location: Location, position: vscode.Position): Promise { - const completions: vscode.CompletionItem[] = []; - - if (location.path.length === 2) { - // Key - if (location.path[1] === '') { - const range = this.getReplaceRange(location, position); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Files with Extension"), - documentation: vscode.l10n.t("Map all files matching the glob pattern in their filename to the language with the given identifier."), - snippet: location.isAtPropertyKey ? '"*.${1:extension}": "${2:language}"' : '{ "*.${1:extension}": "${2:language}" }', - range - })); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Files with Path"), - documentation: vscode.l10n.t("Map all files matching the absolute path glob pattern in their path to the language with the given identifier."), - snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }', - range - })); - } else if (this.isCompletingPropertyValue(location, position)) { - // Value - return this.provideLanguageCompletionItemsForLanguageOverrides(this.getReplaceRange(location, position)); - } - } - - return completions; - } - - private async provideExcludeCompletionItems(location: Location, position: vscode.Position): Promise { - const completions: vscode.CompletionItem[] = []; - - // Key - if (location.path.length === 1 || (location.path.length === 2 && location.path[1] === '')) { - const range = this.getReplaceRange(location, position); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Files by Extension"), - documentation: vscode.l10n.t("Match all files of a specific file extension."), - snippet: location.path.length === 2 ? '"**/*.${1:extension}": true' : '{ "**/*.${1:extension}": true }', - range - })); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Files with Multiple Extensions"), - documentation: vscode.l10n.t("Match all files with any of the file extensions."), - snippet: location.path.length === 2 ? '"**/*.{ext1,ext2,ext3}": true' : '{ "**/*.{ext1,ext2,ext3}": true }', - range - })); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Files with Siblings by Name"), - documentation: vscode.l10n.t("Match files that have siblings with the same name but a different extension."), - snippet: location.path.length === 2 ? '"**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" }' : '{ "**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" } }', - range - })); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Folder by Name (Top Level)"), - documentation: vscode.l10n.t("Match a top level folder with a specific name."), - snippet: location.path.length === 2 ? '"${1:name}": true' : '{ "${1:name}": true }', - range - })); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Folders with Multiple Names (Top Level)"), - documentation: vscode.l10n.t("Match multiple top level folders."), - snippet: location.path.length === 2 ? '"{folder1,folder2,folder3}": true' : '{ "{folder1,folder2,folder3}": true }', - range - })); - - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Folder by Name (Any Location)"), - documentation: vscode.l10n.t("Match a folder with a specific name in any location."), - snippet: location.path.length === 2 ? '"**/${1:name}": true' : '{ "**/${1:name}": true }', - range - })); - } - - // Value - else if (location.path.length === 2 && this.isCompletingPropertyValue(location, position)) { - const range = this.getReplaceRange(location, position); - completions.push(this.newSnippetCompletionItem({ - label: vscode.l10n.t("Files with Siblings by Name"), - documentation: vscode.l10n.t("Match files that have siblings with the same name but a different extension."), - snippet: '{ "when": "$(basename).${1:extension}" }', - range - })); - } - - return completions; - } - - private async provideLanguageCompletionItems(location: Location, position: vscode.Position): Promise { - if (location.path.length === 1 && this.isCompletingPropertyValue(location, position)) { - const range = this.getReplaceRange(location, position); - const languages = await vscode.languages.getLanguages(); - return [ - this.newSimpleCompletionItem(JSON.stringify('${activeEditorLanguage}'), range, vscode.l10n.t("Use the language of the currently active text editor if any")), - ...languages.map(l => this.newSimpleCompletionItem(JSON.stringify(l), range)) - ]; - } - return []; - } - - private async provideLanguageCompletionItemsForLanguageOverrides(range: vscode.Range): Promise { - const languages = await vscode.languages.getLanguages(); - const completionItems = []; - for (const language of languages) { - const item = new vscode.CompletionItem(JSON.stringify(language)); - item.kind = vscode.CompletionItemKind.Property; - item.range = range; - completionItems.push(item); - } - return completionItems; - } - - private async provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): Promise { - if (location.path.length === 1 && location.isAtPropertyKey && location.previousNode && typeof location.previousNode.value === 'string' && location.previousNode.value.startsWith('[')) { - const startPosition = this.document.positionAt(location.previousNode.offset + 1); - const endPosition = startPosition.translate(undefined, location.previousNode.value.length); - const donotSuggestLanguages: string[] = []; - const languageOverridesRanges: vscode.Range[] = []; - let matches = OVERRIDE_IDENTIFIER_REGEX.exec(location.previousNode.value); - let lastLanguageOverrideRange: vscode.Range | undefined; - while (matches?.length) { - lastLanguageOverrideRange = new vscode.Range(this.document.positionAt(location.previousNode.offset + 1 + matches.index), this.document.positionAt(location.previousNode.offset + 1 + matches.index + matches[0].length)); - languageOverridesRanges.push(lastLanguageOverrideRange); - /* Suggest the configured language if the position is in the match range */ - if (!lastLanguageOverrideRange.contains(position)) { - donotSuggestLanguages.push(matches[1].trim()); - } - matches = OVERRIDE_IDENTIFIER_REGEX.exec(location.previousNode.value); - } - const lastLanguageOverrideEndPosition = lastLanguageOverrideRange ? lastLanguageOverrideRange.end : startPosition; - if (lastLanguageOverrideEndPosition.isBefore(endPosition)) { - languageOverridesRanges.push(new vscode.Range(lastLanguageOverrideEndPosition, endPosition)); - } - const languageOverrideRange = languageOverridesRanges.find(range => range.contains(position)); - - /** - * Skip if suggestions are for first language override range - * Since VSCode registers language overrides to the schema, JSON language server does suggestions for first language override. - */ - if (languageOverrideRange && !languageOverrideRange.isEqual(languageOverridesRanges[0])) { - const languages = await vscode.languages.getLanguages(); - const completionItems = []; - for (const language of languages) { - if (!donotSuggestLanguages.includes(language)) { - const item = new vscode.CompletionItem(`[${language}]`); - item.kind = vscode.CompletionItemKind.Property; - item.range = languageOverrideRange; - completionItems.push(item); - } - } - return completionItems; - } - } - return []; - } - - private providePortsAttributesCompletionItem(range: vscode.Range): vscode.CompletionItem[] { - return [this.newSnippetCompletionItem( - { - label: '\"3000\"', - documentation: 'Single Port Attribute', - range, - snippet: '\n \"${1:3000}\": {\n \"label\": \"${2:Application}\",\n \"onAutoForward\": \"${3:openPreview}\"\n }\n' - }), - this.newSnippetCompletionItem( - { - label: '\"5000-6000\"', - documentation: 'Ranged Port Attribute', - range, - snippet: '\n \"${1:40000-55000}\": {\n \"onAutoForward\": \"${2:ignore}\"\n }\n' - }), - this.newSnippetCompletionItem( - { - label: '\".+\\\\/server.js\"', - documentation: 'Command Match Port Attribute', - range, - snippet: '\n \"${1:.+\\\\/server.js\}\": {\n \"label\": \"${2:Application}\",\n \"onAutoForward\": \"${3:openPreview}\"\n }\n' - }) - ]; - } - - private newSimpleCompletionItem(text: string, range: vscode.Range, description?: string, insertText?: string): vscode.CompletionItem { - const item = new vscode.CompletionItem(text); - item.kind = vscode.CompletionItemKind.Value; - item.detail = description; - item.insertText = insertText ? insertText : text; - item.range = range; - return item; - } - - private newSnippetCompletionItem(o: { label: string; documentation?: string; snippet: string; range: vscode.Range }): vscode.CompletionItem { - const item = new vscode.CompletionItem(o.label); - item.kind = vscode.CompletionItemKind.Value; - item.documentation = o.documentation; - item.insertText = new vscode.SnippetString(o.snippet); - item.range = o.range; - return item; - } + constructor(private document: vscode.TextDocument) { } + public async provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): Promise { + const location = getLocation(this.document.getText(), this.document.offsetAt(position)); + // window.title + if (location.path[0] === 'window.title') { + return this.provideWindowTitleCompletionItems(location, position); + } + // files.association + if (location.path[0] === 'files.associations') { + return this.provideFilesAssociationsCompletionItems(location, position); + } + // files.exclude, search.exclude, explorer.autoRevealExclude + if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude' || location.path[0] === 'explorer.autoRevealExclude') { + return this.provideExcludeCompletionItems(location, position); + } + // files.defaultLanguage + if (location.path[0] === 'files.defaultLanguage') { + return this.provideLanguageCompletionItems(location, position); + } + // workbench.editor.label + if (location.path[0] === 'workbench.editor.label.patterns') { + return this.provideEditorLabelCompletionItems(location, position); + } + // settingsSync.ignoredExtensions + if (location.path[0] === 'settingsSync.ignoredExtensions') { + let ignoredExtensions = []; + try { + ignoredExtensions = parse(this.document.getText())['settingsSync.ignoredExtensions']; + } + catch (e) { /* ignore error */ } + const range = this.getReplaceRange(location, position); + return provideInstalledExtensionProposals(ignoredExtensions, '', range, true); + } + // remote.extensionKind + if (location.path[0] === 'remote.extensionKind' && location.path.length === 2 && location.isAtPropertyKey) { + let alreadyConfigured: string[] = []; + try { + alreadyConfigured = Object.keys(parse(this.document.getText())['remote.extensionKind']); + } + catch (e) { /* ignore error */ } + const range = this.getReplaceRange(location, position); + return provideInstalledExtensionProposals(alreadyConfigured, location.previousNode ? '' : `: [\n\t"ui"\n]`, range, true); + } + // remote.portsAttributes + if (location.path[0] === 'remote.portsAttributes' && location.path.length === 2 && location.isAtPropertyKey) { + return this.providePortsAttributesCompletionItem(this.getReplaceRange(location, position)); + } + return this.provideLanguageOverridesCompletionItems(location, position); + } + private getReplaceRange(location: Location, position: vscode.Position) { + const node = location.previousNode; + if (node) { + const nodeStart = this.document.positionAt(node.offset), nodeEnd = this.document.positionAt(node.offset + node.length); + if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) { + return new vscode.Range(nodeStart, nodeEnd); + } + } + return new vscode.Range(position, position); + } + private isCompletingPropertyValue(location: Location, pos: vscode.Position) { + if (location.isAtPropertyKey) { + return false; + } + const previousNode = location.previousNode; + if (previousNode) { + const offset = this.document.offsetAt(pos); + return offset >= previousNode.offset && offset <= previousNode.offset + previousNode.length; + } + return true; + } + private async provideWindowTitleCompletionItems(location: Location, pos: vscode.Position): Promise { + const completions: vscode.CompletionItem[] = []; + if (!this.isCompletingPropertyValue(location, pos)) { + return completions; + } + let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) { + range = new vscode.Range(pos, pos); + } + const getText = (variable: string) => { + const text = '${' + variable + '}'; + return location.previousNode ? text : JSON.stringify(text); + }; + completions.push(this.newSimpleCompletionItem(getText('activeEditorShort'), range, vscode.l10n.t("the file name (e.g. myFile.txt)"))); + completions.push(this.newSimpleCompletionItem(getText('activeEditorMedium'), range, vscode.l10n.t("the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)"))); + completions.push(this.newSimpleCompletionItem(getText('activeEditorLong'), range, vscode.l10n.t("the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)"))); + completions.push(this.newSimpleCompletionItem(getText('activeFolderShort'), range, vscode.l10n.t("the name of the folder the file is contained in (e.g. myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('activeFolderMedium'), range, vscode.l10n.t("the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('activeFolderLong'), range, vscode.l10n.t("the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('rootName'), range, vscode.l10n.t("name of the workspace with optional remote name and workspace indicator if applicable (e.g. myFolder, myRemoteFolder [SSH] or myWorkspace (Workspace))"))); + completions.push(this.newSimpleCompletionItem(getText('rootNameShort'), range, vscode.l10n.t("shortened name of the workspace without suffixes (e.g. myFolder or myWorkspace)"))); + completions.push(this.newSimpleCompletionItem(getText('rootPath'), range, vscode.l10n.t("file path of the workspace (e.g. /Users/Development/myWorkspace)"))); + completions.push(this.newSimpleCompletionItem(getText('folderName'), range, vscode.l10n.t("name of the workspace folder the file is contained in (e.g. myFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, vscode.l10n.t("file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. VS Code"))); + completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); + completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); + completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); + completions.push(this.newSimpleCompletionItem(getText('activeRepositoryName'), range, vscode.l10n.t("the name of the active repository (e.g. vscode)"))); + completions.push(this.newSimpleCompletionItem(getText('activeRepositoryBranchName'), range, vscode.l10n.t("the name of the active branch in the active repository (e.g. main)"))); + return completions; + } + private async provideEditorLabelCompletionItems(location: Location, pos: vscode.Position): Promise { + const completions: vscode.CompletionItem[] = []; + if (!this.isCompletingPropertyValue(location, pos)) { + return completions; + } + let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) { + range = new vscode.Range(pos, pos); + } + const getText = (variable: string) => { + const text = '${' + variable + '}'; + return location.previousNode ? text : JSON.stringify(text); + }; + completions.push(this.newSimpleCompletionItem(getText('dirname'), range, vscode.l10n.t("The parent folder name of the editor (e.g. myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('dirname(1)'), range, vscode.l10n.t("The nth parent folder name of the editor"))); + completions.push(this.newSimpleCompletionItem(getText('filename'), range, vscode.l10n.t("The file name of the editor without its directory or extension (e.g. myFile)"))); + completions.push(this.newSimpleCompletionItem(getText('extname'), range, vscode.l10n.t("The file extension of the editor (e.g. txt)"))); + return completions; + } + private async provideFilesAssociationsCompletionItems(location: Location, position: vscode.Position): Promise { + const completions: vscode.CompletionItem[] = []; + if (location.path.length === 2) { + // Key + if (location.path[1] === '') { + const range = this.getReplaceRange(location, position); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Files with Extension"), + documentation: vscode.l10n.t("Map all files matching the glob pattern in their filename to the language with the given identifier."), + snippet: location.isAtPropertyKey ? '"*.${1:extension}": "${2:language}"' : '{ "*.${1:extension}": "${2:language}" }', + range + })); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Files with Path"), + documentation: vscode.l10n.t("Map all files matching the absolute path glob pattern in their path to the language with the given identifier."), + snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }', + range + })); + } + else if (this.isCompletingPropertyValue(location, position)) { + // Value + return this.provideLanguageCompletionItemsForLanguageOverrides(this.getReplaceRange(location, position)); + } + } + return completions; + } + private async provideExcludeCompletionItems(location: Location, position: vscode.Position): Promise { + const completions: vscode.CompletionItem[] = []; + // Key + if (location.path.length === 1 || (location.path.length === 2 && location.path[1] === '')) { + const range = this.getReplaceRange(location, position); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Files by Extension"), + documentation: vscode.l10n.t("Match all files of a specific file extension."), + snippet: location.path.length === 2 ? '"**/*.${1:extension}": true' : '{ "**/*.${1:extension}": true }', + range + })); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Files with Multiple Extensions"), + documentation: vscode.l10n.t("Match all files with any of the file extensions."), + snippet: location.path.length === 2 ? '"**/*.{ext1,ext2,ext3}": true' : '{ "**/*.{ext1,ext2,ext3}": true }', + range + })); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Files with Siblings by Name"), + documentation: vscode.l10n.t("Match files that have siblings with the same name but a different extension."), + snippet: location.path.length === 2 ? '"**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" }' : '{ "**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" } }', + range + })); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Folder by Name (Top Level)"), + documentation: vscode.l10n.t("Match a top level folder with a specific name."), + snippet: location.path.length === 2 ? '"${1:name}": true' : '{ "${1:name}": true }', + range + })); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Folders with Multiple Names (Top Level)"), + documentation: vscode.l10n.t("Match multiple top level folders."), + snippet: location.path.length === 2 ? '"{folder1,folder2,folder3}": true' : '{ "{folder1,folder2,folder3}": true }', + range + })); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Folder by Name (Any Location)"), + documentation: vscode.l10n.t("Match a folder with a specific name in any location."), + snippet: location.path.length === 2 ? '"**/${1:name}": true' : '{ "**/${1:name}": true }', + range + })); + } + // Value + else if (location.path.length === 2 && this.isCompletingPropertyValue(location, position)) { + const range = this.getReplaceRange(location, position); + completions.push(this.newSnippetCompletionItem({ + label: vscode.l10n.t("Files with Siblings by Name"), + documentation: vscode.l10n.t("Match files that have siblings with the same name but a different extension."), + snippet: '{ "when": "$(basename).${1:extension}" }', + range + })); + } + return completions; + } + private async provideLanguageCompletionItems(location: Location, position: vscode.Position): Promise { + if (location.path.length === 1 && this.isCompletingPropertyValue(location, position)) { + const range = this.getReplaceRange(location, position); + const languages = await vscode.languages.getLanguages(); + return [ + this.newSimpleCompletionItem(JSON.stringify('${activeEditorLanguage}'), range, vscode.l10n.t("Use the language of the currently active text editor if any")), + ...languages.map(l => this.newSimpleCompletionItem(JSON.stringify(l), range)) + ]; + } + return []; + } + private async provideLanguageCompletionItemsForLanguageOverrides(range: vscode.Range): Promise { + const languages = await vscode.languages.getLanguages(); + const completionItems = []; + for (const language of languages) { + const item = new vscode.CompletionItem(JSON.stringify(language)); + item.kind = vscode.CompletionItemKind.Property; + item.range = range; + completionItems.push(item); + } + return completionItems; + } + private async provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): Promise { + if (location.path.length === 1 && location.isAtPropertyKey && location.previousNode && typeof location.previousNode.value === 'string' && location.previousNode.value.startsWith('[')) { + const startPosition = this.document.positionAt(location.previousNode.offset + 1); + const endPosition = startPosition.translate(undefined, location.previousNode.value.length); + const donotSuggestLanguages: string[] = []; + const languageOverridesRanges: vscode.Range[] = []; + let matches = OVERRIDE_IDENTIFIER_REGEX.exec(location.previousNode.value); + let lastLanguageOverrideRange: vscode.Range | undefined; + while (matches?.length) { + lastLanguageOverrideRange = new vscode.Range(this.document.positionAt(location.previousNode.offset + 1 + matches.index), this.document.positionAt(location.previousNode.offset + 1 + matches.index + matches[0].length)); + languageOverridesRanges.push(lastLanguageOverrideRange); + /* Suggest the configured language if the position is in the match range */ + if (!lastLanguageOverrideRange.contains(position)) { + donotSuggestLanguages.push(matches[1].trim()); + } + matches = OVERRIDE_IDENTIFIER_REGEX.exec(location.previousNode.value); + } + const lastLanguageOverrideEndPosition = lastLanguageOverrideRange ? lastLanguageOverrideRange.end : startPosition; + if (lastLanguageOverrideEndPosition.isBefore(endPosition)) { + languageOverridesRanges.push(new vscode.Range(lastLanguageOverrideEndPosition, endPosition)); + } + const languageOverrideRange = languageOverridesRanges.find(range => range.contains(position)); + /** + * Skip if suggestions are for first language override range + * Since VSCode registers language overrides to the schema, JSON language server does suggestions for first language override. + */ + if (languageOverrideRange && !languageOverrideRange.isEqual(languageOverridesRanges[0])) { + const languages = await vscode.languages.getLanguages(); + const completionItems = []; + for (const language of languages) { + if (!donotSuggestLanguages.includes(language)) { + const item = new vscode.CompletionItem(`[${language}]`); + item.kind = vscode.CompletionItemKind.Property; + item.range = languageOverrideRange; + completionItems.push(item); + } + } + return completionItems; + } + } + return []; + } + private providePortsAttributesCompletionItem(range: vscode.Range): vscode.CompletionItem[] { + return [this.newSnippetCompletionItem({ + label: '\"3000\"', + documentation: 'Single Port Attribute', + range, + snippet: '\n \"${1:3000}\": {\n \"label\": \"${2:Application}\",\n \"onAutoForward\": \"${3:openPreview}\"\n }\n' + }), + this.newSnippetCompletionItem({ + label: '\"5000-6000\"', + documentation: 'Ranged Port Attribute', + range, + snippet: '\n \"${1:40000-55000}\": {\n \"onAutoForward\": \"${2:ignore}\"\n }\n' + }), + this.newSnippetCompletionItem({ + label: '\".+\\\\/server.js\"', + documentation: 'Command Match Port Attribute', + range, + snippet: '\n \"${1:.+\\\\/server.js\}\": {\n \"label\": \"${2:Application}\",\n \"onAutoForward\": \"${3:openPreview}\"\n }\n' + }) + ]; + } + private newSimpleCompletionItem(text: string, range: vscode.Range, description?: string, insertText?: string): vscode.CompletionItem { + const item = new vscode.CompletionItem(text); + item.kind = vscode.CompletionItemKind.Value; + item.detail = description; + item.insertText = insertText ? insertText : text; + item.range = range; + return item; + } + private newSnippetCompletionItem(o: { + label: string; + documentation?: string; + snippet: string; + range: vscode.Range; + }): vscode.CompletionItem { + const item = new vscode.CompletionItem(o.label); + item.kind = vscode.CompletionItemKind.Value; + item.documentation = o.documentation; + item.insertText = new vscode.SnippetString(o.snippet); + item.range = o.range; + return item; + } } diff --git a/extensions/css-language-features/client/Source/browser/cssClientMain.ts b/extensions/css-language-features/client/Source/browser/cssClientMain.ts index 1d4153d983672..d9e5472915a3f 100644 --- a/extensions/css-language-features/client/Source/browser/cssClientMain.ts +++ b/extensions/css-language-features/client/Source/browser/cssClientMain.ts @@ -2,38 +2,31 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ExtensionContext, Uri, l10n } from 'vscode'; import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../cssClient'; import { LanguageClient } from 'vscode-languageclient/browser'; import { registerDropOrPasteResourceSupport } from '../dropOrPaste/dropOrPasteResource'; - let client: BaseLanguageClient | undefined; - // this method is called when vs code is activated export async function activate(context: ExtensionContext) { - const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/cssServerMain.js'); - try { - const worker = new Worker(serverMain.toString()); - worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); - - const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { - return new LanguageClient(id, name, worker, clientOptions); - }; - - client = await startClient(context, newLanguageClient, { TextDecoder }); - - context.subscriptions.push(registerDropOrPasteResourceSupport({ language: 'css', scheme: '*' })); - } catch (e) { - console.log(e); - } + const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/cssServerMain.js'); + try { + const worker = new Worker(serverMain.toString()); + worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, worker, clientOptions); + }; + client = await startClient(context, newLanguageClient, { TextDecoder }); + context.subscriptions.push(registerDropOrPasteResourceSupport({ language: 'css', scheme: '*' })); + } + catch (e) { + console.log(e); + } } - export async function deactivate(): Promise { - if (client) { - await client.stop(); - client = undefined; - } + if (client) { + await client.stop(); + client = undefined; + } } - diff --git a/extensions/css-language-features/client/Source/cssClient.ts b/extensions/css-language-features/client/Source/cssClient.ts index f6e8fe3513e12..0a2bc12b2404f 100644 --- a/extensions/css-language-features/client/Source/cssClient.ts +++ b/extensions/css-language-features/client/Source/cssClient.ts @@ -2,210 +2,185 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList, FormattingOptions, workspace, l10n } from 'vscode'; import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, BaseLanguageClient, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; import { getCustomDataSource } from './customData'; import { RequestService, serveFileSystemRequests } from './requests'; - namespace CustomDataChangedNotification { - export const type: NotificationType = new NotificationType('css/customDataChanged'); + export const type: NotificationType = new NotificationType('css/customDataChanged'); } - export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; - export interface Runtime { - TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; - fs?: RequestService; + TextDecoder: { + new (encoding?: string): { + decode(buffer: ArrayBuffer): string; + }; + }; + fs?: RequestService; } - interface FormatterRegistration { - readonly languageId: string; - readonly settingId: string; - provider: Disposable | undefined; + readonly languageId: string; + readonly settingId: string; + provider: Disposable | undefined; } - interface CSSFormatSettings { - newlineBetweenSelectors?: boolean; - newlineBetweenRules?: boolean; - spaceAroundSelectorSeparator?: boolean; - braceStyle?: 'collapse' | 'expand'; - preserveNewLines?: boolean; - maxPreserveNewLines?: number | null; + newlineBetweenSelectors?: boolean; + newlineBetweenRules?: boolean; + spaceAroundSelectorSeparator?: boolean; + braceStyle?: 'collapse' | 'expand'; + preserveNewLines?: boolean; + maxPreserveNewLines?: number | null; } - const cssFormatSettingKeys: (keyof CSSFormatSettings)[] = ['newlineBetweenSelectors', 'newlineBetweenRules', 'spaceAroundSelectorSeparator', 'braceStyle', 'preserveNewLines', 'maxPreserveNewLines']; - export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { - - const customDataSource = getCustomDataSource(context.subscriptions); - - const documentSelector = ['css', 'scss', 'less']; - - const formatterRegistrations: FormatterRegistration[] = documentSelector.map(languageId => ({ - languageId, settingId: `${languageId}.format.enable`, provider: undefined - })); - - // Options to control the language client - const clientOptions: LanguageClientOptions = { - documentSelector, - synchronize: { - configurationSection: ['css', 'scss', 'less'] - }, - initializationOptions: { - handledSchemas: ['file'], - provideFormatter: false, // tell the server to not provide formatting capability - customCapabilities: { rangeFormatting: { editLimit: 10000 } } - }, - middleware: { - provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { - // testing the replace / insert mode - function updateRanges(item: CompletionItem) { - const range = item.range; - if (range instanceof Range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { - item.range = { inserting: new Range(range.start, position), replacing: range }; - - } - } - function updateLabel(item: CompletionItem) { - if (item.kind === CompletionItemKind.Color) { - item.label = { - label: item.label as string, - description: (item.documentation as string) - }; - } - } - // testing the new completion - function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { - if (r) { - (Array.isArray(r) ? r : r.items).forEach(updateRanges); - (Array.isArray(r) ? r : r.items).forEach(updateLabel); - } - return r; - } - const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; - - const r = next(document, position, context, token); - if (isThenable(r)) { - return r.then(updateProposals); - } - return updateProposals(r); - } - } - }; - - // Create the language client and start the client. - const client = newLanguageClient('css', l10n.t('CSS Language Server'), clientOptions); - client.registerProposedFeatures(); - - await client.start(); - - client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - customDataSource.onDidChange(() => { - client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - - // manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652. - for (const registration of formatterRegistrations) { - updateFormatterRegistration(registration); - context.subscriptions.push({ dispose: () => registration.provider?.dispose() }); - context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration))); - } - - serveFileSystemRequests(client, runtime); - - - context.subscriptions.push(initCompletionProvider()); - - function initCompletionProvider(): Disposable { - const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/; - - return languages.registerCompletionItemProvider(documentSelector, { - provideCompletionItems(doc: TextDocument, pos: Position) { - const lineUntilPos = doc.getText(new Range(new Position(pos.line, 0), pos)); - const match = lineUntilPos.match(regionCompletionRegExpr); - if (match) { - const range = new Range(new Position(pos.line, match[1].length), pos); - const beginProposal = new CompletionItem('#region', CompletionItemKind.Snippet); - beginProposal.range = range; TextEdit.replace(range, '/* #region */'); - beginProposal.insertText = new SnippetString('/* #region $1*/'); - beginProposal.documentation = l10n.t('Folding Region Start'); - beginProposal.filterText = match[2]; - beginProposal.sortText = 'za'; - const endProposal = new CompletionItem('#endregion', CompletionItemKind.Snippet); - endProposal.range = range; - endProposal.insertText = '/* #endregion */'; - endProposal.documentation = l10n.t('Folding Region End'); - endProposal.sortText = 'zb'; - endProposal.filterText = match[2]; - return [beginProposal, endProposal]; - } - return null; - } - }); - } - - commands.registerCommand('_css.applyCodeAction', applyCodeAction); - - function applyCodeAction(uri: string, documentVersion: number, edits: TextEdit[]) { - const textEditor = window.activeTextEditor; - if (textEditor && textEditor.document.uri.toString() === uri) { - if (textEditor.document.version !== documentVersion) { - window.showInformationMessage(l10n.t('CSS fix is outdated and can\'t be applied to the document.')); - } - textEditor.edit(mutator => { - for (const edit of edits) { - mutator.replace(client.protocol2CodeConverter.asRange(edit.range), edit.newText); - } - }).then(success => { - if (!success) { - window.showErrorMessage(l10n.t('Failed to apply CSS fix to the document. Please consider opening an issue with steps to reproduce.')); - } - }); - } - } - - function updateFormatterRegistration(registration: FormatterRegistration) { - const formatEnabled = workspace.getConfiguration().get(registration.settingId); - if (!formatEnabled && registration.provider) { - registration.provider.dispose(); - registration.provider = undefined; - } else if (formatEnabled && !registration.provider) { - registration.provider = languages.registerDocumentRangeFormattingEditProvider(registration.languageId, { - provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { - const filesConfig = workspace.getConfiguration('files', document); - - const fileFormattingOptions = { - trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), - trimFinalNewlines: filesConfig.get('trimFinalNewlines'), - insertFinalNewline: filesConfig.get('insertFinalNewline'), - }; - const params: DocumentRangeFormattingParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - range: client.code2ProtocolConverter.asRange(range), - options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions) - }; - // add the css formatter options from the settings - const formatterSettings = workspace.getConfiguration(registration.languageId, document).get('format'); - if (formatterSettings) { - for (const key of cssFormatSettingKeys) { - const val = formatterSettings[key]; - if (val !== undefined && val !== null) { - params.options[key] = val; - } - } - } - return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( - client.protocol2CodeConverter.asTextEdits, - (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); - return Promise.resolve([]); - } - ); - } - }); - } - } - - return client; + const customDataSource = getCustomDataSource(context.subscriptions); + const documentSelector = ['css', 'scss', 'less']; + const formatterRegistrations: FormatterRegistration[] = documentSelector.map(languageId => ({ + languageId, settingId: `${languageId}.format.enable`, provider: undefined + })); + // Options to control the language client + const clientOptions: LanguageClientOptions = { + documentSelector, + synchronize: { + configurationSection: ['css', 'scss', 'less'] + }, + initializationOptions: { + handledSchemas: ['file'], + provideFormatter: false, // tell the server to not provide formatting capability + customCapabilities: { rangeFormatting: { editLimit: 10000 } } + }, + middleware: { + provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { + // testing the replace / insert mode + function updateRanges(item: CompletionItem) { + const range = item.range; + if (range instanceof Range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { + item.range = { inserting: new Range(range.start, position), replacing: range }; + } + } + function updateLabel(item: CompletionItem) { + if (item.kind === CompletionItemKind.Color) { + item.label = { + label: item.label as string, + description: (item.documentation as string) + }; + } + } + // testing the new completion + function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { + if (r) { + (Array.isArray(r) ? r : r.items).forEach(updateRanges); + (Array.isArray(r) ? r : r.items).forEach(updateLabel); + } + return r; + } + const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + const r = next(document, position, context, token); + if (isThenable(r)) { + return r.then(updateProposals); + } + return updateProposals(r); + } + } + }; + // Create the language client and start the client. + const client = newLanguageClient('css', l10n.t('CSS Language Server'), clientOptions); + client.registerProposedFeatures(); + await client.start(); + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); + customDataSource.onDidChange(() => { + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); + }); + // manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652. + for (const registration of formatterRegistrations) { + updateFormatterRegistration(registration); + context.subscriptions.push({ dispose: () => registration.provider?.dispose() }); + context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration))); + } + serveFileSystemRequests(client, runtime); + context.subscriptions.push(initCompletionProvider()); + function initCompletionProvider(): Disposable { + const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/; + return languages.registerCompletionItemProvider(documentSelector, { + provideCompletionItems(doc: TextDocument, pos: Position) { + const lineUntilPos = doc.getText(new Range(new Position(pos.line, 0), pos)); + const match = lineUntilPos.match(regionCompletionRegExpr); + if (match) { + const range = new Range(new Position(pos.line, match[1].length), pos); + const beginProposal = new CompletionItem('#region', CompletionItemKind.Snippet); + beginProposal.range = range; + TextEdit.replace(range, '/* #region */'); + beginProposal.insertText = new SnippetString('/* #region $1*/'); + beginProposal.documentation = l10n.t('Folding Region Start'); + beginProposal.filterText = match[2]; + beginProposal.sortText = 'za'; + const endProposal = new CompletionItem('#endregion', CompletionItemKind.Snippet); + endProposal.range = range; + endProposal.insertText = '/* #endregion */'; + endProposal.documentation = l10n.t('Folding Region End'); + endProposal.sortText = 'zb'; + endProposal.filterText = match[2]; + return [beginProposal, endProposal]; + } + return null; + } + }); + } + commands.registerCommand('_css.applyCodeAction', applyCodeAction); + function applyCodeAction(uri: string, documentVersion: number, edits: TextEdit[]) { + const textEditor = window.activeTextEditor; + if (textEditor && textEditor.document.uri.toString() === uri) { + if (textEditor.document.version !== documentVersion) { + window.showInformationMessage(l10n.t('CSS fix is outdated and can\'t be applied to the document.')); + } + textEditor.edit(mutator => { + for (const edit of edits) { + mutator.replace(client.protocol2CodeConverter.asRange(edit.range), edit.newText); + } + }).then(success => { + if (!success) { + window.showErrorMessage(l10n.t('Failed to apply CSS fix to the document. Please consider opening an issue with steps to reproduce.')); + } + }); + } + } + function updateFormatterRegistration(registration: FormatterRegistration) { + const formatEnabled = workspace.getConfiguration().get(registration.settingId); + if (!formatEnabled && registration.provider) { + registration.provider.dispose(); + registration.provider = undefined; + } + else if (formatEnabled && !registration.provider) { + registration.provider = languages.registerDocumentRangeFormattingEditProvider(registration.languageId, { + provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { + const filesConfig = workspace.getConfiguration('files', document); + const fileFormattingOptions = { + trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), + trimFinalNewlines: filesConfig.get('trimFinalNewlines'), + insertFinalNewline: filesConfig.get('insertFinalNewline'), + }; + const params: DocumentRangeFormattingParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + range: client.code2ProtocolConverter.asRange(range), + options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions) + }; + // add the css formatter options from the settings + const formatterSettings = workspace.getConfiguration(registration.languageId, document).get('format'); + if (formatterSettings) { + for (const key of cssFormatSettingKeys) { + const val = formatterSettings[key]; + if (val !== undefined && val !== null) { + params.options[key] = val; + } + } + } + return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(client.protocol2CodeConverter.asTextEdits, (error) => { + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); + return Promise.resolve([]); + }); + } + }); + } + } + return client; } diff --git a/extensions/css-language-features/client/Source/customData.ts b/extensions/css-language-features/client/Source/customData.ts index bab5cc407e9dd..e40006a71c19b 100644 --- a/extensions/css-language-features/client/Source/customData.ts +++ b/extensions/css-language-features/client/Source/customData.ts @@ -2,87 +2,74 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode'; import { Utils } from 'vscode-uri'; - export function getCustomDataSource(toDispose: Disposable[]) { - let pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); - let pathsInExtensions = getCustomDataPathsFromAllExtensions(); - - const onChange = new EventEmitter(); - - toDispose.push(extensions.onDidChange(_ => { - const newPathsInExtensions = getCustomDataPathsFromAllExtensions(); - if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) { - pathsInExtensions = newPathsInExtensions; - onChange.fire(); - } - })); - toDispose.push(workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('css.customData')) { - pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); - onChange.fire(); - } - })); - - return { - get uris() { - return pathsInWorkspace.concat(pathsInExtensions); - }, - get onDidChange() { - return onChange.event; - } - }; + let pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); + let pathsInExtensions = getCustomDataPathsFromAllExtensions(); + const onChange = new EventEmitter(); + toDispose.push(extensions.onDidChange(_ => { + const newPathsInExtensions = getCustomDataPathsFromAllExtensions(); + if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) { + pathsInExtensions = newPathsInExtensions; + onChange.fire(); + } + })); + toDispose.push(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('css.customData')) { + pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); + onChange.fire(); + } + })); + return { + get uris() { + return pathsInWorkspace.concat(pathsInExtensions); + }, + get onDidChange() { + return onChange.event; + } + }; } - - function getCustomDataPathsInAllWorkspaces(): string[] { - const workspaceFolders = workspace.workspaceFolders; - - const dataPaths: string[] = []; - - if (!workspaceFolders) { - return dataPaths; - } - - const collect = (paths: string[] | undefined, rootFolder: Uri) => { - if (Array.isArray(paths)) { - for (const path of paths) { - if (typeof path === 'string') { - dataPaths.push(Utils.resolvePath(rootFolder, path).toString()); - } - } - } - }; - - for (let i = 0; i < workspaceFolders.length; i++) { - const folderUri = workspaceFolders[i].uri; - const allCssConfig = workspace.getConfiguration('css', folderUri); - const customDataInspect = allCssConfig.inspect('customData'); - if (customDataInspect) { - collect(customDataInspect.workspaceFolderValue, folderUri); - if (i === 0) { - if (workspace.workspaceFile) { - collect(customDataInspect.workspaceValue, workspace.workspaceFile); - } - collect(customDataInspect.globalValue, folderUri); - } - } - - } - return dataPaths; + const workspaceFolders = workspace.workspaceFolders; + const dataPaths: string[] = []; + if (!workspaceFolders) { + return dataPaths; + } + const collect = (paths: string[] | undefined, rootFolder: Uri) => { + if (Array.isArray(paths)) { + for (const path of paths) { + if (typeof path === 'string') { + dataPaths.push(Utils.resolvePath(rootFolder, path).toString()); + } + } + } + }; + for (let i = 0; i < workspaceFolders.length; i++) { + const folderUri = workspaceFolders[i].uri; + const allCssConfig = workspace.getConfiguration('css', folderUri); + const customDataInspect = allCssConfig.inspect('customData'); + if (customDataInspect) { + collect(customDataInspect.workspaceFolderValue, folderUri); + if (i === 0) { + if (workspace.workspaceFile) { + collect(customDataInspect.workspaceValue, workspace.workspaceFile); + } + collect(customDataInspect.globalValue, folderUri); + } + } + } + return dataPaths; } - function getCustomDataPathsFromAllExtensions(): string[] { - const dataPaths: string[] = []; - for (const extension of extensions.all) { - const customData = extension.packageJSON?.contributes?.css?.customData; - if (Array.isArray(customData)) { - for (const rp of customData) { - dataPaths.push(Utils.joinPath(extension.extensionUri, rp).toString()); - } - } - } - return dataPaths; + const dataPaths: string[] = []; + for (const extension of extensions.all) { + const customData = extension.packageJSON?.contributes?.css?.customData; + if (Array.isArray(customData)) { + for (const rp of customData) { + dataPaths.push(Utils.joinPath(extension.extensionUri, rp).toString()); + } + } + } + return dataPaths; } diff --git a/extensions/css-language-features/client/Source/dropOrPaste/dropOrPasteResource.ts b/extensions/css-language-features/client/Source/dropOrPaste/dropOrPasteResource.ts index 6a4c38d241715..ce821247b8333 100644 --- a/extensions/css-language-features/client/Source/dropOrPaste/dropOrPasteResource.ts +++ b/extensions/css-language-features/client/Source/dropOrPaste/dropOrPasteResource.ts @@ -2,151 +2,119 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as path from 'path'; import * as vscode from 'vscode'; import { getDocumentDir, Mimes, Schemes } from './shared'; import { UriList } from './uriList'; - class DropOrPasteResourceProvider implements vscode.DocumentDropEditProvider, vscode.DocumentPasteEditProvider { - readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('css', 'url'); - - async provideDocumentDropEdits( - document: vscode.TextDocument, - position: vscode.Position, - dataTransfer: vscode.DataTransfer, - token: vscode.CancellationToken, - ): Promise { - const uriList = await this.getUriList(dataTransfer); - if (!uriList.entries.length || token.isCancellationRequested) { - return; - } - - const snippet = await this.createUriListSnippet(document.uri, uriList); - if (!snippet || token.isCancellationRequested) { - return; - } - - return { - kind: this.kind, - title: snippet.label, - insertText: snippet.snippet.value, - yieldTo: this.pasteAsCssUrlByDefault(document, position) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')] - }; - } - - async provideDocumentPasteEdits( - document: vscode.TextDocument, - ranges: readonly vscode.Range[], - dataTransfer: vscode.DataTransfer, - _context: vscode.DocumentPasteEditContext, - token: vscode.CancellationToken - ): Promise { - const uriList = await this.getUriList(dataTransfer); - if (!uriList.entries.length || token.isCancellationRequested) { - return; - } - - const snippet = await this.createUriListSnippet(document.uri, uriList); - if (!snippet || token.isCancellationRequested) { - return; - } - - return [{ - kind: this.kind, - title: snippet.label, - insertText: snippet.snippet.value, - yieldTo: this.pasteAsCssUrlByDefault(document, ranges[0].start) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')] - }]; - } - - private async getUriList(dataTransfer: vscode.DataTransfer): Promise { - const urlList = await dataTransfer.get(Mimes.uriList)?.asString(); - if (urlList) { - return UriList.from(urlList); - } - - // Find file entries - const uris: vscode.Uri[] = []; - for (const [_, entry] of dataTransfer) { - const file = entry.asFile(); - if (file?.uri) { - uris.push(file.uri); - } - } - - return new UriList(uris.map(uri => ({ uri, str: uri.toString(true) }))); - } - - private async createUriListSnippet(docUri: vscode.Uri, uriList: UriList): Promise<{ readonly snippet: vscode.SnippetString; readonly label: string } | undefined> { - if (!uriList.entries.length) { - return; - } - - const snippet = new vscode.SnippetString(); - for (let i = 0; i < uriList.entries.length; i++) { - const uri = uriList.entries[i]; - const relativePath = getRelativePath(getDocumentDir(docUri), uri.uri); - const urlText = relativePath ?? uri.str; - - snippet.appendText(`url(${urlText})`); - if (i !== uriList.entries.length - 1) { - snippet.appendText(' '); - } - } - - return { - snippet, - label: uriList.entries.length > 1 - ? vscode.l10n.t('Insert url() Functions') - : vscode.l10n.t('Insert url() Function') - }; - } - - private pasteAsCssUrlByDefault(document: vscode.TextDocument, position: vscode.Position): boolean { - const regex = /url\(.+?\)/gi; - for (const match of Array.from(document.lineAt(position.line).text.matchAll(regex))) { - if (position.character > match.index && position.character < match.index + match[0].length) { - return false; - } - } - return true; - } + readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('css', 'url'); + async provideDocumentDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { + const uriList = await this.getUriList(dataTransfer); + if (!uriList.entries.length || token.isCancellationRequested) { + return; + } + const snippet = await this.createUriListSnippet(document.uri, uriList); + if (!snippet || token.isCancellationRequested) { + return; + } + return { + kind: this.kind, + title: snippet.label, + insertText: snippet.snippet.value, + yieldTo: this.pasteAsCssUrlByDefault(document, position) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')] + }; + } + async provideDocumentPasteEdits(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, _context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken): Promise { + const uriList = await this.getUriList(dataTransfer); + if (!uriList.entries.length || token.isCancellationRequested) { + return; + } + const snippet = await this.createUriListSnippet(document.uri, uriList); + if (!snippet || token.isCancellationRequested) { + return; + } + return [{ + kind: this.kind, + title: snippet.label, + insertText: snippet.snippet.value, + yieldTo: this.pasteAsCssUrlByDefault(document, ranges[0].start) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')] + }]; + } + private async getUriList(dataTransfer: vscode.DataTransfer): Promise { + const urlList = await dataTransfer.get(Mimes.uriList)?.asString(); + if (urlList) { + return UriList.from(urlList); + } + // Find file entries + const uris: vscode.Uri[] = []; + for (const [_, entry] of dataTransfer) { + const file = entry.asFile(); + if (file?.uri) { + uris.push(file.uri); + } + } + return new UriList(uris.map(uri => ({ uri, str: uri.toString(true) }))); + } + private async createUriListSnippet(docUri: vscode.Uri, uriList: UriList): Promise<{ + readonly snippet: vscode.SnippetString; + readonly label: string; + } | undefined> { + if (!uriList.entries.length) { + return; + } + const snippet = new vscode.SnippetString(); + for (let i = 0; i < uriList.entries.length; i++) { + const uri = uriList.entries[i]; + const relativePath = getRelativePath(getDocumentDir(docUri), uri.uri); + const urlText = relativePath ?? uri.str; + snippet.appendText(`url(${urlText})`); + if (i !== uriList.entries.length - 1) { + snippet.appendText(' '); + } + } + return { + snippet, + label: uriList.entries.length > 1 + ? vscode.l10n.t('Insert url() Functions') + : vscode.l10n.t('Insert url() Function') + }; + } + private pasteAsCssUrlByDefault(document: vscode.TextDocument, position: vscode.Position): boolean { + const regex = /url\(.+?\)/gi; + for (const match of Array.from(document.lineAt(position.line).text.matchAll(regex))) { + if (position.character > match.index && position.character < match.index + match[0].length) { + return false; + } + } + return true; + } } - function getRelativePath(fromFile: vscode.Uri | undefined, toFile: vscode.Uri): string | undefined { - if (fromFile && fromFile.scheme === toFile.scheme && fromFile.authority === toFile.authority) { - if (toFile.scheme === Schemes.file) { - // On windows, we must use the native `path.relative` to generate the relative path - // so that drive-letters are resolved cast insensitively. However we then want to - // convert back to a posix path to insert in to the document - const relativePath = path.relative(fromFile.fsPath, toFile.fsPath); - return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep)); - } - - return path.posix.relative(fromFile.path, toFile.path); - } - - return undefined; + if (fromFile && fromFile.scheme === toFile.scheme && fromFile.authority === toFile.authority) { + if (toFile.scheme === Schemes.file) { + // On windows, we must use the native `path.relative` to generate the relative path + // so that drive-letters are resolved cast insensitively. However we then want to + // convert back to a posix path to insert in to the document + const relativePath = path.relative(fromFile.fsPath, toFile.fsPath); + return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep)); + } + return path.posix.relative(fromFile.path, toFile.path); + } + return undefined; } - export function registerDropOrPasteResourceSupport(selector: vscode.DocumentSelector): vscode.Disposable { - const provider = new DropOrPasteResourceProvider(); - - return vscode.Disposable.from( - vscode.languages.registerDocumentDropEditProvider(selector, provider, { - providedDropEditKinds: [provider.kind], - dropMimeTypes: [ - Mimes.uriList, - 'files' - ] - }), - vscode.languages.registerDocumentPasteEditProvider(selector, provider, { - providedPasteEditKinds: [provider.kind], - pasteMimeTypes: [ - Mimes.uriList, - 'files' - ] - }) - ); + const provider = new DropOrPasteResourceProvider(); + return vscode.Disposable.from(vscode.languages.registerDocumentDropEditProvider(selector, provider, { + providedDropEditKinds: [provider.kind], + dropMimeTypes: [ + Mimes.uriList, + 'files' + ] + }), vscode.languages.registerDocumentPasteEditProvider(selector, provider, { + providedPasteEditKinds: [provider.kind], + pasteMimeTypes: [ + Mimes.uriList, + 'files' + ] + })); } diff --git a/extensions/css-language-features/client/Source/dropOrPaste/shared.ts b/extensions/css-language-features/client/Source/dropOrPaste/shared.ts index 548bccfec6948..5b8b978e07028 100644 --- a/extensions/css-language-features/client/Source/dropOrPaste/shared.ts +++ b/extensions/css-language-features/client/Source/dropOrPaste/shared.ts @@ -2,41 +2,34 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { Utils } from 'vscode-uri'; - export const Schemes = Object.freeze({ - file: 'file', - notebookCell: 'vscode-notebook-cell', - untitled: 'untitled', + file: 'file', + notebookCell: 'vscode-notebook-cell', + untitled: 'untitled', }); - export const Mimes = Object.freeze({ - plain: 'text/plain', - uriList: 'text/uri-list', + plain: 'text/plain', + uriList: 'text/uri-list', }); - - export function getDocumentDir(uri: vscode.Uri): vscode.Uri | undefined { - const docUri = getParentDocumentUri(uri); - if (docUri.scheme === Schemes.untitled) { - return vscode.workspace.workspaceFolders?.[0]?.uri; - } - return Utils.dirname(docUri); + const docUri = getParentDocumentUri(uri); + if (docUri.scheme === Schemes.untitled) { + return vscode.workspace.workspaceFolders?.[0]?.uri; + } + return Utils.dirname(docUri); } - function getParentDocumentUri(uri: vscode.Uri): vscode.Uri { - if (uri.scheme === Schemes.notebookCell) { - // is notebook documents necessary? - for (const notebook of vscode.workspace.notebookDocuments) { - for (const cell of notebook.getCells()) { - if (cell.document.uri.toString() === uri.toString()) { - return notebook.uri; - } - } - } - } - - return uri; + if (uri.scheme === Schemes.notebookCell) { + // is notebook documents necessary? + for (const notebook of vscode.workspace.notebookDocuments) { + for (const cell of notebook.getCells()) { + if (cell.document.uri.toString() === uri.toString()) { + return notebook.uri; + } + } + } + } + return uri; } diff --git a/extensions/css-language-features/client/Source/dropOrPaste/uriList.ts b/extensions/css-language-features/client/Source/dropOrPaste/uriList.ts index ed20b1ee79765..106141b447d8a 100644 --- a/extensions/css-language-features/client/Source/dropOrPaste/uriList.ts +++ b/extensions/css-language-features/client/Source/dropOrPaste/uriList.ts @@ -2,37 +2,32 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - function splitUriList(str: string): string[] { - return str.split('\r\n'); + return str.split('\r\n'); } - function parseUriList(str: string): string[] { - return splitUriList(str) - .filter(value => !value.startsWith('#')) // Remove comments - .map(value => value.trim()); + return splitUriList(str) + .filter(value => !value.startsWith('#')) // Remove comments + .map(value => value.trim()); } - export class UriList { - - static from(str: string): UriList { - return new UriList(coalesce(parseUriList(str).map(line => { - try { - return { uri: vscode.Uri.parse(line), str: line }; - } catch { - // Uri parse failure - return undefined; - } - }))); - } - - constructor( - public readonly entries: ReadonlyArray<{ readonly uri: vscode.Uri; readonly str: string }> - ) { } + static from(str: string): UriList { + return new UriList(coalesce(parseUriList(str).map(line => { + try { + return { uri: vscode.Uri.parse(line), str: line }; + } + catch { + // Uri parse failure + return undefined; + } + }))); + } + constructor(public readonly entries: ReadonlyArray<{ + readonly uri: vscode.Uri; + readonly str: string; + }>) { } } - function coalesce(array: ReadonlyArray): T[] { - return array.filter(e => !!e); + return array.filter(e => !!e); } diff --git a/extensions/css-language-features/client/Source/node/cssClientMain.ts b/extensions/css-language-features/client/Source/node/cssClientMain.ts index 96926979b2a64..b764ae31f0d58 100644 --- a/extensions/css-language-features/client/Source/node/cssClientMain.ts +++ b/extensions/css-language-features/client/Source/node/cssClientMain.ts @@ -2,49 +2,37 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { TextDecoder } from 'util'; import { ExtensionContext, extensions, l10n } from 'vscode'; import { BaseLanguageClient, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; import { LanguageClientConstructor, startClient } from '../cssClient'; import { getNodeFSRequestService } from './nodeFs'; import { registerDropOrPasteResourceSupport } from '../dropOrPaste/dropOrPasteResource'; - let client: BaseLanguageClient | undefined; - // this method is called when vs code is activated export async function activate(context: ExtensionContext) { - const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || ''; - - const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/cssServerMain`; - const serverModule = context.asAbsolutePath(serverMain); - - // The debug options for the server - const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (7000 + Math.round(Math.random() * 999))] }; - - // If the extension is launch in debug mode the debug server options are use - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } - }; - - const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { - return new LanguageClient(id, name, serverOptions, clientOptions); - }; - - // pass the location of the localization bundle to the server - process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? ''; - - client = await startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); - - context.subscriptions.push(registerDropOrPasteResourceSupport({ language: 'css', scheme: '*' })); + const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || ''; + const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/cssServerMain`; + const serverModule = context.asAbsolutePath(serverMain); + // The debug options for the server + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (7000 + Math.round(Math.random() * 999))] }; + // If the extension is launch in debug mode the debug server options are use + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + }; + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, serverOptions, clientOptions); + }; + // pass the location of the localization bundle to the server + process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? ''; + client = await startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); + context.subscriptions.push(registerDropOrPasteResourceSupport({ language: 'css', scheme: '*' })); } - export async function deactivate(): Promise { - if (client) { - await client.stop(); - client = undefined; - } + if (client) { + await client.stop(); + client = undefined; + } } - diff --git a/extensions/css-language-features/client/Source/node/nodeFs.ts b/extensions/css-language-features/client/Source/node/nodeFs.ts index e0d3f9e140385..ee84496e8ab90 100644 --- a/extensions/css-language-features/client/Source/node/nodeFs.ts +++ b/extensions/css-language-features/client/Source/node/nodeFs.ts @@ -2,84 +2,84 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as fs from 'fs'; import { Uri } from 'vscode'; import { RequestService, FileType } from '../requests'; - export function getNodeFSRequestService(): RequestService { - function ensureFileUri(location: string) { - if (!location.startsWith('file://')) { - throw new Error('fileRequestService can only handle file URLs'); - } - } - return { - getContent(location: string, encoding?: BufferEncoding) { - ensureFileUri(location); - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.readFile(uri.fsPath, encoding, (err, buf) => { - if (err) { - return e(err); - } - c(buf.toString()); - - }); - }); - }, - stat(location: string) { - ensureFileUri(location); - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.stat(uri.fsPath, (err, stats) => { - if (err) { - if (err.code === 'ENOENT') { - return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 }); - } else { - return e(err); - } - } - - let type = FileType.Unknown; - if (stats.isFile()) { - type = FileType.File; - } else if (stats.isDirectory()) { - type = FileType.Directory; - } else if (stats.isSymbolicLink()) { - type = FileType.SymbolicLink; - } - - c({ - type, - ctime: stats.ctime.getTime(), - mtime: stats.mtime.getTime(), - size: stats.size - }); - }); - }); - }, - readDirectory(location: string) { - ensureFileUri(location); - return new Promise((c, e) => { - const path = Uri.parse(location).fsPath; - - fs.readdir(path, { withFileTypes: true }, (err, children) => { - if (err) { - return e(err); - } - c(children.map(stat => { - if (stat.isSymbolicLink()) { - return [stat.name, FileType.SymbolicLink]; - } else if (stat.isDirectory()) { - return [stat.name, FileType.Directory]; - } else if (stat.isFile()) { - return [stat.name, FileType.File]; - } else { - return [stat.name, FileType.Unknown]; - } - })); - }); - }); - } - }; + function ensureFileUri(location: string) { + if (!location.startsWith('file://')) { + throw new Error('fileRequestService can only handle file URLs'); + } + } + return { + getContent(location: string, encoding?: BufferEncoding) { + ensureFileUri(location); + return new Promise((c, e) => { + const uri = Uri.parse(location); + fs.readFile(uri.fsPath, encoding, (err, buf) => { + if (err) { + return e(err); + } + c(buf.toString()); + }); + }); + }, + stat(location: string) { + ensureFileUri(location); + return new Promise((c, e) => { + const uri = Uri.parse(location); + fs.stat(uri.fsPath, (err, stats) => { + if (err) { + if (err.code === 'ENOENT') { + return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 }); + } + else { + return e(err); + } + } + let type = FileType.Unknown; + if (stats.isFile()) { + type = FileType.File; + } + else if (stats.isDirectory()) { + type = FileType.Directory; + } + else if (stats.isSymbolicLink()) { + type = FileType.SymbolicLink; + } + c({ + type, + ctime: stats.ctime.getTime(), + mtime: stats.mtime.getTime(), + size: stats.size + }); + }); + }); + }, + readDirectory(location: string) { + ensureFileUri(location); + return new Promise((c, e) => { + const path = Uri.parse(location).fsPath; + fs.readdir(path, { withFileTypes: true }, (err, children) => { + if (err) { + return e(err); + } + c(children.map(stat => { + if (stat.isSymbolicLink()) { + return [stat.name, FileType.SymbolicLink]; + } + else if (stat.isDirectory()) { + return [stat.name, FileType.Directory]; + } + else if (stat.isFile()) { + return [stat.name, FileType.File]; + } + else { + return [stat.name, FileType.Unknown]; + } + })); + }); + }); + } + }; } diff --git a/extensions/css-language-features/client/Source/requests.ts b/extensions/css-language-features/client/Source/requests.ts index f19918e57ef9e..af86e0f02965f 100644 --- a/extensions/css-language-features/client/Source/requests.ts +++ b/extensions/css-language-features/client/Source/requests.ts @@ -2,89 +2,94 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { Uri, workspace } from 'vscode'; import { RequestType, BaseLanguageClient } from 'vscode-languageclient'; import { Runtime } from './cssClient'; - export namespace FsContentRequest { - export const type: RequestType<{ uri: string; encoding?: string }, string, any> = new RequestType('fs/content'); + export const type: RequestType<{ + uri: string; + encoding?: string; + }, string, any> = new RequestType('fs/content'); } export namespace FsStatRequest { - export const type: RequestType = new RequestType('fs/stat'); + export const type: RequestType = new RequestType('fs/stat'); } - export namespace FsReadDirRequest { - export const type: RequestType = new RequestType('fs/readDir'); + export const type: RequestType = new RequestType('fs/readDir'); } - export function serveFileSystemRequests(client: BaseLanguageClient, runtime: Runtime) { - client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string }) => { - const uri = Uri.parse(param.uri); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.getContent(param.uri); - } - return workspace.fs.readFile(uri).then(buffer => { - return new runtime.TextDecoder(param.encoding).decode(buffer); - }); - }); - client.onRequest(FsReadDirRequest.type, (uriString: string) => { - const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.readDirectory(uriString); - } - return workspace.fs.readDirectory(uri); - }); - client.onRequest(FsStatRequest.type, (uriString: string) => { - const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.stat(uriString); - } - return workspace.fs.stat(uri); - }); + client.onRequest(FsContentRequest.type, (param: { + uri: string; + encoding?: string; + }) => { + const uri = Uri.parse(param.uri); + if (uri.scheme === 'file' && runtime.fs) { + return runtime.fs.getContent(param.uri); + } + return workspace.fs.readFile(uri).then(buffer => { + return new runtime.TextDecoder(param.encoding).decode(buffer); + }); + }); + client.onRequest(FsReadDirRequest.type, (uriString: string) => { + const uri = Uri.parse(uriString); + if (uri.scheme === 'file' && runtime.fs) { + return runtime.fs.readDirectory(uriString); + } + return workspace.fs.readDirectory(uri); + }); + client.onRequest(FsStatRequest.type, (uriString: string) => { + const uri = Uri.parse(uriString); + if (uri.scheme === 'file' && runtime.fs) { + return runtime.fs.stat(uriString); + } + return workspace.fs.stat(uri); + }); } - export enum FileType { - /** - * The file type is unknown. - */ - Unknown = 0, - /** - * A regular file. - */ - File = 1, - /** - * A directory. - */ - Directory = 2, - /** - * A symbolic link to a file. - */ - SymbolicLink = 64 + /** + * The file type is unknown. + */ + Unknown = 0, + /** + * A regular file. + */ + File = 1, + /** + * A directory. + */ + Directory = 2, + /** + * A symbolic link to a file. + */ + SymbolicLink = 64 } export interface FileStat { - /** - * The type of the file, e.g. is a regular file, a directory, or symbolic link - * to a file. - */ - type: FileType; - /** - * The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - */ - ctime: number; - /** - * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - */ - mtime: number; - /** - * The size in bytes. - */ - size: number; + /** + * The type of the file, e.g. is a regular file, a directory, or symbolic link + * to a file. + */ + type: FileType; + /** + * The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + */ + ctime: number; + /** + * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + */ + mtime: number; + /** + * The size in bytes. + */ + size: number; } - export interface RequestService { - getContent(uri: string, encoding?: string): Promise; - - stat(uri: string): Promise; - readDirectory(uri: string): Promise<[string, FileType][]>; + getContent(uri: string, encoding?: string): Promise; + stat(uri: string): Promise; + readDirectory(uri: string): Promise<[ + string, + FileType + ][]>; } diff --git a/extensions/css-language-features/server/Source/browser/cssServerMain.ts b/extensions/css-language-features/server/Source/browser/cssServerMain.ts index 1de9527ae766f..f4d92f0edbea2 100644 --- a/extensions/css-language-features/server/Source/browser/cssServerMain.ts +++ b/extensions/css-language-features/server/Source/browser/cssServerMain.ts @@ -2,29 +2,23 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { createConnection, BrowserMessageReader, BrowserMessageWriter, Disposable } from 'vscode-languageserver/browser'; import { RuntimeEnvironment, startServer } from '../cssServer'; - const messageReader = new BrowserMessageReader(self); const messageWriter = new BrowserMessageWriter(self); - const connection = createConnection(messageReader, messageWriter); - console.log = connection.console.log.bind(connection.console); console.error = connection.console.error.bind(connection.console); - const runtime: RuntimeEnvironment = { - timer: { - setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { - const handle = setTimeout(callback, 0, ...args); - return { dispose: () => clearTimeout(handle) }; - }, - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { - const handle = setTimeout(callback, ms, ...args); - return { dispose: () => clearTimeout(handle) }; - } - } + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setTimeout(callback, 0, ...args); + return { dispose: () => clearTimeout(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + } }; - startServer(connection, runtime); diff --git a/extensions/css-language-features/server/Source/browser/cssServerWorkerMain.ts b/extensions/css-language-features/server/Source/browser/cssServerWorkerMain.ts index 4f2ae207c168c..6d4d29642657e 100644 --- a/extensions/css-language-features/server/Source/browser/cssServerWorkerMain.ts +++ b/extensions/css-language-features/server/Source/browser/cssServerWorkerMain.ts @@ -2,34 +2,35 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as l10n from '@vscode/l10n'; - let initialized = false; const pendingMessages: any[] = []; const messageHandler = async (e: any) => { - if (!initialized) { - const l10nLog: string[] = []; - initialized = true; - const i10lLocation = e.data.i10lLocation; - if (i10lLocation) { - try { - await l10n.config({ uri: i10lLocation }); - l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}.`); - } catch (e) { - l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}.`); - } - } else { - l10nLog.push(`l10n: No bundle configured.`); - } - await import('./cssServerMain.js'); - if (self.onmessage !== messageHandler) { - pendingMessages.forEach(msg => self.onmessage?.(msg)); - pendingMessages.length = 0; - } - l10nLog.forEach(console.log); - } else { - pendingMessages.push(e); - } + if (!initialized) { + const l10nLog: string[] = []; + initialized = true; + const i10lLocation = e.data.i10lLocation; + if (i10lLocation) { + try { + await l10n.config({ uri: i10lLocation }); + l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}.`); + } + catch (e) { + l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}.`); + } + } + else { + l10nLog.push(`l10n: No bundle configured.`); + } + await import('./cssServerMain.js'); + if (self.onmessage !== messageHandler) { + pendingMessages.forEach(msg => self.onmessage?.(msg)); + pendingMessages.length = 0; + } + l10nLog.forEach(console.log); + } + else { + pendingMessages.push(e); + } }; self.onmessage = messageHandler; diff --git a/extensions/css-language-features/server/Source/cssServer.ts b/extensions/css-language-features/server/Source/cssServer.ts index c5db57340fd5d..0220ab06a6996 100644 --- a/extensions/css-language-features/server/Source/cssServer.ts +++ b/extensions/css-language-features/server/Source/cssServer.ts @@ -2,10 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -import { - Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit, Diagnostic -} from 'vscode-languageserver'; +import { Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit, Diagnostic } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; @@ -14,376 +11,328 @@ import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnostics import { getDocumentContext } from './utils/documentContext'; import { fetchDataProviders } from './customData'; import { RequestService, getRequestService } from './requests'; - namespace CustomDataChangedNotification { - export const type: NotificationType = new NotificationType('css/customDataChanged'); + export const type: NotificationType = new NotificationType('css/customDataChanged'); } - export interface Settings { - css: LanguageSettings; - less: LanguageSettings; - scss: LanguageSettings; + css: LanguageSettings; + less: LanguageSettings; + scss: LanguageSettings; } - export interface RuntimeEnvironment { - readonly file?: RequestService; - readonly http?: RequestService; - readonly timer: { - setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable; - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; - }; + readonly file?: RequestService; + readonly http?: RequestService; + readonly timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable; + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable; + }; } - export function startServer(connection: Connection, runtime: RuntimeEnvironment) { - - // Create a text document manager. - const documents = new TextDocuments(TextDocument); - // Make the text document manager listen on the connection - // for open, change and close text document events - documents.listen(connection); - - const stylesheets = getLanguageModelCache(10, 60, document => getLanguageService(document).parseStylesheet(document)); - documents.onDidClose(e => { - stylesheets.onDocumentRemoved(e.document); - }); - connection.onShutdown(() => { - stylesheets.dispose(); - }); - - let scopedSettingsSupport = false; - let foldingRangeLimit = Number.MAX_VALUE; - let workspaceFolders: WorkspaceFolder[]; - let formatterMaxNumberOfEdits = Number.MAX_VALUE; - - let dataProvidersReady: Promise = Promise.resolve(); - - let diagnosticsSupport: DiagnosticsSupport | undefined; - - const languageServices: { [id: string]: LanguageService } = {}; - - const notReady = () => Promise.reject('Not Ready'); - let requestService: RequestService = { getContent: notReady, stat: notReady, readDirectory: notReady }; - - // After the server has started the client sends an initialize request. The server receives - // in the passed params the rootPath of the workspace plus the client capabilities. - connection.onInitialize((params: InitializeParams): InitializeResult => { - - const initializationOptions = params.initializationOptions as any || {}; - - workspaceFolders = (params).workspaceFolders; - if (!Array.isArray(workspaceFolders)) { - workspaceFolders = []; - if (params.rootPath) { - workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString(true) }); - } - } - - requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); - - function getClientCapability(name: string, def: T) { - const keys = name.split('.'); - let c: any = params.capabilities; - for (let i = 0; c && i < keys.length; i++) { - if (!c.hasOwnProperty(keys[i])) { - return def; - } - c = c[keys[i]]; - } - return c; - } - const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false); - scopedSettingsSupport = !!getClientCapability('workspace.configuration', false); - foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); - - formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; - - languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); - languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); - languageServices.less = getLESSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); - - const supportsDiagnosticPull = getClientCapability('textDocument.diagnostic', undefined); - if (supportsDiagnosticPull === undefined) { - diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); - } else { - diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); - } - - const capabilities: ServerCapabilities = { - textDocumentSync: TextDocumentSyncKind.Incremental, - completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-', ':'] } : undefined, - hoverProvider: true, - documentSymbolProvider: true, - referencesProvider: true, - definitionProvider: true, - documentHighlightProvider: true, - documentLinkProvider: { - resolveProvider: false - }, - codeActionProvider: true, - renameProvider: true, - colorProvider: {}, - foldingRangeProvider: true, - selectionRangeProvider: true, - diagnosticProvider: { - documentSelector: null, - interFileDependencies: false, - workspaceDiagnostics: false - }, - documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, - documentFormattingProvider: initializationOptions?.provideFormatter === true, - }; - return { capabilities }; - }); - - function getLanguageService(document: TextDocument) { - let service = languageServices[document.languageId]; - if (!service) { - connection.console.log('Document type is ' + document.languageId + ', using css instead.'); - service = languageServices['css']; - } - return service; - } - - let documentSettings: { [key: string]: Thenable } = {}; - // remove document settings on close - documents.onDidClose(e => { - delete documentSettings[e.document.uri]; - }); - function getDocumentSettings(textDocument: TextDocument): Thenable { - if (scopedSettingsSupport) { - let promise = documentSettings[textDocument.uri]; - if (!promise) { - const configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] }; - promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0] as LanguageSettings | undefined); - documentSettings[textDocument.uri] = promise; - } - return promise; - } - return Promise.resolve(undefined); - } - - // The settings have changed. Is send on server activation as well. - connection.onDidChangeConfiguration(change => { - updateConfiguration(change.settings as any); - }); - - function updateConfiguration(settings: any) { - for (const languageId in languageServices) { - languageServices[languageId].configure(settings[languageId]); - } - // reset all document settings - documentSettings = {}; - diagnosticsSupport?.requestRefresh(); - } - - async function validateTextDocument(textDocument: TextDocument): Promise { - const settingsPromise = getDocumentSettings(textDocument); - const [settings] = await Promise.all([settingsPromise, dataProvidersReady]); - - const stylesheet = stylesheets.get(textDocument); - return getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); - } - - function updateDataProviders(dataPaths: string[]) { - dataProvidersReady = fetchDataProviders(dataPaths, requestService).then(customDataProviders => { - for (const lang in languageServices) { - languageServices[lang].setDataProviders(true, customDataProviders); - } - }); - } - - connection.onCompletion((textDocumentPosition, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(textDocumentPosition.textDocument.uri); - if (document) { - const [settings,] = await Promise.all([getDocumentSettings(document), dataProvidersReady]); - const styleSheet = stylesheets.get(document); - const documentContext = getDocumentContext(document.uri, workspaceFolders); - return getLanguageService(document).doComplete2(document, textDocumentPosition.position, styleSheet, documentContext, settings?.completion); - } - return null; - }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token); - }); - - connection.onHover((textDocumentPosition, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(textDocumentPosition.textDocument.uri); - if (document) { - const [settings,] = await Promise.all([getDocumentSettings(document), dataProvidersReady]); - const styleSheet = stylesheets.get(document); - return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet, settings?.hover); - } - return null; - }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token); - }); - - connection.onDocumentSymbol((documentSymbolParams, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(documentSymbolParams.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentSymbols2(document, stylesheet); - } - return []; - }, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`, token); - }); - - connection.onDefinition((documentDefinitionParams, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(documentDefinitionParams.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).findDefinition(document, documentDefinitionParams.position, stylesheet); - } - return null; - }, null, `Error while computing definitions for ${documentDefinitionParams.textDocument.uri}`, token); - }); - - connection.onDocumentHighlight((documentHighlightParams, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(documentHighlightParams.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentHighlights(document, documentHighlightParams.position, stylesheet); - } - return []; - }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token); - }); - - - connection.onDocumentLinks(async (documentLinkParams, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(documentLinkParams.textDocument.uri); - if (document) { - await dataProvidersReady; - const documentContext = getDocumentContext(document.uri, workspaceFolders); - const stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentLinks2(document, stylesheet, documentContext); - } - return []; - }, [], `Error while computing document links for ${documentLinkParams.textDocument.uri}`, token); - }); - - - connection.onReferences((referenceParams, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(referenceParams.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet); - } - return []; - }, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token); - }); - - connection.onCodeAction((codeActionParams, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(codeActionParams.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet); - } - return []; - }, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`, token); - }); - - connection.onDocumentColor((params, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(params.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentColors(document, stylesheet); - } - return []; - }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); - }); - - connection.onColorPresentation((params, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(params.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range); - } - return []; - }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token); - }); - - connection.onRenameRequest((renameParameters, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(renameParameters.textDocument.uri); - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet); - } - return null; - }, null, `Error while computing renames for ${renameParameters.textDocument.uri}`, token); - }); - - connection.onFoldingRanges((params, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(params.textDocument.uri); - if (document) { - await dataProvidersReady; - return getLanguageService(document).getFoldingRanges(document, { rangeLimit: foldingRangeLimit }); - } - return null; - }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); - }); - - connection.onSelectionRanges((params, token) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(params.textDocument.uri); - const positions: Position[] = params.positions; - - if (document) { - await dataProvidersReady; - const stylesheet = stylesheets.get(document); - return getLanguageService(document).getSelectionRanges(document, positions, stylesheet); - } - return []; - }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); - }); - - async function onFormat(textDocument: TextDocumentIdentifier, range: Range | undefined, options: FormattingOptions): Promise { - const document = documents.get(textDocument.uri); - if (document) { - const edits = getLanguageService(document).format(document, range ?? getFullRange(document), options); - if (edits.length > formatterMaxNumberOfEdits) { - const newText = TextDocument.applyEdits(document, edits); - return [TextEdit.replace(getFullRange(document), newText)]; - } - return edits; - } - return []; - } - - connection.onDocumentRangeFormatting((formatParams, token) => { - return runSafeAsync(runtime, () => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); - }); - - connection.onDocumentFormatting((formatParams, token) => { - return runSafeAsync(runtime, () => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token); - }); - - connection.onNotification(CustomDataChangedNotification.type, updateDataProviders); - - // Listen on the connection - connection.listen(); - + // Create a text document manager. + const documents = new TextDocuments(TextDocument); + // Make the text document manager listen on the connection + // for open, change and close text document events + documents.listen(connection); + const stylesheets = getLanguageModelCache(10, 60, document => getLanguageService(document).parseStylesheet(document)); + documents.onDidClose(e => { + stylesheets.onDocumentRemoved(e.document); + }); + connection.onShutdown(() => { + stylesheets.dispose(); + }); + let scopedSettingsSupport = false; + let foldingRangeLimit = Number.MAX_VALUE; + let workspaceFolders: WorkspaceFolder[]; + let formatterMaxNumberOfEdits = Number.MAX_VALUE; + let dataProvidersReady: Promise = Promise.resolve(); + let diagnosticsSupport: DiagnosticsSupport | undefined; + const languageServices: { + [id: string]: LanguageService; + } = {}; + const notReady = () => Promise.reject('Not Ready'); + let requestService: RequestService = { getContent: notReady, stat: notReady, readDirectory: notReady }; + // After the server has started the client sends an initialize request. The server receives + // in the passed params the rootPath of the workspace plus the client capabilities. + connection.onInitialize((params: InitializeParams): InitializeResult => { + const initializationOptions = params.initializationOptions as any || {}; + workspaceFolders = (params).workspaceFolders; + if (!Array.isArray(workspaceFolders)) { + workspaceFolders = []; + if (params.rootPath) { + workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString(true) }); + } + } + requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); + function getClientCapability(name: string, def: T) { + const keys = name.split('.'); + let c: any = params.capabilities; + for (let i = 0; c && i < keys.length; i++) { + if (!c.hasOwnProperty(keys[i])) { + return def; + } + c = c[keys[i]]; + } + return c; + } + const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false); + scopedSettingsSupport = !!getClientCapability('workspace.configuration', false); + foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); + formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); + languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); + languageServices.less = getLESSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); + const supportsDiagnosticPull = getClientCapability('textDocument.diagnostic', undefined); + if (supportsDiagnosticPull === undefined) { + diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); + } + else { + diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); + } + const capabilities: ServerCapabilities = { + textDocumentSync: TextDocumentSyncKind.Incremental, + completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-', ':'] } : undefined, + hoverProvider: true, + documentSymbolProvider: true, + referencesProvider: true, + definitionProvider: true, + documentHighlightProvider: true, + documentLinkProvider: { + resolveProvider: false + }, + codeActionProvider: true, + renameProvider: true, + colorProvider: {}, + foldingRangeProvider: true, + selectionRangeProvider: true, + diagnosticProvider: { + documentSelector: null, + interFileDependencies: false, + workspaceDiagnostics: false + }, + documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, + documentFormattingProvider: initializationOptions?.provideFormatter === true, + }; + return { capabilities }; + }); + function getLanguageService(document: TextDocument) { + let service = languageServices[document.languageId]; + if (!service) { + connection.console.log('Document type is ' + document.languageId + ', using css instead.'); + service = languageServices['css']; + } + return service; + } + let documentSettings: { + [key: string]: Thenable; + } = {}; + // remove document settings on close + documents.onDidClose(e => { + delete documentSettings[e.document.uri]; + }); + function getDocumentSettings(textDocument: TextDocument): Thenable { + if (scopedSettingsSupport) { + let promise = documentSettings[textDocument.uri]; + if (!promise) { + const configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] }; + promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0] as LanguageSettings | undefined); + documentSettings[textDocument.uri] = promise; + } + return promise; + } + return Promise.resolve(undefined); + } + // The settings have changed. Is send on server activation as well. + connection.onDidChangeConfiguration(change => { + updateConfiguration(change.settings as any); + }); + function updateConfiguration(settings: any) { + for (const languageId in languageServices) { + languageServices[languageId].configure(settings[languageId]); + } + // reset all document settings + documentSettings = {}; + diagnosticsSupport?.requestRefresh(); + } + async function validateTextDocument(textDocument: TextDocument): Promise { + const settingsPromise = getDocumentSettings(textDocument); + const [settings] = await Promise.all([settingsPromise, dataProvidersReady]); + const stylesheet = stylesheets.get(textDocument); + return getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); + } + function updateDataProviders(dataPaths: string[]) { + dataProvidersReady = fetchDataProviders(dataPaths, requestService).then(customDataProviders => { + for (const lang in languageServices) { + languageServices[lang].setDataProviders(true, customDataProviders); + } + }); + } + connection.onCompletion((textDocumentPosition, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(textDocumentPosition.textDocument.uri); + if (document) { + const [settings,] = await Promise.all([getDocumentSettings(document), dataProvidersReady]); + const styleSheet = stylesheets.get(document); + const documentContext = getDocumentContext(document.uri, workspaceFolders); + return getLanguageService(document).doComplete2(document, textDocumentPosition.position, styleSheet, documentContext, settings?.completion); + } + return null; + }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token); + }); + connection.onHover((textDocumentPosition, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(textDocumentPosition.textDocument.uri); + if (document) { + const [settings,] = await Promise.all([getDocumentSettings(document), dataProvidersReady]); + const styleSheet = stylesheets.get(document); + return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet, settings?.hover); + } + return null; + }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token); + }); + connection.onDocumentSymbol((documentSymbolParams, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(documentSymbolParams.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentSymbols2(document, stylesheet); + } + return []; + }, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`, token); + }); + connection.onDefinition((documentDefinitionParams, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(documentDefinitionParams.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).findDefinition(document, documentDefinitionParams.position, stylesheet); + } + return null; + }, null, `Error while computing definitions for ${documentDefinitionParams.textDocument.uri}`, token); + }); + connection.onDocumentHighlight((documentHighlightParams, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(documentHighlightParams.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentHighlights(document, documentHighlightParams.position, stylesheet); + } + return []; + }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token); + }); + connection.onDocumentLinks(async (documentLinkParams, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(documentLinkParams.textDocument.uri); + if (document) { + await dataProvidersReady; + const documentContext = getDocumentContext(document.uri, workspaceFolders); + const stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentLinks2(document, stylesheet, documentContext); + } + return []; + }, [], `Error while computing document links for ${documentLinkParams.textDocument.uri}`, token); + }); + connection.onReferences((referenceParams, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(referenceParams.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet); + } + return []; + }, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token); + }); + connection.onCodeAction((codeActionParams, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(codeActionParams.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet); + } + return []; + }, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`, token); + }); + connection.onDocumentColor((params, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentColors(document, stylesheet); + } + return []; + }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); + }); + connection.onColorPresentation((params, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range); + } + return []; + }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token); + }); + connection.onRenameRequest((renameParameters, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(renameParameters.textDocument.uri); + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet); + } + return null; + }, null, `Error while computing renames for ${renameParameters.textDocument.uri}`, token); + }); + connection.onFoldingRanges((params, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + await dataProvidersReady; + return getLanguageService(document).getFoldingRanges(document, { rangeLimit: foldingRangeLimit }); + } + return null; + }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); + }); + connection.onSelectionRanges((params, token) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + const positions: Position[] = params.positions; + if (document) { + await dataProvidersReady; + const stylesheet = stylesheets.get(document); + return getLanguageService(document).getSelectionRanges(document, positions, stylesheet); + } + return []; + }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); + }); + async function onFormat(textDocument: TextDocumentIdentifier, range: Range | undefined, options: FormattingOptions): Promise { + const document = documents.get(textDocument.uri); + if (document) { + const edits = getLanguageService(document).format(document, range ?? getFullRange(document), options); + if (edits.length > formatterMaxNumberOfEdits) { + const newText = TextDocument.applyEdits(document, edits); + return [TextEdit.replace(getFullRange(document), newText)]; + } + return edits; + } + return []; + } + connection.onDocumentRangeFormatting((formatParams, token) => { + return runSafeAsync(runtime, () => onFormat(formatParams.textDocument, formatParams.range, formatParams.options), [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); + }); + connection.onDocumentFormatting((formatParams, token) => { + return runSafeAsync(runtime, () => onFormat(formatParams.textDocument, undefined, formatParams.options), [], `Error while formatting ${formatParams.textDocument.uri}`, token); + }); + connection.onNotification(CustomDataChangedNotification.type, updateDataProviders); + // Listen on the connection + connection.listen(); } - function getFullRange(document: TextDocument): Range { - return Range.create(Position.create(0, 0), document.positionAt(document.getText().length)); + return Range.create(Position.create(0, 0), document.positionAt(document.getText().length)); } - - - diff --git a/extensions/css-language-features/server/Source/customData.ts b/extensions/css-language-features/server/Source/customData.ts index ccfc706452c27..33743389c63af 100644 --- a/extensions/css-language-features/server/Source/customData.ts +++ b/extensions/css-language-features/server/Source/customData.ts @@ -2,37 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ICSSDataProvider, newCSSDataProvider } from 'vscode-css-languageservice'; import { RequestService } from './requests'; - export function fetchDataProviders(dataPaths: string[], requestService: RequestService): Promise { - const providers = dataPaths.map(async p => { - try { - const content = await requestService.getContent(p); - return parseCSSData(content); - } catch (e) { - return newCSSDataProvider({ version: 1 }); - } - }); - - return Promise.all(providers); + const providers = dataPaths.map(async (p) => { + try { + const content = await requestService.getContent(p); + return parseCSSData(content); + } + catch (e) { + return newCSSDataProvider({ version: 1 }); + } + }); + return Promise.all(providers); } - function parseCSSData(source: string): ICSSDataProvider { - let rawData: any; - - try { - rawData = JSON.parse(source); - } catch (err) { - return newCSSDataProvider({ version: 1 }); - } - - return newCSSDataProvider({ - version: rawData.version || 1, - properties: rawData.properties || [], - atDirectives: rawData.atDirectives || [], - pseudoClasses: rawData.pseudoClasses || [], - pseudoElements: rawData.pseudoElements || [] - }); + let rawData: any; + try { + rawData = JSON.parse(source); + } + catch (err) { + return newCSSDataProvider({ version: 1 }); + } + return newCSSDataProvider({ + version: rawData.version || 1, + properties: rawData.properties || [], + atDirectives: rawData.atDirectives || [], + pseudoClasses: rawData.pseudoClasses || [], + pseudoElements: rawData.pseudoElements || [] + }); } diff --git a/extensions/css-language-features/server/Source/languageModelCache.ts b/extensions/css-language-features/server/Source/languageModelCache.ts index df498ed9f20e6..fffb26751af23 100644 --- a/extensions/css-language-features/server/Source/languageModelCache.ts +++ b/extensions/css-language-features/server/Source/languageModelCache.ts @@ -2,81 +2,81 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { TextDocument } from 'vscode-css-languageservice'; - export interface LanguageModelCache { - get(document: TextDocument): T; - onDocumentRemoved(document: TextDocument): void; - dispose(): void; + get(document: TextDocument): T; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; } - export function getLanguageModelCache(maxEntries: number, cleanupIntervalTimeInSec: number, parse: (document: TextDocument) => T): LanguageModelCache { - let languageModels: { [uri: string]: { version: number; languageId: string; cTime: number; languageModel: T } } = {}; - let nModels = 0; - - let cleanupInterval: NodeJS.Timeout | undefined = undefined; - if (cleanupIntervalTimeInSec > 0) { - cleanupInterval = setInterval(() => { - const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; - const uris = Object.keys(languageModels); - for (const uri of uris) { - const languageModelInfo = languageModels[uri]; - if (languageModelInfo.cTime < cutoffTime) { - delete languageModels[uri]; - nModels--; - } - } - }, cleanupIntervalTimeInSec * 1000); - } - - return { - get(document: TextDocument): T { - const version = document.version; - const languageId = document.languageId; - const languageModelInfo = languageModels[document.uri]; - if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) { - languageModelInfo.cTime = Date.now(); - return languageModelInfo.languageModel; - } - const languageModel = parse(document); - languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() }; - if (!languageModelInfo) { - nModels++; - } - - if (nModels === maxEntries) { - let oldestTime = Number.MAX_VALUE; - let oldestUri = null; - for (const uri in languageModels) { - const languageModelInfo = languageModels[uri]; - if (languageModelInfo.cTime < oldestTime) { - oldestUri = uri; - oldestTime = languageModelInfo.cTime; - } - } - if (oldestUri) { - delete languageModels[oldestUri]; - nModels--; - } - } - return languageModel; - - }, - onDocumentRemoved(document: TextDocument) { - const uri = document.uri; - if (languageModels[uri]) { - delete languageModels[uri]; - nModels--; - } - }, - dispose() { - if (typeof cleanupInterval !== 'undefined') { - clearInterval(cleanupInterval); - cleanupInterval = undefined; - languageModels = {}; - nModels = 0; - } - } - }; + let languageModels: { + [uri: string]: { + version: number; + languageId: string; + cTime: number; + languageModel: T; + }; + } = {}; + let nModels = 0; + let cleanupInterval: NodeJS.Timeout | undefined = undefined; + if (cleanupIntervalTimeInSec > 0) { + cleanupInterval = setInterval(() => { + const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; + const uris = Object.keys(languageModels); + for (const uri of uris) { + const languageModelInfo = languageModels[uri]; + if (languageModelInfo.cTime < cutoffTime) { + delete languageModels[uri]; + nModels--; + } + } + }, cleanupIntervalTimeInSec * 1000); + } + return { + get(document: TextDocument): T { + const version = document.version; + const languageId = document.languageId; + const languageModelInfo = languageModels[document.uri]; + if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) { + languageModelInfo.cTime = Date.now(); + return languageModelInfo.languageModel; + } + const languageModel = parse(document); + languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() }; + if (!languageModelInfo) { + nModels++; + } + if (nModels === maxEntries) { + let oldestTime = Number.MAX_VALUE; + let oldestUri = null; + for (const uri in languageModels) { + const languageModelInfo = languageModels[uri]; + if (languageModelInfo.cTime < oldestTime) { + oldestUri = uri; + oldestTime = languageModelInfo.cTime; + } + } + if (oldestUri) { + delete languageModels[oldestUri]; + nModels--; + } + } + return languageModel; + }, + onDocumentRemoved(document: TextDocument) { + const uri = document.uri; + if (languageModels[uri]) { + delete languageModels[uri]; + nModels--; + } + }, + dispose() { + if (typeof cleanupInterval !== 'undefined') { + clearInterval(cleanupInterval); + cleanupInterval = undefined; + languageModels = {}; + nModels = 0; + } + } + }; } diff --git a/extensions/css-language-features/server/Source/node/cssServerMain.ts b/extensions/css-language-features/server/Source/node/cssServerMain.ts index 501022dc34d69..72009644c404c 100644 --- a/extensions/css-language-features/server/Source/node/cssServerMain.ts +++ b/extensions/css-language-features/server/Source/node/cssServerMain.ts @@ -2,34 +2,28 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { createConnection, Connection, Disposable } from 'vscode-languageserver/node'; import { formatError } from '../utils/runner'; import { RuntimeEnvironment, startServer } from '../cssServer'; import { getNodeFSRequestService } from './nodeFs'; - // Create a connection for the server. const connection: Connection = createConnection(); - console.log = connection.console.log.bind(connection.console); console.error = connection.console.error.bind(connection.console); - process.on('unhandledRejection', (e: any) => { - connection.console.error(formatError(`Unhandled exception`, e)); + connection.console.error(formatError(`Unhandled exception`, e)); }); - const runtime: RuntimeEnvironment = { - timer: { - setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { - const handle = setImmediate(callback, ...args); - return { dispose: () => clearImmediate(handle) }; - }, - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { - const handle = setTimeout(callback, ms, ...args); - return { dispose: () => clearTimeout(handle) }; - } - }, - file: getNodeFSRequestService() + timer: { + setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { + const handle = setImmediate(callback, ...args); + return { dispose: () => clearImmediate(handle) }; + }, + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable { + const handle = setTimeout(callback, ms, ...args); + return { dispose: () => clearTimeout(handle) }; + } + }, + file: getNodeFSRequestService() }; - startServer(connection, runtime); diff --git a/extensions/css-language-features/server/Source/node/cssServerNodeMain.ts b/extensions/css-language-features/server/Source/node/cssServerNodeMain.ts index 67d8439fd9bd1..bc5e0f6b1b2e0 100644 --- a/extensions/css-language-features/server/Source/node/cssServerNodeMain.ts +++ b/extensions/css-language-features/server/Source/node/cssServerNodeMain.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as l10n from '@vscode/l10n'; async function setupMain() { - const l10nLog: string[] = []; - - const i10lLocation = process.env['VSCODE_L10N_BUNDLE_LOCATION']; - if (i10lLocation) { - try { - await l10n.config({ uri: i10lLocation }); - l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}`); - } catch (e) { - l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}`); - } - } - await import('./cssServerMain.js'); - l10nLog.forEach(console.log); + const l10nLog: string[] = []; + const i10lLocation = process.env['VSCODE_L10N_BUNDLE_LOCATION']; + if (i10lLocation) { + try { + await l10n.config({ uri: i10lLocation }); + l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}`); + } + catch (e) { + l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}`); + } + } + await import('./cssServerMain.js'); + l10nLog.forEach(console.log); } setupMain(); diff --git a/extensions/css-language-features/server/Source/node/nodeFs.ts b/extensions/css-language-features/server/Source/node/nodeFs.ts index 35e55622dc9e3..873201421c3a6 100644 --- a/extensions/css-language-features/server/Source/node/nodeFs.ts +++ b/extensions/css-language-features/server/Source/node/nodeFs.ts @@ -2,86 +2,85 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { RequestService } from '../requests'; import { URI as Uri } from 'vscode-uri'; - import * as fs from 'fs'; import { FileType } from 'vscode-css-languageservice'; - export function getNodeFSRequestService(): RequestService { - function ensureFileUri(location: string) { - if (!location.startsWith('file://')) { - throw new Error('fileRequestService can only handle file URLs'); - } - } - return { - getContent(location: string, encoding?: BufferEncoding) { - ensureFileUri(location); - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.readFile(uri.fsPath, encoding, (err, buf) => { - if (err) { - return e(err); - } - c(buf.toString()); - - }); - }); - }, - stat(location: string) { - ensureFileUri(location); - return new Promise((c, e) => { - const uri = Uri.parse(location); - fs.stat(uri.fsPath, (err, stats) => { - if (err) { - if (err.code === 'ENOENT') { - return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 }); - } else { - return e(err); - } - } - - let type = FileType.Unknown; - if (stats.isFile()) { - type = FileType.File; - } else if (stats.isDirectory()) { - type = FileType.Directory; - } else if (stats.isSymbolicLink()) { - type = FileType.SymbolicLink; - } - - c({ - type, - ctime: stats.ctime.getTime(), - mtime: stats.mtime.getTime(), - size: stats.size - }); - }); - }); - }, - readDirectory(location: string) { - ensureFileUri(location); - return new Promise((c, e) => { - const path = Uri.parse(location).fsPath; - - fs.readdir(path, { withFileTypes: true }, (err, children) => { - if (err) { - return e(err); - } - c(children.map(stat => { - if (stat.isSymbolicLink()) { - return [stat.name, FileType.SymbolicLink]; - } else if (stat.isDirectory()) { - return [stat.name, FileType.Directory]; - } else if (stat.isFile()) { - return [stat.name, FileType.File]; - } else { - return [stat.name, FileType.Unknown]; - } - })); - }); - }); - } - }; + function ensureFileUri(location: string) { + if (!location.startsWith('file://')) { + throw new Error('fileRequestService can only handle file URLs'); + } + } + return { + getContent(location: string, encoding?: BufferEncoding) { + ensureFileUri(location); + return new Promise((c, e) => { + const uri = Uri.parse(location); + fs.readFile(uri.fsPath, encoding, (err, buf) => { + if (err) { + return e(err); + } + c(buf.toString()); + }); + }); + }, + stat(location: string) { + ensureFileUri(location); + return new Promise((c, e) => { + const uri = Uri.parse(location); + fs.stat(uri.fsPath, (err, stats) => { + if (err) { + if (err.code === 'ENOENT') { + return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 }); + } + else { + return e(err); + } + } + let type = FileType.Unknown; + if (stats.isFile()) { + type = FileType.File; + } + else if (stats.isDirectory()) { + type = FileType.Directory; + } + else if (stats.isSymbolicLink()) { + type = FileType.SymbolicLink; + } + c({ + type, + ctime: stats.ctime.getTime(), + mtime: stats.mtime.getTime(), + size: stats.size + }); + }); + }); + }, + readDirectory(location: string) { + ensureFileUri(location); + return new Promise((c, e) => { + const path = Uri.parse(location).fsPath; + fs.readdir(path, { withFileTypes: true }, (err, children) => { + if (err) { + return e(err); + } + c(children.map(stat => { + if (stat.isSymbolicLink()) { + return [stat.name, FileType.SymbolicLink]; + } + else if (stat.isDirectory()) { + return [stat.name, FileType.Directory]; + } + else if (stat.isFile()) { + return [stat.name, FileType.File]; + } + else { + return [stat.name, FileType.Unknown]; + } + })); + }); + }); + } + }; } diff --git a/extensions/css-language-features/server/Source/requests.ts b/extensions/css-language-features/server/Source/requests.ts index 3d21e54236260..2d3830ee89437 100644 --- a/extensions/css-language-features/server/Source/requests.ts +++ b/extensions/css-language-features/server/Source/requests.ts @@ -2,102 +2,108 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { RequestType, Connection } from 'vscode-languageserver'; import { RuntimeEnvironment } from './cssServer'; - export namespace FsContentRequest { - export const type: RequestType<{ uri: string; encoding?: string }, string, any> = new RequestType('fs/content'); + export const type: RequestType<{ + uri: string; + encoding?: string; + }, string, any> = new RequestType('fs/content'); } export namespace FsStatRequest { - export const type: RequestType = new RequestType('fs/stat'); + export const type: RequestType = new RequestType('fs/stat'); } - export namespace FsReadDirRequest { - export const type: RequestType = new RequestType('fs/readDir'); + export const type: RequestType = new RequestType('fs/readDir'); } - export enum FileType { - /** - * The file type is unknown. - */ - Unknown = 0, - /** - * A regular file. - */ - File = 1, - /** - * A directory. - */ - Directory = 2, - /** - * A symbolic link to a file. - */ - SymbolicLink = 64 + /** + * The file type is unknown. + */ + Unknown = 0, + /** + * A regular file. + */ + File = 1, + /** + * A directory. + */ + Directory = 2, + /** + * A symbolic link to a file. + */ + SymbolicLink = 64 } export interface FileStat { - /** - * The type of the file, e.g. is a regular file, a directory, or symbolic link - * to a file. - */ - type: FileType; - /** - * The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - */ - ctime: number; - /** - * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - */ - mtime: number; - /** - * The size in bytes. - */ - size: number; + /** + * The type of the file, e.g. is a regular file, a directory, or symbolic link + * to a file. + */ + type: FileType; + /** + * The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + */ + ctime: number; + /** + * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + */ + mtime: number; + /** + * The size in bytes. + */ + size: number; } - export interface RequestService { - getContent(uri: string, encoding?: string): Promise; - - stat(uri: string): Promise; - readDirectory(uri: string): Promise<[string, FileType][]>; + getContent(uri: string, encoding?: string): Promise; + stat(uri: string): Promise; + readDirectory(uri: string): Promise<[ + string, + FileType + ][]>; } - - export function getRequestService(handledSchemas: string[], connection: Connection, runtime: RuntimeEnvironment): RequestService { - const builtInHandlers: { [protocol: string]: RequestService | undefined } = {}; - for (const protocol of handledSchemas) { - if (protocol === 'file') { - builtInHandlers[protocol] = runtime.file; - } else if (protocol === 'http' || protocol === 'https') { - builtInHandlers[protocol] = runtime.http; - } - } - return { - async stat(uri: string): Promise { - const handler = builtInHandlers[getScheme(uri)]; - if (handler) { - return handler.stat(uri); - } - const res = await connection.sendRequest(FsStatRequest.type, uri.toString()); - return res; - }, - readDirectory(uri: string): Promise<[string, FileType][]> { - const handler = builtInHandlers[getScheme(uri)]; - if (handler) { - return handler.readDirectory(uri); - } - return connection.sendRequest(FsReadDirRequest.type, uri.toString()); - }, - getContent(uri: string, encoding?: string): Promise { - const handler = builtInHandlers[getScheme(uri)]; - if (handler) { - return handler.getContent(uri, encoding); - } - return connection.sendRequest(FsContentRequest.type, { uri: uri.toString(), encoding }); - } - }; + const builtInHandlers: { + [protocol: string]: RequestService | undefined; + } = {}; + for (const protocol of handledSchemas) { + if (protocol === 'file') { + builtInHandlers[protocol] = runtime.file; + } + else if (protocol === 'http' || protocol === 'https') { + builtInHandlers[protocol] = runtime.http; + } + } + return { + async stat(uri: string): Promise { + const handler = builtInHandlers[getScheme(uri)]; + if (handler) { + return handler.stat(uri); + } + const res = await connection.sendRequest(FsStatRequest.type, uri.toString()); + return res; + }, + readDirectory(uri: string): Promise<[ + string, + FileType + ][]> { + const handler = builtInHandlers[getScheme(uri)]; + if (handler) { + return handler.readDirectory(uri); + } + return connection.sendRequest(FsReadDirRequest.type, uri.toString()); + }, + getContent(uri: string, encoding?: string): Promise { + const handler = builtInHandlers[getScheme(uri)]; + if (handler) { + return handler.getContent(uri, encoding); + } + return connection.sendRequest(FsContentRequest.type, { uri: uri.toString(), encoding }); + } + }; } - function getScheme(uri: string) { - return uri.substr(0, uri.indexOf(':')); + return uri.substr(0, uri.indexOf(':')); } diff --git a/extensions/css-language-features/server/Source/utils/documentContext.ts b/extensions/css-language-features/server/Source/utils/documentContext.ts index c9f46fb75789b..d81ea63a9de5b 100644 --- a/extensions/css-language-features/server/Source/utils/documentContext.ts +++ b/extensions/css-language-features/server/Source/utils/documentContext.ts @@ -2,38 +2,34 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { DocumentContext } from 'vscode-css-languageservice'; import { endsWith, startsWith } from '../utils/strings'; import { WorkspaceFolder } from 'vscode-languageserver'; import { Utils, URI } from 'vscode-uri'; - export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext { - function getRootFolder(): string | undefined { - for (const folder of workspaceFolders) { - let folderURI = folder.uri; - if (!endsWith(folderURI, '/')) { - folderURI = folderURI + '/'; - } - if (startsWith(documentUri, folderURI)) { - return folderURI; - } - } - return undefined; - } - - return { - resolveReference: (ref: string, base = documentUri) => { - if (ref[0] === '/') { // resolve absolute path against the current workspace folder - const folderUri = getRootFolder(); - if (folderUri) { - return folderUri + ref.substring(1); - } - } - const baseUri = URI.parse(base); - const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); - return Utils.resolvePath(baseUriDir, ref).toString(true); - }, - }; + function getRootFolder(): string | undefined { + for (const folder of workspaceFolders) { + let folderURI = folder.uri; + if (!endsWith(folderURI, '/')) { + folderURI = folderURI + '/'; + } + if (startsWith(documentUri, folderURI)) { + return folderURI; + } + } + return undefined; + } + return { + resolveReference: (ref: string, base = documentUri) => { + if (ref[0] === '/') { // resolve absolute path against the current workspace folder + const folderUri = getRootFolder(); + if (folderUri) { + return folderUri + ref.substring(1); + } + } + const baseUri = URI.parse(base); + const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); + return Utils.resolvePath(baseUriDir, ref).toString(true); + }, + }; } - diff --git a/extensions/css-language-features/server/Source/utils/runner.ts b/extensions/css-language-features/server/Source/utils/runner.ts index 383b88e448754..c5f31ff31319b 100644 --- a/extensions/css-language-features/server/Source/utils/runner.ts +++ b/extensions/css-language-features/server/Source/utils/runner.ts @@ -2,44 +2,43 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ResponseError, CancellationToken, LSPErrorCodes } from 'vscode-languageserver'; import { RuntimeEnvironment } from '../cssServer'; - export function formatError(message: string, err: any): string { - if (err instanceof Error) { - const error = err; - return `${message}: ${error.message}\n${error.stack}`; - } else if (typeof err === 'string') { - return `${message}: ${err}`; - } else if (err) { - return `${message}: ${err.toString()}`; - } - return message; + if (err instanceof Error) { + const error = err; + return `${message}: ${error.message}\n${error.stack}`; + } + else if (typeof err === 'string') { + return `${message}: ${err}`; + } + else if (err) { + return `${message}: ${err.toString()}`; + } + return message; } - export function runSafeAsync(runtime: RuntimeEnvironment, func: () => Thenable, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { - return new Promise>((resolve) => { - runtime.timer.setImmediate(() => { - if (token.isCancellationRequested) { - resolve(cancelValue()); - return; - } - return func().then(result => { - if (token.isCancellationRequested) { - resolve(cancelValue()); - return; - } else { - resolve(result); - } - }, e => { - console.error(formatError(errorMessage, e)); - resolve(errorVal); - }); - }); - }); + return new Promise>((resolve) => { + runtime.timer.setImmediate(() => { + if (token.isCancellationRequested) { + resolve(cancelValue()); + return; + } + return func().then(result => { + if (token.isCancellationRequested) { + resolve(cancelValue()); + return; + } + else { + resolve(result); + } + }, e => { + console.error(formatError(errorMessage, e)); + resolve(errorVal); + }); + }); + }); } - function cancelValue() { - return new ResponseError(LSPErrorCodes.RequestCancelled, 'Request cancelled'); + return new ResponseError(LSPErrorCodes.RequestCancelled, 'Request cancelled'); } diff --git a/extensions/css-language-features/server/Source/utils/strings.ts b/extensions/css-language-features/server/Source/utils/strings.ts index 1e53c4204fd30..decb69284851f 100644 --- a/extensions/css-language-features/server/Source/utils/strings.ts +++ b/extensions/css-language-features/server/Source/utils/strings.ts @@ -2,31 +2,29 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export function startsWith(haystack: string, needle: string): boolean { - if (haystack.length < needle.length) { - return false; - } - - for (let i = 0; i < needle.length; i++) { - if (haystack[i] !== needle[i]) { - return false; - } - } - - return true; + if (haystack.length < needle.length) { + return false; + } + for (let i = 0; i < needle.length; i++) { + if (haystack[i] !== needle[i]) { + return false; + } + } + return true; } - /** * Determines if haystack ends with needle. */ export function endsWith(haystack: string, needle: string): boolean { - const diff = haystack.length - needle.length; - if (diff > 0) { - return haystack.lastIndexOf(needle) === diff; - } else if (diff === 0) { - return haystack === needle; - } else { - return false; - } + const diff = haystack.length - needle.length; + if (diff > 0) { + return haystack.lastIndexOf(needle) === diff; + } + else if (diff === 0) { + return haystack === needle; + } + else { + return false; + } } diff --git a/extensions/css-language-features/server/Source/utils/validation.ts b/extensions/css-language-features/server/Source/utils/validation.ts index edd5f5618c754..3177c62a19651 100644 --- a/extensions/css-language-features/server/Source/utils/validation.ts +++ b/extensions/css-language-features/server/Source/utils/validation.ts @@ -2,107 +2,93 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver'; import { TextDocument } from 'vscode-css-languageservice'; import { formatError, runSafeAsync } from './runner'; import { RuntimeEnvironment } from '../cssServer'; - export type Validator = (textDocument: TextDocument) => Promise; export type DiagnosticsSupport = { - dispose(): void; - requestRefresh(): void; + dispose(): void; + requestRefresh(): void; }; - export function registerDiagnosticsPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { - - const pendingValidationRequests: { [uri: string]: Disposable } = {}; - const validationDelayMs = 500; - - const disposables: Disposable[] = []; - - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent(change => { - triggerValidation(change.document); - }, undefined, disposables); - - // a document has closed: clear all diagnostics - documents.onDidClose(event => { - cleanPendingValidation(event.document); - connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); - }, undefined, disposables); - - function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri]; - if (request) { - request.dispose(); - delete pendingValidationRequests[textDocument.uri]; - } - } - - function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument); - const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { - if (request === pendingValidationRequests[textDocument.uri]) { - try { - const diagnostics = await validate(textDocument); - if (request === pendingValidationRequests[textDocument.uri]) { - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); - } - delete pendingValidationRequests[textDocument.uri]; - } catch (e) { - connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); - } - } - }, validationDelayMs); - } - - return { - requestRefresh: () => { - documents.all().forEach(triggerValidation); - }, - dispose: () => { - disposables.forEach(d => d.dispose()); - disposables.length = 0; - const keys = Object.keys(pendingValidationRequests); - for (const key of keys) { - pendingValidationRequests[key].dispose(); - delete pendingValidationRequests[key]; - } - } - }; + const pendingValidationRequests: { + [uri: string]: Disposable; + } = {}; + const validationDelayMs = 500; + const disposables: Disposable[] = []; + // The content of a text document has changed. This event is emitted + // when the text document first opened or when its content has changed. + documents.onDidChangeContent(change => { + triggerValidation(change.document); + }, undefined, disposables); + // a document has closed: clear all diagnostics + documents.onDidClose(event => { + cleanPendingValidation(event.document); + connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); + }, undefined, disposables); + function cleanPendingValidation(textDocument: TextDocument): void { + const request = pendingValidationRequests[textDocument.uri]; + if (request) { + request.dispose(); + delete pendingValidationRequests[textDocument.uri]; + } + } + function triggerValidation(textDocument: TextDocument): void { + cleanPendingValidation(textDocument); + const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { + if (request === pendingValidationRequests[textDocument.uri]) { + try { + const diagnostics = await validate(textDocument); + if (request === pendingValidationRequests[textDocument.uri]) { + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + } + delete pendingValidationRequests[textDocument.uri]; + } + catch (e) { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); + } + } + }, validationDelayMs); + } + return { + requestRefresh: () => { + documents.all().forEach(triggerValidation); + }, + dispose: () => { + disposables.forEach(d => d.dispose()); + disposables.length = 0; + const keys = Object.keys(pendingValidationRequests); + for (const key of keys) { + pendingValidationRequests[key].dispose(); + delete pendingValidationRequests[key]; + } + } + }; } - export function registerDiagnosticsPullSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { - - function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { - return { - kind: DocumentDiagnosticReportKind.Full, - items: diagnostics - }; - } - - const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(params.textDocument.uri); - if (document) { - return newDocumentDiagnosticReport(await validate(document)); - } - return newDocumentDiagnosticReport([]); - - }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); - }); - - function requestRefresh(): void { - connection.languages.diagnostics.refresh(); - } - - return { - requestRefresh, - dispose: () => { - registration.dispose(); - } - }; - + function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { + return { + kind: DocumentDiagnosticReportKind.Full, + items: diagnostics + }; + } + const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + return newDocumentDiagnosticReport(await validate(document)); + } + return newDocumentDiagnosticReport([]); + }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); + }); + function requestRefresh(): void { + connection.languages.diagnostics.refresh(); + } + return { + requestRefresh, + dispose: () => { + registration.dispose(); + } + }; } diff --git a/extensions/debug-auto-launch/Source/extension.ts b/extensions/debug-auto-launch/Source/extension.ts index 7d06c56d47ffc..7a40799eea2de 100644 --- a/extensions/debug-auto-launch/Source/extension.ts +++ b/extensions/debug-auto-launch/Source/extension.ts @@ -2,403 +2,354 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { promises as fs } from 'fs'; import { createServer, Server } from 'net'; import { dirname } from 'path'; import * as vscode from 'vscode'; - const enum State { - Disabled = 'disabled', - OnlyWithFlag = 'onlyWithFlag', - Smart = 'smart', - Always = 'always', + Disabled = 'disabled', + OnlyWithFlag = 'onlyWithFlag', + Smart = 'smart', + Always = 'always' } const TEXT_STATUSBAR_LABEL = { - [State.Disabled]: vscode.l10n.t('Auto Attach: Disabled'), - [State.Always]: vscode.l10n.t('Auto Attach: Always'), - [State.Smart]: vscode.l10n.t('Auto Attach: Smart'), - [State.OnlyWithFlag]: vscode.l10n.t('Auto Attach: With Flag'), + [State.Disabled]: vscode.l10n.t('Auto Attach: Disabled'), + [State.Always]: vscode.l10n.t('Auto Attach: Always'), + [State.Smart]: vscode.l10n.t('Auto Attach: Smart'), + [State.OnlyWithFlag]: vscode.l10n.t('Auto Attach: With Flag'), }; - const TEXT_STATE_LABEL = { - [State.Disabled]: vscode.l10n.t('Disabled'), - [State.Always]: vscode.l10n.t('Always'), - [State.Smart]: vscode.l10n.t('Smart'), - [State.OnlyWithFlag]: vscode.l10n.t('Only With Flag'), + [State.Disabled]: vscode.l10n.t('Disabled'), + [State.Always]: vscode.l10n.t('Always'), + [State.Smart]: vscode.l10n.t('Smart'), + [State.OnlyWithFlag]: vscode.l10n.t('Only With Flag'), }; const TEXT_STATE_DESCRIPTION = { - [State.Disabled]: vscode.l10n.t('Auto attach is disabled and not shown in status bar'), - [State.Always]: vscode.l10n.t('Auto attach to every Node.js process launched in the terminal'), - [State.Smart]: vscode.l10n.t("Auto attach when running scripts that aren't in a node_modules folder"), - [State.OnlyWithFlag]: vscode.l10n.t('Only auto attach when the `--inspect` flag is given') + [State.Disabled]: vscode.l10n.t('Auto attach is disabled and not shown in status bar'), + [State.Always]: vscode.l10n.t('Auto attach to every Node.js process launched in the terminal'), + [State.Smart]: vscode.l10n.t("Auto attach when running scripts that aren't in a node_modules folder"), + [State.OnlyWithFlag]: vscode.l10n.t('Only auto attach when the `--inspect` flag is given') }; const TEXT_TOGGLE_WORKSPACE = vscode.l10n.t('Toggle auto attach in this workspace'); const TEXT_TOGGLE_GLOBAL = vscode.l10n.t('Toggle auto attach on this machine'); const TEXT_TEMP_DISABLE = vscode.l10n.t('Temporarily disable auto attach in this session'); const TEXT_TEMP_ENABLE = vscode.l10n.t('Re-enable auto attach'); const TEXT_TEMP_DISABLE_LABEL = vscode.l10n.t('Auto Attach: Disabled'); - const TOGGLE_COMMAND = 'extension.node-debug.toggleAutoAttach'; const STORAGE_IPC = 'jsDebugIpcState'; - const SETTING_SECTION = 'debug.javascript'; const SETTING_STATE = 'autoAttachFilter'; - /** * settings that, when changed, should cause us to refresh the state vars */ -const SETTINGS_CAUSE_REFRESH = new Set( - ['autoAttachSmartPattern', SETTING_STATE].map(s => `${SETTING_SECTION}.${s}`), -); - - -let currentState: Promise<{ context: vscode.ExtensionContext; state: State | null }>; +const SETTINGS_CAUSE_REFRESH = new Set(['autoAttachSmartPattern', SETTING_STATE].map(s => `${SETTING_SECTION}.${s}`)); +let currentState: Promise<{ + context: vscode.ExtensionContext; + state: State | null; +}>; let statusItem: vscode.StatusBarItem | undefined; // and there is no status bar item let server: Promise | undefined; // auto attach server let isTemporarilyDisabled = false; // whether the auto attach server is disabled temporarily, reset whenever the state changes - export function activate(context: vscode.ExtensionContext): void { - currentState = Promise.resolve({ context, state: null }); - - context.subscriptions.push( - vscode.commands.registerCommand(TOGGLE_COMMAND, toggleAutoAttachSetting.bind(null, context)), - ); - - context.subscriptions.push( - vscode.workspace.onDidChangeConfiguration(e => { - // Whenever a setting is changed, disable auto attach, and re-enable - // it (if necessary) to refresh variables. - if ( - e.affectsConfiguration(`${SETTING_SECTION}.${SETTING_STATE}`) || - [...SETTINGS_CAUSE_REFRESH].some(setting => e.affectsConfiguration(setting)) - ) { - refreshAutoAttachVars(); - } - }), - ); - - updateAutoAttach(readCurrentState()); + currentState = Promise.resolve({ context, state: null }); + context.subscriptions.push(vscode.commands.registerCommand(TOGGLE_COMMAND, toggleAutoAttachSetting.bind(null, context))); + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => { + // Whenever a setting is changed, disable auto attach, and re-enable + // it (if necessary) to refresh variables. + if (e.affectsConfiguration(`${SETTING_SECTION}.${SETTING_STATE}`) || + [...SETTINGS_CAUSE_REFRESH].some(setting => e.affectsConfiguration(setting))) { + refreshAutoAttachVars(); + } + })); + updateAutoAttach(readCurrentState()); } - export async function deactivate(): Promise { - await destroyAttachServer(); + await destroyAttachServer(); } - function refreshAutoAttachVars() { - updateAutoAttach(State.Disabled); - updateAutoAttach(readCurrentState()); + updateAutoAttach(State.Disabled); + updateAutoAttach(readCurrentState()); } - function getDefaultScope(info: ReturnType) { - if (!info) { - return vscode.ConfigurationTarget.Global; - } else if (info.workspaceFolderValue) { - return vscode.ConfigurationTarget.WorkspaceFolder; - } else if (info.workspaceValue) { - return vscode.ConfigurationTarget.Workspace; - } else if (info.globalValue) { - return vscode.ConfigurationTarget.Global; - } - - return vscode.ConfigurationTarget.Global; + if (!info) { + return vscode.ConfigurationTarget.Global; + } + else if (info.workspaceFolderValue) { + return vscode.ConfigurationTarget.WorkspaceFolder; + } + else if (info.workspaceValue) { + return vscode.ConfigurationTarget.Workspace; + } + else if (info.globalValue) { + return vscode.ConfigurationTarget.Global; + } + return vscode.ConfigurationTarget.Global; } - -type PickResult = { state: State } | { setTempDisabled: boolean } | { scope: vscode.ConfigurationTarget } | undefined; -type PickItem = vscode.QuickPickItem & ({ state: State } | { setTempDisabled: boolean }); - +type PickResult = { + state: State; +} | { + setTempDisabled: boolean; +} | { + scope: vscode.ConfigurationTarget; +} | undefined; +type PickItem = vscode.QuickPickItem & ({ + state: State; +} | { + setTempDisabled: boolean; +}); async function toggleAutoAttachSetting(context: vscode.ExtensionContext, scope?: vscode.ConfigurationTarget): Promise { - const section = vscode.workspace.getConfiguration(SETTING_SECTION); - scope = scope || getDefaultScope(section.inspect(SETTING_STATE)); - - const isGlobalScope = scope === vscode.ConfigurationTarget.Global; - const quickPick = vscode.window.createQuickPick(); - const current = readCurrentState(); - - const items: PickItem[] = [State.Always, State.Smart, State.OnlyWithFlag, State.Disabled].map(state => ({ - state, - label: TEXT_STATE_LABEL[state], - description: TEXT_STATE_DESCRIPTION[state], - alwaysShow: true, - })); - - if (current !== State.Disabled) { - items.unshift({ - setTempDisabled: !isTemporarilyDisabled, - label: isTemporarilyDisabled ? TEXT_TEMP_ENABLE : TEXT_TEMP_DISABLE, - alwaysShow: true, - }); - } - - quickPick.items = items; - quickPick.activeItems = isTemporarilyDisabled - ? [items[0]] - : quickPick.items.filter(i => 'state' in i && i.state === current); - quickPick.title = isGlobalScope ? TEXT_TOGGLE_GLOBAL : TEXT_TOGGLE_WORKSPACE; - quickPick.buttons = [ - { - iconPath: new vscode.ThemeIcon(isGlobalScope ? 'folder' : 'globe'), - tooltip: isGlobalScope ? TEXT_TOGGLE_WORKSPACE : TEXT_TOGGLE_GLOBAL, - }, - ]; - - quickPick.show(); - - let result = await new Promise(resolve => { - quickPick.onDidAccept(() => resolve(quickPick.selectedItems[0])); - quickPick.onDidHide(() => resolve(undefined)); - quickPick.onDidTriggerButton(() => { - resolve({ - scope: isGlobalScope - ? vscode.ConfigurationTarget.Workspace - : vscode.ConfigurationTarget.Global, - }); - }); - }); - - quickPick.dispose(); - - if (!result) { - return; - } - - if ('scope' in result) { - return await toggleAutoAttachSetting(context, result.scope); - } - - if ('state' in result) { - if (result.state !== current) { - section.update(SETTING_STATE, result.state, scope); - } else if (isTemporarilyDisabled) { - result = { setTempDisabled: false }; - } - } - - if ('setTempDisabled' in result) { - updateStatusBar(context, current, true); - isTemporarilyDisabled = result.setTempDisabled; - if (result.setTempDisabled) { - await destroyAttachServer(); - } else { - await createAttachServer(context); // unsets temp disabled var internally - } - updateStatusBar(context, current, false); - } + const section = vscode.workspace.getConfiguration(SETTING_SECTION); + scope = scope || getDefaultScope(section.inspect(SETTING_STATE)); + const isGlobalScope = scope === vscode.ConfigurationTarget.Global; + const quickPick = vscode.window.createQuickPick(); + const current = readCurrentState(); + const items: PickItem[] = [State.Always, State.Smart, State.OnlyWithFlag, State.Disabled].map(state => ({ + state, + label: TEXT_STATE_LABEL[state], + description: TEXT_STATE_DESCRIPTION[state], + alwaysShow: true, + })); + if (current !== State.Disabled) { + items.unshift({ + setTempDisabled: !isTemporarilyDisabled, + label: isTemporarilyDisabled ? TEXT_TEMP_ENABLE : TEXT_TEMP_DISABLE, + alwaysShow: true, + }); + } + quickPick.items = items; + quickPick.activeItems = isTemporarilyDisabled + ? [items[0]] + : quickPick.items.filter(i => 'state' in i && i.state === current); + quickPick.title = isGlobalScope ? TEXT_TOGGLE_GLOBAL : TEXT_TOGGLE_WORKSPACE; + quickPick.buttons = [ + { + iconPath: new vscode.ThemeIcon(isGlobalScope ? 'folder' : 'globe'), + tooltip: isGlobalScope ? TEXT_TOGGLE_WORKSPACE : TEXT_TOGGLE_GLOBAL, + }, + ]; + quickPick.show(); + let result = await new Promise(resolve => { + quickPick.onDidAccept(() => resolve(quickPick.selectedItems[0])); + quickPick.onDidHide(() => resolve(undefined)); + quickPick.onDidTriggerButton(() => { + resolve({ + scope: isGlobalScope + ? vscode.ConfigurationTarget.Workspace + : vscode.ConfigurationTarget.Global, + }); + }); + }); + quickPick.dispose(); + if (!result) { + return; + } + if ('scope' in result) { + return await toggleAutoAttachSetting(context, result.scope); + } + if ('state' in result) { + if (result.state !== current) { + section.update(SETTING_STATE, result.state, scope); + } + else if (isTemporarilyDisabled) { + result = { setTempDisabled: false }; + } + } + if ('setTempDisabled' in result) { + updateStatusBar(context, current, true); + isTemporarilyDisabled = result.setTempDisabled; + if (result.setTempDisabled) { + await destroyAttachServer(); + } + else { + await createAttachServer(context); // unsets temp disabled var internally + } + updateStatusBar(context, current, false); + } } - function readCurrentState(): State { - const section = vscode.workspace.getConfiguration(SETTING_SECTION); - return section.get(SETTING_STATE) ?? State.Disabled; + const section = vscode.workspace.getConfiguration(SETTING_SECTION); + return section.get(SETTING_STATE) ?? State.Disabled; } - async function clearJsDebugAttachState(context: vscode.ExtensionContext) { - if (server || await context.workspaceState.get(STORAGE_IPC)) { - await context.workspaceState.update(STORAGE_IPC, undefined); - await vscode.commands.executeCommand('extension.js-debug.clearAutoAttachVariables'); - await destroyAttachServer(); - } + if (server || await context.workspaceState.get(STORAGE_IPC)) { + await context.workspaceState.update(STORAGE_IPC, undefined); + await vscode.commands.executeCommand('extension.js-debug.clearAutoAttachVariables'); + await destroyAttachServer(); + } } - /** * Turns auto attach on, and returns the server auto attach is listening on * if it's successful. */ async function createAttachServer(context: vscode.ExtensionContext) { - const ipcAddress = await getIpcAddress(context); - if (!ipcAddress) { - return undefined; - } - - server = createServerInner(ipcAddress).catch(async err => { - console.error('[debug-auto-launch] Error creating auto attach server: ', err); - - if (process.platform !== 'win32') { - // On macOS, and perhaps some Linux distros, the temporary directory can - // sometimes change. If it looks like that's the cause of a listener - // error, automatically refresh the auto attach vars. - try { - await fs.access(dirname(ipcAddress)); - } catch { - console.error('[debug-auto-launch] Refreshing variables from error'); - refreshAutoAttachVars(); - return undefined; - } - } - - return undefined; - }); - - return await server; + const ipcAddress = await getIpcAddress(context); + if (!ipcAddress) { + return undefined; + } + server = createServerInner(ipcAddress).catch(async (err) => { + console.error('[debug-auto-launch] Error creating auto attach server: ', err); + if (process.platform !== 'win32') { + // On macOS, and perhaps some Linux distros, the temporary directory can + // sometimes change. If it looks like that's the cause of a listener + // error, automatically refresh the auto attach vars. + try { + await fs.access(dirname(ipcAddress)); + } + catch { + console.error('[debug-auto-launch] Refreshing variables from error'); + refreshAutoAttachVars(); + return undefined; + } + } + return undefined; + }); + return await server; } - const createServerInner = async (ipcAddress: string) => { - try { - return await createServerInstance(ipcAddress); - } catch (e) { - // On unix/linux, the file can 'leak' if the process exits unexpectedly. - // If we see this, try to delete the file and then listen again. - await fs.unlink(ipcAddress).catch(() => undefined); - return await createServerInstance(ipcAddress); - } + try { + return await createServerInstance(ipcAddress); + } + catch (e) { + // On unix/linux, the file can 'leak' if the process exits unexpectedly. + // If we see this, try to delete the file and then listen again. + await fs.unlink(ipcAddress).catch(() => undefined); + return await createServerInstance(ipcAddress); + } }; - -const createServerInstance = (ipcAddress: string) => - new Promise((resolve, reject) => { - const s = createServer(socket => { - const data: Buffer[] = []; - socket.on('data', async chunk => { - if (chunk[chunk.length - 1] !== 0) { - // terminated with NUL byte - data.push(chunk); - return; - } - - data.push(chunk.slice(0, -1)); - - try { - await vscode.commands.executeCommand( - 'extension.js-debug.autoAttachToProcess', - JSON.parse(Buffer.concat(data).toString()), - ); - socket.write(Buffer.from([0])); - } catch (err) { - socket.write(Buffer.from([1])); - console.error(err); - } - }); - }) - .on('error', reject) - .listen(ipcAddress, () => resolve(s)); - }); - +const createServerInstance = (ipcAddress: string) => new Promise((resolve, reject) => { + const s = createServer(socket => { + const data: Buffer[] = []; + socket.on('data', async (chunk) => { + if (chunk[chunk.length - 1] !== 0) { + // terminated with NUL byte + data.push(chunk); + return; + } + data.push(chunk.slice(0, -1)); + try { + await vscode.commands.executeCommand('extension.js-debug.autoAttachToProcess', JSON.parse(Buffer.concat(data).toString())); + socket.write(Buffer.from([0])); + } + catch (err) { + socket.write(Buffer.from([1])); + console.error(err); + } + }); + }) + .on('error', reject) + .listen(ipcAddress, () => resolve(s)); +}); /** * Destroys the auto-attach server, if it's running. */ async function destroyAttachServer() { - const instance = await server; - if (instance) { - await new Promise(r => instance.close(r)); - } + const instance = await server; + if (instance) { + await new Promise(r => instance.close(r)); + } } - interface CachedIpcState { - ipcAddress: string; - jsDebugPath: string | undefined; - settingsValue: string; + ipcAddress: string; + jsDebugPath: string | undefined; + settingsValue: string; } - /** * Map of logic that happens when auto attach states are entered and exited. * All state transitions are queued and run in order; promises are awaited. */ -const transitions: { [S in State]: (context: vscode.ExtensionContext) => Promise } = { - async [State.Disabled](context) { - await clearJsDebugAttachState(context); - }, - - async [State.OnlyWithFlag](context) { - await createAttachServer(context); - }, - - async [State.Smart](context) { - await createAttachServer(context); - }, - - async [State.Always](context) { - await createAttachServer(context); - }, +const transitions: { + [S in State]: (context: vscode.ExtensionContext) => Promise; +} = { + async [State.Disabled](context) { + await clearJsDebugAttachState(context); + }, + async [State.OnlyWithFlag](context) { + await createAttachServer(context); + }, + async [State.Smart](context) { + await createAttachServer(context); + }, + async [State.Always](context) { + await createAttachServer(context); + }, }; - /** * Ensures the status bar text reflects the current state. */ function updateStatusBar(context: vscode.ExtensionContext, state: State, busy = false) { - if (state === State.Disabled && !busy) { - statusItem?.hide(); - return; - } - - if (!statusItem) { - statusItem = vscode.window.createStatusBarItem('status.debug.autoAttach', vscode.StatusBarAlignment.Left); - statusItem.name = vscode.l10n.t("Debug Auto Attach"); - statusItem.command = TOGGLE_COMMAND; - statusItem.tooltip = vscode.l10n.t("Automatically attach to node.js processes in debug mode"); - context.subscriptions.push(statusItem); - } - - let text = busy ? '$(loading) ' : ''; - text += isTemporarilyDisabled ? TEXT_TEMP_DISABLE_LABEL : TEXT_STATUSBAR_LABEL[state]; - statusItem.text = text; - statusItem.show(); + if (state === State.Disabled && !busy) { + statusItem?.hide(); + return; + } + if (!statusItem) { + statusItem = vscode.window.createStatusBarItem('status.debug.autoAttach', vscode.StatusBarAlignment.Left); + statusItem.name = vscode.l10n.t("Debug Auto Attach"); + statusItem.command = TOGGLE_COMMAND; + statusItem.tooltip = vscode.l10n.t("Automatically attach to node.js processes in debug mode"); + context.subscriptions.push(statusItem); + } + let text = busy ? '$(loading) ' : ''; + text += isTemporarilyDisabled ? TEXT_TEMP_DISABLE_LABEL : TEXT_STATUSBAR_LABEL[state]; + statusItem.text = text; + statusItem.show(); } - /** * Updates the auto attach feature based on the user or workspace setting */ function updateAutoAttach(newState: State) { - currentState = currentState.then(async ({ context, state: oldState }) => { - if (newState === oldState) { - return { context, state: oldState }; - } - - if (oldState !== null) { - updateStatusBar(context, oldState, true); - } - - await transitions[newState](context); - isTemporarilyDisabled = false; - updateStatusBar(context, newState, false); - return { context, state: newState }; - }); + currentState = currentState.then(async ({ context, state: oldState }) => { + if (newState === oldState) { + return { context, state: oldState }; + } + if (oldState !== null) { + updateStatusBar(context, oldState, true); + } + await transitions[newState](context); + isTemporarilyDisabled = false; + updateStatusBar(context, newState, false); + return { context, state: newState }; + }); } - /** * Gets the IPC address for the server to listen on for js-debug sessions. This * is cached such that we can reuse the address of previous activations. */ async function getIpcAddress(context: vscode.ExtensionContext) { - // Iff the `cachedData` is present, the js-debug registered environment - // variables for this workspace--cachedData is set after successfully - // invoking the attachment command. - const cachedIpc = context.workspaceState.get(STORAGE_IPC); - - // We invalidate the IPC data if the js-debug path changes, since that - // indicates the extension was updated or reinstalled and the - // environment variables will have been lost. - // todo: make a way in the API to read environment data directly without activating js-debug? - const jsDebugPath = - vscode.extensions.getExtension('ms-vscode.js-debug-nightly')?.extensionPath || - vscode.extensions.getExtension('ms-vscode.js-debug')?.extensionPath; - - const settingsValue = getJsDebugSettingKey(); - if (cachedIpc?.jsDebugPath === jsDebugPath && cachedIpc?.settingsValue === settingsValue) { - return cachedIpc.ipcAddress; - } - - const result = await vscode.commands.executeCommand<{ ipcAddress: string }>( - 'extension.js-debug.setAutoAttachVariables', - cachedIpc?.ipcAddress, - ); - if (!result) { - return; - } - - const ipcAddress = result.ipcAddress; - await context.workspaceState.update(STORAGE_IPC, { - ipcAddress, - jsDebugPath, - settingsValue, - } satisfies CachedIpcState); - - return ipcAddress; + // Iff the `cachedData` is present, the js-debug registered environment + // variables for this workspace--cachedData is set after successfully + // invoking the attachment command. + const cachedIpc = context.workspaceState.get(STORAGE_IPC); + // We invalidate the IPC data if the js-debug path changes, since that + // indicates the extension was updated or reinstalled and the + // environment variables will have been lost. + // todo: make a way in the API to read environment data directly without activating js-debug? + const jsDebugPath = vscode.extensions.getExtension('ms-vscode.js-debug-nightly')?.extensionPath || + vscode.extensions.getExtension('ms-vscode.js-debug')?.extensionPath; + const settingsValue = getJsDebugSettingKey(); + if (cachedIpc?.jsDebugPath === jsDebugPath && cachedIpc?.settingsValue === settingsValue) { + return cachedIpc.ipcAddress; + } + const result = await vscode.commands.executeCommand<{ + ipcAddress: string; + }>('extension.js-debug.setAutoAttachVariables', cachedIpc?.ipcAddress); + if (!result) { + return; + } + const ipcAddress = result.ipcAddress; + await context.workspaceState.update(STORAGE_IPC, { + ipcAddress, + jsDebugPath, + settingsValue, + } satisfies CachedIpcState); + return ipcAddress; } - function getJsDebugSettingKey() { - const o: { [key: string]: unknown } = {}; - const config = vscode.workspace.getConfiguration(SETTING_SECTION); - for (const setting of SETTINGS_CAUSE_REFRESH) { - o[setting] = config.get(setting); - } - - return JSON.stringify(o); + const o: { + [key: string]: unknown; + } = {}; + const config = vscode.workspace.getConfiguration(SETTING_SECTION); + for (const setting of SETTINGS_CAUSE_REFRESH) { + o[setting] = config.get(setting); + } + return JSON.stringify(o); } diff --git a/extensions/debug-server-ready/Source/extension.ts b/extensions/debug-server-ready/Source/extension.ts index 22a8ff836d34c..4697248bcc556 100644 --- a/extensions/debug-server-ready/Source/extension.ts +++ b/extensions/debug-server-ready/Source/extension.ts @@ -2,385 +2,331 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import * as util from 'util'; import { randomUUID } from 'crypto'; - const PATTERN = 'listening on.* (https?://\\S+|[0-9]+)'; // matches "listening on port 3000" or "Now listening on: https://localhost:5001" const URI_PORT_FORMAT = 'http://localhost:%s'; const URI_FORMAT = '%s'; const WEB_ROOT = '${workspaceFolder}'; - interface ServerReadyAction { - pattern: string; - action?: 'openExternally' | 'debugWithChrome' | 'debugWithEdge' | 'startDebugging'; - uriFormat?: string; - webRoot?: string; - name?: string; - config?: vscode.DebugConfiguration; - killOnServerStop?: boolean; + pattern: string; + action?: 'openExternally' | 'debugWithChrome' | 'debugWithEdge' | 'startDebugging'; + uriFormat?: string; + webRoot?: string; + name?: string; + config?: vscode.DebugConfiguration; + killOnServerStop?: boolean; } - // From src/vs/base/common/strings.ts const CSI_SEQUENCE = /(?:(?:\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~])|(:?\x1b\].*?\x07)/g; - /** * Froms vs/base/common/strings.ts in core * @see https://github.com/microsoft/vscode/blob/22a2a0e833175c32a2005b977d7fbd355582e416/src/vs/base/common/strings.ts#L736 */ function removeAnsiEscapeCodes(str: string): string { - if (str) { - str = str.replace(CSI_SEQUENCE, ''); - } - - return str; + if (str) { + str = str.replace(CSI_SEQUENCE, ''); + } + return str; } - class Trigger { - private _fired = false; - - public get hasFired() { - return this._fired; - } - - public fire() { - this._fired = true; - } + private _fired = false; + public get hasFired() { + return this._fired; + } + public fire() { + this._fired = true; + } } - class ServerReadyDetector extends vscode.Disposable { - - private static detectors = new Map(); - private static terminalDataListener: vscode.Disposable | undefined; - - private readonly stoppedEmitter = new vscode.EventEmitter(); - private readonly onDidSessionStop = this.stoppedEmitter.event; - private readonly disposables = new Set([]); - private trigger: Trigger; - private shellPid?: number; - private regexp: RegExp; - - static start(session: vscode.DebugSession): ServerReadyDetector | undefined { - if (session.configuration.serverReadyAction) { - let detector = ServerReadyDetector.detectors.get(session); - if (!detector) { - detector = new ServerReadyDetector(session); - ServerReadyDetector.detectors.set(session, detector); - } - return detector; - } - return undefined; - } - - static stop(session: vscode.DebugSession): void { - const detector = ServerReadyDetector.detectors.get(session); - if (detector) { - ServerReadyDetector.detectors.delete(session); - detector.sessionStopped(); - detector.dispose(); - } - } - - static rememberShellPid(session: vscode.DebugSession, pid: number) { - const detector = ServerReadyDetector.detectors.get(session); - if (detector) { - detector.shellPid = pid; - } - } - - static async startListeningTerminalData() { - if (!this.terminalDataListener) { - this.terminalDataListener = vscode.window.onDidWriteTerminalData(async e => { - - // first find the detector with a matching pid - const pid = await e.terminal.processId; - const str = removeAnsiEscapeCodes(e.data); - for (const [, detector] of this.detectors) { - if (detector.shellPid === pid) { - detector.detectPattern(str); - return; - } - } - - // if none found, try all detectors until one matches - for (const [, detector] of this.detectors) { - if (detector.detectPattern(str)) { - return; - } - } - }); - } - } - - private constructor(private session: vscode.DebugSession) { - super(() => this.internalDispose()); - - // Re-used the triggered of the parent session, if one exists - if (session.parentSession) { - this.trigger = ServerReadyDetector.start(session.parentSession)?.trigger ?? new Trigger(); - } else { - this.trigger = new Trigger(); - } - - this.regexp = new RegExp(session.configuration.serverReadyAction.pattern || PATTERN, 'i'); - } - - private internalDispose() { - this.disposables.forEach(d => d.dispose()); - this.disposables.clear(); - } - - public sessionStopped() { - this.stoppedEmitter.fire(); - } - - detectPattern(s: string): boolean { - if (!this.trigger.hasFired) { - const matches = this.regexp.exec(s); - if (matches && matches.length >= 1) { - this.openExternalWithString(this.session, matches.length > 1 ? matches[1] : ''); - this.trigger.fire(); - return true; - } - } - return false; - } - - private openExternalWithString(session: vscode.DebugSession, captureString: string) { - const args: ServerReadyAction = session.configuration.serverReadyAction; - - let uri; - if (captureString === '') { - // nothing captured by reg exp -> use the uriFormat as the target uri without substitution - // verify that format does not contain '%s' - const format = args.uriFormat || ''; - if (format.indexOf('%s') >= 0) { - const errMsg = vscode.l10n.t("Format uri ('{0}') uses a substitution placeholder but pattern did not capture anything.", format); - vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined); - return; - } - uri = format; - } else { - // if no uriFormat is specified guess the appropriate format based on the captureString - const format = args.uriFormat || (/^[0-9]+$/.test(captureString) ? URI_PORT_FORMAT : URI_FORMAT); - // verify that format only contains a single '%s' - const s = format.split('%s'); - if (s.length !== 2) { - const errMsg = vscode.l10n.t("Format uri ('{0}') must contain exactly one substitution placeholder.", format); - vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined); - return; - } - uri = util.format(format, captureString); - } - - this.openExternalWithUri(session, uri); - } - - private async openExternalWithUri(session: vscode.DebugSession, uri: string) { - - const args: ServerReadyAction = session.configuration.serverReadyAction; - switch (args.action || 'openExternally') { - - case 'openExternally': - await vscode.env.openExternal(vscode.Uri.parse(uri)); - break; - - case 'debugWithChrome': - await this.debugWithBrowser('pwa-chrome', session, uri); - break; - - case 'debugWithEdge': - await this.debugWithBrowser('pwa-msedge', session, uri); - break; - - case 'startDebugging': - if (args.config) { - await this.startDebugSession(session, args.config.name, args.config); - } else { - await this.startDebugSession(session, args.name || 'unspecified'); - } - break; - - default: - // not supported - break; - } - } - - private async debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) { - const args = session.configuration.serverReadyAction as ServerReadyAction; - if (!args.killOnServerStop) { - await this.startBrowserDebugSession(type, session, uri); - return; - } - - const trackerId = randomUUID(); - const cts = new vscode.CancellationTokenSource(); - const newSessionPromise = this.catchStartedDebugSession(session => session.configuration._debugServerReadySessionId === trackerId, cts.token); - - if (!await this.startBrowserDebugSession(type, session, uri, trackerId)) { - cts.cancel(); - cts.dispose(); - return; - } - - const createdSession = await newSessionPromise; - cts.dispose(); - - if (!createdSession) { - return; - } - - const stopListener = this.onDidSessionStop(async () => { - stopListener.dispose(); - this.disposables.delete(stopListener); - await vscode.debug.stopDebugging(createdSession); - }); - this.disposables.add(stopListener); - } - - private startBrowserDebugSession(type: string, session: vscode.DebugSession, uri: string, trackerId?: string) { - return vscode.debug.startDebugging(session.workspaceFolder, { - type, - name: 'Browser Debug', - request: 'launch', - url: uri, - webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT, - _debugServerReadySessionId: trackerId, - }); - } - - /** - * Starts a debug session given a debug configuration name (saved in launch.json) or a debug configuration object. - * - * @param session The parent debugSession - * @param name The name of the configuration to launch. If config it set, it assumes it is the same as config.name. - * @param config [Optional] Instead of starting a debug session by debug configuration name, use a debug configuration object instead. - */ - private async startDebugSession(session: vscode.DebugSession, name: string, config?: vscode.DebugConfiguration) { - const args = session.configuration.serverReadyAction as ServerReadyAction; - if (!args.killOnServerStop) { - await vscode.debug.startDebugging(session.workspaceFolder, config ?? name); - return; - } - - const cts = new vscode.CancellationTokenSource(); - const newSessionPromise = this.catchStartedDebugSession(x => x.name === name, cts.token); - - if (!await vscode.debug.startDebugging(session.workspaceFolder, config ?? name)) { - cts.cancel(); - cts.dispose(); - return; - } - - const createdSession = await newSessionPromise; - cts.dispose(); - - if (!createdSession) { - return; - } - - const stopListener = this.onDidSessionStop(async () => { - stopListener.dispose(); - this.disposables.delete(stopListener); - await vscode.debug.stopDebugging(createdSession); - }); - this.disposables.add(stopListener); - } - - private catchStartedDebugSession(predicate: (session: vscode.DebugSession) => boolean, cancellationToken: vscode.CancellationToken): Promise { - return new Promise(_resolve => { - const done = (value?: vscode.DebugSession) => { - listener.dispose(); - cancellationListener.dispose(); - this.disposables.delete(listener); - this.disposables.delete(cancellationListener); - _resolve(value); - }; - - const cancellationListener = cancellationToken.onCancellationRequested(done); - const listener = vscode.debug.onDidStartDebugSession(session => { - if (predicate(session)) { - done(session); - } - }); - - // In case the debug session of interest was never caught anyhow. - this.disposables.add(listener); - this.disposables.add(cancellationListener); - }); - } + private static detectors = new Map(); + private static terminalDataListener: vscode.Disposable | undefined; + private readonly stoppedEmitter = new vscode.EventEmitter(); + private readonly onDidSessionStop = this.stoppedEmitter.event; + private readonly disposables = new Set([]); + private trigger: Trigger; + private shellPid?: number; + private regexp: RegExp; + static start(session: vscode.DebugSession): ServerReadyDetector | undefined { + if (session.configuration.serverReadyAction) { + let detector = ServerReadyDetector.detectors.get(session); + if (!detector) { + detector = new ServerReadyDetector(session); + ServerReadyDetector.detectors.set(session, detector); + } + return detector; + } + return undefined; + } + static stop(session: vscode.DebugSession): void { + const detector = ServerReadyDetector.detectors.get(session); + if (detector) { + ServerReadyDetector.detectors.delete(session); + detector.sessionStopped(); + detector.dispose(); + } + } + static rememberShellPid(session: vscode.DebugSession, pid: number) { + const detector = ServerReadyDetector.detectors.get(session); + if (detector) { + detector.shellPid = pid; + } + } + static async startListeningTerminalData() { + if (!this.terminalDataListener) { + this.terminalDataListener = vscode.window.onDidWriteTerminalData(async (e) => { + // first find the detector with a matching pid + const pid = await e.terminal.processId; + const str = removeAnsiEscapeCodes(e.data); + for (const [, detector] of this.detectors) { + if (detector.shellPid === pid) { + detector.detectPattern(str); + return; + } + } + // if none found, try all detectors until one matches + for (const [, detector] of this.detectors) { + if (detector.detectPattern(str)) { + return; + } + } + }); + } + } + private constructor(private session: vscode.DebugSession) { + super(() => this.internalDispose()); + // Re-used the triggered of the parent session, if one exists + if (session.parentSession) { + this.trigger = ServerReadyDetector.start(session.parentSession)?.trigger ?? new Trigger(); + } + else { + this.trigger = new Trigger(); + } + this.regexp = new RegExp(session.configuration.serverReadyAction.pattern || PATTERN, 'i'); + } + private internalDispose() { + this.disposables.forEach(d => d.dispose()); + this.disposables.clear(); + } + public sessionStopped() { + this.stoppedEmitter.fire(); + } + detectPattern(s: string): boolean { + if (!this.trigger.hasFired) { + const matches = this.regexp.exec(s); + if (matches && matches.length >= 1) { + this.openExternalWithString(this.session, matches.length > 1 ? matches[1] : ''); + this.trigger.fire(); + return true; + } + } + return false; + } + private openExternalWithString(session: vscode.DebugSession, captureString: string) { + const args: ServerReadyAction = session.configuration.serverReadyAction; + let uri; + if (captureString === '') { + // nothing captured by reg exp -> use the uriFormat as the target uri without substitution + // verify that format does not contain '%s' + const format = args.uriFormat || ''; + if (format.indexOf('%s') >= 0) { + const errMsg = vscode.l10n.t("Format uri ('{0}') uses a substitution placeholder but pattern did not capture anything.", format); + vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined); + return; + } + uri = format; + } + else { + // if no uriFormat is specified guess the appropriate format based on the captureString + const format = args.uriFormat || (/^[0-9]+$/.test(captureString) ? URI_PORT_FORMAT : URI_FORMAT); + // verify that format only contains a single '%s' + const s = format.split('%s'); + if (s.length !== 2) { + const errMsg = vscode.l10n.t("Format uri ('{0}') must contain exactly one substitution placeholder.", format); + vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined); + return; + } + uri = util.format(format, captureString); + } + this.openExternalWithUri(session, uri); + } + private async openExternalWithUri(session: vscode.DebugSession, uri: string) { + const args: ServerReadyAction = session.configuration.serverReadyAction; + switch (args.action || 'openExternally') { + case 'openExternally': + await vscode.env.openExternal(vscode.Uri.parse(uri)); + break; + case 'debugWithChrome': + await this.debugWithBrowser('pwa-chrome', session, uri); + break; + case 'debugWithEdge': + await this.debugWithBrowser('pwa-msedge', session, uri); + break; + case 'startDebugging': + if (args.config) { + await this.startDebugSession(session, args.config.name, args.config); + } + else { + await this.startDebugSession(session, args.name || 'unspecified'); + } + break; + default: + // not supported + break; + } + } + private async debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) { + const args = session.configuration.serverReadyAction as ServerReadyAction; + if (!args.killOnServerStop) { + await this.startBrowserDebugSession(type, session, uri); + return; + } + const trackerId = randomUUID(); + const cts = new vscode.CancellationTokenSource(); + const newSessionPromise = this.catchStartedDebugSession(session => session.configuration._debugServerReadySessionId === trackerId, cts.token); + if (!await this.startBrowserDebugSession(type, session, uri, trackerId)) { + cts.cancel(); + cts.dispose(); + return; + } + const createdSession = await newSessionPromise; + cts.dispose(); + if (!createdSession) { + return; + } + const stopListener = this.onDidSessionStop(async () => { + stopListener.dispose(); + this.disposables.delete(stopListener); + await vscode.debug.stopDebugging(createdSession); + }); + this.disposables.add(stopListener); + } + private startBrowserDebugSession(type: string, session: vscode.DebugSession, uri: string, trackerId?: string) { + return vscode.debug.startDebugging(session.workspaceFolder, { + type, + name: 'Browser Debug', + request: 'launch', + url: uri, + webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT, + _debugServerReadySessionId: trackerId, + }); + } + /** + * Starts a debug session given a debug configuration name (saved in launch.json) or a debug configuration object. + * + * @param session The parent debugSession + * @param name The name of the configuration to launch. If config it set, it assumes it is the same as config.name. + * @param config [Optional] Instead of starting a debug session by debug configuration name, use a debug configuration object instead. + */ + private async startDebugSession(session: vscode.DebugSession, name: string, config?: vscode.DebugConfiguration) { + const args = session.configuration.serverReadyAction as ServerReadyAction; + if (!args.killOnServerStop) { + await vscode.debug.startDebugging(session.workspaceFolder, config ?? name); + return; + } + const cts = new vscode.CancellationTokenSource(); + const newSessionPromise = this.catchStartedDebugSession(x => x.name === name, cts.token); + if (!await vscode.debug.startDebugging(session.workspaceFolder, config ?? name)) { + cts.cancel(); + cts.dispose(); + return; + } + const createdSession = await newSessionPromise; + cts.dispose(); + if (!createdSession) { + return; + } + const stopListener = this.onDidSessionStop(async () => { + stopListener.dispose(); + this.disposables.delete(stopListener); + await vscode.debug.stopDebugging(createdSession); + }); + this.disposables.add(stopListener); + } + private catchStartedDebugSession(predicate: (session: vscode.DebugSession) => boolean, cancellationToken: vscode.CancellationToken): Promise { + return new Promise(_resolve => { + const done = (value?: vscode.DebugSession) => { + listener.dispose(); + cancellationListener.dispose(); + this.disposables.delete(listener); + this.disposables.delete(cancellationListener); + _resolve(value); + }; + const cancellationListener = cancellationToken.onCancellationRequested(done); + const listener = vscode.debug.onDidStartDebugSession(session => { + if (predicate(session)) { + done(session); + } + }); + // In case the debug session of interest was never caught anyhow. + this.disposables.add(listener); + this.disposables.add(cancellationListener); + }); + } } - export function activate(context: vscode.ExtensionContext) { - - context.subscriptions.push(vscode.debug.onDidStartDebugSession(session => { - if (session.configuration.serverReadyAction) { - const detector = ServerReadyDetector.start(session); - if (detector) { - ServerReadyDetector.startListeningTerminalData(); - } - } - })); - - context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(session => { - ServerReadyDetector.stop(session); - })); - - const trackers = new Set(); - - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('*', { - resolveDebugConfigurationWithSubstitutedVariables(_folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration) { - if (debugConfiguration.type && debugConfiguration.serverReadyAction) { - if (!trackers.has(debugConfiguration.type)) { - trackers.add(debugConfiguration.type); - startTrackerForType(context, debugConfiguration.type); - } - } - return debugConfiguration; - } - })); + context.subscriptions.push(vscode.debug.onDidStartDebugSession(session => { + if (session.configuration.serverReadyAction) { + const detector = ServerReadyDetector.start(session); + if (detector) { + ServerReadyDetector.startListeningTerminalData(); + } + } + })); + context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(session => { + ServerReadyDetector.stop(session); + })); + const trackers = new Set(); + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('*', { + resolveDebugConfigurationWithSubstitutedVariables(_folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration) { + if (debugConfiguration.type && debugConfiguration.serverReadyAction) { + if (!trackers.has(debugConfiguration.type)) { + trackers.add(debugConfiguration.type); + startTrackerForType(context, debugConfiguration.type); + } + } + return debugConfiguration; + } + })); } - function startTrackerForType(context: vscode.ExtensionContext, type: string) { - - // scan debug console output for a PORT message - context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory(type, { - createDebugAdapterTracker(session: vscode.DebugSession) { - const detector = ServerReadyDetector.start(session); - if (detector) { - let runInTerminalRequestSeq: number | undefined; - return { - onDidSendMessage: m => { - if (m.type === 'event' && m.event === 'output' && m.body) { - switch (m.body.category) { - case 'console': - case 'stderr': - case 'stdout': - if (m.body.output) { - detector.detectPattern(m.body.output); - } - break; - default: - break; - } - } - if (m.type === 'request' && m.command === 'runInTerminal' && m.arguments) { - if (m.arguments.kind === 'integrated') { - runInTerminalRequestSeq = m.seq; // remember this to find matching response - } - } - }, - onWillReceiveMessage: m => { - if (runInTerminalRequestSeq && m.type === 'response' && m.command === 'runInTerminal' && m.body && runInTerminalRequestSeq === m.request_seq) { - runInTerminalRequestSeq = undefined; - ServerReadyDetector.rememberShellPid(session, m.body.shellProcessId); - } - } - }; - } - return undefined; - } - })); + // scan debug console output for a PORT message + context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory(type, { + createDebugAdapterTracker(session: vscode.DebugSession) { + const detector = ServerReadyDetector.start(session); + if (detector) { + let runInTerminalRequestSeq: number | undefined; + return { + onDidSendMessage: m => { + if (m.type === 'event' && m.event === 'output' && m.body) { + switch (m.body.category) { + case 'console': + case 'stderr': + case 'stdout': + if (m.body.output) { + detector.detectPattern(m.body.output); + } + break; + default: + break; + } + } + if (m.type === 'request' && m.command === 'runInTerminal' && m.arguments) { + if (m.arguments.kind === 'integrated') { + runInTerminalRequestSeq = m.seq; // remember this to find matching response + } + } + }, + onWillReceiveMessage: m => { + if (runInTerminalRequestSeq && m.type === 'response' && m.command === 'runInTerminal' && m.body && runInTerminalRequestSeq === m.request_seq) { + runInTerminalRequestSeq = undefined; + ServerReadyDetector.rememberShellPid(session, m.body.shellProcessId); + } + } + }; + } + return undefined; + } + })); } diff --git a/extensions/emmet/Source/abbreviationActions.ts b/extensions/emmet/Source/abbreviationActions.ts index 3326722905c6b..3dcb170492822 100644 --- a/extensions/emmet/Source/abbreviationActions.ts +++ b/extensions/emmet/Source/abbreviationActions.ts @@ -2,426 +2,371 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetFlatNode'; import { getEmmetHelper, getFlatNode, getHtmlFlatNode, getMappingForIncludedLanguages, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument, isOffsetInsideOpenOrCloseTag } from './util'; import { getRootNode as parseDocument } from './parseDocument'; - const trimRegex = /[\u00a0]*[\d#\-\*\u2022]+\.?/; const hexColorRegex = /^#[\da-fA-F]{0,6}$/; - interface ExpandAbbreviationInput { - syntax: string; - abbreviation: string; - rangeToReplace: vscode.Range; - textToWrap?: string[]; - filter?: string; - indent?: string; - baseIndent?: string; + syntax: string; + abbreviation: string; + rangeToReplace: vscode.Range; + textToWrap?: string[]; + filter?: string; + indent?: string; + baseIndent?: string; } - interface PreviewRangesWithContent { - previewRange: vscode.Range; - originalRange: vscode.Range; - originalContent: string; - textToWrapInPreview: string[]; - baseIndent: string; + previewRange: vscode.Range; + originalRange: vscode.Range; + originalContent: string; + textToWrapInPreview: string[]; + baseIndent: string; } - export async function wrapWithAbbreviation(args: any): Promise { - if (!validate(false)) { - return false; - } - - const editor = vscode.window.activeTextEditor!; - const document = editor.document; - - args = args || {}; - if (!args['language']) { - args['language'] = document.languageId; - } - // we know it's not stylesheet due to the validate(false) call above - const syntax = getSyntaxFromArgs(args) || 'html'; - const rootNode = parseDocument(document, true); - - const helper = getEmmetHelper(); - - const operationRanges = Array.from(editor.selections).sort((a, b) => a.start.compareTo(b.start)).map(selection => { - let rangeToReplace: vscode.Range = selection; - // wrap around the node if the selection falls inside its open or close tag - { - let { start, end } = rangeToReplace; - - const startOffset = document.offsetAt(start); - const documentText = document.getText(); - const startNode = getHtmlFlatNode(documentText, rootNode, startOffset, true); - if (startNode && isOffsetInsideOpenOrCloseTag(startNode, startOffset)) { - start = document.positionAt(startNode.start); - const nodeEndPosition = document.positionAt(startNode.end); - end = nodeEndPosition.isAfter(end) ? nodeEndPosition : end; - } - - const endOffset = document.offsetAt(end); - const endNode = getHtmlFlatNode(documentText, rootNode, endOffset, true); - if (endNode && isOffsetInsideOpenOrCloseTag(endNode, endOffset)) { - const nodeStartPosition = document.positionAt(endNode.start); - start = nodeStartPosition.isBefore(start) ? nodeStartPosition : start; - const nodeEndPosition = document.positionAt(endNode.end); - end = nodeEndPosition.isAfter(end) ? nodeEndPosition : end; - } - - rangeToReplace = new vscode.Range(start, end); - } - // in case of multi-line, exclude last empty line from rangeToReplace - if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) { - const previousLine = rangeToReplace.end.line - 1; - rangeToReplace = new vscode.Range(rangeToReplace.start, document.lineAt(previousLine).range.end); - } - // wrap line the cursor is on - if (rangeToReplace.isEmpty) { - rangeToReplace = document.lineAt(rangeToReplace.start).range; - } - - // ignore whitespace on the first line - const firstLineOfRange = document.lineAt(rangeToReplace.start); - if (!firstLineOfRange.isEmptyOrWhitespace && firstLineOfRange.firstNonWhitespaceCharacterIndex > rangeToReplace.start.character) { - rangeToReplace = rangeToReplace.with(new vscode.Position(rangeToReplace.start.line, firstLineOfRange.firstNonWhitespaceCharacterIndex)); - } - - return rangeToReplace; - }).reduce((mergedRanges, range) => { - // Merge overlapping ranges - if (mergedRanges.length > 0 && range.intersection(mergedRanges[mergedRanges.length - 1])) { - mergedRanges.push(range.union(mergedRanges.pop()!)); - } else { - mergedRanges.push(range); - } - return mergedRanges; - }, [] as vscode.Range[]); - - // Backup orginal selections and update selections - // Also helps with https://github.com/microsoft/vscode/issues/113930 by avoiding `editor.linkedEditing` - // execution if selection is inside an open or close tag - const oldSelections = editor.selections; - editor.selections = operationRanges.map(range => new vscode.Selection(range.start, range.end)); - - // Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents - const rangesToReplace: PreviewRangesWithContent[] = operationRanges.map(rangeToReplace => { - let textToWrapInPreview: string[]; - const textToReplace = document.getText(rangeToReplace); - - // the following assumes all the lines are indented the same way as the first - // this assumption helps with applyPreview later - const wholeFirstLine = document.lineAt(rangeToReplace.start).text; - const otherMatches = wholeFirstLine.match(/^(\s*)/); - const baseIndent = otherMatches ? otherMatches[1] : ''; - textToWrapInPreview = rangeToReplace.isSingleLine ? - [textToReplace] : - textToReplace.split('\n' + baseIndent).map(x => x.trimEnd()); - - // escape $ characters, fixes #52640 - textToWrapInPreview = textToWrapInPreview.map(e => e.replace(/(\$\d)/g, '\\$1')); - - return { - previewRange: rangeToReplace, - originalRange: rangeToReplace, - originalContent: textToReplace, - textToWrapInPreview, - baseIndent - }; - }); - - const { tabSize, insertSpaces } = editor.options; - const indent = insertSpaces ? ' '.repeat(tabSize as number) : '\t'; - - function revertPreview(): Thenable { - return editor.edit(builder => { - for (const rangeToReplace of rangesToReplace) { - builder.replace(rangeToReplace.previewRange, rangeToReplace.originalContent); - rangeToReplace.previewRange = rangeToReplace.originalRange; - } - }, { undoStopBefore: false, undoStopAfter: false }); - } - - function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable { - let lastOldPreviewRange = new vscode.Range(0, 0, 0, 0); - let lastNewPreviewRange = new vscode.Range(0, 0, 0, 0); - let totalNewLinesInserted = 0; - - return editor.edit(builder => { - // the edits are applied in order top-down - for (let i = 0; i < rangesToReplace.length; i++) { - const expandedText = expandAbbr(expandAbbrList[i]) || ''; - if (!expandedText) { - // Failed to expand text. We already showed an error inside expandAbbr. - break; - } - - // get the current preview range, format the new wrapped text, and then replace - // the text in the preview range with that new text - const oldPreviewRange = rangesToReplace[i].previewRange; - const newText = expandedText - .replace(/\$\{[\d]*\}/g, '|') // Removing Tabstops - .replace(/\$\{[\d]*:([^}]*)\}/g, (_, placeholder) => placeholder) // Replacing Placeholders - .replace(/\\\$/g, '$'); // Remove backslashes before $ - builder.replace(oldPreviewRange, newText); - - // calculate the new preview range to use for future previews - // we also have to take into account that the previous expansions could: - // - cause new lines to appear - // - be on the same line as other expansions - const expandedTextLines = newText.split('\n'); - const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1; - const newLinesInserted = expandedTextLines.length - oldPreviewLines; - - const newPreviewLineStart = oldPreviewRange.start.line + totalNewLinesInserted; - let newPreviewStart = oldPreviewRange.start.character; - const newPreviewLineEnd = oldPreviewRange.end.line + totalNewLinesInserted + newLinesInserted; - let newPreviewEnd = expandedTextLines[expandedTextLines.length - 1].length; - if (i > 0 && newPreviewLineEnd === lastNewPreviewRange.end.line) { - // If newPreviewLineEnd is equal to the previous expandedText lineEnd, - // set newPreviewStart to the length of the previous expandedText in that line - // plus the number of characters between both selections. - newPreviewStart = lastNewPreviewRange.end.character + (oldPreviewRange.start.character - lastOldPreviewRange.end.character); - newPreviewEnd += newPreviewStart; - } else if (i > 0 && newPreviewLineStart === lastNewPreviewRange.end.line) { - // Same as above but expandedTextLines.length > 1 so newPreviewEnd keeps its value. - newPreviewStart = lastNewPreviewRange.end.character + (oldPreviewRange.start.character - lastOldPreviewRange.end.character); - } else if (expandedTextLines.length === 1) { - // If the expandedText is single line, add the length of preceeding text as it will not be included in line length. - newPreviewEnd += oldPreviewRange.start.character; - } - - lastOldPreviewRange = rangesToReplace[i].previewRange; - lastNewPreviewRange = new vscode.Range(newPreviewLineStart, newPreviewStart, newPreviewLineEnd, newPreviewEnd); - rangesToReplace[i].previewRange = lastNewPreviewRange; - totalNewLinesInserted += newLinesInserted; - } - }, { undoStopBefore: false, undoStopAfter: false }); - } - - let inPreviewMode = false; - async function makeChanges(inputAbbreviation: string | undefined, previewChanges: boolean): Promise { - const isAbbreviationValid = !!inputAbbreviation && !!inputAbbreviation.trim() && helper.isAbbreviationValid(syntax, inputAbbreviation); - const extractedResults = isAbbreviationValid ? helper.extractAbbreviationFromText(inputAbbreviation!, syntax) : undefined; - if (!extractedResults) { - if (inPreviewMode) { - inPreviewMode = false; - await revertPreview(); - } - return false; - } - - const { abbreviation, filter } = extractedResults; - if (abbreviation !== inputAbbreviation) { - // Not clear what should we do in this case. Warn the user? How? - } - - if (previewChanges) { - const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => - ({ syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter, indent, baseIndent: rangesAndContent.baseIndent }) - ); - - inPreviewMode = true; - return applyPreview(expandAbbrList); - } - - const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => - ({ syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter, indent }) - ); - - if (inPreviewMode) { - inPreviewMode = false; - await revertPreview(); - } - - return expandAbbreviationInRange(editor, expandAbbrList, false); - } - - let currentValue = ''; - async function inputChanged(value: string): Promise { - if (value !== currentValue) { - currentValue = value; - await makeChanges(value, true); - } - return ''; - } - - const prompt = vscode.l10n.t("Enter Abbreviation"); - const inputAbbreviation = (args && args['abbreviation']) - ? (args['abbreviation'] as string) - : await vscode.window.showInputBox({ prompt, validateInput: inputChanged }); - - const changesWereMade = await makeChanges(inputAbbreviation, false); - if (!changesWereMade) { - editor.selections = oldSelections; - } - - return changesWereMade; + if (!validate(false)) { + return false; + } + const editor = vscode.window.activeTextEditor!; + const document = editor.document; + args = args || {}; + if (!args['language']) { + args['language'] = document.languageId; + } + // we know it's not stylesheet due to the validate(false) call above + const syntax = getSyntaxFromArgs(args) || 'html'; + const rootNode = parseDocument(document, true); + const helper = getEmmetHelper(); + const operationRanges = Array.from(editor.selections).sort((a, b) => a.start.compareTo(b.start)).map(selection => { + let rangeToReplace: vscode.Range = selection; + // wrap around the node if the selection falls inside its open or close tag + { + let { start, end } = rangeToReplace; + const startOffset = document.offsetAt(start); + const documentText = document.getText(); + const startNode = getHtmlFlatNode(documentText, rootNode, startOffset, true); + if (startNode && isOffsetInsideOpenOrCloseTag(startNode, startOffset)) { + start = document.positionAt(startNode.start); + const nodeEndPosition = document.positionAt(startNode.end); + end = nodeEndPosition.isAfter(end) ? nodeEndPosition : end; + } + const endOffset = document.offsetAt(end); + const endNode = getHtmlFlatNode(documentText, rootNode, endOffset, true); + if (endNode && isOffsetInsideOpenOrCloseTag(endNode, endOffset)) { + const nodeStartPosition = document.positionAt(endNode.start); + start = nodeStartPosition.isBefore(start) ? nodeStartPosition : start; + const nodeEndPosition = document.positionAt(endNode.end); + end = nodeEndPosition.isAfter(end) ? nodeEndPosition : end; + } + rangeToReplace = new vscode.Range(start, end); + } + // in case of multi-line, exclude last empty line from rangeToReplace + if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) { + const previousLine = rangeToReplace.end.line - 1; + rangeToReplace = new vscode.Range(rangeToReplace.start, document.lineAt(previousLine).range.end); + } + // wrap line the cursor is on + if (rangeToReplace.isEmpty) { + rangeToReplace = document.lineAt(rangeToReplace.start).range; + } + // ignore whitespace on the first line + const firstLineOfRange = document.lineAt(rangeToReplace.start); + if (!firstLineOfRange.isEmptyOrWhitespace && firstLineOfRange.firstNonWhitespaceCharacterIndex > rangeToReplace.start.character) { + rangeToReplace = rangeToReplace.with(new vscode.Position(rangeToReplace.start.line, firstLineOfRange.firstNonWhitespaceCharacterIndex)); + } + return rangeToReplace; + }).reduce((mergedRanges, range) => { + // Merge overlapping ranges + if (mergedRanges.length > 0 && range.intersection(mergedRanges[mergedRanges.length - 1])) { + mergedRanges.push(range.union(mergedRanges.pop()!)); + } + else { + mergedRanges.push(range); + } + return mergedRanges; + }, [] as vscode.Range[]); + // Backup orginal selections and update selections + // Also helps with https://github.com/microsoft/vscode/issues/113930 by avoiding `editor.linkedEditing` + // execution if selection is inside an open or close tag + const oldSelections = editor.selections; + editor.selections = operationRanges.map(range => new vscode.Selection(range.start, range.end)); + // Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents + const rangesToReplace: PreviewRangesWithContent[] = operationRanges.map(rangeToReplace => { + let textToWrapInPreview: string[]; + const textToReplace = document.getText(rangeToReplace); + // the following assumes all the lines are indented the same way as the first + // this assumption helps with applyPreview later + const wholeFirstLine = document.lineAt(rangeToReplace.start).text; + const otherMatches = wholeFirstLine.match(/^(\s*)/); + const baseIndent = otherMatches ? otherMatches[1] : ''; + textToWrapInPreview = rangeToReplace.isSingleLine ? + [textToReplace] : + textToReplace.split('\n' + baseIndent).map(x => x.trimEnd()); + // escape $ characters, fixes #52640 + textToWrapInPreview = textToWrapInPreview.map(e => e.replace(/(\$\d)/g, '\\$1')); + return { + previewRange: rangeToReplace, + originalRange: rangeToReplace, + originalContent: textToReplace, + textToWrapInPreview, + baseIndent + }; + }); + const { tabSize, insertSpaces } = editor.options; + const indent = insertSpaces ? ' '.repeat(tabSize as number) : '\t'; + function revertPreview(): Thenable { + return editor.edit(builder => { + for (const rangeToReplace of rangesToReplace) { + builder.replace(rangeToReplace.previewRange, rangeToReplace.originalContent); + rangeToReplace.previewRange = rangeToReplace.originalRange; + } + }, { undoStopBefore: false, undoStopAfter: false }); + } + function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable { + let lastOldPreviewRange = new vscode.Range(0, 0, 0, 0); + let lastNewPreviewRange = new vscode.Range(0, 0, 0, 0); + let totalNewLinesInserted = 0; + return editor.edit(builder => { + // the edits are applied in order top-down + for (let i = 0; i < rangesToReplace.length; i++) { + const expandedText = expandAbbr(expandAbbrList[i]) || ''; + if (!expandedText) { + // Failed to expand text. We already showed an error inside expandAbbr. + break; + } + // get the current preview range, format the new wrapped text, and then replace + // the text in the preview range with that new text + const oldPreviewRange = rangesToReplace[i].previewRange; + const newText = expandedText + .replace(/\$\{[\d]*\}/g, '|') // Removing Tabstops + .replace(/\$\{[\d]*:([^}]*)\}/g, (_, placeholder) => placeholder) // Replacing Placeholders + .replace(/\\\$/g, '$'); // Remove backslashes before $ + builder.replace(oldPreviewRange, newText); + // calculate the new preview range to use for future previews + // we also have to take into account that the previous expansions could: + // - cause new lines to appear + // - be on the same line as other expansions + const expandedTextLines = newText.split('\n'); + const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1; + const newLinesInserted = expandedTextLines.length - oldPreviewLines; + const newPreviewLineStart = oldPreviewRange.start.line + totalNewLinesInserted; + let newPreviewStart = oldPreviewRange.start.character; + const newPreviewLineEnd = oldPreviewRange.end.line + totalNewLinesInserted + newLinesInserted; + let newPreviewEnd = expandedTextLines[expandedTextLines.length - 1].length; + if (i > 0 && newPreviewLineEnd === lastNewPreviewRange.end.line) { + // If newPreviewLineEnd is equal to the previous expandedText lineEnd, + // set newPreviewStart to the length of the previous expandedText in that line + // plus the number of characters between both selections. + newPreviewStart = lastNewPreviewRange.end.character + (oldPreviewRange.start.character - lastOldPreviewRange.end.character); + newPreviewEnd += newPreviewStart; + } + else if (i > 0 && newPreviewLineStart === lastNewPreviewRange.end.line) { + // Same as above but expandedTextLines.length > 1 so newPreviewEnd keeps its value. + newPreviewStart = lastNewPreviewRange.end.character + (oldPreviewRange.start.character - lastOldPreviewRange.end.character); + } + else if (expandedTextLines.length === 1) { + // If the expandedText is single line, add the length of preceeding text as it will not be included in line length. + newPreviewEnd += oldPreviewRange.start.character; + } + lastOldPreviewRange = rangesToReplace[i].previewRange; + lastNewPreviewRange = new vscode.Range(newPreviewLineStart, newPreviewStart, newPreviewLineEnd, newPreviewEnd); + rangesToReplace[i].previewRange = lastNewPreviewRange; + totalNewLinesInserted += newLinesInserted; + } + }, { undoStopBefore: false, undoStopAfter: false }); + } + let inPreviewMode = false; + async function makeChanges(inputAbbreviation: string | undefined, previewChanges: boolean): Promise { + const isAbbreviationValid = !!inputAbbreviation && !!inputAbbreviation.trim() && helper.isAbbreviationValid(syntax, inputAbbreviation); + const extractedResults = isAbbreviationValid ? helper.extractAbbreviationFromText(inputAbbreviation!, syntax) : undefined; + if (!extractedResults) { + if (inPreviewMode) { + inPreviewMode = false; + await revertPreview(); + } + return false; + } + const { abbreviation, filter } = extractedResults; + if (abbreviation !== inputAbbreviation) { + // Not clear what should we do in this case. Warn the user? How? + } + if (previewChanges) { + const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => ({ syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter, indent, baseIndent: rangesAndContent.baseIndent })); + inPreviewMode = true; + return applyPreview(expandAbbrList); + } + const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => ({ syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter, indent })); + if (inPreviewMode) { + inPreviewMode = false; + await revertPreview(); + } + return expandAbbreviationInRange(editor, expandAbbrList, false); + } + let currentValue = ''; + async function inputChanged(value: string): Promise { + if (value !== currentValue) { + currentValue = value; + await makeChanges(value, true); + } + return ''; + } + const prompt = vscode.l10n.t("Enter Abbreviation"); + const inputAbbreviation = (args && args['abbreviation']) + ? (args['abbreviation'] as string) + : await vscode.window.showInputBox({ prompt, validateInput: inputChanged }); + const changesWereMade = await makeChanges(inputAbbreviation, false); + if (!changesWereMade) { + editor.selections = oldSelections; + } + return changesWereMade; } - export function expandEmmetAbbreviation(args: any): Thenable { - if (!validate() || !vscode.window.activeTextEditor) { - return fallbackTab(); - } - - /** - * Short circuit the parsing. If previous character is space, do not expand. - */ - if (vscode.window.activeTextEditor.selections.length === 1 && - vscode.window.activeTextEditor.selection.isEmpty - ) { - const anchor = vscode.window.activeTextEditor.selection.anchor; - if (anchor.character === 0) { - return fallbackTab(); - } - - const prevPositionAnchor = anchor.translate(0, -1); - const prevText = vscode.window.activeTextEditor.document.getText(new vscode.Range(prevPositionAnchor, anchor)); - if (prevText === ' ' || prevText === '\t') { - return fallbackTab(); - } - } - - args = args || {}; - if (!args['language']) { - args['language'] = vscode.window.activeTextEditor.document.languageId; - } else { - const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : []; - if (excludedLanguages.includes(vscode.window.activeTextEditor.document.languageId)) { - return fallbackTab(); - } - } - const syntax = getSyntaxFromArgs(args); - if (!syntax) { - return fallbackTab(); - } - - const editor = vscode.window.activeTextEditor; - - // When tabbed on a non empty selection, do not treat it as an emmet abbreviation, and fallback to tab instead - if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true && editor.selections.find(x => !x.isEmpty)) { - return fallbackTab(); - } - - const abbreviationList: ExpandAbbreviationInput[] = []; - let firstAbbreviation: string; - let allAbbreviationsSame: boolean = true; - const helper = getEmmetHelper(); - - const getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, syntax: string): [vscode.Range | null, string, string | undefined] => { - position = document.validatePosition(position); - let rangeToReplace: vscode.Range = selection; - let abbr = document.getText(rangeToReplace); - if (!rangeToReplace.isEmpty) { - const extractedResults = helper.extractAbbreviationFromText(abbr, syntax); - if (extractedResults) { - return [rangeToReplace, extractedResults.abbreviation, extractedResults.filter]; - } - return [null, '', '']; - } - - const currentLine = editor.document.lineAt(position.line).text; - const textTillPosition = currentLine.substr(0, position.character); - - // Expand cases like
explicitly - // else we will end up with <
- if (syntax === 'html') { - const matches = textTillPosition.match(/<(\w+)$/); - if (matches) { - abbr = matches[1]; - rangeToReplace = new vscode.Range(position.translate(0, -(abbr.length + 1)), position); - return [rangeToReplace, abbr, '']; - } - } - const extractedResults = helper.extractAbbreviation(toLSTextDocument(editor.document), position, { lookAhead: false }); - if (!extractedResults) { - return [null, '', '']; - } - - const { abbreviationRange, abbreviation, filter } = extractedResults; - return [new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character), abbreviation, filter]; - }; - - const selectionsInReverseOrder = editor.selections.slice(0); - selectionsInReverseOrder.sort((a, b) => { - const posA = a.isReversed ? a.anchor : a.active; - const posB = b.isReversed ? b.anchor : b.active; - return posA.compareTo(posB) * -1; - }); - - let rootNode: Node | undefined; - function getRootNode() { - if (rootNode) { - return rootNode; - } - - const usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true; - if (editor.selections.length === 1 && isStyleSheet(editor.document.languageId) && usePartialParsing && editor.document.lineCount > 1000) { - rootNode = parsePartialStylesheet(editor.document, editor.selection.isReversed ? editor.selection.anchor : editor.selection.active); - } else { - rootNode = parseDocument(editor.document, true); - } - - return rootNode; - } - - selectionsInReverseOrder.forEach(selection => { - const position = selection.isReversed ? selection.anchor : selection.active; - const [rangeToReplace, abbreviation, filter] = getAbbreviation(editor.document, selection, position, syntax); - if (!rangeToReplace) { - return; - } - if (!helper.isAbbreviationValid(syntax, abbreviation)) { - return; - } - if (isStyleSheet(syntax) && abbreviation.endsWith(':')) { - // Fix for https://github.com/Microsoft/vscode/issues/1623 - return; - } - - const offset = editor.document.offsetAt(position); - let currentNode = getFlatNode(getRootNode(), offset, true); - let validateLocation = true; - let syntaxToUse = syntax; - - if (editor.document.languageId === 'html') { - if (isStyleAttribute(currentNode, offset)) { - syntaxToUse = 'css'; - validateLocation = false; - } else { - const embeddedCssNode = getEmbeddedCssNodeIfAny(editor.document, currentNode, position); - if (embeddedCssNode) { - currentNode = getFlatNode(embeddedCssNode, offset, true); - syntaxToUse = 'css'; - } - } - } - - if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, offset, rangeToReplace)) { - return; - } - - if (!firstAbbreviation) { - firstAbbreviation = abbreviation; - } else if (allAbbreviationsSame && firstAbbreviation !== abbreviation) { - allAbbreviationsSame = false; - } - - abbreviationList.push({ syntax: syntaxToUse, abbreviation, rangeToReplace, filter }); - }); - - return expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame).then(success => { - return success ? Promise.resolve(undefined) : fallbackTab(); - }); + if (!validate() || !vscode.window.activeTextEditor) { + return fallbackTab(); + } + /** + * Short circuit the parsing. If previous character is space, do not expand. + */ + if (vscode.window.activeTextEditor.selections.length === 1 && + vscode.window.activeTextEditor.selection.isEmpty) { + const anchor = vscode.window.activeTextEditor.selection.anchor; + if (anchor.character === 0) { + return fallbackTab(); + } + const prevPositionAnchor = anchor.translate(0, -1); + const prevText = vscode.window.activeTextEditor.document.getText(new vscode.Range(prevPositionAnchor, anchor)); + if (prevText === ' ' || prevText === '\t') { + return fallbackTab(); + } + } + args = args || {}; + if (!args['language']) { + args['language'] = vscode.window.activeTextEditor.document.languageId; + } + else { + const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : []; + if (excludedLanguages.includes(vscode.window.activeTextEditor.document.languageId)) { + return fallbackTab(); + } + } + const syntax = getSyntaxFromArgs(args); + if (!syntax) { + return fallbackTab(); + } + const editor = vscode.window.activeTextEditor; + // When tabbed on a non empty selection, do not treat it as an emmet abbreviation, and fallback to tab instead + if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true && editor.selections.find(x => !x.isEmpty)) { + return fallbackTab(); + } + const abbreviationList: ExpandAbbreviationInput[] = []; + let firstAbbreviation: string; + let allAbbreviationsSame: boolean = true; + const helper = getEmmetHelper(); + const getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, syntax: string): [ + vscode.Range | null, + string, + string | undefined + ] => { + position = document.validatePosition(position); + let rangeToReplace: vscode.Range = selection; + let abbr = document.getText(rangeToReplace); + if (!rangeToReplace.isEmpty) { + const extractedResults = helper.extractAbbreviationFromText(abbr, syntax); + if (extractedResults) { + return [rangeToReplace, extractedResults.abbreviation, extractedResults.filter]; + } + return [null, '', '']; + } + const currentLine = editor.document.lineAt(position.line).text; + const textTillPosition = currentLine.substr(0, position.character); + // Expand cases like
explicitly + // else we will end up with <
+ if (syntax === 'html') { + const matches = textTillPosition.match(/<(\w+)$/); + if (matches) { + abbr = matches[1]; + rangeToReplace = new vscode.Range(position.translate(0, -(abbr.length + 1)), position); + return [rangeToReplace, abbr, '']; + } + } + const extractedResults = helper.extractAbbreviation(toLSTextDocument(editor.document), position, { lookAhead: false }); + if (!extractedResults) { + return [null, '', '']; + } + const { abbreviationRange, abbreviation, filter } = extractedResults; + return [new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character), abbreviation, filter]; + }; + const selectionsInReverseOrder = editor.selections.slice(0); + selectionsInReverseOrder.sort((a, b) => { + const posA = a.isReversed ? a.anchor : a.active; + const posB = b.isReversed ? b.anchor : b.active; + return posA.compareTo(posB) * -1; + }); + let rootNode: Node | undefined; + function getRootNode() { + if (rootNode) { + return rootNode; + } + const usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true; + if (editor.selections.length === 1 && isStyleSheet(editor.document.languageId) && usePartialParsing && editor.document.lineCount > 1000) { + rootNode = parsePartialStylesheet(editor.document, editor.selection.isReversed ? editor.selection.anchor : editor.selection.active); + } + else { + rootNode = parseDocument(editor.document, true); + } + return rootNode; + } + selectionsInReverseOrder.forEach(selection => { + const position = selection.isReversed ? selection.anchor : selection.active; + const [rangeToReplace, abbreviation, filter] = getAbbreviation(editor.document, selection, position, syntax); + if (!rangeToReplace) { + return; + } + if (!helper.isAbbreviationValid(syntax, abbreviation)) { + return; + } + if (isStyleSheet(syntax) && abbreviation.endsWith(':')) { + // Fix for https://github.com/Microsoft/vscode/issues/1623 + return; + } + const offset = editor.document.offsetAt(position); + let currentNode = getFlatNode(getRootNode(), offset, true); + let validateLocation = true; + let syntaxToUse = syntax; + if (editor.document.languageId === 'html') { + if (isStyleAttribute(currentNode, offset)) { + syntaxToUse = 'css'; + validateLocation = false; + } + else { + const embeddedCssNode = getEmbeddedCssNodeIfAny(editor.document, currentNode, position); + if (embeddedCssNode) { + currentNode = getFlatNode(embeddedCssNode, offset, true); + syntaxToUse = 'css'; + } + } + } + if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, offset, rangeToReplace)) { + return; + } + if (!firstAbbreviation) { + firstAbbreviation = abbreviation; + } + else if (allAbbreviationsSame && firstAbbreviation !== abbreviation) { + allAbbreviationsSame = false; + } + abbreviationList.push({ syntax: syntaxToUse, abbreviation, rangeToReplace, filter }); + }); + return expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame).then(success => { + return success ? Promise.resolve(undefined) : fallbackTab(); + }); } - function fallbackTab(): Thenable { - if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true) { - return vscode.commands.executeCommand('tab'); - } - return Promise.resolve(true); + if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true) { + return vscode.commands.executeCommand('tab'); + } + return Promise.resolve(true); } /** * Checks if given position is a valid location to expand emmet abbreviation. @@ -434,284 +379,256 @@ function fallbackTab(): Thenable { * @param abbreviationRange The range of the abbreviation for which given position is being validated */ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | undefined, syntax: string, offset: number, abbreviationRange: vscode.Range): boolean { - if (isStyleSheet(syntax)) { - const stylesheet = rootNode; - if (stylesheet && (stylesheet.comments || []).some(x => offset >= x.start && offset <= x.end)) { - return false; - } - // Continue validation only if the file was parse-able and the currentNode has been found - if (!currentNode) { - return true; - } - - // Get the abbreviation right now - // Fixes https://github.com/microsoft/vscode/issues/74505 - // Stylesheet abbreviations starting with @ should bring up suggestions - // even at outer-most level - const abbreviation = document.getText(new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character)); - if (abbreviation.startsWith('@')) { - return true; - } - - // Fix for https://github.com/microsoft/vscode/issues/34162 - // Other than sass, stylus, we can make use of the terminator tokens to validate position - if (syntax !== 'sass' && syntax !== 'stylus' && currentNode.type === 'property') { - // Fix for upstream issue https://github.com/emmetio/css-parser/issues/3 - if (currentNode.parent - && currentNode.parent.type !== 'rule' - && currentNode.parent.type !== 'at-rule') { - return false; - } - - const propertyNode = currentNode; - if (propertyNode.terminatorToken - && propertyNode.separator - && offset >= propertyNode.separatorToken.end - && offset <= propertyNode.terminatorToken.start - && !abbreviation.includes(':')) { - return hexColorRegex.test(abbreviation) || abbreviation === '!'; - } - if (!propertyNode.terminatorToken - && propertyNode.separator - && offset >= propertyNode.separatorToken.end - && !abbreviation.includes(':')) { - return hexColorRegex.test(abbreviation) || abbreviation === '!'; - } - if (hexColorRegex.test(abbreviation) || abbreviation === '!') { - return false; - } - } - - // If current node is a rule or at-rule, then perform additional checks to ensure - // emmet suggestions are not provided in the rule selector - if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') { - return true; - } - - const currentCssNode = currentNode; - - // Position is valid if it occurs after the `{` that marks beginning of rule contents - if (offset > currentCssNode.contentStartToken.end) { - return true; - } - - // Workaround for https://github.com/microsoft/vscode/30188 - // The line above the rule selector is considered as part of the selector by the css-parser - // But we should assume it is a valid location for css properties under the parent rule - if (currentCssNode.parent - && (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule') - && currentCssNode.selectorToken) { - const position = document.positionAt(offset); - const tokenStartPos = document.positionAt(currentCssNode.selectorToken.start); - const tokenEndPos = document.positionAt(currentCssNode.selectorToken.end); - if (position.line !== tokenEndPos.line - && tokenStartPos.character === abbreviationRange.start.character - && tokenStartPos.line === abbreviationRange.start.line - ) { - return true; - } - } - - return false; - } - - const startAngle = '<'; - const endAngle = '>'; - const escape = '\\'; - const question = '?'; - const currentHtmlNode = currentNode; - let start = 0; - - if (currentHtmlNode) { - if (currentHtmlNode.name === 'script') { - const typeAttribute = (currentHtmlNode.attributes || []).filter(x => x.name.toString() === 'type')[0]; - const typeValue = typeAttribute ? typeAttribute.value.toString() : ''; - - if (allowedMimeTypesInScriptTag.includes(typeValue)) { - return true; - } - - const isScriptJavascriptType = !typeValue || typeValue === 'application/javascript' || typeValue === 'text/javascript'; - if (isScriptJavascriptType) { - return !!getSyntaxFromArgs({ language: 'javascript' }); - } - return false; - } - - // Fix for https://github.com/microsoft/vscode/issues/28829 - if (!currentHtmlNode.open || !currentHtmlNode.close || - !(currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start)) { - return false; - } - - // Fix for https://github.com/microsoft/vscode/issues/35128 - // Find the position up till where we will backtrack looking for unescaped < or > - // to decide if current position is valid for emmet expansion - start = currentHtmlNode.open.end; - let lastChildBeforePosition = currentHtmlNode.firstChild; - while (lastChildBeforePosition) { - if (lastChildBeforePosition.end > offset) { - break; - } - start = lastChildBeforePosition.end; - lastChildBeforePosition = lastChildBeforePosition.nextSibling; - } - } - const startPos = document.positionAt(start); - let textToBackTrack = document.getText(new vscode.Range(startPos.line, startPos.character, abbreviationRange.start.line, abbreviationRange.start.character)); - - // Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked - // Backtrack only 500 offsets to ensure we dont waste time doing this - if (textToBackTrack.length > 500) { - textToBackTrack = textToBackTrack.substr(textToBackTrack.length - 500); - } - - if (!textToBackTrack.trim()) { - return true; - } - - let valid = true; - let foundSpace = false; // If < is found before finding whitespace, then its valid abbreviation. E.g.: = 0) { - const char = textToBackTrack[i]; - i--; - if (!foundSpace && /\s/.test(char)) { - foundSpace = true; - continue; - } - if (char === question && textToBackTrack[i] === startAngle) { - i--; - continue; - } - // Fix for https://github.com/microsoft/vscode/issues/55411 - // A space is not a valid character right after < in a tag name. - if (/\s/.test(char) && textToBackTrack[i] === startAngle) { - i--; - continue; - } - if (char !== startAngle && char !== endAngle) { - continue; - } - if (i >= 0 && textToBackTrack[i] === escape) { - i--; - continue; - } - if (char === endAngle) { - if (i >= 0 && textToBackTrack[i] === '=') { - continue; // False alarm of cases like => - } else { - break; - } - } - if (char === startAngle) { - valid = !foundSpace; - break; - } - } - - return valid; + if (isStyleSheet(syntax)) { + const stylesheet = rootNode; + if (stylesheet && (stylesheet.comments || []).some(x => offset >= x.start && offset <= x.end)) { + return false; + } + // Continue validation only if the file was parse-able and the currentNode has been found + if (!currentNode) { + return true; + } + // Get the abbreviation right now + // Fixes https://github.com/microsoft/vscode/issues/74505 + // Stylesheet abbreviations starting with @ should bring up suggestions + // even at outer-most level + const abbreviation = document.getText(new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character)); + if (abbreviation.startsWith('@')) { + return true; + } + // Fix for https://github.com/microsoft/vscode/issues/34162 + // Other than sass, stylus, we can make use of the terminator tokens to validate position + if (syntax !== 'sass' && syntax !== 'stylus' && currentNode.type === 'property') { + // Fix for upstream issue https://github.com/emmetio/css-parser/issues/3 + if (currentNode.parent + && currentNode.parent.type !== 'rule' + && currentNode.parent.type !== 'at-rule') { + return false; + } + const propertyNode = currentNode; + if (propertyNode.terminatorToken + && propertyNode.separator + && offset >= propertyNode.separatorToken.end + && offset <= propertyNode.terminatorToken.start + && !abbreviation.includes(':')) { + return hexColorRegex.test(abbreviation) || abbreviation === '!'; + } + if (!propertyNode.terminatorToken + && propertyNode.separator + && offset >= propertyNode.separatorToken.end + && !abbreviation.includes(':')) { + return hexColorRegex.test(abbreviation) || abbreviation === '!'; + } + if (hexColorRegex.test(abbreviation) || abbreviation === '!') { + return false; + } + } + // If current node is a rule or at-rule, then perform additional checks to ensure + // emmet suggestions are not provided in the rule selector + if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') { + return true; + } + const currentCssNode = currentNode; + // Position is valid if it occurs after the `{` that marks beginning of rule contents + if (offset > currentCssNode.contentStartToken.end) { + return true; + } + // Workaround for https://github.com/microsoft/vscode/30188 + // The line above the rule selector is considered as part of the selector by the css-parser + // But we should assume it is a valid location for css properties under the parent rule + if (currentCssNode.parent + && (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule') + && currentCssNode.selectorToken) { + const position = document.positionAt(offset); + const tokenStartPos = document.positionAt(currentCssNode.selectorToken.start); + const tokenEndPos = document.positionAt(currentCssNode.selectorToken.end); + if (position.line !== tokenEndPos.line + && tokenStartPos.character === abbreviationRange.start.character + && tokenStartPos.line === abbreviationRange.start.line) { + return true; + } + } + return false; + } + const startAngle = '<'; + const endAngle = '>'; + const escape = '\\'; + const question = '?'; + const currentHtmlNode = currentNode; + let start = 0; + if (currentHtmlNode) { + if (currentHtmlNode.name === 'script') { + const typeAttribute = (currentHtmlNode.attributes || []).filter(x => x.name.toString() === 'type')[0]; + const typeValue = typeAttribute ? typeAttribute.value.toString() : ''; + if (allowedMimeTypesInScriptTag.includes(typeValue)) { + return true; + } + const isScriptJavascriptType = !typeValue || typeValue === 'application/javascript' || typeValue === 'text/javascript'; + if (isScriptJavascriptType) { + return !!getSyntaxFromArgs({ language: 'javascript' }); + } + return false; + } + // Fix for https://github.com/microsoft/vscode/issues/28829 + if (!currentHtmlNode.open || !currentHtmlNode.close || + !(currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start)) { + return false; + } + // Fix for https://github.com/microsoft/vscode/issues/35128 + // Find the position up till where we will backtrack looking for unescaped < or > + // to decide if current position is valid for emmet expansion + start = currentHtmlNode.open.end; + let lastChildBeforePosition = currentHtmlNode.firstChild; + while (lastChildBeforePosition) { + if (lastChildBeforePosition.end > offset) { + break; + } + start = lastChildBeforePosition.end; + lastChildBeforePosition = lastChildBeforePosition.nextSibling; + } + } + const startPos = document.positionAt(start); + let textToBackTrack = document.getText(new vscode.Range(startPos.line, startPos.character, abbreviationRange.start.line, abbreviationRange.start.character)); + // Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked + // Backtrack only 500 offsets to ensure we dont waste time doing this + if (textToBackTrack.length > 500) { + textToBackTrack = textToBackTrack.substr(textToBackTrack.length - 500); + } + if (!textToBackTrack.trim()) { + return true; + } + let valid = true; + let foundSpace = false; // If < is found before finding whitespace, then its valid abbreviation. E.g.: = 0) { + const char = textToBackTrack[i]; + i--; + if (!foundSpace && /\s/.test(char)) { + foundSpace = true; + continue; + } + if (char === question && textToBackTrack[i] === startAngle) { + i--; + continue; + } + // Fix for https://github.com/microsoft/vscode/issues/55411 + // A space is not a valid character right after < in a tag name. + if (/\s/.test(char) && textToBackTrack[i] === startAngle) { + i--; + continue; + } + if (char !== startAngle && char !== endAngle) { + continue; + } + if (i >= 0 && textToBackTrack[i] === escape) { + i--; + continue; + } + if (char === endAngle) { + if (i >= 0 && textToBackTrack[i] === '=') { + continue; // False alarm of cases like => + } + else { + break; + } + } + if (char === startAngle) { + valid = !foundSpace; + break; + } + } + return valid; } - /** * Expands abbreviations as detailed in expandAbbrList in the editor * * @returns false if no snippet can be inserted. */ async function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], insertSameSnippet: boolean): Promise { - if (!expandAbbrList || expandAbbrList.length === 0) { - return false; - } - - // Snippet to replace at multiple cursors are not the same - // `editor.insertSnippet` will have to be called for each instance separately - // We will not be able to maintain multiple cursors after snippet insertion - let insertedSnippetsCount = 0; - if (!insertSameSnippet) { - expandAbbrList.sort((a: ExpandAbbreviationInput, b: ExpandAbbreviationInput) => { return b.rangeToReplace.start.compareTo(a.rangeToReplace.start); }); - for (const expandAbbrInput of expandAbbrList) { - const expandedText = expandAbbr(expandAbbrInput); - if (expandedText) { - await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }); - insertedSnippetsCount++; - } - } - return insertedSnippetsCount > 0; - } - - // Snippet to replace at all cursors are the same - // We can pass all ranges to `editor.insertSnippet` in a single call so that - // all cursors are maintained after snippet insertion - const anyExpandAbbrInput = expandAbbrList[0]; - const expandedText = expandAbbr(anyExpandAbbrInput); - const allRanges = expandAbbrList.map(value => value.rangeToReplace); - if (expandedText) { - return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges); - } - return false; + if (!expandAbbrList || expandAbbrList.length === 0) { + return false; + } + // Snippet to replace at multiple cursors are not the same + // `editor.insertSnippet` will have to be called for each instance separately + // We will not be able to maintain multiple cursors after snippet insertion + let insertedSnippetsCount = 0; + if (!insertSameSnippet) { + expandAbbrList.sort((a: ExpandAbbreviationInput, b: ExpandAbbreviationInput) => { return b.rangeToReplace.start.compareTo(a.rangeToReplace.start); }); + for (const expandAbbrInput of expandAbbrList) { + const expandedText = expandAbbr(expandAbbrInput); + if (expandedText) { + await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }); + insertedSnippetsCount++; + } + } + return insertedSnippetsCount > 0; + } + // Snippet to replace at all cursors are the same + // We can pass all ranges to `editor.insertSnippet` in a single call so that + // all cursors are maintained after snippet insertion + const anyExpandAbbrInput = expandAbbrList[0]; + const expandedText = expandAbbr(anyExpandAbbrInput); + const allRanges = expandAbbrList.map(value => value.rangeToReplace); + if (expandedText) { + return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges); + } + return false; } - /** * Expands abbreviation as detailed in given input. */ function expandAbbr(input: ExpandAbbreviationInput): string | undefined { - const helper = getEmmetHelper(); - const expandOptions = helper.getExpandOptions(input.syntax, getEmmetConfiguration(input.syntax), input.filter); - - if (input.textToWrap) { - // escape ${ sections, fixes #122231 - input.textToWrap = input.textToWrap.map(e => e.replace(/\$\{/g, '\\\$\{')); - if (input.filter && input.filter.includes('t')) { - input.textToWrap = input.textToWrap.map(line => { - return line.replace(trimRegex, '').trim(); - }); - } - expandOptions['text'] = input.textToWrap; - - if (expandOptions.options) { - // Below fixes https://github.com/microsoft/vscode/issues/29898 - // With this, Emmet formats inline elements as block elements - // ensuring the wrapped multi line text does not get merged to a single line - if (!input.rangeToReplace.isSingleLine) { - expandOptions.options['output.inlineBreak'] = 1; - } - - if (input.indent) { - expandOptions.options['output.indent'] = input.indent; - } - if (input.baseIndent) { - expandOptions.options['output.baseIndent'] = input.baseIndent; - } - } - } - - let expandedText: string | undefined; - try { - expandedText = helper.expandAbbreviation(input.abbreviation, expandOptions); - } catch (e) { - void vscode.window.showErrorMessage('Failed to expand abbreviation'); - } - - return expandedText; + const helper = getEmmetHelper(); + const expandOptions = helper.getExpandOptions(input.syntax, getEmmetConfiguration(input.syntax), input.filter); + if (input.textToWrap) { + // escape ${ sections, fixes #122231 + input.textToWrap = input.textToWrap.map(e => e.replace(/\$\{/g, '\\\$\{')); + if (input.filter && input.filter.includes('t')) { + input.textToWrap = input.textToWrap.map(line => { + return line.replace(trimRegex, '').trim(); + }); + } + expandOptions['text'] = input.textToWrap; + if (expandOptions.options) { + // Below fixes https://github.com/microsoft/vscode/issues/29898 + // With this, Emmet formats inline elements as block elements + // ensuring the wrapped multi line text does not get merged to a single line + if (!input.rangeToReplace.isSingleLine) { + expandOptions.options['output.inlineBreak'] = 1; + } + if (input.indent) { + expandOptions.options['output.indent'] = input.indent; + } + if (input.baseIndent) { + expandOptions.options['output.baseIndent'] = input.baseIndent; + } + } + } + let expandedText: string | undefined; + try { + expandedText = helper.expandAbbreviation(input.abbreviation, expandOptions); + } + catch (e) { + void vscode.window.showErrorMessage('Failed to expand abbreviation'); + } + return expandedText; } - -export function getSyntaxFromArgs(args: { [x: string]: string }): string | undefined { - const mappedModes = getMappingForIncludedLanguages(); - const language: string = args['language']; - const parentMode: string = args['parentMode']; - const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : []; - if (excludedLanguages.includes(language)) { - return; - } - - let syntax = getEmmetMode(mappedModes[language] ?? language, mappedModes, excludedLanguages); - if (!syntax) { - syntax = getEmmetMode(mappedModes[parentMode] ?? parentMode, mappedModes, excludedLanguages); - } - - return syntax; +export function getSyntaxFromArgs(args: { + [x: string]: string; +}): string | undefined { + const mappedModes = getMappingForIncludedLanguages(); + const language: string = args['language']; + const parentMode: string = args['parentMode']; + const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : []; + if (excludedLanguages.includes(language)) { + return; + } + let syntax = getEmmetMode(mappedModes[language] ?? language, mappedModes, excludedLanguages); + if (!syntax) { + syntax = getEmmetMode(mappedModes[parentMode] ?? parentMode, mappedModes, excludedLanguages); + } + return syntax; } diff --git a/extensions/emmet/Source/balance.ts b/extensions/emmet/Source/balance.ts index 0b5710ca898e3..9d639c92a9a58 100644 --- a/extensions/emmet/Source/balance.ts +++ b/extensions/emmet/Source/balance.ts @@ -2,132 +2,116 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { getHtmlFlatNode, offsetRangeToSelection, validate } from './util'; import { getRootNode } from './parseDocument'; import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode'; - let balanceOutStack: Array = []; let lastBalancedSelections: readonly vscode.Selection[] = []; - export function balanceOut() { - balance(true); + balance(true); } - export function balanceIn() { - balance(false); + balance(false); } - function balance(out: boolean) { - if (!validate(false) || !vscode.window.activeTextEditor) { - return; - } - const editor = vscode.window.activeTextEditor; - const document = editor.document; - const rootNode = getRootNode(document, true); - if (!rootNode) { - return; - } - - const rangeFn = out ? getRangeToBalanceOut : getRangeToBalanceIn; - let newSelections: readonly vscode.Selection[] = editor.selections.map(selection => { - return rangeFn(document, rootNode, selection); - }); - - // check whether we are starting a balance elsewhere - if (areSameSelections(lastBalancedSelections, editor.selections)) { - // we are not starting elsewhere, so use the stack as-is - if (out) { - // make sure we are able to expand outwards - if (!areSameSelections(editor.selections, newSelections)) { - balanceOutStack.push(editor.selections); - } - } else if (balanceOutStack.length) { - newSelections = balanceOutStack.pop()!; - } - } else { - // we are starting elsewhere, so reset the stack - balanceOutStack = out ? [editor.selections] : []; - } - - editor.selections = newSelections; - lastBalancedSelections = editor.selections; + if (!validate(false) || !vscode.window.activeTextEditor) { + return; + } + const editor = vscode.window.activeTextEditor; + const document = editor.document; + const rootNode = getRootNode(document, true); + if (!rootNode) { + return; + } + const rangeFn = out ? getRangeToBalanceOut : getRangeToBalanceIn; + let newSelections: readonly vscode.Selection[] = editor.selections.map(selection => { + return rangeFn(document, rootNode, selection); + }); + // check whether we are starting a balance elsewhere + if (areSameSelections(lastBalancedSelections, editor.selections)) { + // we are not starting elsewhere, so use the stack as-is + if (out) { + // make sure we are able to expand outwards + if (!areSameSelections(editor.selections, newSelections)) { + balanceOutStack.push(editor.selections); + } + } + else if (balanceOutStack.length) { + newSelections = balanceOutStack.pop()!; + } + } + else { + // we are starting elsewhere, so reset the stack + balanceOutStack = out ? [editor.selections] : []; + } + editor.selections = newSelections; + lastBalancedSelections = editor.selections; } - function getRangeToBalanceOut(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection { - const offset = document.offsetAt(selection.start); - const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, false); - if (!nodeToBalance) { - return selection; - } - if (!nodeToBalance.open || !nodeToBalance.close) { - return offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end); - } - - // Set reverse direction if we were in the end tag - let innerSelection: vscode.Selection; - let outerSelection: vscode.Selection; - if (nodeToBalance.close.start <= offset && nodeToBalance.close.end > offset) { - innerSelection = offsetRangeToSelection(document, nodeToBalance.close.start, nodeToBalance.open.end); - outerSelection = offsetRangeToSelection(document, nodeToBalance.close.end, nodeToBalance.open.start); - } - else { - innerSelection = offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start); - outerSelection = offsetRangeToSelection(document, nodeToBalance.open.start, nodeToBalance.close.end); - } - - if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) { - return innerSelection; - } - if (outerSelection.contains(selection) && !outerSelection.isEqual(selection)) { - return outerSelection; - } - return selection; + const offset = document.offsetAt(selection.start); + const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, false); + if (!nodeToBalance) { + return selection; + } + if (!nodeToBalance.open || !nodeToBalance.close) { + return offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end); + } + // Set reverse direction if we were in the end tag + let innerSelection: vscode.Selection; + let outerSelection: vscode.Selection; + if (nodeToBalance.close.start <= offset && nodeToBalance.close.end > offset) { + innerSelection = offsetRangeToSelection(document, nodeToBalance.close.start, nodeToBalance.open.end); + outerSelection = offsetRangeToSelection(document, nodeToBalance.close.end, nodeToBalance.open.start); + } + else { + innerSelection = offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start); + outerSelection = offsetRangeToSelection(document, nodeToBalance.open.start, nodeToBalance.close.end); + } + if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) { + return innerSelection; + } + if (outerSelection.contains(selection) && !outerSelection.isEqual(selection)) { + return outerSelection; + } + return selection; } - function getRangeToBalanceIn(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection { - const offset = document.offsetAt(selection.start); - const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, true); - if (!nodeToBalance) { - return selection; - } - - const selectionStart = document.offsetAt(selection.start); - const selectionEnd = document.offsetAt(selection.end); - if (nodeToBalance.open && nodeToBalance.close) { - const entireNodeSelected = selectionStart === nodeToBalance.start && selectionEnd === nodeToBalance.end; - const startInOpenTag = selectionStart > nodeToBalance.open.start && selectionStart < nodeToBalance.open.end; - const startInCloseTag = selectionStart > nodeToBalance.close.start && selectionStart < nodeToBalance.close.end; - - if (entireNodeSelected || startInOpenTag || startInCloseTag) { - return offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start); - } - } - - if (!nodeToBalance.firstChild) { - return selection; - } - - const firstChild = nodeToBalance.firstChild; - if (selectionStart === firstChild.start - && selectionEnd === firstChild.end - && firstChild.open - && firstChild.close) { - return offsetRangeToSelection(document, firstChild.open.end, firstChild.close.start); - } - - return offsetRangeToSelection(document, firstChild.start, firstChild.end); + const offset = document.offsetAt(selection.start); + const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, true); + if (!nodeToBalance) { + return selection; + } + const selectionStart = document.offsetAt(selection.start); + const selectionEnd = document.offsetAt(selection.end); + if (nodeToBalance.open && nodeToBalance.close) { + const entireNodeSelected = selectionStart === nodeToBalance.start && selectionEnd === nodeToBalance.end; + const startInOpenTag = selectionStart > nodeToBalance.open.start && selectionStart < nodeToBalance.open.end; + const startInCloseTag = selectionStart > nodeToBalance.close.start && selectionStart < nodeToBalance.close.end; + if (entireNodeSelected || startInOpenTag || startInCloseTag) { + return offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start); + } + } + if (!nodeToBalance.firstChild) { + return selection; + } + const firstChild = nodeToBalance.firstChild; + if (selectionStart === firstChild.start + && selectionEnd === firstChild.end + && firstChild.open + && firstChild.close) { + return offsetRangeToSelection(document, firstChild.open.end, firstChild.close.start); + } + return offsetRangeToSelection(document, firstChild.start, firstChild.end); } - function areSameSelections(a: readonly vscode.Selection[], b: readonly vscode.Selection[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (!a[i].isEqual(b[i])) { - return false; - } - } - return true; + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!a[i].isEqual(b[i])) { + return false; + } + } + return true; } diff --git a/extensions/emmet/Source/browser/emmetBrowserMain.ts b/extensions/emmet/Source/browser/emmetBrowserMain.ts index 6ab165aa79248..c282701fdad4b 100644 --- a/extensions/emmet/Source/browser/emmetBrowserMain.ts +++ b/extensions/emmet/Source/browser/emmetBrowserMain.ts @@ -2,10 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { activateEmmetExtension } from '../emmetCommon'; - export function activate(context: vscode.ExtensionContext) { - activateEmmetExtension(context); + activateEmmetExtension(context); } diff --git a/extensions/emmet/Source/bufferStream.ts b/extensions/emmet/Source/bufferStream.ts index 60376c1e2d729..c91f8b3b79d7b 100644 --- a/extensions/emmet/Source/bufferStream.ts +++ b/extensions/emmet/Source/bufferStream.ts @@ -2,140 +2,123 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - /* Based on @sergeche's work in his emmet plugin */ - import { TextDocument } from 'vscode'; - /** * A stream reader for VSCode's `TextDocument` * Based on @emmetio/stream-reader and @emmetio/atom-plugin */ export class DocumentStreamReader { - private document: TextDocument; - private start: number; - private _eof: number; - private _sof: number; - public pos: number; - - constructor(document: TextDocument, pos?: number, limit?: [number, number]) { - this.document = document; - this.start = this.pos = pos ? pos : 0; - this._sof = limit ? limit[0] : 0; - this._eof = limit ? limit[1] : document.getText().length; - } - - /** - * Returns true only if the stream is at the start of the file. - */ - sof(): boolean { - return this.pos <= this._sof; - } - - /** - * Returns true only if the stream is at the end of the file. - */ - eof(): boolean { - return this.pos >= this._eof; - } - - /** - * Creates a new stream instance which is limited to given range for given document - */ - limit(start: number, end: number): DocumentStreamReader { - return new DocumentStreamReader(this.document, start, [start, end]); - } - - /** - * Returns the next character code in the stream without advancing it. - * Will return NaN at the end of the file. - */ - peek(): number { - if (this.eof()) { - return NaN; - } - return this.document.getText().charCodeAt(this.pos); - } - - /** - * Returns the next character in the stream and advances it. - * Also returns NaN when no more characters are available. - */ - next(): number { - if (this.eof()) { - return NaN; - } - - const code = this.document.getText().charCodeAt(this.pos); - this.pos++; - - if (this.eof()) { - // restrict pos to eof, if in case it got moved beyond eof - this.pos = this._eof; - } - - return code; - } - - /** - * Backs up the stream n characters. Backing it up further than the - * start of the current token will cause things to break, so be careful. - */ - backUp(n: number): number { - this.pos -= n; - if (this.pos < 0) { - this.pos = 0; - } - return this.peek(); - } - - /** - * Get the string between the start of the current token and the - * current stream position. - */ - current(): string { - return this.substring(this.start, this.pos); - } - - /** - * Returns contents for given range - */ - substring(from: number, to: number): string { - return this.document.getText().substring(from, to); - } - - /** - * Creates error object with current stream state - */ - error(message: string): Error { - const err = new Error(`${message} at offset ${this.pos}`); - return err; - } - - /** - * `match` can be a character code or a function that takes a character code - * and returns a boolean. If the next character in the stream 'matches' - * the given argument, it is consumed and returned. - * Otherwise, `false` is returned. - */ - eat(match: number | Function): boolean { - const ch = this.peek(); - const ok = typeof match === 'function' ? match(ch) : ch === match; - - if (ok) { - this.next(); - } - - return ok; - } - - /** - * Repeatedly calls eat with the given argument, until it - * fails. Returns true if any characters were eaten. - */ - eatWhile(match: number | Function): boolean { - const start = this.pos; - while (!this.eof() && this.eat(match)) { } - return this.pos !== start; - } + private document: TextDocument; + private start: number; + private _eof: number; + private _sof: number; + public pos: number; + constructor(document: TextDocument, pos?: number, limit?: [ + number, + number + ]) { + this.document = document; + this.start = this.pos = pos ? pos : 0; + this._sof = limit ? limit[0] : 0; + this._eof = limit ? limit[1] : document.getText().length; + } + /** + * Returns true only if the stream is at the start of the file. + */ + sof(): boolean { + return this.pos <= this._sof; + } + /** + * Returns true only if the stream is at the end of the file. + */ + eof(): boolean { + return this.pos >= this._eof; + } + /** + * Creates a new stream instance which is limited to given range for given document + */ + limit(start: number, end: number): DocumentStreamReader { + return new DocumentStreamReader(this.document, start, [start, end]); + } + /** + * Returns the next character code in the stream without advancing it. + * Will return NaN at the end of the file. + */ + peek(): number { + if (this.eof()) { + return NaN; + } + return this.document.getText().charCodeAt(this.pos); + } + /** + * Returns the next character in the stream and advances it. + * Also returns NaN when no more characters are available. + */ + next(): number { + if (this.eof()) { + return NaN; + } + const code = this.document.getText().charCodeAt(this.pos); + this.pos++; + if (this.eof()) { + // restrict pos to eof, if in case it got moved beyond eof + this.pos = this._eof; + } + return code; + } + /** + * Backs up the stream n characters. Backing it up further than the + * start of the current token will cause things to break, so be careful. + */ + backUp(n: number): number { + this.pos -= n; + if (this.pos < 0) { + this.pos = 0; + } + return this.peek(); + } + /** + * Get the string between the start of the current token and the + * current stream position. + */ + current(): string { + return this.substring(this.start, this.pos); + } + /** + * Returns contents for given range + */ + substring(from: number, to: number): string { + return this.document.getText().substring(from, to); + } + /** + * Creates error object with current stream state + */ + error(message: string): Error { + const err = new Error(`${message} at offset ${this.pos}`); + return err; + } + /** + * `match` can be a character code or a function that takes a character code + * and returns a boolean. If the next character in the stream 'matches' + * the given argument, it is consumed and returned. + * Otherwise, `false` is returned. + */ + eat(match: number | Function): boolean { + const ch = this.peek(); + const ok = typeof match === 'function' ? match(ch) : ch === match; + if (ok) { + this.next(); + } + return ok; + } + /** + * Repeatedly calls eat with the given argument, until it + * fails. Returns true if any characters were eaten. + */ + eatWhile(match: number | Function): boolean { + const start = this.pos; + while (!this.eof() && this.eat(match)) { } + return this.pos !== start; + } } diff --git a/extensions/emmet/Source/defaultCompletionProvider.ts b/extensions/emmet/Source/defaultCompletionProvider.ts index 0876cfa6f6a3e..072398de0560b 100644 --- a/extensions/emmet/Source/defaultCompletionProvider.ts +++ b/extensions/emmet/Source/defaultCompletionProvider.ts @@ -2,216 +2,195 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { Node, Stylesheet } from 'EmmetFlatNode'; import { isValidLocationForEmmetAbbreviation, getSyntaxFromArgs } from './abbreviationActions'; import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, getFlatNode, allowedMimeTypesInScriptTag, toLSTextDocument, getHtmlFlatNode, getEmbeddedCssNodeIfAny } from './util'; import { Range as LSRange } from 'vscode-languageserver-textdocument'; import { getRootNode } from './parseDocument'; - export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider { - - private lastCompletionType: string | undefined; - - public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.CancellationToken, context: vscode.CompletionContext): Thenable | undefined { - const completionResult = this.provideCompletionItemsInternal(document, position, context); - if (!completionResult) { - this.lastCompletionType = undefined; - return; - } - - return completionResult.then(completionList => { - if (!completionList || !completionList.items.length) { - this.lastCompletionType = undefined; - return completionList; - } - const item = completionList.items[0]; - const expandedText = item.documentation ? item.documentation.toString() : ''; - - if (expandedText.startsWith('<')) { - this.lastCompletionType = 'html'; - } else if (expandedText.indexOf(':') > 0 && expandedText.endsWith(';')) { - this.lastCompletionType = 'css'; - } else { - this.lastCompletionType = undefined; - } - return completionList; - }); - } - - private provideCompletionItemsInternal(document: vscode.TextDocument, position: vscode.Position, context: vscode.CompletionContext): Thenable | undefined { - const emmetConfig = vscode.workspace.getConfiguration('emmet'); - const excludedLanguages = emmetConfig['excludeLanguages'] ? emmetConfig['excludeLanguages'] : []; - if (excludedLanguages.includes(document.languageId)) { - return; - } - - const mappedLanguages = getMappingForIncludedLanguages(); - const isSyntaxMapped = mappedLanguages[document.languageId] ? true : false; - const emmetMode = getEmmetMode((isSyntaxMapped ? mappedLanguages[document.languageId] : document.languageId), mappedLanguages, excludedLanguages); - - if (!emmetMode - || emmetConfig['showExpandedAbbreviation'] === 'never' - || ((isSyntaxMapped || emmetMode === 'jsx') && emmetConfig['showExpandedAbbreviation'] !== 'always')) { - return; - } - - let syntax = emmetMode; - - let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml'; - let rootNode: Node | undefined; - let currentNode: Node | undefined; - - const lsDoc = toLSTextDocument(document); - position = document.validatePosition(position); - - // Don't show completions if there's a comment at the beginning of the line - const lineRange = new vscode.Range(position.line, 0, position.line, position.character); - if (document.getText(lineRange).trimStart().startsWith('//')) { - return; - } - - const helper = getEmmetHelper(); - if (syntax === 'html') { - if (context.triggerKind === vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) { - switch (this.lastCompletionType) { - case 'html': - validateLocation = false; - break; - case 'css': - validateLocation = false; - syntax = 'css'; - break; - default: - break; - } - } - if (validateLocation) { - const positionOffset = document.offsetAt(position); - const emmetRootNode = getRootNode(document, true); - const foundNode = getHtmlFlatNode(document.getText(), emmetRootNode, positionOffset, false); - if (foundNode) { - if (foundNode.name === 'script') { - const typeNode = foundNode.attributes.find(attr => attr.name.toString() === 'type'); - if (typeNode) { - const typeAttrValue = typeNode.value.toString(); - if (typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript') { - if (!getSyntaxFromArgs({ language: 'javascript' })) { - return; - } else { - validateLocation = false; - } - } - else if (allowedMimeTypesInScriptTag.includes(typeAttrValue)) { - validateLocation = false; - } - } else { - return; - } - } - else if (foundNode.name === 'style') { - syntax = 'css'; - validateLocation = false; - } else { - const styleNode = foundNode.attributes.find(attr => attr.name.toString() === 'style'); - if (styleNode && styleNode.value.start <= positionOffset && positionOffset <= styleNode.value.end) { - syntax = 'css'; - validateLocation = false; - } - } - } - } - } - - const expandOptions = isStyleSheet(syntax) ? - { lookAhead: false, syntax: 'stylesheet' } : - { lookAhead: true, syntax: 'markup' }; - const extractAbbreviationResults = helper.extractAbbreviation(lsDoc, position, expandOptions); - if (!extractAbbreviationResults || !helper.isAbbreviationValid(syntax, extractAbbreviationResults.abbreviation)) { - return; - } - - const offset = document.offsetAt(position); - if (isStyleSheet(document.languageId) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) { - validateLocation = true; - const usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true; - rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : getRootNode(document, true); - if (!rootNode) { - return; - } - currentNode = getFlatNode(rootNode, offset, true); - } - - // Fix for https://github.com/microsoft/vscode/issues/107578 - // Validate location if syntax is of styleSheet type to ensure that location is valid for emmet abbreviation. - // For an html document containing a \n'; - - return ret; - } - - private _getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, imageInfo: readonly ImageInfo[]): string { - const baseStyles: string[] = []; - for (const resource of this._contributionProvider.contributions.previewStyles) { - baseStyles.push(``); - } - - return `${baseStyles.join('\n')} + } + ret += '\n'; + return ret; + } + private _getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, imageInfo: readonly ImageInfo[]): string { + const baseStyles: string[] = []; + for (const resource of this._contributionProvider.contributions.previewStyles) { + baseStyles.push(``); + } + return `${baseStyles.join('\n')} ${this._computeCustomStyleSheetIncludes(resourceProvider, resource, config)} ${this._getImageStabilizerStyles(imageInfo)}`; - } - - private _getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string { - const out: string[] = []; - for (const resource of this._contributionProvider.contributions.previewScripts) { - out.push(``); - } - return out.join('\n'); - } - - private _getCsp( - provider: WebviewResourceProvider, - resource: vscode.Uri, - nonce: string - ): string { - const rule = provider.cspSource; - switch (this._cspArbiter.getSecurityLevelForResource(resource)) { - case MarkdownPreviewSecurityLevel.AllowInsecureContent: - return ``; - - case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent: - return ``; - - case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent: - return ''; - - case MarkdownPreviewSecurityLevel.Strict: - default: - return ``; - } - } + } + return out.join('\n'); + } + private _getCsp(provider: WebviewResourceProvider, resource: vscode.Uri, nonce: string): string { + const rule = provider.cspSource; + switch (this._cspArbiter.getSecurityLevelForResource(resource)) { + case MarkdownPreviewSecurityLevel.AllowInsecureContent: + return ``; + case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent: + return ``; + case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent: + return ''; + case MarkdownPreviewSecurityLevel.Strict: + default: + return ``; + } + } } diff --git a/extensions/markdown-language-features/Source/preview/preview.ts b/extensions/markdown-language-features/Source/preview/preview.ts index 7ccbc625b4734..755d52d958185 100644 --- a/extensions/markdown-language-features/Source/preview/preview.ts +++ b/extensions/markdown-language-features/Source/preview/preview.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import * as uri from 'vscode-uri'; import { ILogger } from '../logging'; @@ -17,778 +16,563 @@ import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { scrollEditorToLine, StartingScrollFragment, StartingScrollLine, StartingScrollLocation } from './scrolling'; import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from './topmostLineMonitor'; import type { FromWebviewMessage, ToWebviewMessage } from '../../types/previewMessaging'; - export class PreviewDocumentVersion { - - public readonly resource: vscode.Uri; - private readonly _version: number; - - public constructor(document: vscode.TextDocument) { - this.resource = document.uri; - this._version = document.version; - } - - public equals(other: PreviewDocumentVersion): boolean { - return this.resource.fsPath === other.resource.fsPath - && this._version === other._version; - } + public readonly resource: vscode.Uri; + private readonly _version: number; + public constructor(document: vscode.TextDocument) { + this.resource = document.uri; + this._version = document.version; + } + public equals(other: PreviewDocumentVersion): boolean { + return this.resource.fsPath === other.resource.fsPath + && this._version === other._version; + } } - interface MarkdownPreviewDelegate { - getTitle?(resource: vscode.Uri): string; - getAdditionalState(): {}; - openPreviewLinkToMarkdownFile(markdownLink: vscode.Uri, fragment: string | undefined): void; + getTitle?(resource: vscode.Uri): string; + getAdditionalState(): {}; + openPreviewLinkToMarkdownFile(markdownLink: vscode.Uri, fragment: string | undefined): void; } - class MarkdownPreview extends Disposable implements WebviewResourceProvider { - - private static readonly _unwatchedImageSchemes = new Set(['https', 'http', 'data']); - - private _disposed: boolean = false; - - private readonly _delay = 300; - private _throttleTimer: any; - - private readonly _resource: vscode.Uri; - private readonly _webviewPanel: vscode.WebviewPanel; - - private _line: number | undefined; - private _scrollToFragment: string | undefined; - private _firstUpdate = true; - private _currentVersion?: PreviewDocumentVersion; - private _isScrolling = false; - - private _imageInfo: readonly ImageInfo[] = []; - private readonly _fileWatchersBySrc = new Map(); - - private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); - public readonly onScroll = this._onScrollEmitter.event; - - private readonly _disposeCts = this._register(new vscode.CancellationTokenSource()); - - constructor( - webview: vscode.WebviewPanel, - resource: vscode.Uri, - startingScroll: StartingScrollLocation | undefined, - private readonly _delegate: MarkdownPreviewDelegate, - private readonly _contentProvider: MdDocumentRenderer, - private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, - private readonly _logger: ILogger, - private readonly _contributionProvider: MarkdownContributionProvider, - private readonly _opener: MdLinkOpener, - ) { - super(); - - this._webviewPanel = webview; - this._resource = resource; - - switch (startingScroll?.type) { - case 'line': - if (!isNaN(startingScroll.line!)) { - this._line = startingScroll.line; - } - break; - - case 'fragment': - this._scrollToFragment = startingScroll.fragment; - break; - } - - this._register(_contributionProvider.onContributionsChanged(() => { - setTimeout(() => this.refresh(true), 0); - })); - - this._register(vscode.workspace.onDidChangeTextDocument(event => { - if (this.isPreviewOf(event.document.uri)) { - this.refresh(); - } - })); - - this._register(vscode.workspace.onDidOpenTextDocument(document => { - if (this.isPreviewOf(document.uri)) { - this.refresh(); - } - })); - - const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*'))); - this._register(watcher.onDidChange(uri => { - if (this.isPreviewOf(uri)) { - // Only use the file system event when VS Code does not already know about the file - if (!vscode.workspace.textDocuments.some(doc => doc.uri.toString() === uri.toString())) { - this.refresh(); - } - } - })); - - this._register(this._webviewPanel.webview.onDidReceiveMessage((e: FromWebviewMessage.Type) => { - if (e.source !== this._resource.toString()) { - return; - } - - switch (e.type) { - case 'cacheImageSizes': - this._imageInfo = e.imageData; - break; - - case 'revealLine': - this._onDidScrollPreview(e.line); - break; - - case 'didClick': - this._onDidClickPreview(e.line); - break; - - case 'openLink': - this._onDidClickPreviewLink(e.href); - break; - - case 'showPreviewSecuritySelector': - vscode.commands.executeCommand('markdown.showPreviewSecuritySelector', e.source); - break; - - case 'previewStyleLoadError': - vscode.window.showWarningMessage( - vscode.l10n.t("Could not load 'markdown.styles': {0}", e.unloadedStyles.join(', '))); - break; - } - })); - - this.refresh(); - } - - override dispose() { - this._disposeCts.cancel(); - - super.dispose(); - - this._disposed = true; - - clearTimeout(this._throttleTimer); - for (const entry of this._fileWatchersBySrc.values()) { - entry.dispose(); - } - this._fileWatchersBySrc.clear(); - } - - public get resource(): vscode.Uri { - return this._resource; - } - - public get state() { - return { - resource: this._resource.toString(), - line: this._line, - fragment: this._scrollToFragment, - ...this._delegate.getAdditionalState(), - }; - } - - /** - * The first call immediately refreshes the preview, - * calls happening shortly thereafter are debounced. - */ - public refresh(forceUpdate: boolean = false) { - // Schedule update if none is pending - if (!this._throttleTimer) { - if (this._firstUpdate) { - this._updatePreview(true); - } else { - this._throttleTimer = setTimeout(() => this._updatePreview(forceUpdate), this._delay); - } - } - - this._firstUpdate = false; - } - - - public isPreviewOf(resource: vscode.Uri): boolean { - return this._resource.fsPath === resource.fsPath; - } - - public postMessage(msg: ToWebviewMessage.Type) { - if (!this._disposed) { - this._webviewPanel.webview.postMessage(msg); - } - } - - public scrollTo(topLine: number) { - if (this._disposed) { - return; - } - - if (this._isScrolling) { - this._isScrolling = false; - return; - } - - this._logger.verbose('MarkdownPreview', 'updateForView', { markdownFile: this._resource }); - this._line = topLine; - this.postMessage({ - type: 'updateView', - line: topLine, - source: this._resource.toString() - }); - } - - private async _updatePreview(forceUpdate?: boolean): Promise { - clearTimeout(this._throttleTimer); - this._throttleTimer = undefined; - - if (this._disposed) { - return; - } - - let document: vscode.TextDocument; - try { - document = await vscode.workspace.openTextDocument(this._resource); - } catch { - if (!this._disposed) { - await this._showFileNotFoundError(); - } - return; - } - - if (this._disposed) { - return; - } - - const pendingVersion = new PreviewDocumentVersion(document); - if (!forceUpdate && this._currentVersion?.equals(pendingVersion)) { - if (this._line) { - this.scrollTo(this._line); - } - return; - } - - const shouldReloadPage = forceUpdate || !this._currentVersion || this._currentVersion.resource.toString() !== pendingVersion.resource.toString() || !this._webviewPanel.visible; - this._currentVersion = pendingVersion; - - let selectedLine: number | undefined = undefined; - for (const editor of vscode.window.visibleTextEditors) { - if (this.isPreviewOf(editor.document.uri)) { - selectedLine = editor.selection.active.line; - break; - } - } - - const content = await (shouldReloadPage - ? this._contentProvider.renderDocument(document, this, this._previewConfigurations, this._line, selectedLine, this.state, this._imageInfo, this._disposeCts.token) - : this._contentProvider.renderBody(document, this)); - - // Another call to `doUpdate` may have happened. - // Make sure we are still updating for the correct document - if (this._currentVersion?.equals(pendingVersion)) { - this._updateWebviewContent(content.html, shouldReloadPage); - this._updateImageWatchers(content.containingImages); - } - } - - private _onDidScrollPreview(line: number) { - this._line = line; - this._onScrollEmitter.fire({ line: this._line, uri: this._resource }); - const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource); - if (!config.scrollEditorWithPreview) { - return; - } - - for (const editor of vscode.window.visibleTextEditors) { - if (!this.isPreviewOf(editor.document.uri)) { - continue; - } - - this._isScrolling = true; - scrollEditorToLine(line, editor); - } - } - - private async _onDidClickPreview(line: number): Promise { - // fix #82457, find currently opened but unfocused source tab - await vscode.commands.executeCommand('markdown.showSource'); - - const revealLineInEditor = (editor: vscode.TextEditor) => { - const position = new vscode.Position(line, 0); - const newSelection = new vscode.Selection(position, position); - editor.selection = newSelection; - editor.revealRange(newSelection, vscode.TextEditorRevealType.InCenterIfOutsideViewport); - }; - - for (const visibleEditor of vscode.window.visibleTextEditors) { - if (this.isPreviewOf(visibleEditor.document.uri)) { - const editor = await vscode.window.showTextDocument(visibleEditor.document, visibleEditor.viewColumn); - revealLineInEditor(editor); - return; - } - } - - await vscode.workspace.openTextDocument(this._resource) - .then(vscode.window.showTextDocument) - .then((editor) => { - revealLineInEditor(editor); - }, () => { - vscode.window.showErrorMessage(vscode.l10n.t('Could not open {0}', this._resource.toString())); - }); - } - - private async _showFileNotFoundError() { - this._webviewPanel.webview.html = this._contentProvider.renderFileNotFoundDocument(this._resource); - } - - private _updateWebviewContent(html: string, reloadPage: boolean): void { - if (this._disposed) { - return; - } - - if (this._delegate.getTitle) { - this._webviewPanel.title = this._delegate.getTitle(this._resource); - } - this._webviewPanel.webview.options = this._getWebviewOptions(); - - if (reloadPage) { - this._webviewPanel.webview.html = html; - } else { - this.postMessage({ - type: 'updateContent', - content: html, - source: this._resource.toString(), - }); - } - } - - private _updateImageWatchers(srcs: Set) { - // Delete stale file watchers. - for (const [src, watcher] of this._fileWatchersBySrc) { - if (!srcs.has(src)) { - watcher.dispose(); - this._fileWatchersBySrc.delete(src); - } - } - - // Create new file watchers. - const root = vscode.Uri.joinPath(this._resource, '../'); - for (const src of srcs) { - const uri = urlToUri(src, root); - if (uri && !MarkdownPreview._unwatchedImageSchemes.has(uri.scheme) && !this._fileWatchersBySrc.has(src)) { - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*')); - watcher.onDidChange(() => { - this.refresh(true); - }); - this._fileWatchersBySrc.set(src, watcher); - } - } - } - - private _getWebviewOptions(): vscode.WebviewOptions { - return { - enableScripts: true, - enableForms: false, - localResourceRoots: this._getLocalResourceRoots() - }; - } - - private _getLocalResourceRoots(): ReadonlyArray { - const baseRoots = Array.from(this._contributionProvider.contributions.previewResourceRoots); - - const folder = vscode.workspace.getWorkspaceFolder(this._resource); - if (folder) { - const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri); - if (workspaceRoots) { - baseRoots.push(...workspaceRoots); - } - } else { - baseRoots.push(uri.Utils.dirname(this._resource)); - } - - return baseRoots; - } - - private async _onDidClickPreviewLink(href: string) { - const config = vscode.workspace.getConfiguration('markdown', this.resource); - const openLinks = config.get('preview.openMarkdownLinks', 'inPreview'); - if (openLinks === 'inPreview') { - const resolved = await this._opener.resolveDocumentLink(href, this.resource); - if (resolved.kind === 'file') { - try { - const doc = await vscode.workspace.openTextDocument(vscode.Uri.from(resolved.uri)); - if (isMarkdownFile(doc)) { - return this._delegate.openPreviewLinkToMarkdownFile(doc.uri, resolved.fragment ? decodeURIComponent(resolved.fragment) : undefined); - } - } catch { - // Noop - } - } - } - - return this._opener.openDocumentLink(href, this.resource); - } - - //#region WebviewResourceProvider - - asWebviewUri(resource: vscode.Uri) { - return this._webviewPanel.webview.asWebviewUri(resource); - } - - get cspSource() { - return this._webviewPanel.webview.cspSource; - } - - //#endregion + private static readonly _unwatchedImageSchemes = new Set(['https', 'http', 'data']); + private _disposed: boolean = false; + private readonly _delay = 300; + private _throttleTimer: any; + private readonly _resource: vscode.Uri; + private readonly _webviewPanel: vscode.WebviewPanel; + private _line: number | undefined; + private _scrollToFragment: string | undefined; + private _firstUpdate = true; + private _currentVersion?: PreviewDocumentVersion; + private _isScrolling = false; + private _imageInfo: readonly ImageInfo[] = []; + private readonly _fileWatchersBySrc = new Map(); + private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); + public readonly onScroll = this._onScrollEmitter.event; + private readonly _disposeCts = this._register(new vscode.CancellationTokenSource()); + constructor(webview: vscode.WebviewPanel, resource: vscode.Uri, startingScroll: StartingScrollLocation | undefined, private readonly _delegate: MarkdownPreviewDelegate, private readonly _contentProvider: MdDocumentRenderer, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, private readonly _logger: ILogger, private readonly _contributionProvider: MarkdownContributionProvider, private readonly _opener: MdLinkOpener) { + super(); + this._webviewPanel = webview; + this._resource = resource; + switch (startingScroll?.type) { + case 'line': + if (!isNaN(startingScroll.line!)) { + this._line = startingScroll.line; + } + break; + case 'fragment': + this._scrollToFragment = startingScroll.fragment; + break; + } + this._register(_contributionProvider.onContributionsChanged(() => { + setTimeout(() => this.refresh(true), 0); + })); + this._register(vscode.workspace.onDidChangeTextDocument(event => { + if (this.isPreviewOf(event.document.uri)) { + this.refresh(); + } + })); + this._register(vscode.workspace.onDidOpenTextDocument(document => { + if (this.isPreviewOf(document.uri)) { + this.refresh(); + } + })); + const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*'))); + this._register(watcher.onDidChange(uri => { + if (this.isPreviewOf(uri)) { + // Only use the file system event when VS Code does not already know about the file + if (!vscode.workspace.textDocuments.some(doc => doc.uri.toString() === uri.toString())) { + this.refresh(); + } + } + })); + this._register(this._webviewPanel.webview.onDidReceiveMessage((e: FromWebviewMessage.Type) => { + if (e.source !== this._resource.toString()) { + return; + } + switch (e.type) { + case 'cacheImageSizes': + this._imageInfo = e.imageData; + break; + case 'revealLine': + this._onDidScrollPreview(e.line); + break; + case 'didClick': + this._onDidClickPreview(e.line); + break; + case 'openLink': + this._onDidClickPreviewLink(e.href); + break; + case 'showPreviewSecuritySelector': + vscode.commands.executeCommand('markdown.showPreviewSecuritySelector', e.source); + break; + case 'previewStyleLoadError': + vscode.window.showWarningMessage(vscode.l10n.t("Could not load 'markdown.styles': {0}", e.unloadedStyles.join(', '))); + break; + } + })); + this.refresh(); + } + override dispose() { + this._disposeCts.cancel(); + super.dispose(); + this._disposed = true; + clearTimeout(this._throttleTimer); + for (const entry of this._fileWatchersBySrc.values()) { + entry.dispose(); + } + this._fileWatchersBySrc.clear(); + } + public get resource(): vscode.Uri { + return this._resource; + } + public get state() { + return { + resource: this._resource.toString(), + line: this._line, + fragment: this._scrollToFragment, + ...this._delegate.getAdditionalState(), + }; + } + /** + * The first call immediately refreshes the preview, + * calls happening shortly thereafter are debounced. + */ + public refresh(forceUpdate: boolean = false) { + // Schedule update if none is pending + if (!this._throttleTimer) { + if (this._firstUpdate) { + this._updatePreview(true); + } + else { + this._throttleTimer = setTimeout(() => this._updatePreview(forceUpdate), this._delay); + } + } + this._firstUpdate = false; + } + public isPreviewOf(resource: vscode.Uri): boolean { + return this._resource.fsPath === resource.fsPath; + } + public postMessage(msg: ToWebviewMessage.Type) { + if (!this._disposed) { + this._webviewPanel.webview.postMessage(msg); + } + } + public scrollTo(topLine: number) { + if (this._disposed) { + return; + } + if (this._isScrolling) { + this._isScrolling = false; + return; + } + this._logger.verbose('MarkdownPreview', 'updateForView', { markdownFile: this._resource }); + this._line = topLine; + this.postMessage({ + type: 'updateView', + line: topLine, + source: this._resource.toString() + }); + } + private async _updatePreview(forceUpdate?: boolean): Promise { + clearTimeout(this._throttleTimer); + this._throttleTimer = undefined; + if (this._disposed) { + return; + } + let document: vscode.TextDocument; + try { + document = await vscode.workspace.openTextDocument(this._resource); + } + catch { + if (!this._disposed) { + await this._showFileNotFoundError(); + } + return; + } + if (this._disposed) { + return; + } + const pendingVersion = new PreviewDocumentVersion(document); + if (!forceUpdate && this._currentVersion?.equals(pendingVersion)) { + if (this._line) { + this.scrollTo(this._line); + } + return; + } + const shouldReloadPage = forceUpdate || !this._currentVersion || this._currentVersion.resource.toString() !== pendingVersion.resource.toString() || !this._webviewPanel.visible; + this._currentVersion = pendingVersion; + let selectedLine: number | undefined = undefined; + for (const editor of vscode.window.visibleTextEditors) { + if (this.isPreviewOf(editor.document.uri)) { + selectedLine = editor.selection.active.line; + break; + } + } + const content = await (shouldReloadPage + ? this._contentProvider.renderDocument(document, this, this._previewConfigurations, this._line, selectedLine, this.state, this._imageInfo, this._disposeCts.token) + : this._contentProvider.renderBody(document, this)); + // Another call to `doUpdate` may have happened. + // Make sure we are still updating for the correct document + if (this._currentVersion?.equals(pendingVersion)) { + this._updateWebviewContent(content.html, shouldReloadPage); + this._updateImageWatchers(content.containingImages); + } + } + private _onDidScrollPreview(line: number) { + this._line = line; + this._onScrollEmitter.fire({ line: this._line, uri: this._resource }); + const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource); + if (!config.scrollEditorWithPreview) { + return; + } + for (const editor of vscode.window.visibleTextEditors) { + if (!this.isPreviewOf(editor.document.uri)) { + continue; + } + this._isScrolling = true; + scrollEditorToLine(line, editor); + } + } + private async _onDidClickPreview(line: number): Promise { + // fix #82457, find currently opened but unfocused source tab + await vscode.commands.executeCommand('markdown.showSource'); + const revealLineInEditor = (editor: vscode.TextEditor) => { + const position = new vscode.Position(line, 0); + const newSelection = new vscode.Selection(position, position); + editor.selection = newSelection; + editor.revealRange(newSelection, vscode.TextEditorRevealType.InCenterIfOutsideViewport); + }; + for (const visibleEditor of vscode.window.visibleTextEditors) { + if (this.isPreviewOf(visibleEditor.document.uri)) { + const editor = await vscode.window.showTextDocument(visibleEditor.document, visibleEditor.viewColumn); + revealLineInEditor(editor); + return; + } + } + await vscode.workspace.openTextDocument(this._resource) + .then(vscode.window.showTextDocument) + .then((editor) => { + revealLineInEditor(editor); + }, () => { + vscode.window.showErrorMessage(vscode.l10n.t('Could not open {0}', this._resource.toString())); + }); + } + private async _showFileNotFoundError() { + this._webviewPanel.webview.html = this._contentProvider.renderFileNotFoundDocument(this._resource); + } + private _updateWebviewContent(html: string, reloadPage: boolean): void { + if (this._disposed) { + return; + } + if (this._delegate.getTitle) { + this._webviewPanel.title = this._delegate.getTitle(this._resource); + } + this._webviewPanel.webview.options = this._getWebviewOptions(); + if (reloadPage) { + this._webviewPanel.webview.html = html; + } + else { + this.postMessage({ + type: 'updateContent', + content: html, + source: this._resource.toString(), + }); + } + } + private _updateImageWatchers(srcs: Set) { + // Delete stale file watchers. + for (const [src, watcher] of this._fileWatchersBySrc) { + if (!srcs.has(src)) { + watcher.dispose(); + this._fileWatchersBySrc.delete(src); + } + } + // Create new file watchers. + const root = vscode.Uri.joinPath(this._resource, '../'); + for (const src of srcs) { + const uri = urlToUri(src, root); + if (uri && !MarkdownPreview._unwatchedImageSchemes.has(uri.scheme) && !this._fileWatchersBySrc.has(src)) { + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*')); + watcher.onDidChange(() => { + this.refresh(true); + }); + this._fileWatchersBySrc.set(src, watcher); + } + } + } + private _getWebviewOptions(): vscode.WebviewOptions { + return { + enableScripts: true, + enableForms: false, + localResourceRoots: this._getLocalResourceRoots() + }; + } + private _getLocalResourceRoots(): ReadonlyArray { + const baseRoots = Array.from(this._contributionProvider.contributions.previewResourceRoots); + const folder = vscode.workspace.getWorkspaceFolder(this._resource); + if (folder) { + const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri); + if (workspaceRoots) { + baseRoots.push(...workspaceRoots); + } + } + else { + baseRoots.push(uri.Utils.dirname(this._resource)); + } + return baseRoots; + } + private async _onDidClickPreviewLink(href: string) { + const config = vscode.workspace.getConfiguration('markdown', this.resource); + const openLinks = config.get('preview.openMarkdownLinks', 'inPreview'); + if (openLinks === 'inPreview') { + const resolved = await this._opener.resolveDocumentLink(href, this.resource); + if (resolved.kind === 'file') { + try { + const doc = await vscode.workspace.openTextDocument(vscode.Uri.from(resolved.uri)); + if (isMarkdownFile(doc)) { + return this._delegate.openPreviewLinkToMarkdownFile(doc.uri, resolved.fragment ? decodeURIComponent(resolved.fragment) : undefined); + } + } + catch { + // Noop + } + } + } + return this._opener.openDocumentLink(href, this.resource); + } + //#region WebviewResourceProvider + asWebviewUri(resource: vscode.Uri) { + return this._webviewPanel.webview.asWebviewUri(resource); + } + get cspSource() { + return this._webviewPanel.webview.cspSource; + } } - export interface IManagedMarkdownPreview { - - readonly resource: vscode.Uri; - readonly resourceColumn: vscode.ViewColumn; - - readonly onDispose: vscode.Event; - readonly onDidChangeViewState: vscode.Event; - - copyImage(id: string): void; - dispose(): void; - refresh(): void; - updateConfiguration(): void; - - matchesResource( - otherResource: vscode.Uri, - otherPosition: vscode.ViewColumn | undefined, - otherLocked: boolean - ): boolean; + readonly resource: vscode.Uri; + readonly resourceColumn: vscode.ViewColumn; + readonly onDispose: vscode.Event; + readonly onDidChangeViewState: vscode.Event; + copyImage(id: string): void; + dispose(): void; + refresh(): void; + updateConfiguration(): void; + matchesResource(otherResource: vscode.Uri, otherPosition: vscode.ViewColumn | undefined, otherLocked: boolean): boolean; } - export class StaticMarkdownPreview extends Disposable implements IManagedMarkdownPreview { - - public static readonly customEditorViewType = 'vscode.markdown.preview.editor'; - - public static revive( - resource: vscode.Uri, - webview: vscode.WebviewPanel, - contentProvider: MdDocumentRenderer, - previewConfigurations: MarkdownPreviewConfigurationManager, - topmostLineMonitor: TopmostLineMonitor, - logger: ILogger, - contributionProvider: MarkdownContributionProvider, - opener: MdLinkOpener, - scrollLine?: number, - ): StaticMarkdownPreview { - return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, opener, scrollLine); - } - - private readonly _preview: MarkdownPreview; - - private constructor( - private readonly _webviewPanel: vscode.WebviewPanel, - resource: vscode.Uri, - contentProvider: MdDocumentRenderer, - private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, - topmostLineMonitor: TopmostLineMonitor, - logger: ILogger, - contributionProvider: MarkdownContributionProvider, - opener: MdLinkOpener, - scrollLine?: number, - ) { - super(); - const topScrollLocation = scrollLine ? new StartingScrollLine(scrollLine) : undefined; - this._preview = this._register(new MarkdownPreview(this._webviewPanel, resource, topScrollLocation, { - getAdditionalState: () => { return {}; }, - openPreviewLinkToMarkdownFile: (markdownLink, fragment) => { - return vscode.commands.executeCommand('vscode.openWith', markdownLink.with({ - fragment - }), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn); - } - }, contentProvider, _previewConfigurations, logger, contributionProvider, opener)); - - this._register(this._webviewPanel.onDidDispose(() => { - this.dispose(); - })); - - this._register(this._webviewPanel.onDidChangeViewState(e => { - this._onDidChangeViewState.fire(e); - })); - - this._register(this._preview.onScroll((scrollInfo) => { - topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo); - })); - - this._register(topmostLineMonitor.onDidChanged(event => { - if (this._preview.isPreviewOf(event.resource)) { - this._preview.scrollTo(event.line); - } - })); - } - - copyImage(id: string) { - this._webviewPanel.reveal(); - this._preview.postMessage({ - type: 'copyImage', - source: this.resource.toString(), - id: id - }); - } - - private readonly _onDispose = this._register(new vscode.EventEmitter()); - public readonly onDispose = this._onDispose.event; - - private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter()); - public readonly onDidChangeViewState = this._onDidChangeViewState.event; - - override dispose() { - this._onDispose.fire(); - super.dispose(); - } - - public matchesResource( - _otherResource: vscode.Uri, - _otherPosition: vscode.ViewColumn | undefined, - _otherLocked: boolean - ): boolean { - return false; - } - - public refresh() { - this._preview.refresh(true); - } - - public updateConfiguration() { - if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) { - this.refresh(); - } - } - - public get resource() { - return this._preview.resource; - } - - public get resourceColumn() { - return this._webviewPanel.viewColumn || vscode.ViewColumn.One; - } + public static readonly customEditorViewType = 'vscode.markdown.preview.editor'; + public static revive(resource: vscode.Uri, webview: vscode.WebviewPanel, contentProvider: MdDocumentRenderer, previewConfigurations: MarkdownPreviewConfigurationManager, topmostLineMonitor: TopmostLineMonitor, logger: ILogger, contributionProvider: MarkdownContributionProvider, opener: MdLinkOpener, scrollLine?: number): StaticMarkdownPreview { + return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, opener, scrollLine); + } + private readonly _preview: MarkdownPreview; + private constructor(private readonly _webviewPanel: vscode.WebviewPanel, resource: vscode.Uri, contentProvider: MdDocumentRenderer, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, topmostLineMonitor: TopmostLineMonitor, logger: ILogger, contributionProvider: MarkdownContributionProvider, opener: MdLinkOpener, scrollLine?: number) { + super(); + const topScrollLocation = scrollLine ? new StartingScrollLine(scrollLine) : undefined; + this._preview = this._register(new MarkdownPreview(this._webviewPanel, resource, topScrollLocation, { + getAdditionalState: () => { return {}; }, + openPreviewLinkToMarkdownFile: (markdownLink, fragment) => { + return vscode.commands.executeCommand('vscode.openWith', markdownLink.with({ + fragment + }), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn); + } + }, contentProvider, _previewConfigurations, logger, contributionProvider, opener)); + this._register(this._webviewPanel.onDidDispose(() => { + this.dispose(); + })); + this._register(this._webviewPanel.onDidChangeViewState(e => { + this._onDidChangeViewState.fire(e); + })); + this._register(this._preview.onScroll((scrollInfo) => { + topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo); + })); + this._register(topmostLineMonitor.onDidChanged(event => { + if (this._preview.isPreviewOf(event.resource)) { + this._preview.scrollTo(event.line); + } + })); + } + copyImage(id: string) { + this._webviewPanel.reveal(); + this._preview.postMessage({ + type: 'copyImage', + source: this.resource.toString(), + id: id + }); + } + private readonly _onDispose = this._register(new vscode.EventEmitter()); + public readonly onDispose = this._onDispose.event; + private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter()); + public readonly onDidChangeViewState = this._onDidChangeViewState.event; + override dispose() { + this._onDispose.fire(); + super.dispose(); + } + public matchesResource(_otherResource: vscode.Uri, _otherPosition: vscode.ViewColumn | undefined, _otherLocked: boolean): boolean { + return false; + } + public refresh() { + this._preview.refresh(true); + } + public updateConfiguration() { + if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) { + this.refresh(); + } + } + public get resource() { + return this._preview.resource; + } + public get resourceColumn() { + return this._webviewPanel.viewColumn || vscode.ViewColumn.One; + } } - interface DynamicPreviewInput { - readonly resource: vscode.Uri; - readonly resourceColumn: vscode.ViewColumn; - readonly locked: boolean; - readonly line?: number; + readonly resource: vscode.Uri; + readonly resourceColumn: vscode.ViewColumn; + readonly locked: boolean; + readonly line?: number; } - export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdownPreview { - - public static readonly viewType = 'markdown.preview'; - - private readonly _resourceColumn: vscode.ViewColumn; - private _locked: boolean; - - private readonly _webviewPanel: vscode.WebviewPanel; - private _preview: MarkdownPreview; - - public static revive( - input: DynamicPreviewInput, - webview: vscode.WebviewPanel, - contentProvider: MdDocumentRenderer, - previewConfigurations: MarkdownPreviewConfigurationManager, - logger: ILogger, - topmostLineMonitor: TopmostLineMonitor, - contributionProvider: MarkdownContributionProvider, - opener: MdLinkOpener, - ): DynamicMarkdownPreview { - webview.iconPath = contentProvider.iconPath; - - return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener); - } - - public static create( - input: DynamicPreviewInput, - previewColumn: vscode.ViewColumn, - contentProvider: MdDocumentRenderer, - previewConfigurations: MarkdownPreviewConfigurationManager, - logger: ILogger, - topmostLineMonitor: TopmostLineMonitor, - contributionProvider: MarkdownContributionProvider, - opener: MdLinkOpener, - ): DynamicMarkdownPreview { - const webview = vscode.window.createWebviewPanel( - DynamicMarkdownPreview.viewType, - DynamicMarkdownPreview._getPreviewTitle(input.resource, input.locked), - previewColumn, { enableFindWidget: true, }); - - webview.iconPath = contentProvider.iconPath; - - return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener); - } - - private constructor( - webview: vscode.WebviewPanel, - input: DynamicPreviewInput, - private readonly _contentProvider: MdDocumentRenderer, - private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, - private readonly _logger: ILogger, - private readonly _topmostLineMonitor: TopmostLineMonitor, - private readonly _contributionProvider: MarkdownContributionProvider, - private readonly _opener: MdLinkOpener, - ) { - super(); - - this._webviewPanel = webview; - - this._resourceColumn = input.resourceColumn; - this._locked = input.locked; - - this._preview = this._createPreview(input.resource, typeof input.line === 'number' ? new StartingScrollLine(input.line) : undefined); - - this._register(webview.onDidDispose(() => { this.dispose(); })); - - this._register(this._webviewPanel.onDidChangeViewState(e => { - this._onDidChangeViewStateEmitter.fire(e); - })); - - this._register(this._topmostLineMonitor.onDidChanged(event => { - if (this._preview.isPreviewOf(event.resource)) { - this._preview.scrollTo(event.line); - } - })); - - this._register(vscode.window.onDidChangeTextEditorSelection(event => { - if (this._preview.isPreviewOf(event.textEditor.document.uri)) { - this._preview.postMessage({ - type: 'onDidChangeTextEditorSelection', - line: event.selections[0].active.line, - source: this._preview.resource.toString() - }); - } - })); - - this._register(vscode.window.onDidChangeActiveTextEditor(editor => { - // Only allow previewing normal text editors which have a viewColumn: See #101514 - if (typeof editor?.viewColumn === 'undefined') { - return; - } - - if (isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) { - const line = getVisibleLine(editor); - this.update(editor.document.uri, line ? new StartingScrollLine(line) : undefined); - } - })); - } - - copyImage(id: string) { - this._webviewPanel.reveal(); - this._preview.postMessage({ - type: 'copyImage', - source: this.resource.toString(), - id: id - }); - } - - private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter()); - public readonly onDispose = this._onDisposeEmitter.event; - - private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter()); - public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event; - - override dispose() { - this._preview.dispose(); - this._webviewPanel.dispose(); - - this._onDisposeEmitter.fire(); - this._onDisposeEmitter.dispose(); - super.dispose(); - } - - public get resource() { - return this._preview.resource; - } - - public get resourceColumn() { - return this._resourceColumn; - } - - public reveal(viewColumn: vscode.ViewColumn) { - this._webviewPanel.reveal(viewColumn); - } - - public refresh() { - this._preview.refresh(true); - } - - public updateConfiguration() { - if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) { - this.refresh(); - } - } - - public update(newResource: vscode.Uri, scrollLocation?: StartingScrollLocation) { - if (this._preview.isPreviewOf(newResource)) { - switch (scrollLocation?.type) { - case 'line': - this._preview.scrollTo(scrollLocation.line); - return; - - case 'fragment': - // Workaround. For fragments, just reload the entire preview - break; - - default: - return; - } - } - - this._preview.dispose(); - this._preview = this._createPreview(newResource, scrollLocation); - } - - public toggleLock() { - this._locked = !this._locked; - this._webviewPanel.title = DynamicMarkdownPreview._getPreviewTitle(this._preview.resource, this._locked); - } - - private static _getPreviewTitle(resource: vscode.Uri, locked: boolean): string { - const resourceLabel = uri.Utils.basename(resource); - return locked - ? vscode.l10n.t('[Preview] {0}', resourceLabel) - : vscode.l10n.t('Preview {0}', resourceLabel); - } - - public get position(): vscode.ViewColumn | undefined { - return this._webviewPanel.viewColumn; - } - - public matchesResource( - otherResource: vscode.Uri, - otherPosition: vscode.ViewColumn | undefined, - otherLocked: boolean - ): boolean { - if (this.position !== otherPosition) { - return false; - } - - if (this._locked) { - return otherLocked && this._preview.isPreviewOf(otherResource); - } else { - return !otherLocked; - } - } - - public matches(otherPreview: DynamicMarkdownPreview): boolean { - return this.matchesResource(otherPreview._preview.resource, otherPreview.position, otherPreview._locked); - } - - private _createPreview(resource: vscode.Uri, startingScroll?: StartingScrollLocation): MarkdownPreview { - return new MarkdownPreview(this._webviewPanel, resource, startingScroll, { - getTitle: (resource) => DynamicMarkdownPreview._getPreviewTitle(resource, this._locked), - getAdditionalState: () => { - return { - resourceColumn: this.resourceColumn, - locked: this._locked, - }; - }, - openPreviewLinkToMarkdownFile: (link: vscode.Uri, fragment?: string) => { - this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined); - } - }, - this._contentProvider, - this._previewConfigurations, - this._logger, - this._contributionProvider, - this._opener); - } + public static readonly viewType = 'markdown.preview'; + private readonly _resourceColumn: vscode.ViewColumn; + private _locked: boolean; + private readonly _webviewPanel: vscode.WebviewPanel; + private _preview: MarkdownPreview; + public static revive(input: DynamicPreviewInput, webview: vscode.WebviewPanel, contentProvider: MdDocumentRenderer, previewConfigurations: MarkdownPreviewConfigurationManager, logger: ILogger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, opener: MdLinkOpener): DynamicMarkdownPreview { + webview.iconPath = contentProvider.iconPath; + return new DynamicMarkdownPreview(webview, input, contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener); + } + public static create(input: DynamicPreviewInput, previewColumn: vscode.ViewColumn, contentProvider: MdDocumentRenderer, previewConfigurations: MarkdownPreviewConfigurationManager, logger: ILogger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, opener: MdLinkOpener): DynamicMarkdownPreview { + const webview = vscode.window.createWebviewPanel(DynamicMarkdownPreview.viewType, DynamicMarkdownPreview._getPreviewTitle(input.resource, input.locked), previewColumn, { enableFindWidget: true, }); + webview.iconPath = contentProvider.iconPath; + return new DynamicMarkdownPreview(webview, input, contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener); + } + private constructor(webview: vscode.WebviewPanel, input: DynamicPreviewInput, private readonly _contentProvider: MdDocumentRenderer, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, private readonly _logger: ILogger, private readonly _topmostLineMonitor: TopmostLineMonitor, private readonly _contributionProvider: MarkdownContributionProvider, private readonly _opener: MdLinkOpener) { + super(); + this._webviewPanel = webview; + this._resourceColumn = input.resourceColumn; + this._locked = input.locked; + this._preview = this._createPreview(input.resource, typeof input.line === 'number' ? new StartingScrollLine(input.line) : undefined); + this._register(webview.onDidDispose(() => { this.dispose(); })); + this._register(this._webviewPanel.onDidChangeViewState(e => { + this._onDidChangeViewStateEmitter.fire(e); + })); + this._register(this._topmostLineMonitor.onDidChanged(event => { + if (this._preview.isPreviewOf(event.resource)) { + this._preview.scrollTo(event.line); + } + })); + this._register(vscode.window.onDidChangeTextEditorSelection(event => { + if (this._preview.isPreviewOf(event.textEditor.document.uri)) { + this._preview.postMessage({ + type: 'onDidChangeTextEditorSelection', + line: event.selections[0].active.line, + source: this._preview.resource.toString() + }); + } + })); + this._register(vscode.window.onDidChangeActiveTextEditor(editor => { + // Only allow previewing normal text editors which have a viewColumn: See #101514 + if (typeof editor?.viewColumn === 'undefined') { + return; + } + if (isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) { + const line = getVisibleLine(editor); + this.update(editor.document.uri, line ? new StartingScrollLine(line) : undefined); + } + })); + } + copyImage(id: string) { + this._webviewPanel.reveal(); + this._preview.postMessage({ + type: 'copyImage', + source: this.resource.toString(), + id: id + }); + } + private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter()); + public readonly onDispose = this._onDisposeEmitter.event; + private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter()); + public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event; + override dispose() { + this._preview.dispose(); + this._webviewPanel.dispose(); + this._onDisposeEmitter.fire(); + this._onDisposeEmitter.dispose(); + super.dispose(); + } + public get resource() { + return this._preview.resource; + } + public get resourceColumn() { + return this._resourceColumn; + } + public reveal(viewColumn: vscode.ViewColumn) { + this._webviewPanel.reveal(viewColumn); + } + public refresh() { + this._preview.refresh(true); + } + public updateConfiguration() { + if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) { + this.refresh(); + } + } + public update(newResource: vscode.Uri, scrollLocation?: StartingScrollLocation) { + if (this._preview.isPreviewOf(newResource)) { + switch (scrollLocation?.type) { + case 'line': + this._preview.scrollTo(scrollLocation.line); + return; + case 'fragment': + // Workaround. For fragments, just reload the entire preview + break; + default: + return; + } + } + this._preview.dispose(); + this._preview = this._createPreview(newResource, scrollLocation); + } + public toggleLock() { + this._locked = !this._locked; + this._webviewPanel.title = DynamicMarkdownPreview._getPreviewTitle(this._preview.resource, this._locked); + } + private static _getPreviewTitle(resource: vscode.Uri, locked: boolean): string { + const resourceLabel = uri.Utils.basename(resource); + return locked + ? vscode.l10n.t('[Preview] {0}', resourceLabel) + : vscode.l10n.t('Preview {0}', resourceLabel); + } + public get position(): vscode.ViewColumn | undefined { + return this._webviewPanel.viewColumn; + } + public matchesResource(otherResource: vscode.Uri, otherPosition: vscode.ViewColumn | undefined, otherLocked: boolean): boolean { + if (this.position !== otherPosition) { + return false; + } + if (this._locked) { + return otherLocked && this._preview.isPreviewOf(otherResource); + } + else { + return !otherLocked; + } + } + public matches(otherPreview: DynamicMarkdownPreview): boolean { + return this.matchesResource(otherPreview._preview.resource, otherPreview.position, otherPreview._locked); + } + private _createPreview(resource: vscode.Uri, startingScroll?: StartingScrollLocation): MarkdownPreview { + return new MarkdownPreview(this._webviewPanel, resource, startingScroll, { + getTitle: (resource) => DynamicMarkdownPreview._getPreviewTitle(resource, this._locked), + getAdditionalState: () => { + return { + resourceColumn: this.resourceColumn, + locked: this._locked, + }; + }, + openPreviewLinkToMarkdownFile: (link: vscode.Uri, fragment?: string) => { + this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined); + } + }, this._contentProvider, this._previewConfigurations, this._logger, this._contributionProvider, this._opener); + } } diff --git a/extensions/markdown-language-features/Source/preview/previewConfig.ts b/extensions/markdown-language-features/Source/preview/previewConfig.ts index 01c9a66c32c9e..dcaf7b5d4dacd 100644 --- a/extensions/markdown-language-features/Source/preview/previewConfig.ts +++ b/extensions/markdown-language-features/Source/preview/previewConfig.ts @@ -2,100 +2,73 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { equals } from '../util/arrays'; - export class MarkdownPreviewConfiguration { - public static getForResource(resource: vscode.Uri | null) { - return new MarkdownPreviewConfiguration(resource); - } - - public readonly scrollBeyondLastLine: boolean; - public readonly wordWrap: boolean; - - public readonly previewLineBreaks: boolean; - public readonly previewLinkify: boolean; - public readonly previewTypographer: boolean; - - public readonly doubleClickToSwitchToEditor: boolean; - public readonly scrollEditorWithPreview: boolean; - public readonly scrollPreviewWithEditor: boolean; - public readonly markEditorSelection: boolean; - - public readonly lineHeight: number; - public readonly fontSize: number; - public readonly fontFamily: string | undefined; - public readonly styles: readonly string[]; - - private constructor(resource: vscode.Uri | null) { - const editorConfig = vscode.workspace.getConfiguration('editor', resource); - const markdownConfig = vscode.workspace.getConfiguration('markdown', resource); - const markdownEditorConfig = vscode.workspace.getConfiguration('[markdown]', resource); - - this.scrollBeyondLastLine = editorConfig.get('scrollBeyondLastLine', false); - - this.wordWrap = editorConfig.get('wordWrap', 'off') !== 'off'; - if (markdownEditorConfig && markdownEditorConfig['editor.wordWrap']) { - this.wordWrap = markdownEditorConfig['editor.wordWrap'] !== 'off'; - } - - this.scrollPreviewWithEditor = !!markdownConfig.get('preview.scrollPreviewWithEditor', true); - this.scrollEditorWithPreview = !!markdownConfig.get('preview.scrollEditorWithPreview', true); - - this.previewLineBreaks = !!markdownConfig.get('preview.breaks', false); - this.previewLinkify = !!markdownConfig.get('preview.linkify', true); - this.previewTypographer = !!markdownConfig.get('preview.typographer', false); - - this.doubleClickToSwitchToEditor = !!markdownConfig.get('preview.doubleClickToSwitchToEditor', true); - this.markEditorSelection = !!markdownConfig.get('preview.markEditorSelection', true); - - this.fontFamily = markdownConfig.get('preview.fontFamily', undefined); - this.fontSize = Math.max(8, +markdownConfig.get('preview.fontSize', NaN)); - this.lineHeight = Math.max(0.6, +markdownConfig.get('preview.lineHeight', NaN)); - - this.styles = markdownConfig.get('styles', []); - } - - public isEqualTo(otherConfig: MarkdownPreviewConfiguration) { - for (const key in this) { - if (this.hasOwnProperty(key) && key !== 'styles') { - if (this[key] !== otherConfig[key]) { - return false; - } - } - } - - return equals(this.styles, otherConfig.styles); - } - - readonly [key: string]: any; + public static getForResource(resource: vscode.Uri | null) { + return new MarkdownPreviewConfiguration(resource); + } + public readonly scrollBeyondLastLine: boolean; + public readonly wordWrap: boolean; + public readonly previewLineBreaks: boolean; + public readonly previewLinkify: boolean; + public readonly previewTypographer: boolean; + public readonly doubleClickToSwitchToEditor: boolean; + public readonly scrollEditorWithPreview: boolean; + public readonly scrollPreviewWithEditor: boolean; + public readonly markEditorSelection: boolean; + public readonly lineHeight: number; + public readonly fontSize: number; + public readonly fontFamily: string | undefined; + public readonly styles: readonly string[]; + private constructor(resource: vscode.Uri | null) { + const editorConfig = vscode.workspace.getConfiguration('editor', resource); + const markdownConfig = vscode.workspace.getConfiguration('markdown', resource); + const markdownEditorConfig = vscode.workspace.getConfiguration('[markdown]', resource); + this.scrollBeyondLastLine = editorConfig.get('scrollBeyondLastLine', false); + this.wordWrap = editorConfig.get('wordWrap', 'off') !== 'off'; + if (markdownEditorConfig && markdownEditorConfig['editor.wordWrap']) { + this.wordWrap = markdownEditorConfig['editor.wordWrap'] !== 'off'; + } + this.scrollPreviewWithEditor = !!markdownConfig.get('preview.scrollPreviewWithEditor', true); + this.scrollEditorWithPreview = !!markdownConfig.get('preview.scrollEditorWithPreview', true); + this.previewLineBreaks = !!markdownConfig.get('preview.breaks', false); + this.previewLinkify = !!markdownConfig.get('preview.linkify', true); + this.previewTypographer = !!markdownConfig.get('preview.typographer', false); + this.doubleClickToSwitchToEditor = !!markdownConfig.get('preview.doubleClickToSwitchToEditor', true); + this.markEditorSelection = !!markdownConfig.get('preview.markEditorSelection', true); + this.fontFamily = markdownConfig.get('preview.fontFamily', undefined); + this.fontSize = Math.max(8, +markdownConfig.get('preview.fontSize', NaN)); + this.lineHeight = Math.max(0.6, +markdownConfig.get('preview.lineHeight', NaN)); + this.styles = markdownConfig.get('styles', []); + } + public isEqualTo(otherConfig: MarkdownPreviewConfiguration) { + for (const key in this) { + if (this.hasOwnProperty(key) && key !== 'styles') { + if (this[key] !== otherConfig[key]) { + return false; + } + } + } + return equals(this.styles, otherConfig.styles); + } + readonly [key: string]: any; } - export class MarkdownPreviewConfigurationManager { - private readonly _previewConfigurationsForWorkspaces = new Map(); - - public loadAndCacheConfiguration( - resource: vscode.Uri - ): MarkdownPreviewConfiguration { - const config = MarkdownPreviewConfiguration.getForResource(resource); - this._previewConfigurationsForWorkspaces.set(this._getKey(resource), config); - return config; - } - - public hasConfigurationChanged( - resource: vscode.Uri - ): boolean { - const key = this._getKey(resource); - const currentConfig = this._previewConfigurationsForWorkspaces.get(key); - const newConfig = MarkdownPreviewConfiguration.getForResource(resource); - return (!currentConfig || !currentConfig.isEqualTo(newConfig)); - } - - private _getKey( - resource: vscode.Uri - ): string { - const folder = vscode.workspace.getWorkspaceFolder(resource); - return folder ? folder.uri.toString() : ''; - } + private readonly _previewConfigurationsForWorkspaces = new Map(); + public loadAndCacheConfiguration(resource: vscode.Uri): MarkdownPreviewConfiguration { + const config = MarkdownPreviewConfiguration.getForResource(resource); + this._previewConfigurationsForWorkspaces.set(this._getKey(resource), config); + return config; + } + public hasConfigurationChanged(resource: vscode.Uri): boolean { + const key = this._getKey(resource); + const currentConfig = this._previewConfigurationsForWorkspaces.get(key); + const newConfig = MarkdownPreviewConfiguration.getForResource(resource); + return (!currentConfig || !currentConfig.isEqualTo(newConfig)); + } + private _getKey(resource: vscode.Uri): string { + const folder = vscode.workspace.getWorkspaceFolder(resource); + return folder ? folder.uri.toString() : ''; + } } diff --git a/extensions/markdown-language-features/Source/preview/previewManager.ts b/extensions/markdown-language-features/Source/preview/previewManager.ts index 3b46c2a432203..7c3f0bbf4727d 100644 --- a/extensions/markdown-language-features/Source/preview/previewManager.ts +++ b/extensions/markdown-language-features/Source/preview/previewManager.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { ILogger } from '../logging'; import { MarkdownContributionProvider } from '../markdownExtensions'; @@ -14,187 +13,134 @@ import { DynamicMarkdownPreview, IManagedMarkdownPreview, StaticMarkdownPreview import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { scrollEditorToLine, StartingScrollFragment } from './scrolling'; import { TopmostLineMonitor } from './topmostLineMonitor'; - - export interface DynamicPreviewSettings { - readonly resourceColumn: vscode.ViewColumn; - readonly previewColumn: vscode.ViewColumn; - readonly locked: boolean; + readonly resourceColumn: vscode.ViewColumn; + readonly previewColumn: vscode.ViewColumn; + readonly locked: boolean; } - class PreviewStore extends Disposable { - - private readonly _previews = new Set(); - - public override dispose(): void { - super.dispose(); - for (const preview of this._previews) { - preview.dispose(); - } - this._previews.clear(); - } - - [Symbol.iterator](): Iterator { - return this._previews[Symbol.iterator](); - } - - public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined { - const previewColumn = this._resolvePreviewColumn(previewSettings); - for (const preview of this._previews) { - if (preview.matchesResource(resource, previewColumn, previewSettings.locked)) { - return preview; - } - } - return undefined; - } - - public add(preview: T) { - this._previews.add(preview); - } - - public delete(preview: T) { - this._previews.delete(preview); - } - - private _resolvePreviewColumn(previewSettings: DynamicPreviewSettings): vscode.ViewColumn | undefined { - if (previewSettings.previewColumn === vscode.ViewColumn.Active) { - return vscode.window.tabGroups.activeTabGroup.viewColumn; - } - - if (previewSettings.previewColumn === vscode.ViewColumn.Beside) { - return vscode.window.tabGroups.activeTabGroup.viewColumn + 1; - } - - return previewSettings.previewColumn; - } + private readonly _previews = new Set(); + public override dispose(): void { + super.dispose(); + for (const preview of this._previews) { + preview.dispose(); + } + this._previews.clear(); + } + [Symbol.iterator](): Iterator { + return this._previews[Symbol.iterator](); + } + public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined { + const previewColumn = this._resolvePreviewColumn(previewSettings); + for (const preview of this._previews) { + if (preview.matchesResource(resource, previewColumn, previewSettings.locked)) { + return preview; + } + } + return undefined; + } + public add(preview: T) { + this._previews.add(preview); + } + public delete(preview: T) { + this._previews.delete(preview); + } + private _resolvePreviewColumn(previewSettings: DynamicPreviewSettings): vscode.ViewColumn | undefined { + if (previewSettings.previewColumn === vscode.ViewColumn.Active) { + return vscode.window.tabGroups.activeTabGroup.viewColumn; + } + if (previewSettings.previewColumn === vscode.ViewColumn.Beside) { + return vscode.window.tabGroups.activeTabGroup.viewColumn + 1; + } + return previewSettings.previewColumn; + } } - export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider { - - private readonly _topmostLineMonitor = new TopmostLineMonitor(); - private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager(); - - private readonly _dynamicPreviews = this._register(new PreviewStore()); - private readonly _staticPreviews = this._register(new PreviewStore()); - - private _activePreview: IManagedMarkdownPreview | undefined = undefined; - - public constructor( - private readonly _contentProvider: MdDocumentRenderer, - private readonly _logger: ILogger, - private readonly _contributions: MarkdownContributionProvider, - private readonly _opener: MdLinkOpener, - ) { - super(); - - this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); - - this._register(vscode.window.registerCustomEditorProvider(StaticMarkdownPreview.customEditorViewType, this, { - webviewOptions: { enableFindWidget: true } - })); - - this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => { - // When at a markdown file, apply existing scroll settings - if (textEditor?.document && isMarkdownFile(textEditor.document)) { - const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri); - if (typeof line === 'number') { - scrollEditorToLine(line, textEditor); - } - } - })); - } - - public refresh() { - for (const preview of this._dynamicPreviews) { - preview.refresh(); - } - for (const preview of this._staticPreviews) { - preview.refresh(); - } - } - - public updateConfiguration() { - for (const preview of this._dynamicPreviews) { - preview.updateConfiguration(); - } - for (const preview of this._staticPreviews) { - preview.updateConfiguration(); - } - } - - public openDynamicPreview( - resource: vscode.Uri, - settings: DynamicPreviewSettings - ): void { - let preview = this._dynamicPreviews.get(resource, settings); - if (preview) { - preview.reveal(settings.previewColumn); - } else { - preview = this._createNewDynamicPreview(resource, settings); - } - - preview.update( - resource, - resource.fragment ? new StartingScrollFragment(resource.fragment) : undefined - ); - } - - public get activePreviewResource() { - return this._activePreview?.resource; - } - - public get activePreviewResourceColumn() { - return this._activePreview?.resourceColumn; - } - - public findPreview(resource: vscode.Uri): IManagedMarkdownPreview | undefined { - for (const preview of [...this._dynamicPreviews, ...this._staticPreviews]) { - if (preview.resource.fsPath === resource.fsPath) { - return preview; - } - } - return undefined; - } - - public toggleLock() { - const preview = this._activePreview; - if (preview instanceof DynamicMarkdownPreview) { - preview.toggleLock(); - - // Close any previews that are now redundant, such as having two dynamic previews in the same editor group - for (const otherPreview of this._dynamicPreviews) { - if (otherPreview !== preview && preview.matches(otherPreview)) { - otherPreview.dispose(); - } - } - } - } - - public async deserializeWebviewPanel( - webview: vscode.WebviewPanel, - state: any - ): Promise { - try { - const resource = vscode.Uri.parse(state.resource); - const locked = state.locked; - const line = state.line; - const resourceColumn = state.resourceColumn; - - const preview = DynamicMarkdownPreview.revive( - { resource, locked, line, resourceColumn }, - webview, - this._contentProvider, - this._previewConfigurations, - this._logger, - this._topmostLineMonitor, - this._contributions, - this._opener); - - this._registerDynamicPreview(preview); - } catch (e) { - console.error(e); - - webview.webview.html = /* html */` + private readonly _topmostLineMonitor = new TopmostLineMonitor(); + private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager(); + private readonly _dynamicPreviews = this._register(new PreviewStore()); + private readonly _staticPreviews = this._register(new PreviewStore()); + private _activePreview: IManagedMarkdownPreview | undefined = undefined; + public constructor(private readonly _contentProvider: MdDocumentRenderer, private readonly _logger: ILogger, private readonly _contributions: MarkdownContributionProvider, private readonly _opener: MdLinkOpener) { + super(); + this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); + this._register(vscode.window.registerCustomEditorProvider(StaticMarkdownPreview.customEditorViewType, this, { + webviewOptions: { enableFindWidget: true } + })); + this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => { + // When at a markdown file, apply existing scroll settings + if (textEditor?.document && isMarkdownFile(textEditor.document)) { + const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri); + if (typeof line === 'number') { + scrollEditorToLine(line, textEditor); + } + } + })); + } + public refresh() { + for (const preview of this._dynamicPreviews) { + preview.refresh(); + } + for (const preview of this._staticPreviews) { + preview.refresh(); + } + } + public updateConfiguration() { + for (const preview of this._dynamicPreviews) { + preview.updateConfiguration(); + } + for (const preview of this._staticPreviews) { + preview.updateConfiguration(); + } + } + public openDynamicPreview(resource: vscode.Uri, settings: DynamicPreviewSettings): void { + let preview = this._dynamicPreviews.get(resource, settings); + if (preview) { + preview.reveal(settings.previewColumn); + } + else { + preview = this._createNewDynamicPreview(resource, settings); + } + preview.update(resource, resource.fragment ? new StartingScrollFragment(resource.fragment) : undefined); + } + public get activePreviewResource() { + return this._activePreview?.resource; + } + public get activePreviewResourceColumn() { + return this._activePreview?.resourceColumn; + } + public findPreview(resource: vscode.Uri): IManagedMarkdownPreview | undefined { + for (const preview of [...this._dynamicPreviews, ...this._staticPreviews]) { + if (preview.resource.fsPath === resource.fsPath) { + return preview; + } + } + return undefined; + } + public toggleLock() { + const preview = this._activePreview; + if (preview instanceof DynamicMarkdownPreview) { + preview.toggleLock(); + // Close any previews that are now redundant, such as having two dynamic previews in the same editor group + for (const otherPreview of this._dynamicPreviews) { + if (otherPreview !== preview && preview.matches(otherPreview)) { + otherPreview.dispose(); + } + } + } + } + public async deserializeWebviewPanel(webview: vscode.WebviewPanel, state: any): Promise { + try { + const resource = vscode.Uri.parse(state.resource); + const locked = state.locked; + const line = state.line; + const resourceColumn = state.resourceColumn; + const preview = DynamicMarkdownPreview.revive({ resource, locked, line, resourceColumn }, webview, this._contentProvider, this._previewConfigurations, this._logger, this._topmostLineMonitor, this._contributions, this._opener); + this._registerDynamicPreview(preview); + } + catch (e) { + console.error(e); + webview.webview.html = /* html */ ` @@ -225,91 +171,54 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview

${vscode.l10n.t("An unexpected error occurred while restoring the Markdown preview.")}

`; - } - } - - public async resolveCustomTextEditor( - document: vscode.TextDocument, - webview: vscode.WebviewPanel - ): Promise { - const lineNumber = this._topmostLineMonitor.getPreviousStaticTextEditorLineByUri(document.uri); - const preview = StaticMarkdownPreview.revive( - document.uri, - webview, - this._contentProvider, - this._previewConfigurations, - this._topmostLineMonitor, - this._logger, - this._contributions, - this._opener, - lineNumber - ); - this._registerStaticPreview(preview); - this._activePreview = preview; - } - - private _createNewDynamicPreview( - resource: vscode.Uri, - previewSettings: DynamicPreviewSettings - ): DynamicMarkdownPreview { - const activeTextEditorURI = vscode.window.activeTextEditor?.document.uri; - const scrollLine = (activeTextEditorURI?.toString() === resource.toString()) ? vscode.window.activeTextEditor?.visibleRanges[0].start.line : undefined; - const preview = DynamicMarkdownPreview.create( - { - resource, - resourceColumn: previewSettings.resourceColumn, - locked: previewSettings.locked, - line: scrollLine, - }, - previewSettings.previewColumn, - this._contentProvider, - this._previewConfigurations, - this._logger, - this._topmostLineMonitor, - this._contributions, - this._opener); - - this._activePreview = preview; - return this._registerDynamicPreview(preview); - } - - private _registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview { - this._dynamicPreviews.add(preview); - - preview.onDispose(() => { - this._dynamicPreviews.delete(preview); - }); - - this._trackActive(preview); - - preview.onDidChangeViewState(() => { - // Remove other dynamic previews in our column - disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview))); - }); - return preview; - } - - private _registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview { - this._staticPreviews.add(preview); - - preview.onDispose(() => { - this._staticPreviews.delete(preview); - }); - - this._trackActive(preview); - return preview; - } - - private _trackActive(preview: IManagedMarkdownPreview): void { - preview.onDidChangeViewState(({ webviewPanel }) => { - this._activePreview = webviewPanel.active ? preview : undefined; - }); - - preview.onDispose(() => { - if (this._activePreview === preview) { - this._activePreview = undefined; - } - }); - } - + } + } + public async resolveCustomTextEditor(document: vscode.TextDocument, webview: vscode.WebviewPanel): Promise { + const lineNumber = this._topmostLineMonitor.getPreviousStaticTextEditorLineByUri(document.uri); + const preview = StaticMarkdownPreview.revive(document.uri, webview, this._contentProvider, this._previewConfigurations, this._topmostLineMonitor, this._logger, this._contributions, this._opener, lineNumber); + this._registerStaticPreview(preview); + this._activePreview = preview; + } + private _createNewDynamicPreview(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): DynamicMarkdownPreview { + const activeTextEditorURI = vscode.window.activeTextEditor?.document.uri; + const scrollLine = (activeTextEditorURI?.toString() === resource.toString()) ? vscode.window.activeTextEditor?.visibleRanges[0].start.line : undefined; + const preview = DynamicMarkdownPreview.create({ + resource, + resourceColumn: previewSettings.resourceColumn, + locked: previewSettings.locked, + line: scrollLine, + }, previewSettings.previewColumn, this._contentProvider, this._previewConfigurations, this._logger, this._topmostLineMonitor, this._contributions, this._opener); + this._activePreview = preview; + return this._registerDynamicPreview(preview); + } + private _registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview { + this._dynamicPreviews.add(preview); + preview.onDispose(() => { + this._dynamicPreviews.delete(preview); + }); + this._trackActive(preview); + preview.onDidChangeViewState(() => { + // Remove other dynamic previews in our column + disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview))); + }); + return preview; + } + private _registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview { + this._staticPreviews.add(preview); + preview.onDispose(() => { + this._staticPreviews.delete(preview); + }); + this._trackActive(preview); + return preview; + } + private _trackActive(preview: IManagedMarkdownPreview): void { + preview.onDidChangeViewState(({ webviewPanel }) => { + this._activePreview = webviewPanel.active ? preview : undefined; + }); + preview.onDispose(() => { + if (this._activePreview === preview) { + this._activePreview = undefined; + } + }); + } } diff --git a/extensions/markdown-language-features/Source/preview/scrolling.ts b/extensions/markdown-language-features/Source/preview/scrolling.ts index aa0798fb747fb..d4f6ab118c47f 100644 --- a/extensions/markdown-language-features/Source/preview/scrolling.ts +++ b/extensions/markdown-language-features/Source/preview/scrolling.ts @@ -3,45 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; - /** * Change the top-most visible line of `editor` to be at `line` */ -export function scrollEditorToLine( - line: number, - editor: vscode.TextEditor -) { - const revealRange = toRevealRange(line, editor); - editor.revealRange(revealRange, vscode.TextEditorRevealType.AtTop); +export function scrollEditorToLine(line: number, editor: vscode.TextEditor) { + const revealRange = toRevealRange(line, editor); + editor.revealRange(revealRange, vscode.TextEditorRevealType.AtTop); } - function toRevealRange(line: number, editor: vscode.TextEditor): vscode.Range { - line = Math.max(0, line); - const sourceLine = Math.floor(line); - if (sourceLine >= editor.document.lineCount) { - return new vscode.Range(editor.document.lineCount - 1, 0, editor.document.lineCount - 1, 0); - } - - const fraction = line - sourceLine; - const text = editor.document.lineAt(sourceLine).text; - const start = Math.floor(fraction * text.length); - return new vscode.Range(sourceLine, start, sourceLine + 1, 0); + line = Math.max(0, line); + const sourceLine = Math.floor(line); + if (sourceLine >= editor.document.lineCount) { + return new vscode.Range(editor.document.lineCount - 1, 0, editor.document.lineCount - 1, 0); + } + const fraction = line - sourceLine; + const text = editor.document.lineAt(sourceLine).text; + const start = Math.floor(fraction * text.length); + return new vscode.Range(sourceLine, start, sourceLine + 1, 0); } - export class StartingScrollFragment { - public readonly type = 'fragment'; - - constructor( - public readonly fragment: string, - ) { } + public readonly type = 'fragment'; + constructor(public readonly fragment: string) { } } - export class StartingScrollLine { - public readonly type = 'line'; - - constructor( - public readonly line: number, - ) { } + public readonly type = 'line'; + constructor(public readonly line: number) { } } - export type StartingScrollLocation = StartingScrollLine | StartingScrollFragment; diff --git a/extensions/markdown-language-features/Source/preview/security.ts b/extensions/markdown-language-features/Source/preview/security.ts index 0ba1a9f61f087..2a413029e4d38 100644 --- a/extensions/markdown-language-features/Source/preview/security.ts +++ b/extensions/markdown-language-features/Source/preview/security.ts @@ -2,152 +2,120 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { MarkdownPreviewManager } from './previewManager'; - - export const enum MarkdownPreviewSecurityLevel { - Strict = 0, - AllowInsecureContent = 1, - AllowScriptsAndAllContent = 2, - AllowInsecureLocalContent = 3 + Strict = 0, + AllowInsecureContent = 1, + AllowScriptsAndAllContent = 2, + AllowInsecureLocalContent = 3 } - export interface ContentSecurityPolicyArbiter { - getSecurityLevelForResource(resource: vscode.Uri): MarkdownPreviewSecurityLevel; - - setSecurityLevelForResource(resource: vscode.Uri, level: MarkdownPreviewSecurityLevel): Thenable; - - shouldAllowSvgsForResource(resource: vscode.Uri): void; - - shouldDisableSecurityWarnings(): boolean; - - setShouldDisableSecurityWarning(shouldShow: boolean): Thenable; + getSecurityLevelForResource(resource: vscode.Uri): MarkdownPreviewSecurityLevel; + setSecurityLevelForResource(resource: vscode.Uri, level: MarkdownPreviewSecurityLevel): Thenable; + shouldAllowSvgsForResource(resource: vscode.Uri): void; + shouldDisableSecurityWarnings(): boolean; + setShouldDisableSecurityWarning(shouldShow: boolean): Thenable; } - export class ExtensionContentSecurityPolicyArbiter implements ContentSecurityPolicyArbiter { - private readonly _old_trusted_workspace_key = 'trusted_preview_workspace:'; - private readonly _security_level_key = 'preview_security_level:'; - private readonly _should_disable_security_warning_key = 'preview_should_show_security_warning:'; - - constructor( - private readonly _globalState: vscode.Memento, - private readonly _workspaceState: vscode.Memento - ) { } - - public getSecurityLevelForResource(resource: vscode.Uri): MarkdownPreviewSecurityLevel { - // Use new security level setting first - const level = this._globalState.get(this._security_level_key + this._getRoot(resource), undefined); - if (typeof level !== 'undefined') { - return level; - } - - // Fallback to old trusted workspace setting - if (this._globalState.get(this._old_trusted_workspace_key + this._getRoot(resource), false)) { - return MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent; - } - return MarkdownPreviewSecurityLevel.Strict; - } - - public setSecurityLevelForResource(resource: vscode.Uri, level: MarkdownPreviewSecurityLevel): Thenable { - return this._globalState.update(this._security_level_key + this._getRoot(resource), level); - } - - public shouldAllowSvgsForResource(resource: vscode.Uri) { - const securityLevel = this.getSecurityLevelForResource(resource); - return securityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent || securityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent; - } - - public shouldDisableSecurityWarnings(): boolean { - return this._workspaceState.get(this._should_disable_security_warning_key, false); - } - - public setShouldDisableSecurityWarning(disabled: boolean): Thenable { - return this._workspaceState.update(this._should_disable_security_warning_key, disabled); - } - - private _getRoot(resource: vscode.Uri): vscode.Uri { - if (vscode.workspace.workspaceFolders) { - const folderForResource = vscode.workspace.getWorkspaceFolder(resource); - if (folderForResource) { - return folderForResource.uri; - } - - if (vscode.workspace.workspaceFolders.length) { - return vscode.workspace.workspaceFolders[0].uri; - } - } - - return resource; - } + private readonly _old_trusted_workspace_key = 'trusted_preview_workspace:'; + private readonly _security_level_key = 'preview_security_level:'; + private readonly _should_disable_security_warning_key = 'preview_should_show_security_warning:'; + constructor(private readonly _globalState: vscode.Memento, private readonly _workspaceState: vscode.Memento) { } + public getSecurityLevelForResource(resource: vscode.Uri): MarkdownPreviewSecurityLevel { + // Use new security level setting first + const level = this._globalState.get(this._security_level_key + this._getRoot(resource), undefined); + if (typeof level !== 'undefined') { + return level; + } + // Fallback to old trusted workspace setting + if (this._globalState.get(this._old_trusted_workspace_key + this._getRoot(resource), false)) { + return MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent; + } + return MarkdownPreviewSecurityLevel.Strict; + } + public setSecurityLevelForResource(resource: vscode.Uri, level: MarkdownPreviewSecurityLevel): Thenable { + return this._globalState.update(this._security_level_key + this._getRoot(resource), level); + } + public shouldAllowSvgsForResource(resource: vscode.Uri) { + const securityLevel = this.getSecurityLevelForResource(resource); + return securityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent || securityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent; + } + public shouldDisableSecurityWarnings(): boolean { + return this._workspaceState.get(this._should_disable_security_warning_key, false); + } + public setShouldDisableSecurityWarning(disabled: boolean): Thenable { + return this._workspaceState.update(this._should_disable_security_warning_key, disabled); + } + private _getRoot(resource: vscode.Uri): vscode.Uri { + if (vscode.workspace.workspaceFolders) { + const folderForResource = vscode.workspace.getWorkspaceFolder(resource); + if (folderForResource) { + return folderForResource.uri; + } + if (vscode.workspace.workspaceFolders.length) { + return vscode.workspace.workspaceFolders[0].uri; + } + } + return resource; + } } - export class PreviewSecuritySelector { - - public constructor( - private readonly _cspArbiter: ContentSecurityPolicyArbiter, - private readonly _webviewManager: MarkdownPreviewManager - ) { } - - public async showSecuritySelectorForResource(resource: vscode.Uri): Promise { - interface PreviewSecurityPickItem extends vscode.QuickPickItem { - readonly type: 'moreinfo' | 'toggle' | MarkdownPreviewSecurityLevel; - } - - function markActiveWhen(when: boolean): string { - return when ? '• ' : ''; - } - - const currentSecurityLevel = this._cspArbiter.getSecurityLevelForResource(resource); - const selection = await vscode.window.showQuickPick( - [ - { - type: MarkdownPreviewSecurityLevel.Strict, - label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.Strict) + vscode.l10n.t("Strict"), - description: vscode.l10n.t("Only load secure content"), - }, { - type: MarkdownPreviewSecurityLevel.AllowInsecureLocalContent, - label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowInsecureLocalContent) + vscode.l10n.t("Allow insecure local content"), - description: vscode.l10n.t("Enable loading content over http served from localhost"), - }, { - type: MarkdownPreviewSecurityLevel.AllowInsecureContent, - label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent) + vscode.l10n.t("Allow insecure content"), - description: vscode.l10n.t("Enable loading content over http"), - }, { - type: MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent, - label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent) + vscode.l10n.t("Disable"), - description: vscode.l10n.t("Allow all content and script execution. Not recommended"), - }, { - type: 'moreinfo', - label: vscode.l10n.t("More Information"), - description: '' - }, { - type: 'toggle', - label: this._cspArbiter.shouldDisableSecurityWarnings() - ? vscode.l10n.t("Enable preview security warnings in this workspace") - : vscode.l10n.t("Disable preview security warning in this workspace"), - description: vscode.l10n.t("Does not affect the content security level") - }, - ], { - placeHolder: vscode.l10n.t("Select security settings for Markdown previews in this workspace"), - }); - if (!selection) { - return; - } - - if (selection.type === 'moreinfo') { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=854414')); - return; - } - - if (selection.type === 'toggle') { - this._cspArbiter.setShouldDisableSecurityWarning(!this._cspArbiter.shouldDisableSecurityWarnings()); - this._webviewManager.refresh(); - return; - } else { - await this._cspArbiter.setSecurityLevelForResource(resource, selection.type); - } - this._webviewManager.refresh(); - } + public constructor(private readonly _cspArbiter: ContentSecurityPolicyArbiter, private readonly _webviewManager: MarkdownPreviewManager) { } + public async showSecuritySelectorForResource(resource: vscode.Uri): Promise { + interface PreviewSecurityPickItem extends vscode.QuickPickItem { + readonly type: 'moreinfo' | 'toggle' | MarkdownPreviewSecurityLevel; + } + function markActiveWhen(when: boolean): string { + return when ? '• ' : ''; + } + const currentSecurityLevel = this._cspArbiter.getSecurityLevelForResource(resource); + const selection = await vscode.window.showQuickPick([ + { + type: MarkdownPreviewSecurityLevel.Strict, + label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.Strict) + vscode.l10n.t("Strict"), + description: vscode.l10n.t("Only load secure content"), + }, { + type: MarkdownPreviewSecurityLevel.AllowInsecureLocalContent, + label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowInsecureLocalContent) + vscode.l10n.t("Allow insecure local content"), + description: vscode.l10n.t("Enable loading content over http served from localhost"), + }, { + type: MarkdownPreviewSecurityLevel.AllowInsecureContent, + label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent) + vscode.l10n.t("Allow insecure content"), + description: vscode.l10n.t("Enable loading content over http"), + }, { + type: MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent, + label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent) + vscode.l10n.t("Disable"), + description: vscode.l10n.t("Allow all content and script execution. Not recommended"), + }, { + type: 'moreinfo', + label: vscode.l10n.t("More Information"), + description: '' + }, { + type: 'toggle', + label: this._cspArbiter.shouldDisableSecurityWarnings() + ? vscode.l10n.t("Enable preview security warnings in this workspace") + : vscode.l10n.t("Disable preview security warning in this workspace"), + description: vscode.l10n.t("Does not affect the content security level") + }, + ], { + placeHolder: vscode.l10n.t("Select security settings for Markdown previews in this workspace"), + }); + if (!selection) { + return; + } + if (selection.type === 'moreinfo') { + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=854414')); + return; + } + if (selection.type === 'toggle') { + this._cspArbiter.setShouldDisableSecurityWarning(!this._cspArbiter.shouldDisableSecurityWarnings()); + this._webviewManager.refresh(); + return; + } + else { + await this._cspArbiter.setSecurityLevelForResource(resource, selection.type); + } + this._webviewManager.refresh(); + } } diff --git a/extensions/markdown-language-features/Source/preview/topmostLineMonitor.ts b/extensions/markdown-language-features/Source/preview/topmostLineMonitor.ts index 3db994514a465..72182208d74e9 100644 --- a/extensions/markdown-language-features/Source/preview/topmostLineMonitor.ts +++ b/extensions/markdown-language-features/Source/preview/topmostLineMonitor.ts @@ -2,109 +2,89 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { Disposable } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { ResourceMap } from '../util/resourceMap'; - export interface LastScrollLocation { - readonly line: number; - readonly uri: vscode.Uri; + readonly line: number; + readonly uri: vscode.Uri; } - export class TopmostLineMonitor extends Disposable { - - private readonly _pendingUpdates = new ResourceMap(); - private readonly _throttle = 50; - private _previousTextEditorInfo = new ResourceMap(); - private _previousStaticEditorInfo = new ResourceMap(); - - constructor() { - super(); - - if (vscode.window.activeTextEditor) { - const line = getVisibleLine(vscode.window.activeTextEditor); - this.setPreviousTextEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 }); - } - - this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => { - if (isMarkdownFile(event.textEditor.document)) { - const line = getVisibleLine(event.textEditor); - if (typeof line === 'number') { - this.updateLine(event.textEditor.document.uri, line); - this.setPreviousTextEditorLine({ uri: event.textEditor.document.uri, line: line }); - } - } - })); - } - - private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri; readonly line: number }>()); - public readonly onDidChanged = this._onChanged.event; - - public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation): void { - this._previousStaticEditorInfo.set(scrollLocation.uri, scrollLocation); - } - - public getPreviousStaticEditorLineByUri(resource: vscode.Uri): number | undefined { - const scrollLoc = this._previousStaticEditorInfo.get(resource); - this._previousStaticEditorInfo.delete(resource); - return scrollLoc?.line; - } - - - public setPreviousTextEditorLine(scrollLocation: LastScrollLocation): void { - this._previousTextEditorInfo.set(scrollLocation.uri, scrollLocation); - } - - public getPreviousTextEditorLineByUri(resource: vscode.Uri): number | undefined { - const scrollLoc = this._previousTextEditorInfo.get(resource); - this._previousTextEditorInfo.delete(resource); - return scrollLoc?.line; - } - - public getPreviousStaticTextEditorLineByUri(resource: vscode.Uri): number | undefined { - const state = this._previousStaticEditorInfo.get(resource); - return state?.line; - } - - public updateLine( - resource: vscode.Uri, - line: number - ) { - if (!this._pendingUpdates.has(resource)) { - // schedule update - setTimeout(() => { - if (this._pendingUpdates.has(resource)) { - this._onChanged.fire({ - resource, - line: this._pendingUpdates.get(resource) as number - }); - this._pendingUpdates.delete(resource); - } - }, this._throttle); - } - - this._pendingUpdates.set(resource, line); - } + private readonly _pendingUpdates = new ResourceMap(); + private readonly _throttle = 50; + private _previousTextEditorInfo = new ResourceMap(); + private _previousStaticEditorInfo = new ResourceMap(); + constructor() { + super(); + if (vscode.window.activeTextEditor) { + const line = getVisibleLine(vscode.window.activeTextEditor); + this.setPreviousTextEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 }); + } + this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => { + if (isMarkdownFile(event.textEditor.document)) { + const line = getVisibleLine(event.textEditor); + if (typeof line === 'number') { + this.updateLine(event.textEditor.document.uri, line); + this.setPreviousTextEditorLine({ uri: event.textEditor.document.uri, line: line }); + } + } + })); + } + private readonly _onChanged = this._register(new vscode.EventEmitter<{ + readonly resource: vscode.Uri; + readonly line: number; + }>()); + public readonly onDidChanged = this._onChanged.event; + public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation): void { + this._previousStaticEditorInfo.set(scrollLocation.uri, scrollLocation); + } + public getPreviousStaticEditorLineByUri(resource: vscode.Uri): number | undefined { + const scrollLoc = this._previousStaticEditorInfo.get(resource); + this._previousStaticEditorInfo.delete(resource); + return scrollLoc?.line; + } + public setPreviousTextEditorLine(scrollLocation: LastScrollLocation): void { + this._previousTextEditorInfo.set(scrollLocation.uri, scrollLocation); + } + public getPreviousTextEditorLineByUri(resource: vscode.Uri): number | undefined { + const scrollLoc = this._previousTextEditorInfo.get(resource); + this._previousTextEditorInfo.delete(resource); + return scrollLoc?.line; + } + public getPreviousStaticTextEditorLineByUri(resource: vscode.Uri): number | undefined { + const state = this._previousStaticEditorInfo.get(resource); + return state?.line; + } + public updateLine(resource: vscode.Uri, line: number) { + if (!this._pendingUpdates.has(resource)) { + // schedule update + setTimeout(() => { + if (this._pendingUpdates.has(resource)) { + this._onChanged.fire({ + resource, + line: this._pendingUpdates.get(resource) as number + }); + this._pendingUpdates.delete(resource); + } + }, this._throttle); + } + this._pendingUpdates.set(resource, line); + } } - /** * Get the top-most visible range of `editor`. * * Returns a fractional line number based the visible character within the line. * Floor to get real line number */ -export function getVisibleLine( - editor: vscode.TextEditor -): number | undefined { - if (!editor.visibleRanges.length) { - return undefined; - } - - const firstVisiblePosition = editor.visibleRanges[0].start; - const lineNumber = firstVisiblePosition.line; - const line = editor.document.lineAt(lineNumber); - const progress = firstVisiblePosition.character / (line.text.length + 2); - return lineNumber + progress; +export function getVisibleLine(editor: vscode.TextEditor): number | undefined { + if (!editor.visibleRanges.length) { + return undefined; + } + const firstVisiblePosition = editor.visibleRanges[0].start; + const lineNumber = firstVisiblePosition.line; + const line = editor.document.lineAt(lineNumber); + const progress = firstVisiblePosition.character / (line.text.length + 2); + return lineNumber + progress; } diff --git a/extensions/markdown-language-features/Source/slugify.ts b/extensions/markdown-language-features/Source/slugify.ts index 0d4b1896d8c3a..3a42e01cc24d7 100644 --- a/extensions/markdown-language-features/Source/slugify.ts +++ b/extensions/markdown-language-features/Source/slugify.ts @@ -2,32 +2,25 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export class Slug { - public constructor( - public readonly value: string - ) { } - - public equals(other: Slug): boolean { - return this.value === other.value; - } + public constructor(public readonly value: string) { } + public equals(other: Slug): boolean { + return this.value === other.value; + } } - export interface Slugifier { - fromHeading(heading: string): Slug; + fromHeading(heading: string): Slug; } - export const githubSlugifier: Slugifier = new class implements Slugifier { - fromHeading(heading: string): Slug { - const slugifiedHeading = encodeURI( - heading.trim() - .toLowerCase() - .replace(/\s+/g, '-') // Replace whitespace with - - // allow-any-unicode-next-line - .replace(/[\]\[\!\/\'\"\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known punctuators - .replace(/^\-+/, '') // Remove leading - - .replace(/\-+$/, '') // Remove trailing - - ); - return new Slug(slugifiedHeading); - } + fromHeading(heading: string): Slug { + const slugifiedHeading = encodeURI(heading.trim() + .toLowerCase() + .replace(/\s+/g, '-') // Replace whitespace with - + // allow-any-unicode-next-line + .replace(/[\]\[\!\/\'\"\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known punctuators + .replace(/^\-+/, '') // Remove leading - + .replace(/\-+$/, '') // Remove trailing - + ); + return new Slug(slugifiedHeading); + } }; diff --git a/extensions/markdown-language-features/Source/telemetryReporter.ts b/extensions/markdown-language-features/Source/telemetryReporter.ts index 79787624db99b..a39be56f41311 100644 --- a/extensions/markdown-language-features/Source/telemetryReporter.ts +++ b/extensions/markdown-language-features/Source/telemetryReporter.ts @@ -4,57 +4,47 @@ *--------------------------------------------------------------------------------------------*/ import { default as VSCodeTelemetryReporter } from '@vscode/extension-telemetry'; import * as vscode from 'vscode'; - interface IPackageInfo { - name: string; - version: string; - aiKey: string; + name: string; + version: string; + aiKey: string; } - export interface TelemetryReporter { - dispose(): void; - sendTelemetryEvent(eventName: string, properties?: { - [key: string]: string; - }): void; + dispose(): void; + sendTelemetryEvent(eventName: string, properties?: { + [key: string]: string; + }): void; } - const nullReporter = new class NullTelemetryReporter implements TelemetryReporter { - sendTelemetryEvent() { /** noop */ } - dispose() { /** noop */ } + sendTelemetryEvent() { } + dispose() { } }; - class ExtensionReporter implements TelemetryReporter { - private readonly _reporter: VSCodeTelemetryReporter; - - constructor( - packageInfo: IPackageInfo - ) { - this._reporter = new VSCodeTelemetryReporter(packageInfo.aiKey); - } - sendTelemetryEvent(eventName: string, properties?: { - [key: string]: string; - }) { - this._reporter.sendTelemetryEvent(eventName, properties); - } - - dispose() { - this._reporter.dispose(); - } + private readonly _reporter: VSCodeTelemetryReporter; + constructor(packageInfo: IPackageInfo) { + this._reporter = new VSCodeTelemetryReporter(packageInfo.aiKey); + } + sendTelemetryEvent(eventName: string, properties?: { + [key: string]: string; + }) { + this._reporter.sendTelemetryEvent(eventName, properties); + } + dispose() { + this._reporter.dispose(); + } } - export function loadDefaultTelemetryReporter(): TelemetryReporter { - const packageInfo = getPackageInfo(); - return packageInfo ? new ExtensionReporter(packageInfo) : nullReporter; + const packageInfo = getPackageInfo(); + return packageInfo ? new ExtensionReporter(packageInfo) : nullReporter; } - function getPackageInfo(): IPackageInfo | null { - const extension = vscode.extensions.getExtension('Microsoft.vscode-markdown'); - if (extension && extension.packageJSON) { - return { - name: extension.packageJSON.name, - version: extension.packageJSON.version, - aiKey: extension.packageJSON.aiKey - }; - } - return null; + const extension = vscode.extensions.getExtension('Microsoft.vscode-markdown'); + if (extension && extension.packageJSON) { + return { + name: extension.packageJSON.name, + version: extension.packageJSON.version, + aiKey: extension.packageJSON.aiKey + }; + } + return null; } diff --git a/extensions/markdown-language-features/Source/types/textDocument.ts b/extensions/markdown-language-features/Source/types/textDocument.ts index d8809315c2e29..807474e257499 100644 --- a/extensions/markdown-language-features/Source/types/textDocument.ts +++ b/extensions/markdown-language-features/Source/types/textDocument.ts @@ -2,18 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - /** * Minimal version of {@link vscode.TextDocument}. */ export interface ITextDocument { - readonly uri: vscode.Uri; - readonly version: number; - - getText(range?: vscode.Range): string; - - positionAt(offset: number): vscode.Position; + readonly uri: vscode.Uri; + readonly version: number; + getText(range?: vscode.Range): string; + positionAt(offset: number): vscode.Position; } - diff --git a/extensions/markdown-language-features/Source/util/arrays.ts b/extensions/markdown-language-features/Source/util/arrays.ts index 8163fee0c7308..e7fa50b4f2dd0 100644 --- a/extensions/markdown-language-features/Source/util/arrays.ts +++ b/extensions/markdown-language-features/Source/util/arrays.ts @@ -6,19 +6,16 @@ * @returns New array with all falsy values removed. The original array IS NOT modified. */ export function coalesce(array: ReadonlyArray): T[] { - return array.filter(e => !!e); + return array.filter(e => !!e); } - export function equals(one: ReadonlyArray, other: ReadonlyArray, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { - if (one.length !== other.length) { - return false; - } - - for (let i = 0, len = one.length; i < len; i++) { - if (!itemEquals(one[i], other[i])) { - return false; - } - } - - return true; + if (one.length !== other.length) { + return false; + } + for (let i = 0, len = one.length; i < len; i++) { + if (!itemEquals(one[i], other[i])) { + return false; + } + } + return true; } diff --git a/extensions/markdown-language-features/Source/util/async.ts b/extensions/markdown-language-features/Source/util/async.ts index b1e6b9f479609..407ca2bd6529e 100644 --- a/extensions/markdown-language-features/Source/util/async.ts +++ b/extensions/markdown-language-features/Source/util/async.ts @@ -2,63 +2,53 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export interface ITask { - (): T; + (): T; } - export class Delayer { - - public defaultDelay: number; - private _timeout: any; // Timer - private _cancelTimeout: Promise | null; - private _onSuccess: ((value: T | PromiseLike | undefined) => void) | null; - private _task: ITask | null; - - constructor(defaultDelay: number) { - this.defaultDelay = defaultDelay; - this._timeout = null; - this._cancelTimeout = null; - this._onSuccess = null; - this._task = null; - } - - dispose() { - this._doCancelTimeout(); - } - - public trigger(task: ITask, delay: number = this.defaultDelay): Promise { - this._task = task; - if (delay >= 0) { - this._doCancelTimeout(); - } - - if (!this._cancelTimeout) { - this._cancelTimeout = new Promise((resolve) => { - this._onSuccess = resolve; - }).then(() => { - this._cancelTimeout = null; - this._onSuccess = null; - const result = this._task && this._task?.(); - this._task = null; - return result; - }); - } - - if (delay >= 0 || this._timeout === null) { - this._timeout = setTimeout(() => { - this._timeout = null; - this._onSuccess?.(undefined); - }, delay >= 0 ? delay : this.defaultDelay); - } - - return this._cancelTimeout; - } - - private _doCancelTimeout(): void { - if (this._timeout !== null) { - clearTimeout(this._timeout); - this._timeout = null; - } - } + public defaultDelay: number; + private _timeout: any; // Timer + private _cancelTimeout: Promise | null; + private _onSuccess: ((value: T | PromiseLike | undefined) => void) | null; + private _task: ITask | null; + constructor(defaultDelay: number) { + this.defaultDelay = defaultDelay; + this._timeout = null; + this._cancelTimeout = null; + this._onSuccess = null; + this._task = null; + } + dispose() { + this._doCancelTimeout(); + } + public trigger(task: ITask, delay: number = this.defaultDelay): Promise { + this._task = task; + if (delay >= 0) { + this._doCancelTimeout(); + } + if (!this._cancelTimeout) { + this._cancelTimeout = new Promise((resolve) => { + this._onSuccess = resolve; + }).then(() => { + this._cancelTimeout = null; + this._onSuccess = null; + const result = this._task && this._task?.(); + this._task = null; + return result; + }); + } + if (delay >= 0 || this._timeout === null) { + this._timeout = setTimeout(() => { + this._timeout = null; + this._onSuccess?.(undefined); + }, delay >= 0 ? delay : this.defaultDelay); + } + return this._cancelTimeout; + } + private _doCancelTimeout(): void { + if (this._timeout !== null) { + clearTimeout(this._timeout); + this._timeout = null; + } + } } diff --git a/extensions/markdown-language-features/Source/util/cancellation.ts b/extensions/markdown-language-features/Source/util/cancellation.ts index 45556cb6f1d4a..6447bd627a6dc 100644 --- a/extensions/markdown-language-features/Source/util/cancellation.ts +++ b/extensions/markdown-language-features/Source/util/cancellation.ts @@ -2,12 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - export const noopToken: vscode.CancellationToken = new class implements vscode.CancellationToken { - private readonly _onCancellationRequestedEmitter = new vscode.EventEmitter(); - onCancellationRequested = this._onCancellationRequestedEmitter.event; - - get isCancellationRequested() { return false; } + private readonly _onCancellationRequestedEmitter = new vscode.EventEmitter(); + onCancellationRequested = this._onCancellationRequestedEmitter.event; + get isCancellationRequested() { return false; } }; diff --git a/extensions/markdown-language-features/Source/util/dispose.ts b/extensions/markdown-language-features/Source/util/dispose.ts index f222642908e1f..75a64c6af6068 100644 --- a/extensions/markdown-language-features/Source/util/dispose.ts +++ b/extensions/markdown-language-features/Source/util/dispose.ts @@ -2,54 +2,47 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - export function disposeAll(disposables: Iterable) { - const errors: any[] = []; - - for (const disposable of disposables) { - try { - disposable.dispose(); - } catch (e) { - errors.push(e); - } - } - - if (errors.length === 1) { - throw errors[0]; - } else if (errors.length > 1) { - throw new AggregateError(errors, 'Encountered errors while disposing of store'); - } + const errors: any[] = []; + for (const disposable of disposables) { + try { + disposable.dispose(); + } + catch (e) { + errors.push(e); + } + } + if (errors.length === 1) { + throw errors[0]; + } + else if (errors.length > 1) { + throw new AggregateError(errors, 'Encountered errors while disposing of store'); + } } - export interface IDisposable { - dispose(): void; + dispose(): void; } - export abstract class Disposable { - private _isDisposed = false; - - protected _disposables: vscode.Disposable[] = []; - - public dispose(): any { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - disposeAll(this._disposables); - } - - protected _register(value: T): T { - if (this._isDisposed) { - value.dispose(); - } else { - this._disposables.push(value); - } - return value; - } - - protected get isDisposed() { - return this._isDisposed; - } + private _isDisposed = false; + protected _disposables: vscode.Disposable[] = []; + public dispose(): any { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + disposeAll(this._disposables); + } + protected _register(value: T): T { + if (this._isDisposed) { + value.dispose(); + } + else { + this._disposables.push(value); + } + return value; + } + protected get isDisposed() { + return this._isDisposed; + } } diff --git a/extensions/markdown-language-features/Source/util/document.ts b/extensions/markdown-language-features/Source/util/document.ts index 856226a737692..0b5583114060e 100644 --- a/extensions/markdown-language-features/Source/util/document.ts +++ b/extensions/markdown-language-features/Source/util/document.ts @@ -2,29 +2,25 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { Schemes } from './schemes'; import { Utils } from 'vscode-uri'; - export function getDocumentDir(uri: vscode.Uri): vscode.Uri | undefined { - const docUri = getParentDocumentUri(uri); - if (docUri.scheme === Schemes.untitled) { - return vscode.workspace.workspaceFolders?.[0]?.uri; - } - return Utils.dirname(docUri); + const docUri = getParentDocumentUri(uri); + if (docUri.scheme === Schemes.untitled) { + return vscode.workspace.workspaceFolders?.[0]?.uri; + } + return Utils.dirname(docUri); } - export function getParentDocumentUri(uri: vscode.Uri): vscode.Uri { - if (uri.scheme === Schemes.notebookCell) { - for (const notebook of vscode.workspace.notebookDocuments) { - for (const cell of notebook.getCells()) { - if (cell.document.uri.toString() === uri.toString()) { - return notebook.uri; - } - } - } - } - - return uri; + if (uri.scheme === Schemes.notebookCell) { + for (const notebook of vscode.workspace.notebookDocuments) { + for (const cell of notebook.getCells()) { + if (cell.document.uri.toString() === uri.toString()) { + return notebook.uri; + } + } + } + } + return uri; } diff --git a/extensions/markdown-language-features/Source/util/dom.ts b/extensions/markdown-language-features/Source/util/dom.ts index 0f6c00da9daf7..93f100fc12417 100644 --- a/extensions/markdown-language-features/Source/util/dom.ts +++ b/extensions/markdown-language-features/Source/util/dom.ts @@ -3,16 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; - export function escapeAttribute(value: string | vscode.Uri): string { - return value.toString().replace(/"/g, '"'); + return value.toString().replace(/"/g, '"'); } - export function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 64; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; } diff --git a/extensions/markdown-language-features/Source/util/file.ts b/extensions/markdown-language-features/Source/util/file.ts index 9a4990a05859d..4fa6fbe8e40bd 100644 --- a/extensions/markdown-language-features/Source/util/file.ts +++ b/extensions/markdown-language-features/Source/util/file.ts @@ -2,43 +2,37 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import * as URI from 'vscode-uri'; import { Schemes } from './schemes'; - export const markdownFileExtensions = Object.freeze([ - 'md', - 'mkd', - 'mdwn', - 'mdown', - 'markdown', - 'markdn', - 'mdtxt', - 'mdtext', - 'workbook', + 'md', + 'mkd', + 'mdwn', + 'mdown', + 'markdown', + 'markdn', + 'mdtxt', + 'mdtext', + 'workbook', ]); - export function isMarkdownFile(document: vscode.TextDocument) { - return document.languageId === 'markdown'; + return document.languageId === 'markdown'; } - export function looksLikeMarkdownPath(resolvedHrefPath: vscode.Uri): boolean { - const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resolvedHrefPath.toString()); - if (doc) { - return isMarkdownFile(doc); - } - - if (resolvedHrefPath.scheme === Schemes.notebookCell) { - for (const notebook of vscode.workspace.notebookDocuments) { - for (const cell of notebook.getCells()) { - if (cell.kind === vscode.NotebookCellKind.Markup && isMarkdownFile(cell.document)) { - return true; - } - } - } - return false; - } - - return markdownFileExtensions.includes(URI.Utils.extname(resolvedHrefPath).toLowerCase().replace('.', '')); + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resolvedHrefPath.toString()); + if (doc) { + return isMarkdownFile(doc); + } + if (resolvedHrefPath.scheme === Schemes.notebookCell) { + for (const notebook of vscode.workspace.notebookDocuments) { + for (const cell of notebook.getCells()) { + if (cell.kind === vscode.NotebookCellKind.Markup && isMarkdownFile(cell.document)) { + return true; + } + } + } + return false; + } + return markdownFileExtensions.includes(URI.Utils.extname(resolvedHrefPath).toLowerCase().replace('.', '')); } diff --git a/extensions/markdown-language-features/Source/util/mimes.ts b/extensions/markdown-language-features/Source/util/mimes.ts index f33b807b83e3b..1347670c2b8ff 100644 --- a/extensions/markdown-language-features/Source/util/mimes.ts +++ b/extensions/markdown-language-features/Source/util/mimes.ts @@ -2,22 +2,20 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export const Mime = { - textUriList: 'text/uri-list', - textPlain: 'text/plain', + textUriList: 'text/uri-list', + textPlain: 'text/plain', } as const; - export const mediaMimes = new Set([ - 'image/avif', - 'image/bmp', - 'image/gif', - 'image/jpeg', - 'image/png', - 'image/webp', - 'video/mp4', - 'video/ogg', - 'audio/mpeg', - 'audio/aac', - 'audio/x-wav', + 'image/avif', + 'image/bmp', + 'image/gif', + 'image/jpeg', + 'image/png', + 'image/webp', + 'video/mp4', + 'video/ogg', + 'audio/mpeg', + 'audio/aac', + 'audio/x-wav', ]); diff --git a/extensions/markdown-language-features/Source/util/openDocumentLink.ts b/extensions/markdown-language-features/Source/util/openDocumentLink.ts index 285dd91029b44..5641b9b74c150 100644 --- a/extensions/markdown-language-features/Source/util/openDocumentLink.ts +++ b/extensions/markdown-language-features/Source/util/openDocumentLink.ts @@ -2,71 +2,57 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; import { MdLanguageClient } from '../client/client'; import * as proto from '../client/protocol'; - enum OpenMarkdownLinks { - beside = 'beside', - currentGroup = 'currentGroup', + beside = 'beside', + currentGroup = 'currentGroup' } - export class MdLinkOpener { - - constructor( - private readonly _client: MdLanguageClient, - ) { } - - public async resolveDocumentLink(linkText: string, fromResource: vscode.Uri): Promise { - return this._client.resolveLinkTarget(linkText, fromResource); - } - - public async openDocumentLink(linkText: string, fromResource: vscode.Uri, viewColumn?: vscode.ViewColumn): Promise { - const resolved = await this._client.resolveLinkTarget(linkText, fromResource); - if (!resolved) { - return; - } - - const uri = vscode.Uri.from(resolved.uri); - switch (resolved.kind) { - case 'external': - return vscode.commands.executeCommand('vscode.open', uri); - - case 'folder': - return vscode.commands.executeCommand('revealInExplorer', uri); - - case 'file': { - // If no explicit viewColumn is given, check if the editor is already open in a tab - if (typeof viewColumn === 'undefined') { - for (const tab of vscode.window.tabGroups.all.flatMap(x => x.tabs)) { - if (tab.input instanceof vscode.TabInputText) { - if (tab.input.uri.fsPath === uri.fsPath) { - viewColumn = tab.group.viewColumn; - break; - } - } - } - } - - return vscode.commands.executeCommand('vscode.open', uri, { - selection: resolved.position ? new vscode.Range(resolved.position.line, resolved.position.character, resolved.position.line, resolved.position.character) : undefined, - viewColumn: viewColumn ?? getViewColumn(fromResource), - } satisfies vscode.TextDocumentShowOptions); - } - } - } + constructor(private readonly _client: MdLanguageClient) { } + public async resolveDocumentLink(linkText: string, fromResource: vscode.Uri): Promise { + return this._client.resolveLinkTarget(linkText, fromResource); + } + public async openDocumentLink(linkText: string, fromResource: vscode.Uri, viewColumn?: vscode.ViewColumn): Promise { + const resolved = await this._client.resolveLinkTarget(linkText, fromResource); + if (!resolved) { + return; + } + const uri = vscode.Uri.from(resolved.uri); + switch (resolved.kind) { + case 'external': + return vscode.commands.executeCommand('vscode.open', uri); + case 'folder': + return vscode.commands.executeCommand('revealInExplorer', uri); + case 'file': { + // If no explicit viewColumn is given, check if the editor is already open in a tab + if (typeof viewColumn === 'undefined') { + for (const tab of vscode.window.tabGroups.all.flatMap(x => x.tabs)) { + if (tab.input instanceof vscode.TabInputText) { + if (tab.input.uri.fsPath === uri.fsPath) { + viewColumn = tab.group.viewColumn; + break; + } + } + } + } + return vscode.commands.executeCommand('vscode.open', uri, { + selection: resolved.position ? new vscode.Range(resolved.position.line, resolved.position.character, resolved.position.line, resolved.position.character) : undefined, + viewColumn: viewColumn ?? getViewColumn(fromResource), + } satisfies vscode.TextDocumentShowOptions); + } + } + } } - function getViewColumn(resource: vscode.Uri): vscode.ViewColumn { - const config = vscode.workspace.getConfiguration('markdown', resource); - const openLinks = config.get('links.openLocation', OpenMarkdownLinks.currentGroup); - switch (openLinks) { - case OpenMarkdownLinks.beside: - return vscode.ViewColumn.Beside; - case OpenMarkdownLinks.currentGroup: - default: - return vscode.ViewColumn.Active; - } + const config = vscode.workspace.getConfiguration('markdown', resource); + const openLinks = config.get('links.openLocation', OpenMarkdownLinks.currentGroup); + switch (openLinks) { + case OpenMarkdownLinks.beside: + return vscode.ViewColumn.Beside; + case OpenMarkdownLinks.currentGroup: + default: + return vscode.ViewColumn.Active; + } } - diff --git a/extensions/markdown-language-features/Source/util/resourceMap.ts b/extensions/markdown-language-features/Source/util/resourceMap.ts index 99dd71a8718b9..7c42afd6fa484 100644 --- a/extensions/markdown-language-features/Source/util/resourceMap.ts +++ b/extensions/markdown-language-features/Source/util/resourceMap.ts @@ -2,67 +2,59 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - type ResourceToKey = (uri: vscode.Uri) => string; - const defaultResourceToKey = (resource: vscode.Uri): string => resource.toString(); - export class ResourceMap { - - private readonly _map = new Map(); - - private readonly _toKey: ResourceToKey; - - constructor(toKey: ResourceToKey = defaultResourceToKey) { - this._toKey = toKey; - } - - public set(uri: vscode.Uri, value: T): this { - this._map.set(this._toKey(uri), { uri, value }); - return this; - } - - public get(resource: vscode.Uri): T | undefined { - return this._map.get(this._toKey(resource))?.value; - } - - public has(resource: vscode.Uri): boolean { - return this._map.has(this._toKey(resource)); - } - - public get size(): number { - return this._map.size; - } - - public clear(): void { - this._map.clear(); - } - - public delete(resource: vscode.Uri): boolean { - return this._map.delete(this._toKey(resource)); - } - - public *values(): IterableIterator { - for (const entry of this._map.values()) { - yield entry.value; - } - } - - public *keys(): IterableIterator { - for (const entry of this._map.values()) { - yield entry.uri; - } - } - - public *entries(): IterableIterator<[vscode.Uri, T]> { - for (const entry of this._map.values()) { - yield [entry.uri, entry.value]; - } - } - - public [Symbol.iterator](): IterableIterator<[vscode.Uri, T]> { - return this.entries(); - } + private readonly _map = new Map(); + private readonly _toKey: ResourceToKey; + constructor(toKey: ResourceToKey = defaultResourceToKey) { + this._toKey = toKey; + } + public set(uri: vscode.Uri, value: T): this { + this._map.set(this._toKey(uri), { uri, value }); + return this; + } + public get(resource: vscode.Uri): T | undefined { + return this._map.get(this._toKey(resource))?.value; + } + public has(resource: vscode.Uri): boolean { + return this._map.has(this._toKey(resource)); + } + public get size(): number { + return this._map.size; + } + public clear(): void { + this._map.clear(); + } + public delete(resource: vscode.Uri): boolean { + return this._map.delete(this._toKey(resource)); + } + public *values(): IterableIterator { + for (const entry of this._map.values()) { + yield entry.value; + } + } + public *keys(): IterableIterator { + for (const entry of this._map.values()) { + yield entry.uri; + } + } + public *entries(): IterableIterator<[ + vscode.Uri, + T + ]> { + for (const entry of this._map.values()) { + yield [entry.uri, entry.value]; + } + } + public [Symbol.iterator](): IterableIterator<[ + vscode.Uri, + T + ]> { + return this.entries(); + } } diff --git a/extensions/markdown-language-features/Source/util/resources.ts b/extensions/markdown-language-features/Source/util/resources.ts index f1f2d0886ab23..0076288d2452e 100644 --- a/extensions/markdown-language-features/Source/util/resources.ts +++ b/extensions/markdown-language-features/Source/util/resources.ts @@ -2,12 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - export interface WebviewResourceProvider { - asWebviewUri(resource: vscode.Uri): vscode.Uri; - - readonly cspSource: string; + asWebviewUri(resource: vscode.Uri): vscode.Uri; + readonly cspSource: string; } - diff --git a/extensions/markdown-language-features/Source/util/schemes.ts b/extensions/markdown-language-features/Source/util/schemes.ts index dbb3d14d5e2ad..e815e679f6393 100644 --- a/extensions/markdown-language-features/Source/util/schemes.ts +++ b/extensions/markdown-language-features/Source/util/schemes.ts @@ -2,18 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export const Schemes = Object.freeze({ - http: 'http', - https: 'https', - file: 'file', - untitled: 'untitled', - mailto: 'mailto', - vscode: 'vscode', - 'vscode-insiders': 'vscode-insiders', - notebookCell: 'vscode-notebook-cell', + http: 'http', + https: 'https', + file: 'file', + untitled: 'untitled', + mailto: 'mailto', + vscode: 'vscode', + 'vscode-insiders': 'vscode-insiders', + notebookCell: 'vscode-notebook-cell', }); - export function isOfScheme(scheme: string, link: string): boolean { - return link.toLowerCase().startsWith(scheme + ':'); + return link.toLowerCase().startsWith(scheme + ':'); } diff --git a/extensions/markdown-language-features/Source/util/uriList.ts b/extensions/markdown-language-features/Source/util/uriList.ts index 8b7f52e568f07..e8dc8ef636a61 100644 --- a/extensions/markdown-language-features/Source/util/uriList.ts +++ b/extensions/markdown-language-features/Source/util/uriList.ts @@ -2,34 +2,30 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { coalesce } from './arrays'; import * as vscode from 'vscode'; - function splitUriList(str: string): string[] { - return str.split('\r\n'); + return str.split('\r\n'); } - function parseUriList(str: string): string[] { - return splitUriList(str) - .filter(value => !value.startsWith('#')) // Remove comments - .map(value => value.trim()); + return splitUriList(str) + .filter(value => !value.startsWith('#')) // Remove comments + .map(value => value.trim()); } - export class UriList { - - static from(str: string): UriList { - return new UriList(coalesce(parseUriList(str).map(line => { - try { - return { uri: vscode.Uri.parse(line), str: line }; - } catch { - // Uri parse failure - return undefined; - } - }))); - } - - private constructor( - public readonly entries: ReadonlyArray<{ readonly uri: vscode.Uri; readonly str: string }> - ) { } + static from(str: string): UriList { + return new UriList(coalesce(parseUriList(str).map(line => { + try { + return { uri: vscode.Uri.parse(line), str: line }; + } + catch { + // Uri parse failure + return undefined; + } + }))); + } + private constructor(public readonly entries: ReadonlyArray<{ + readonly uri: vscode.Uri; + readonly str: string; + }>) { } } diff --git a/extensions/markdown-language-features/Source/util/url.ts b/extensions/markdown-language-features/Source/util/url.ts index ba2ead6573215..6320d2c4f550e 100644 --- a/extensions/markdown-language-features/Source/util/url.ts +++ b/extensions/markdown-language-features/Source/util/url.ts @@ -2,22 +2,21 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as vscode from 'vscode'; - /** * Tries to convert an url into a vscode uri and returns undefined if this is not possible. * `url` can be absolute or relative. */ export function urlToUri(url: string, base: vscode.Uri): vscode.Uri | undefined { - try { - // `vscode.Uri.joinPath` cannot be used, since it understands - // `src` as path, not as relative url. This is problematic for query args. - const parsedUrl = new URL(url, base.toString()); - const uri = vscode.Uri.parse(parsedUrl.toString()); - return uri; - } catch (e) { - // Don't crash if `URL` cannot parse `src`. - return undefined; - } + try { + // `vscode.Uri.joinPath` cannot be used, since it understands + // `src` as path, not as relative url. This is problematic for query args. + const parsedUrl = new URL(url, base.toString()); + const uri = vscode.Uri.parse(parsedUrl.toString()); + return uri; + } + catch (e) { + // Don't crash if `URL` cannot parse `src`. + return undefined; + } } diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index d89f9b15d8097..e93f0ea96e704 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -2,151 +2,144 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as DOMPurify from 'dompurify'; import MarkdownIt from 'markdown-it'; import type * as MarkdownItToken from 'markdown-it/lib/token'; import type { ActivationFunction } from 'vscode-notebook-renderer'; - const allowedHtmlTags = Object.freeze(['a', - 'abbr', - 'b', - 'bdo', - 'blockquote', - 'br', - 'caption', - 'cite', - 'code', - 'col', - 'colgroup', - 'dd', - 'del', - 'details', - 'dfn', - 'div', - 'dl', - 'dt', - 'em', - 'figcaption', - 'figure', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'hr', - 'i', - 'img', - 'ins', - 'kbd', - 'label', - 'li', - 'mark', - 'ol', - 'p', - 'pre', - 'q', - 'rp', - 'rt', - 'ruby', - 'samp', - 'small', - 'small', - 'source', - 'span', - 'strike', - 'strong', - 'sub', - 'summary', - 'sup', - 'table', - 'tbody', - 'td', - 'tfoot', - 'th', - 'thead', - 'time', - 'tr', - 'tt', - 'u', - 'ul', - 'var', - 'video', - 'wbr', + 'abbr', + 'b', + 'bdo', + 'blockquote', + 'br', + 'caption', + 'cite', + 'code', + 'col', + 'colgroup', + 'dd', + 'del', + 'details', + 'dfn', + 'div', + 'dl', + 'dt', + 'em', + 'figcaption', + 'figure', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'i', + 'img', + 'ins', + 'kbd', + 'label', + 'li', + 'mark', + 'ol', + 'p', + 'pre', + 'q', + 'rp', + 'rt', + 'ruby', + 'samp', + 'small', + 'small', + 'source', + 'span', + 'strike', + 'strong', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'time', + 'tr', + 'tt', + 'u', + 'ul', + 'var', + 'video', + 'wbr', ]); - const allowedSvgTags = Object.freeze([ - 'svg', - 'a', - 'altglyph', - 'altglyphdef', - 'altglyphitem', - 'animatecolor', - 'animatemotion', - 'animatetransform', - 'circle', - 'clippath', - 'defs', - 'desc', - 'ellipse', - 'filter', - 'font', - 'g', - 'glyph', - 'glyphref', - 'hkern', - 'image', - 'line', - 'lineargradient', - 'marker', - 'mask', - 'metadata', - 'mpath', - 'path', - 'pattern', - 'polygon', - 'polyline', - 'radialgradient', - 'rect', - 'stop', - 'style', - 'switch', - 'symbol', - 'text', - 'textpath', - 'title', - 'tref', - 'tspan', - 'view', - 'vkern', + 'svg', + 'a', + 'altglyph', + 'altglyphdef', + 'altglyphitem', + 'animatecolor', + 'animatemotion', + 'animatetransform', + 'circle', + 'clippath', + 'defs', + 'desc', + 'ellipse', + 'filter', + 'font', + 'g', + 'glyph', + 'glyphref', + 'hkern', + 'image', + 'line', + 'lineargradient', + 'marker', + 'mask', + 'metadata', + 'mpath', + 'path', + 'pattern', + 'polygon', + 'polyline', + 'radialgradient', + 'rect', + 'stop', + 'style', + 'switch', + 'symbol', + 'text', + 'textpath', + 'title', + 'tref', + 'tspan', + 'view', + 'vkern', ]); - const sanitizerOptions: DOMPurify.Config = { - ALLOWED_TAGS: [ - ...allowedHtmlTags, - ...allowedSvgTags, - ], + ALLOWED_TAGS: [ + ...allowedHtmlTags, + ...allowedSvgTags, + ], }; - export const activate: ActivationFunction = (ctx) => { - const markdownIt: MarkdownIt = new MarkdownIt({ - html: true, - linkify: true, - highlight: (str: string, lang?: string) => { - if (lang) { - return `
${markdownIt.utils.escapeHtml(str)}
`; - } - return markdownIt.utils.escapeHtml(str); - } - }); - markdownIt.linkify.set({ fuzzyLink: false }); - - addNamedHeaderRendering(markdownIt); - addLinkRenderer(markdownIt); - - const style = document.createElement('style'); - style.textContent = ` + const markdownIt: MarkdownIt = new MarkdownIt({ + html: true, + linkify: true, + highlight: (str: string, lang?: string) => { + if (lang) { + return `
${markdownIt.utils.escapeHtml(str)}
`; + } + return markdownIt.utils.escapeHtml(str); + } + }); + markdownIt.linkify.set({ fuzzyLink: false }); + addNamedHeaderRendering(markdownIt); + addLinkRenderer(markdownIt); + const style = document.createElement('style'); + style.textContent = ` .emptyMarkdownCell::before { content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}"; font-style: italic; @@ -293,123 +286,114 @@ export const activate: ActivationFunction = (ctx) => { margin-bottom: 0.7em; } `; - const template = document.createElement('template'); - template.classList.add('markdown-style'); - template.content.appendChild(style); - document.head.appendChild(template); - - return { - renderOutputItem: (outputInfo, element) => { - let previewNode: HTMLElement; - if (!element.shadowRoot) { - const previewRoot = element.attachShadow({ mode: 'open' }); - - // Insert styles into markdown preview shadow dom so that they are applied. - // First add default webview style - const defaultStyles = document.getElementById('_defaultStyles') as HTMLStyleElement; - previewRoot.appendChild(defaultStyles.cloneNode(true)); - - // And then contributed styles - for (const element of document.getElementsByClassName('markdown-style')) { - if (element instanceof HTMLTemplateElement) { - previewRoot.appendChild(element.content.cloneNode(true)); - } else { - previewRoot.appendChild(element.cloneNode(true)); - } - } - - previewNode = document.createElement('div'); - previewNode.id = 'preview'; - previewRoot.appendChild(previewNode); - } else { - previewNode = element.shadowRoot.getElementById('preview')!; - } - - const text = outputInfo.text(); - if (text.trim().length === 0) { - previewNode.innerText = ''; - previewNode.classList.add('emptyMarkdownCell'); - } else { - previewNode.classList.remove('emptyMarkdownCell'); - const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` - : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); - const unsanitizedRenderedMarkdown = markdownIt.render(markdownText, { - outputItem: outputInfo, - }); - previewNode.innerHTML = (ctx.workspace.isTrusted - ? unsanitizedRenderedMarkdown - : DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions)) as string; - } - }, - extendMarkdownIt: (f: (md: typeof markdownIt) => void) => { - try { - f(markdownIt); - } catch (err) { - console.error('Error extending markdown-it', err); - } - } - }; + const template = document.createElement('template'); + template.classList.add('markdown-style'); + template.content.appendChild(style); + document.head.appendChild(template); + return { + renderOutputItem: (outputInfo, element) => { + let previewNode: HTMLElement; + if (!element.shadowRoot) { + const previewRoot = element.attachShadow({ mode: 'open' }); + // Insert styles into markdown preview shadow dom so that they are applied. + // First add default webview style + const defaultStyles = document.getElementById('_defaultStyles') as HTMLStyleElement; + previewRoot.appendChild(defaultStyles.cloneNode(true)); + // And then contributed styles + for (const element of document.getElementsByClassName('markdown-style')) { + if (element instanceof HTMLTemplateElement) { + previewRoot.appendChild(element.content.cloneNode(true)); + } + else { + previewRoot.appendChild(element.cloneNode(true)); + } + } + previewNode = document.createElement('div'); + previewNode.id = 'preview'; + previewRoot.appendChild(previewNode); + } + else { + previewNode = element.shadowRoot.getElementById('preview')!; + } + const text = outputInfo.text(); + if (text.trim().length === 0) { + previewNode.innerText = ''; + previewNode.classList.add('emptyMarkdownCell'); + } + else { + previewNode.classList.remove('emptyMarkdownCell'); + const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` + : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); + const unsanitizedRenderedMarkdown = markdownIt.render(markdownText, { + outputItem: outputInfo, + }); + previewNode.innerHTML = (ctx.workspace.isTrusted + ? unsanitizedRenderedMarkdown + : DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions)) as string; + } + }, + extendMarkdownIt: (f: (md: typeof markdownIt) => void) => { + try { + f(markdownIt); + } + catch (err) { + console.error('Error extending markdown-it', err); + } + } + }; }; - - function addNamedHeaderRendering(md: InstanceType): void { - const slugCounter = new Map(); - - const originalHeaderOpen = md.renderer.rules.heading_open; - md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => { - const title = tokens[idx + 1].children!.reduce((acc, t) => acc + t.content, ''); - let slug = slugify(title); - - if (slugCounter.has(slug)) { - const count = slugCounter.get(slug)!; - slugCounter.set(slug, count + 1); - slug = slugify(slug + '-' + (count + 1)); - } else { - slugCounter.set(slug, 0); - } - - tokens[idx].attrSet('id', slug); - - if (originalHeaderOpen) { - return originalHeaderOpen(tokens, idx, options, env, self); - } else { - return self.renderToken(tokens, idx, options); - } - }; - - const originalRender = md.render; - md.render = function () { - slugCounter.clear(); - return originalRender.apply(this, arguments as any); - }; + const slugCounter = new Map(); + const originalHeaderOpen = md.renderer.rules.heading_open; + md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + const title = tokens[idx + 1].children!.reduce((acc, t) => acc + t.content, ''); + let slug = slugify(title); + if (slugCounter.has(slug)) { + const count = slugCounter.get(slug)!; + slugCounter.set(slug, count + 1); + slug = slugify(slug + '-' + (count + 1)); + } + else { + slugCounter.set(slug, 0); + } + tokens[idx].attrSet('id', slug); + if (originalHeaderOpen) { + return originalHeaderOpen(tokens, idx, options, env, self); + } + else { + return self.renderToken(tokens, idx, options); + } + }; + const originalRender = md.render; + md.render = function () { + slugCounter.clear(); + return originalRender.apply(this, arguments as any); + }; } - function addLinkRenderer(md: MarkdownIt): void { - const original = md.renderer.rules.link_open; - - md.renderer.rules.link_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => { - const token = tokens[idx]; - const href = token.attrGet('href'); - if (typeof href === 'string' && href.startsWith('#')) { - token.attrSet('href', '#' + slugify(href.slice(1))); - } - if (original) { - return original(tokens, idx, options, env, self); - } else { - return self.renderToken(tokens, idx, options); - } - }; + const original = md.renderer.rules.link_open; + md.renderer.rules.link_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + const token = tokens[idx]; + const href = token.attrGet('href'); + if (typeof href === 'string' && href.startsWith('#')) { + token.attrSet('href', '#' + slugify(href.slice(1))); + } + if (original) { + return original(tokens, idx, options, env, self); + } + else { + return self.renderToken(tokens, idx, options); + } + }; } - function slugify(text: string): string { - const slugifiedHeading = encodeURI( - text.trim() - .toLowerCase() - .replace(/\s+/g, '-') // Replace whitespace with - - // allow-any-unicode-next-line - .replace(/[\]\[\!\/\'\"\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known punctuators - .replace(/^\-+/, '') // Remove leading - - .replace(/\-+$/, '') // Remove trailing - - ); - return slugifiedHeading; + const slugifiedHeading = encodeURI(text.trim() + .toLowerCase() + .replace(/\s+/g, '-') // Replace whitespace with - + // allow-any-unicode-next-line + .replace(/[\]\[\!\/\'\"\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known punctuators + .replace(/^\-+/, '') // Remove leading - + .replace(/\-+$/, '') // Remove trailing - + ); + return slugifiedHeading; } diff --git a/extensions/markdown-language-features/preview-src/activeLineMarker.ts b/extensions/markdown-language-features/preview-src/activeLineMarker.ts index 75c1ed7cbc962..cdf3a40428b17 100644 --- a/extensions/markdown-language-features/preview-src/activeLineMarker.ts +++ b/extensions/markdown-language-features/preview-src/activeLineMarker.ts @@ -3,33 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { getElementsForSourceLine } from './scroll-sync'; - export class ActiveLineMarker { - private _current: any; - - onDidChangeTextEditorSelection(line: number, documentVersion: number) { - const { previous } = getElementsForSourceLine(line, documentVersion); - this._update(previous && (previous.codeElement || previous.element)); - } - - private _update(before: HTMLElement | undefined) { - this._unmarkActiveElement(this._current); - this._markActiveElement(before); - this._current = before; - } - - private _unmarkActiveElement(element: HTMLElement | undefined) { - if (!element) { - return; - } - element.classList.toggle('code-active-line', false); - } - - private _markActiveElement(element: HTMLElement | undefined) { - if (!element) { - return; - } - - element.classList.toggle('code-active-line', true); - } + private _current: any; + onDidChangeTextEditorSelection(line: number, documentVersion: number) { + const { previous } = getElementsForSourceLine(line, documentVersion); + this._update(previous && (previous.codeElement || previous.element)); + } + private _update(before: HTMLElement | undefined) { + this._unmarkActiveElement(this._current); + this._markActiveElement(before); + this._current = before; + } + private _unmarkActiveElement(element: HTMLElement | undefined) { + if (!element) { + return; + } + element.classList.toggle('code-active-line', false); + } + private _markActiveElement(element: HTMLElement | undefined) { + if (!element) { + return; + } + element.classList.toggle('code-active-line', true); + } } diff --git a/extensions/markdown-language-features/preview-src/csp.ts b/extensions/markdown-language-features/preview-src/csp.ts index ea960dd4ad3ff..0cd1ec8f28cd7 100644 --- a/extensions/markdown-language-features/preview-src/csp.ts +++ b/extensions/markdown-language-features/preview-src/csp.ts @@ -2,65 +2,52 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { MessagePoster } from './messaging'; import { SettingsManager } from './settings'; import { getStrings } from './strings'; - /** * Shows an alert when there is a content security policy violation. */ export class CspAlerter { - private _didShow = false; - private _didHaveCspWarning = false; - - private _messaging?: MessagePoster; - - constructor( - private readonly _settingsManager: SettingsManager, - ) { - document.addEventListener('securitypolicyviolation', () => { - this._onCspWarning(); - }); - - window.addEventListener('message', (event) => { - if (event && event.data && event.data.name === 'vscode-did-block-svg') { - this._onCspWarning(); - } - }); - } - - public setPoster(poster: MessagePoster) { - this._messaging = poster; - if (this._didHaveCspWarning) { - this._showCspWarning(); - } - } - - private _onCspWarning() { - this._didHaveCspWarning = true; - this._showCspWarning(); - } - - private _showCspWarning() { - const strings = getStrings(); - const settings = this._settingsManager.settings; - - if (this._didShow || settings.disableSecurityWarnings || !this._messaging) { - return; - } - this._didShow = true; - - const notification = document.createElement('a'); - notification.innerText = strings.cspAlertMessageText; - notification.setAttribute('id', 'code-csp-warning'); - notification.setAttribute('title', strings.cspAlertMessageTitle); - - notification.setAttribute('role', 'button'); - notification.setAttribute('aria-label', strings.cspAlertMessageLabel); - notification.onclick = () => { - this._messaging!.postMessage('showPreviewSecuritySelector', { source: settings.source }); - }; - document.body.appendChild(notification); - } + private _didShow = false; + private _didHaveCspWarning = false; + private _messaging?: MessagePoster; + constructor(private readonly _settingsManager: SettingsManager) { + document.addEventListener('securitypolicyviolation', () => { + this._onCspWarning(); + }); + window.addEventListener('message', (event) => { + if (event && event.data && event.data.name === 'vscode-did-block-svg') { + this._onCspWarning(); + } + }); + } + public setPoster(poster: MessagePoster) { + this._messaging = poster; + if (this._didHaveCspWarning) { + this._showCspWarning(); + } + } + private _onCspWarning() { + this._didHaveCspWarning = true; + this._showCspWarning(); + } + private _showCspWarning() { + const strings = getStrings(); + const settings = this._settingsManager.settings; + if (this._didShow || settings.disableSecurityWarnings || !this._messaging) { + return; + } + this._didShow = true; + const notification = document.createElement('a'); + notification.innerText = strings.cspAlertMessageText; + notification.setAttribute('id', 'code-csp-warning'); + notification.setAttribute('title', strings.cspAlertMessageTitle); + notification.setAttribute('role', 'button'); + notification.setAttribute('aria-label', strings.cspAlertMessageLabel); + notification.onclick = () => { + this._messaging!.postMessage('showPreviewSecuritySelector', { source: settings.source }); + }; + document.body.appendChild(notification); + } } diff --git a/extensions/markdown-language-features/preview-src/events.ts b/extensions/markdown-language-features/preview-src/events.ts index 81ce5c616406f..304e91104d15a 100644 --- a/extensions/markdown-language-features/preview-src/events.ts +++ b/extensions/markdown-language-features/preview-src/events.ts @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export function onceDocumentLoaded(f: () => void) { - if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') { - document.addEventListener('DOMContentLoaded', f); - } else { - f(); - } -} \ No newline at end of file + if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') { + document.addEventListener('DOMContentLoaded', f); + } + else { + f(); + } +} diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 33c84e0a38477..42174cfeb3c08 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ActiveLineMarker } from './activeLineMarker'; import { onceDocumentLoaded } from './events'; import { createPosterForVsCode } from './messaging'; @@ -11,363 +10,312 @@ import { SettingsManager, getData } from './settings'; import throttle = require('lodash.throttle'); import morphdom from 'morphdom'; import type { ToWebviewMessage } from '../types/previewMessaging'; - let scrollDisabledCount = 0; - const marker = new ActiveLineMarker(); const settings = new SettingsManager(); - let documentVersion = 0; let documentResource = settings.settings.source; - const vscode = acquireVsCodeApi(); - const originalState = vscode.getState() ?? {} as any; const state = { - ...originalState, - ...getData('data-state') + ...originalState, + ...getData('data-state') }; - if (typeof originalState.scrollProgress !== 'undefined' && originalState?.resource !== state.resource) { - state.scrollProgress = 0; + state.scrollProgress = 0; } - // Make sure to sync VS Code state here vscode.setState(state); - const messaging = createPosterForVsCode(vscode, settings); - window.cspAlerter.setPoster(messaging); window.styleLoadingMonitor.setPoster(messaging); - - function doAfterImagesLoaded(cb: () => void) { - const imgElements = document.getElementsByTagName('img'); - if (imgElements.length > 0) { - const ps = Array.from(imgElements, e => { - if (e.complete) { - return Promise.resolve(); - } else { - return new Promise((resolve) => { - e.addEventListener('load', () => resolve()); - e.addEventListener('error', () => resolve()); - }); - } - }); - Promise.all(ps).then(() => setTimeout(cb, 0)); - } else { - setTimeout(cb, 0); - } + const imgElements = document.getElementsByTagName('img'); + if (imgElements.length > 0) { + const ps = Array.from(imgElements, e => { + if (e.complete) { + return Promise.resolve(); + } + else { + return new Promise((resolve) => { + e.addEventListener('load', () => resolve()); + e.addEventListener('error', () => resolve()); + }); + } + }); + Promise.all(ps).then(() => setTimeout(cb, 0)); + } + else { + setTimeout(cb, 0); + } } - onceDocumentLoaded(() => { - const scrollProgress = state.scrollProgress; - - addImageContexts(); - if (typeof scrollProgress === 'number' && !settings.settings.fragment) { - doAfterImagesLoaded(() => { - scrollDisabledCount += 1; - // Always set scroll of at least 1 to prevent VS Code's webview code from auto scrolling us - const scrollToY = Math.max(1, scrollProgress * document.body.clientHeight); - window.scrollTo(0, scrollToY); - }); - return; - } - - if (settings.settings.scrollPreviewWithEditor) { - doAfterImagesLoaded(() => { - // Try to scroll to fragment if available - if (settings.settings.fragment) { - let fragment: string; - try { - fragment = encodeURIComponent(settings.settings.fragment); - } catch { - fragment = settings.settings.fragment; - } - state.fragment = undefined; - vscode.setState(state); - - const element = getLineElementForFragment(fragment, documentVersion); - if (element) { - scrollDisabledCount += 1; - scrollToRevealSourceLine(element.line, documentVersion, settings); - } - } else { - if (!isNaN(settings.settings.line!)) { - scrollDisabledCount += 1; - scrollToRevealSourceLine(settings.settings.line!, documentVersion, settings); - } - } - }); - } - - if (typeof settings.settings.selectedLine === 'number') { - marker.onDidChangeTextEditorSelection(settings.settings.selectedLine, documentVersion); - } + const scrollProgress = state.scrollProgress; + addImageContexts(); + if (typeof scrollProgress === 'number' && !settings.settings.fragment) { + doAfterImagesLoaded(() => { + scrollDisabledCount += 1; + // Always set scroll of at least 1 to prevent VS Code's webview code from auto scrolling us + const scrollToY = Math.max(1, scrollProgress * document.body.clientHeight); + window.scrollTo(0, scrollToY); + }); + return; + } + if (settings.settings.scrollPreviewWithEditor) { + doAfterImagesLoaded(() => { + // Try to scroll to fragment if available + if (settings.settings.fragment) { + let fragment: string; + try { + fragment = encodeURIComponent(settings.settings.fragment); + } + catch { + fragment = settings.settings.fragment; + } + state.fragment = undefined; + vscode.setState(state); + const element = getLineElementForFragment(fragment, documentVersion); + if (element) { + scrollDisabledCount += 1; + scrollToRevealSourceLine(element.line, documentVersion, settings); + } + } + else { + if (!isNaN(settings.settings.line!)) { + scrollDisabledCount += 1; + scrollToRevealSourceLine(settings.settings.line!, documentVersion, settings); + } + } + }); + } + if (typeof settings.settings.selectedLine === 'number') { + marker.onDidChangeTextEditorSelection(settings.settings.selectedLine, documentVersion); + } }); - const onUpdateView = (() => { - const doScroll = throttle((line: number) => { - scrollDisabledCount += 1; - doAfterImagesLoaded(() => scrollToRevealSourceLine(line, documentVersion, settings)); - }, 50); - - return (line: number) => { - if (!isNaN(line)) { - state.line = line; - - doScroll(line); - } - }; + const doScroll = throttle((line: number) => { + scrollDisabledCount += 1; + doAfterImagesLoaded(() => scrollToRevealSourceLine(line, documentVersion, settings)); + }, 50); + return (line: number) => { + if (!isNaN(line)) { + state.line = line; + doScroll(line); + } + }; })(); - window.addEventListener('resize', () => { - scrollDisabledCount += 1; - updateScrollProgress(); + scrollDisabledCount += 1; + updateScrollProgress(); }, true); - function addImageContexts() { - const images = document.getElementsByTagName('img'); - let idNumber = 0; - for (const img of images) { - img.id = 'image-' + idNumber; - idNumber += 1; - img.setAttribute('data-vscode-context', JSON.stringify({ webviewSection: 'image', id: img.id, 'preventDefaultContextMenuItems': true, resource: documentResource })); - } + const images = document.getElementsByTagName('img'); + let idNumber = 0; + for (const img of images) { + img.id = 'image-' + idNumber; + idNumber += 1; + img.setAttribute('data-vscode-context', JSON.stringify({ webviewSection: 'image', id: img.id, 'preventDefaultContextMenuItems': true, resource: documentResource })); + } } - async function copyImage(image: HTMLImageElement, retries = 5) { - if (!document.hasFocus() && retries > 0) { - // copyImage is called at the same time as webview.reveal, which means this function is running whilst the webview is gaining focus. - // Since navigator.clipboard.write requires the document to be focused, we need to wait for focus. - // We cannot use a listener, as there is a high chance the focus is gained during the setup of the listener resulting in us missing it. - setTimeout(() => { copyImage(image, retries - 1); }, 20); - return; - } - - try { - await navigator.clipboard.write([new ClipboardItem({ - 'image/png': new Promise((resolve) => { - const canvas = document.createElement('canvas'); - if (canvas !== null) { - canvas.width = image.naturalWidth; - canvas.height = image.naturalHeight; - const context = canvas.getContext('2d'); - context?.drawImage(image, 0, 0); - } - canvas.toBlob((blob) => { - if (blob) { - resolve(blob); - } - canvas.remove(); - }, 'image/png'); - }) - })]); - } catch (e) { - console.error(e); - } + if (!document.hasFocus() && retries > 0) { + // copyImage is called at the same time as webview.reveal, which means this function is running whilst the webview is gaining focus. + // Since navigator.clipboard.write requires the document to be focused, we need to wait for focus. + // We cannot use a listener, as there is a high chance the focus is gained during the setup of the listener resulting in us missing it. + setTimeout(() => { copyImage(image, retries - 1); }, 20); + return; + } + try { + await navigator.clipboard.write([new ClipboardItem({ + 'image/png': new Promise((resolve) => { + const canvas = document.createElement('canvas'); + if (canvas !== null) { + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const context = canvas.getContext('2d'); + context?.drawImage(image, 0, 0); + } + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } + canvas.remove(); + }, 'image/png'); + }) + })]); + } + catch (e) { + console.error(e); + } } - -window.addEventListener('message', async event => { - const data = event.data as ToWebviewMessage.Type; - switch (data.type) { - case 'copyImage': { - const img = document.getElementById(data.id); - if (img instanceof HTMLImageElement) { - copyImage(img); - } - return; - } - case 'onDidChangeTextEditorSelection': - if (data.source === documentResource) { - marker.onDidChangeTextEditorSelection(data.line, documentVersion); - } - return; - - case 'updateView': - if (data.source === documentResource) { - onUpdateView(data.line); - } - return; - - case 'updateContent': { - const root = document.querySelector('.markdown-body')!; - - const parser = new DOMParser(); - const newContent = parser.parseFromString(data.content, 'text/html'); // CodeQL [SM03712] This renderers content from the workspace into the Markdown preview. Webviews (and the markdown preview) have many other security measures in place to make this safe - - // Strip out meta http-equiv tags - for (const metaElement of Array.from(newContent.querySelectorAll('meta'))) { - if (metaElement.hasAttribute('http-equiv')) { - metaElement.remove(); - } - } - - if (data.source !== documentResource) { - root.replaceWith(newContent.querySelector('.markdown-body')!); - documentResource = data.source; - } else { - const skippedAttrs = [ - 'open', // for details - ]; - - // Compare two elements but some elements - const areEqual = (a: Element, b: Element): boolean => { - if (a.isEqualNode(b)) { - return true; - } - - if (a.tagName !== b.tagName || a.textContent !== b.textContent) { - return false; - } - - const aAttrs = [...a.attributes].filter(attr => !skippedAttrs.includes(attr.name)); - const bAttrs = [...b.attributes].filter(attr => !skippedAttrs.includes(attr.name)); - if (aAttrs.length !== bAttrs.length) { - return false; - } - - for (let i = 0; i < aAttrs.length; ++i) { - const aAttr = aAttrs[i]; - const bAttr = bAttrs[i]; - if (aAttr.name !== bAttr.name) { - return false; - } - if (aAttr.value !== bAttr.value && aAttr.name !== 'data-line') { - return false; - } - } - - const aChildren = Array.from(a.children); - const bChildren = Array.from(b.children); - - return aChildren.length === bChildren.length && aChildren.every((x, i) => areEqual(x, bChildren[i])); - }; - - const newRoot = newContent.querySelector('.markdown-body')!; - - // Move styles to head - // This prevents an ugly flash of unstyled content - const styles = newRoot.querySelectorAll('link'); - for (const style of styles) { - style.remove(); - } - newRoot.prepend(...styles); - morphdom(root, newRoot, { - childrenOnly: true, - onBeforeElUpdated: (fromEl, toEl) => { - if (areEqual(fromEl, toEl)) { - // areEqual doesn't look at `data-line` so copy those over manually - const fromLines = fromEl.querySelectorAll('[data-line]'); - const toLines = toEl.querySelectorAll('[data-line]'); - if (fromLines.length !== toLines.length) { - console.log('unexpected line number change'); - } - - for (let i = 0; i < fromLines.length; ++i) { - const fromChild = fromLines[i]; - const toChild = toLines[i]; - if (toChild) { - fromChild.setAttribute('data-line', toChild.getAttribute('data-line')!); - } - } - - return false; - } - - if (fromEl.tagName === 'DETAILS' && toEl.tagName === 'DETAILS') { - if (fromEl.hasAttribute('open')) { - toEl.setAttribute('open', ''); - } - } - - return true; - } - }); - } - - ++documentVersion; - - window.dispatchEvent(new CustomEvent('vscode.markdown.updateContent')); - addImageContexts(); - break; - } - } +window.addEventListener('message', async (event) => { + const data = event.data as ToWebviewMessage.Type; + switch (data.type) { + case 'copyImage': { + const img = document.getElementById(data.id); + if (img instanceof HTMLImageElement) { + copyImage(img); + } + return; + } + case 'onDidChangeTextEditorSelection': + if (data.source === documentResource) { + marker.onDidChangeTextEditorSelection(data.line, documentVersion); + } + return; + case 'updateView': + if (data.source === documentResource) { + onUpdateView(data.line); + } + return; + case 'updateContent': { + const root = document.querySelector('.markdown-body')!; + const parser = new DOMParser(); + const newContent = parser.parseFromString(data.content, 'text/html'); // CodeQL [SM03712] This renderers content from the workspace into the Markdown preview. Webviews (and the markdown preview) have many other security measures in place to make this safe + // Strip out meta http-equiv tags + for (const metaElement of Array.from(newContent.querySelectorAll('meta'))) { + if (metaElement.hasAttribute('http-equiv')) { + metaElement.remove(); + } + } + if (data.source !== documentResource) { + root.replaceWith(newContent.querySelector('.markdown-body')!); + documentResource = data.source; + } + else { + const skippedAttrs = [ + 'open', // for details + ]; + // Compare two elements but some elements + const areEqual = (a: Element, b: Element): boolean => { + if (a.isEqualNode(b)) { + return true; + } + if (a.tagName !== b.tagName || a.textContent !== b.textContent) { + return false; + } + const aAttrs = [...a.attributes].filter(attr => !skippedAttrs.includes(attr.name)); + const bAttrs = [...b.attributes].filter(attr => !skippedAttrs.includes(attr.name)); + if (aAttrs.length !== bAttrs.length) { + return false; + } + for (let i = 0; i < aAttrs.length; ++i) { + const aAttr = aAttrs[i]; + const bAttr = bAttrs[i]; + if (aAttr.name !== bAttr.name) { + return false; + } + if (aAttr.value !== bAttr.value && aAttr.name !== 'data-line') { + return false; + } + } + const aChildren = Array.from(a.children); + const bChildren = Array.from(b.children); + return aChildren.length === bChildren.length && aChildren.every((x, i) => areEqual(x, bChildren[i])); + }; + const newRoot = newContent.querySelector('.markdown-body')!; + // Move styles to head + // This prevents an ugly flash of unstyled content + const styles = newRoot.querySelectorAll('link'); + for (const style of styles) { + style.remove(); + } + newRoot.prepend(...styles); + morphdom(root, newRoot, { + childrenOnly: true, + onBeforeElUpdated: (fromEl, toEl) => { + if (areEqual(fromEl, toEl)) { + // areEqual doesn't look at `data-line` so copy those over manually + const fromLines = fromEl.querySelectorAll('[data-line]'); + const toLines = toEl.querySelectorAll('[data-line]'); + if (fromLines.length !== toLines.length) { + console.log('unexpected line number change'); + } + for (let i = 0; i < fromLines.length; ++i) { + const fromChild = fromLines[i]; + const toChild = toLines[i]; + if (toChild) { + fromChild.setAttribute('data-line', toChild.getAttribute('data-line')!); + } + } + return false; + } + if (fromEl.tagName === 'DETAILS' && toEl.tagName === 'DETAILS') { + if (fromEl.hasAttribute('open')) { + toEl.setAttribute('open', ''); + } + } + return true; + } + }); + } + ++documentVersion; + window.dispatchEvent(new CustomEvent('vscode.markdown.updateContent')); + addImageContexts(); + break; + } + } }, false); - - - document.addEventListener('dblclick', event => { - if (!settings.settings.doubleClickToSwitchToEditor) { - return; - } - - // Ignore clicks on links - for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) { - if (node.tagName === 'A') { - return; - } - } - - const offset = event.pageY; - const line = getEditorLineNumberForPageOffset(offset, documentVersion); - if (typeof line === 'number' && !isNaN(line)) { - messaging.postMessage('didClick', { line: Math.floor(line) }); - } + if (!settings.settings.doubleClickToSwitchToEditor) { + return; + } + // Ignore clicks on links + for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) { + if (node.tagName === 'A') { + return; + } + } + const offset = event.pageY; + const line = getEditorLineNumberForPageOffset(offset, documentVersion); + if (typeof line === 'number' && !isNaN(line)) { + messaging.postMessage('didClick', { line: Math.floor(line) }); + } }); - const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders:']; - document.addEventListener('click', event => { - if (!event) { - return; - } - - let node: any = event.target; - while (node) { - if (node.tagName && node.tagName === 'A' && node.href) { - if (node.getAttribute('href').startsWith('#')) { - return; - } - - let hrefText = node.getAttribute('data-href'); - if (!hrefText) { - hrefText = node.getAttribute('href'); - // Pass through known schemes - if (passThroughLinkSchemes.some(scheme => hrefText.startsWith(scheme))) { - return; - } - } - - // If original link doesn't look like a url, delegate back to VS Code to resolve - if (!/^[a-z\-]+:/i.test(hrefText)) { - messaging.postMessage('openLink', { href: hrefText }); - event.preventDefault(); - event.stopPropagation(); - return; - } - - return; - } - node = node.parentNode; - } + if (!event) { + return; + } + let node: any = event.target; + while (node) { + if (node.tagName && node.tagName === 'A' && node.href) { + if (node.getAttribute('href').startsWith('#')) { + return; + } + let hrefText = node.getAttribute('data-href'); + if (!hrefText) { + hrefText = node.getAttribute('href'); + // Pass through known schemes + if (passThroughLinkSchemes.some(scheme => hrefText.startsWith(scheme))) { + return; + } + } + // If original link doesn't look like a url, delegate back to VS Code to resolve + if (!/^[a-z\-]+:/i.test(hrefText)) { + messaging.postMessage('openLink', { href: hrefText }); + event.preventDefault(); + event.stopPropagation(); + return; + } + return; + } + node = node.parentNode; + } }, true); - window.addEventListener('scroll', throttle(() => { - updateScrollProgress(); - - if (scrollDisabledCount > 0) { - scrollDisabledCount -= 1; - } else { - const line = getEditorLineNumberForPageOffset(window.scrollY, documentVersion); - if (typeof line === 'number' && !isNaN(line)) { - messaging.postMessage('revealLine', { line }); - } - } + updateScrollProgress(); + if (scrollDisabledCount > 0) { + scrollDisabledCount -= 1; + } + else { + const line = getEditorLineNumberForPageOffset(window.scrollY, documentVersion); + if (typeof line === 'number' && !isNaN(line)) { + messaging.postMessage('revealLine', { line }); + } + } }, 50)); - function updateScrollProgress() { - state.scrollProgress = window.scrollY / document.body.clientHeight; - vscode.setState(state); + state.scrollProgress = window.scrollY / document.body.clientHeight; + vscode.setState(state); } - diff --git a/extensions/markdown-language-features/preview-src/loading.ts b/extensions/markdown-language-features/preview-src/loading.ts index bebd440374d52..3aa64b56aa38b 100644 --- a/extensions/markdown-language-features/preview-src/loading.ts +++ b/extensions/markdown-language-features/preview-src/loading.ts @@ -3,40 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { MessagePoster } from './messaging'; - export class StyleLoadingMonitor { - private _unloadedStyles: string[] = []; - private _finishedLoading: boolean = false; - - private _poster?: MessagePoster; - - constructor() { - const onStyleLoadError = (event: any) => { - const source = event.target.dataset.source; - this._unloadedStyles.push(source); - }; - - window.addEventListener('DOMContentLoaded', () => { - for (const link of document.getElementsByClassName('code-user-style') as HTMLCollectionOf) { - if (link.dataset.source) { - link.onerror = onStyleLoadError; - } - } - }); - - window.addEventListener('load', () => { - if (!this._unloadedStyles.length) { - return; - } - this._finishedLoading = true; - this._poster?.postMessage('previewStyleLoadError', { unloadedStyles: this._unloadedStyles }); - }); - } - - public setPoster(poster: MessagePoster): void { - this._poster = poster; - if (this._finishedLoading) { - poster.postMessage('previewStyleLoadError', { unloadedStyles: this._unloadedStyles }); - } - } + private _unloadedStyles: string[] = []; + private _finishedLoading: boolean = false; + private _poster?: MessagePoster; + constructor() { + const onStyleLoadError = (event: any) => { + const source = event.target.dataset.source; + this._unloadedStyles.push(source); + }; + window.addEventListener('DOMContentLoaded', () => { + for (const link of document.getElementsByClassName('code-user-style') as HTMLCollectionOf) { + if (link.dataset.source) { + link.onerror = onStyleLoadError; + } + } + }); + window.addEventListener('load', () => { + if (!this._unloadedStyles.length) { + return; + } + this._finishedLoading = true; + this._poster?.postMessage('previewStyleLoadError', { unloadedStyles: this._unloadedStyles }); + }); + } + public setPoster(poster: MessagePoster): void { + this._poster = poster; + if (this._finishedLoading) { + poster.postMessage('previewStyleLoadError', { unloadedStyles: this._unloadedStyles }); + } + } } diff --git a/extensions/markdown-language-features/preview-src/messaging.ts b/extensions/markdown-language-features/preview-src/messaging.ts index 1fb29f0b55b94..c18b509be6ba9 100644 --- a/extensions/markdown-language-features/preview-src/messaging.ts +++ b/extensions/markdown-language-features/preview-src/messaging.ts @@ -2,32 +2,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { SettingsManager } from './settings'; import type { FromWebviewMessage } from '../types/previewMessaging'; - export interface MessagePoster { - /** - * Post a message to the markdown extension - */ - postMessage( - type: T['type'], - body: Omit - ): void; + /** + * Post a message to the markdown extension + */ + postMessage(type: T['type'], body: Omit): void; } - export const createPosterForVsCode = (vscode: any, settingsManager: SettingsManager): MessagePoster => { - return { - postMessage( - type: T['type'], - body: Omit - ): void { - vscode.postMessage({ - type, - source: settingsManager.settings!.source, - ...body - }); - } - }; + return { + postMessage(type: T['type'], body: Omit): void { + vscode.postMessage({ + type, + source: settingsManager.settings!.source, + ...body + }); + } + }; }; - diff --git a/extensions/markdown-language-features/preview-src/pre.ts b/extensions/markdown-language-features/preview-src/pre.ts index 5de29c0de2b87..1fc820a76cff5 100644 --- a/extensions/markdown-language-features/preview-src/pre.ts +++ b/extensions/markdown-language-features/preview-src/pre.ts @@ -2,17 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { CspAlerter } from './csp'; import { StyleLoadingMonitor } from './loading'; import { SettingsManager } from './settings'; - declare global { - interface Window { - cspAlerter: CspAlerter; - styleLoadingMonitor: StyleLoadingMonitor; - } + interface Window { + cspAlerter: CspAlerter; + styleLoadingMonitor: StyleLoadingMonitor; + } } - window.cspAlerter = new CspAlerter(new SettingsManager()); window.styleLoadingMonitor = new StyleLoadingMonitor(); diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index a884730a15219..6af89e2a1ede7 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -2,196 +2,185 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { SettingsManager } from './settings'; - const codeLineClass = 'code-line'; - - export class CodeLineElement { - private readonly _detailParentElements: readonly HTMLDetailsElement[]; - - constructor( - readonly element: HTMLElement, - readonly line: number, - readonly codeElement?: HTMLElement, - ) { - this._detailParentElements = Array.from(getParentsWithTagName(element, 'DETAILS')); - } - - get isVisible(): boolean { - return !this._detailParentElements.some(x => !x.open); - } + private readonly _detailParentElements: readonly HTMLDetailsElement[]; + constructor(readonly element: HTMLElement, readonly line: number, readonly codeElement?: HTMLElement) { + this._detailParentElements = Array.from(getParentsWithTagName(element, 'DETAILS')); + } + get isVisible(): boolean { + return !this._detailParentElements.some(x => !x.open); + } } - const getCodeLineElements = (() => { - let cachedElements: CodeLineElement[] | undefined; - let cachedVersion = -1; - return (documentVersion: number) => { - if (!cachedElements || documentVersion !== cachedVersion) { - cachedVersion = documentVersion; - cachedElements = [new CodeLineElement(document.body, -1)]; - for (const element of document.getElementsByClassName(codeLineClass)) { - if (!(element instanceof HTMLElement)) { - continue; - } - - const line = +element.getAttribute('data-line')!; - if (isNaN(line)) { - continue; - } - - - if (element.tagName === 'CODE' && element.parentElement && element.parentElement.tagName === 'PRE') { - // Fenced code blocks are a special case since the `code-line` can only be marked on - // the `` element and not the parent `
` element.
-					cachedElements.push(new CodeLineElement(element.parentElement, line, element));
-				} else if (element.tagName === 'UL' || element.tagName === 'OL') {
-					// Skip adding list elements since the first child has the same code line (and should be preferred)
-				} else {
-					cachedElements.push(new CodeLineElement(element, line));
-				}
-			}
-		}
-		return cachedElements;
-	};
+    let cachedElements: CodeLineElement[] | undefined;
+    let cachedVersion = -1;
+    return (documentVersion: number) => {
+        if (!cachedElements || documentVersion !== cachedVersion) {
+            cachedVersion = documentVersion;
+            cachedElements = [new CodeLineElement(document.body, -1)];
+            for (const element of document.getElementsByClassName(codeLineClass)) {
+                if (!(element instanceof HTMLElement)) {
+                    continue;
+                }
+                const line = +element.getAttribute('data-line')!;
+                if (isNaN(line)) {
+                    continue;
+                }
+                if (element.tagName === 'CODE' && element.parentElement && element.parentElement.tagName === 'PRE') {
+                    // Fenced code blocks are a special case since the `code-line` can only be marked on
+                    // the `` element and not the parent `
` element.
+                    cachedElements.push(new CodeLineElement(element.parentElement, line, element));
+                }
+                else if (element.tagName === 'UL' || element.tagName === 'OL') {
+                    // Skip adding list elements since the first child has the same code line (and should be preferred)
+                }
+                else {
+                    cachedElements.push(new CodeLineElement(element, line));
+                }
+            }
+        }
+        return cachedElements;
+    };
 })();
-
 /**
  * Find the html elements that map to a specific target line in the editor.
  *
  * If an exact match, returns a single element. If the line is between elements,
  * returns the element prior to and the element after the given line.
  */
-export function getElementsForSourceLine(targetLine: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement } {
-	const lineNumber = Math.floor(targetLine);
-	const lines = getCodeLineElements(documentVersion);
-	let previous = lines[0] || null;
-	for (const entry of lines) {
-		if (entry.line === lineNumber) {
-			return { previous: entry, next: undefined };
-		} else if (entry.line > lineNumber) {
-			return { previous, next: entry };
-		}
-		previous = entry;
-	}
-	return { previous };
+export function getElementsForSourceLine(targetLine: number, documentVersion: number): {
+    previous: CodeLineElement;
+    next?: CodeLineElement;
+} {
+    const lineNumber = Math.floor(targetLine);
+    const lines = getCodeLineElements(documentVersion);
+    let previous = lines[0] || null;
+    for (const entry of lines) {
+        if (entry.line === lineNumber) {
+            return { previous: entry, next: undefined };
+        }
+        else if (entry.line > lineNumber) {
+            return { previous, next: entry };
+        }
+        previous = entry;
+    }
+    return { previous };
 }
-
 /**
  * Find the html elements that are at a specific pixel offset on the page.
  */
-export function getLineElementsAtPageOffset(offset: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement } {
-	const lines = getCodeLineElements(documentVersion).filter(x => x.isVisible);
-	const position = offset - window.scrollY;
-	let lo = -1;
-	let hi = lines.length - 1;
-	while (lo + 1 < hi) {
-		const mid = Math.floor((lo + hi) / 2);
-		const bounds = getElementBounds(lines[mid]);
-		if (bounds.top + bounds.height >= position) {
-			hi = mid;
-		}
-		else {
-			lo = mid;
-		}
-	}
-	const hiElement = lines[hi];
-	const hiBounds = getElementBounds(hiElement);
-	if (hi >= 1 && hiBounds.top > position) {
-		const loElement = lines[lo];
-		return { previous: loElement, next: hiElement };
-	}
-	if (hi > 1 && hi < lines.length && hiBounds.top + hiBounds.height > position) {
-		return { previous: hiElement, next: lines[hi + 1] };
-	}
-	return { previous: hiElement };
+export function getLineElementsAtPageOffset(offset: number, documentVersion: number): {
+    previous: CodeLineElement;
+    next?: CodeLineElement;
+} {
+    const lines = getCodeLineElements(documentVersion).filter(x => x.isVisible);
+    const position = offset - window.scrollY;
+    let lo = -1;
+    let hi = lines.length - 1;
+    while (lo + 1 < hi) {
+        const mid = Math.floor((lo + hi) / 2);
+        const bounds = getElementBounds(lines[mid]);
+        if (bounds.top + bounds.height >= position) {
+            hi = mid;
+        }
+        else {
+            lo = mid;
+        }
+    }
+    const hiElement = lines[hi];
+    const hiBounds = getElementBounds(hiElement);
+    if (hi >= 1 && hiBounds.top > position) {
+        const loElement = lines[lo];
+        return { previous: loElement, next: hiElement };
+    }
+    if (hi > 1 && hi < lines.length && hiBounds.top + hiBounds.height > position) {
+        return { previous: hiElement, next: lines[hi + 1] };
+    }
+    return { previous: hiElement };
 }
-
-function getElementBounds({ element }: CodeLineElement): { top: number; height: number } {
-	const myBounds = element.getBoundingClientRect();
-
-	// Some code line elements may contain other code line elements.
-	// In those cases, only take the height up to that child.
-	const codeLineChild = element.querySelector(`.${codeLineClass}`);
-	if (codeLineChild) {
-		const childBounds = codeLineChild.getBoundingClientRect();
-		const height = Math.max(1, (childBounds.top - myBounds.top));
-		return {
-			top: myBounds.top,
-			height: height
-		};
-	}
-
-	return myBounds;
+function getElementBounds({ element }: CodeLineElement): {
+    top: number;
+    height: number;
+} {
+    const myBounds = element.getBoundingClientRect();
+    // Some code line elements may contain other code line elements.
+    // In those cases, only take the height up to that child.
+    const codeLineChild = element.querySelector(`.${codeLineClass}`);
+    if (codeLineChild) {
+        const childBounds = codeLineChild.getBoundingClientRect();
+        const height = Math.max(1, (childBounds.top - myBounds.top));
+        return {
+            top: myBounds.top,
+            height: height
+        };
+    }
+    return myBounds;
 }
-
 /**
  * Attempt to reveal the element for a source line in the editor.
  */
 export function scrollToRevealSourceLine(line: number, documentVersion: number, settingsManager: SettingsManager) {
-	if (!settingsManager.settings?.scrollPreviewWithEditor) {
-		return;
-	}
-
-	if (line <= 0) {
-		window.scroll(window.scrollX, 0);
-		return;
-	}
-
-	const { previous, next } = getElementsForSourceLine(line, documentVersion);
-	if (!previous) {
-		return;
-	}
-	let scrollTo = 0;
-	const rect = getElementBounds(previous);
-	const previousTop = rect.top;
-	if (next && next.line !== previous.line) {
-		// Between two elements. Go to percentage offset between them.
-		const betweenProgress = (line - previous.line) / (next.line - previous.line);
-		const previousEnd = previousTop + rect.height;
-		const betweenHeight = next.element.getBoundingClientRect().top - previousEnd;
-		scrollTo = previousEnd + betweenProgress * betweenHeight;
-	} else {
-		const progressInElement = line - Math.floor(line);
-		scrollTo = previousTop + (rect.height * progressInElement);
-	}
-	scrollTo = Math.abs(scrollTo) < 1 ? Math.sign(scrollTo) : scrollTo;
-	window.scroll(window.scrollX, Math.max(1, window.scrollY + scrollTo));
+    if (!settingsManager.settings?.scrollPreviewWithEditor) {
+        return;
+    }
+    if (line <= 0) {
+        window.scroll(window.scrollX, 0);
+        return;
+    }
+    const { previous, next } = getElementsForSourceLine(line, documentVersion);
+    if (!previous) {
+        return;
+    }
+    let scrollTo = 0;
+    const rect = getElementBounds(previous);
+    const previousTop = rect.top;
+    if (next && next.line !== previous.line) {
+        // Between two elements. Go to percentage offset between them.
+        const betweenProgress = (line - previous.line) / (next.line - previous.line);
+        const previousEnd = previousTop + rect.height;
+        const betweenHeight = next.element.getBoundingClientRect().top - previousEnd;
+        scrollTo = previousEnd + betweenProgress * betweenHeight;
+    }
+    else {
+        const progressInElement = line - Math.floor(line);
+        scrollTo = previousTop + (rect.height * progressInElement);
+    }
+    scrollTo = Math.abs(scrollTo) < 1 ? Math.sign(scrollTo) : scrollTo;
+    window.scroll(window.scrollX, Math.max(1, window.scrollY + scrollTo));
 }
-
 export function getEditorLineNumberForPageOffset(offset: number, documentVersion: number): number | null {
-	const { previous, next } = getLineElementsAtPageOffset(offset, documentVersion);
-	if (previous) {
-		if (previous.line < 0) {
-			return 0;
-		}
-		const previousBounds = getElementBounds(previous);
-		const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
-		if (next) {
-			const progressBetweenElements = offsetFromPrevious / (getElementBounds(next).top - previousBounds.top);
-			return previous.line + progressBetweenElements * (next.line - previous.line);
-		} else {
-			const progressWithinElement = offsetFromPrevious / (previousBounds.height);
-			return previous.line + progressWithinElement;
-		}
-	}
-	return null;
+    const { previous, next } = getLineElementsAtPageOffset(offset, documentVersion);
+    if (previous) {
+        if (previous.line < 0) {
+            return 0;
+        }
+        const previousBounds = getElementBounds(previous);
+        const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
+        if (next) {
+            const progressBetweenElements = offsetFromPrevious / (getElementBounds(next).top - previousBounds.top);
+            return previous.line + progressBetweenElements * (next.line - previous.line);
+        }
+        else {
+            const progressWithinElement = offsetFromPrevious / (previousBounds.height);
+            return previous.line + progressWithinElement;
+        }
+    }
+    return null;
 }
-
 /**
  * Try to find the html element by using a fragment id
  */
 export function getLineElementForFragment(fragment: string, documentVersion: number): CodeLineElement | undefined {
-	return getCodeLineElements(documentVersion).find((element) => {
-		return element.element.id === fragment;
-	});
+    return getCodeLineElements(documentVersion).find((element) => {
+        return element.element.id === fragment;
+    });
 }
-
 function* getParentsWithTagName(element: HTMLElement, tagName: string): Iterable {
-	for (let parent = element.parentElement; parent; parent = parent.parentElement) {
-		if (parent.tagName === tagName) {
-			yield parent as T;
-		}
-	}
+    for (let parent = element.parentElement; parent; parent = parent.parentElement) {
+        if (parent.tagName === tagName) {
+            yield parent as T;
+        }
+    }
 }
diff --git a/extensions/markdown-language-features/preview-src/settings.ts b/extensions/markdown-language-features/preview-src/settings.ts
index 1bbe3477f254b..1810c800829c6 100644
--- a/extensions/markdown-language-features/preview-src/settings.ts
+++ b/extensions/markdown-language-features/preview-src/settings.ts
@@ -2,40 +2,33 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export interface PreviewSettings {
-	readonly source: string;
-	readonly line?: number;
-	readonly fragment?: string;
-	readonly selectedLine?: number;
-
-	readonly scrollPreviewWithEditor?: boolean;
-	readonly scrollEditorWithPreview: boolean;
-	readonly disableSecurityWarnings: boolean;
-	readonly doubleClickToSwitchToEditor: boolean;
-	readonly webviewResourceRoot: string;
+    readonly source: string;
+    readonly line?: number;
+    readonly fragment?: string;
+    readonly selectedLine?: number;
+    readonly scrollPreviewWithEditor?: boolean;
+    readonly scrollEditorWithPreview: boolean;
+    readonly disableSecurityWarnings: boolean;
+    readonly doubleClickToSwitchToEditor: boolean;
+    readonly webviewResourceRoot: string;
 }
-
 export function getData(key: string): T {
-	const element = document.getElementById('vscode-markdown-preview-data');
-	if (element) {
-		const data = element.getAttribute(key);
-		if (data) {
-			return JSON.parse(data);
-		}
-	}
-
-	throw new Error(`Could not load data for ${key}`);
+    const element = document.getElementById('vscode-markdown-preview-data');
+    if (element) {
+        const data = element.getAttribute(key);
+        if (data) {
+            return JSON.parse(data);
+        }
+    }
+    throw new Error(`Could not load data for ${key}`);
 }
-
 export class SettingsManager {
-	private _settings: PreviewSettings = getData('data-settings');
-
-	public get settings(): PreviewSettings {
-		return this._settings;
-	}
-
-	public updateSettings(newSettings: PreviewSettings) {
-		this._settings = newSettings;
-	}
+    private _settings: PreviewSettings = getData('data-settings');
+    public get settings(): PreviewSettings {
+        return this._settings;
+    }
+    public updateSettings(newSettings: PreviewSettings) {
+        this._settings = newSettings;
+    }
 }
diff --git a/extensions/markdown-language-features/preview-src/strings.ts b/extensions/markdown-language-features/preview-src/strings.ts
index 61524535b9b45..91ff23f37fa73 100644
--- a/extensions/markdown-language-features/preview-src/strings.ts
+++ b/extensions/markdown-language-features/preview-src/strings.ts
@@ -2,14 +2,15 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
-export function getStrings(): { [key: string]: string } {
-	const store = document.getElementById('vscode-markdown-preview-data');
-	if (store) {
-		const data = store.getAttribute('data-strings');
-		if (data) {
-			return JSON.parse(data);
-		}
-	}
-	throw new Error('Could not load strings');
+export function getStrings(): {
+    [key: string]: string;
+} {
+    const store = document.getElementById('vscode-markdown-preview-data');
+    if (store) {
+        const data = store.getAttribute('data-strings');
+        if (data) {
+            return JSON.parse(data);
+        }
+    }
+    throw new Error('Could not load strings');
 }
diff --git a/extensions/markdown-math/Source/extension.ts b/extensions/markdown-math/Source/extension.ts
index 6491b0c145967..5c5ba8c222468 100644
--- a/extensions/markdown-math/Source/extension.ts
+++ b/extensions/markdown-math/Source/extension.ts
@@ -3,44 +3,42 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import * as vscode from 'vscode';
-
 declare function require(path: string): any;
-
 const markdownMathSetting = 'markdown.math';
-
 export function activate(context: vscode.ExtensionContext) {
-	function isEnabled(): boolean {
-		const config = vscode.workspace.getConfiguration('markdown');
-		return config.get('math.enabled', true);
-	}
-
-	function getMacros(): { [key: string]: string } {
-		const config = vscode.workspace.getConfiguration('markdown');
-		return config.get<{ [key: string]: string }>('math.macros', {});
-	}
-
-	vscode.workspace.onDidChangeConfiguration(e => {
-		if (e.affectsConfiguration(markdownMathSetting)) {
-			vscode.commands.executeCommand('markdown.api.reloadPlugins');
-		}
-	}, undefined, context.subscriptions);
-
-	return {
-		extendMarkdownIt(md: any) {
-			if (isEnabled()) {
-				const katex = require('@vscode/markdown-it-katex').default;
-				const settingsMacros = getMacros();
-				const options = {
-					enableFencedBlocks: true,
-					globalGroup: true,
-					macros: { ...settingsMacros }
-				};
-				md.core.ruler.push('reset-katex-macros', () => {
-					options.macros = { ...settingsMacros };
-				});
-				return md.use(katex, options);
-			}
-			return md;
-		}
-	};
-}
\ No newline at end of file
+    function isEnabled(): boolean {
+        const config = vscode.workspace.getConfiguration('markdown');
+        return config.get('math.enabled', true);
+    }
+    function getMacros(): {
+        [key: string]: string;
+    } {
+        const config = vscode.workspace.getConfiguration('markdown');
+        return config.get<{
+            [key: string]: string;
+        }>('math.macros', {});
+    }
+    vscode.workspace.onDidChangeConfiguration(e => {
+        if (e.affectsConfiguration(markdownMathSetting)) {
+            vscode.commands.executeCommand('markdown.api.reloadPlugins');
+        }
+    }, undefined, context.subscriptions);
+    return {
+        extendMarkdownIt(md: any) {
+            if (isEnabled()) {
+                const katex = require('@vscode/markdown-it-katex').default;
+                const settingsMacros = getMacros();
+                const options = {
+                    enableFencedBlocks: true,
+                    globalGroup: true,
+                    macros: { ...settingsMacros }
+                };
+                md.core.ruler.push('reset-katex-macros', () => {
+                    options.macros = { ...settingsMacros };
+                });
+                return md.use(katex, options);
+            }
+            return md;
+        }
+    };
+}
diff --git a/extensions/markdown-math/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts
index ccc43046b32ae..fbf1e09585035 100644
--- a/extensions/markdown-math/notebook/katex.ts
+++ b/extensions/markdown-math/notebook/katex.ts
@@ -4,32 +4,27 @@
  *--------------------------------------------------------------------------------------------*/
 import type * as markdownIt from 'markdown-it';
 import type { RendererContext } from 'vscode-notebook-renderer';
-
 const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css');
-
 export async function activate(ctx: RendererContext) {
-	const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as undefined | any;
-	if (!markdownItRenderer) {
-		throw new Error(`Could not load 'vscode.markdown-it-renderer'`);
-	}
-
-	// Add katex styles to be copied to shadow dom
-	const link = document.createElement('link');
-	link.rel = 'stylesheet';
-	link.classList.add('markdown-style');
-	link.href = styleHref;
-
-	// Add same katex style to root document.
-	// This is needed for the font to be loaded correctly inside the shadow dom.
-	//
-	// Seems like https://bugs.chromium.org/p/chromium/issues/detail?id=336876
-	const linkHead = document.createElement('link');
-	linkHead.rel = 'stylesheet';
-	linkHead.href = styleHref;
-	document.head.appendChild(linkHead);
-
-	const style = document.createElement('style');
-	style.textContent = `
+    const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as undefined | any;
+    if (!markdownItRenderer) {
+        throw new Error(`Could not load 'vscode.markdown-it-renderer'`);
+    }
+    // Add katex styles to be copied to shadow dom
+    const link = document.createElement('link');
+    link.rel = 'stylesheet';
+    link.classList.add('markdown-style');
+    link.href = styleHref;
+    // Add same katex style to root document.
+    // This is needed for the font to be loaded correctly inside the shadow dom.
+    //
+    // Seems like https://bugs.chromium.org/p/chromium/issues/detail?id=336876
+    const linkHead = document.createElement('link');
+    linkHead.rel = 'stylesheet';
+    linkHead.href = styleHref;
+    document.head.appendChild(linkHead);
+    const style = document.createElement('style');
+    style.textContent = `
 		.katex-error {
 			color: var(--vscode-editorError-foreground);
 		}
@@ -37,22 +32,20 @@ export async function activate(ctx: RendererContext) {
 			counter-reset: katexEqnNo mmlEqnNo;
 		}
 	`;
-
-	// Put Everything into a template
-	const styleTemplate = document.createElement('template');
-	styleTemplate.classList.add('markdown-style');
-	styleTemplate.content.appendChild(style);
-	styleTemplate.content.appendChild(link);
-	document.head.appendChild(styleTemplate);
-
-	const katex = require('@vscode/markdown-it-katex').default;
-	const macros = {};
-	markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => {
-		return md.use(katex, {
-			globalGroup: true,
-			enableBareBlocks: true,
-			enableFencedBlocks: true,
-			macros,
-		});
-	});
+    // Put Everything into a template
+    const styleTemplate = document.createElement('template');
+    styleTemplate.classList.add('markdown-style');
+    styleTemplate.content.appendChild(style);
+    styleTemplate.content.appendChild(link);
+    document.head.appendChild(styleTemplate);
+    const katex = require('@vscode/markdown-it-katex').default;
+    const macros = {};
+    markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => {
+        return md.use(katex, {
+            globalGroup: true,
+            enableBareBlocks: true,
+            enableFencedBlocks: true,
+            macros,
+        });
+    });
 }
diff --git a/extensions/media-preview/Source/audioPreview.ts b/extensions/media-preview/Source/audioPreview.ts
index e21a4189d7ba1..16c6db38c0a3d 100644
--- a/extensions/media-preview/Source/audioPreview.ts
+++ b/extensions/media-preview/Source/audioPreview.ts
@@ -2,65 +2,43 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
 import { MediaPreview, reopenAsText } from './mediaPreview';
 import { escapeAttribute, getNonce } from './util/dom';
-
 class AudioPreviewProvider implements vscode.CustomReadonlyEditorProvider {
-
-	public static readonly viewType = 'vscode.audioPreview';
-
-	constructor(
-		private readonly extensionRoot: vscode.Uri,
-		private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-	) { }
-
-	public async openCustomDocument(uri: vscode.Uri) {
-		return { uri, dispose: () => { } };
-	}
-
-	public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise {
-		new AudioPreview(this.extensionRoot, document.uri, webviewEditor, this.binarySizeStatusBarEntry);
-	}
+    public static readonly viewType = 'vscode.audioPreview';
+    constructor(private readonly extensionRoot: vscode.Uri, private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry) { }
+    public async openCustomDocument(uri: vscode.Uri) {
+        return { uri, dispose: () => { } };
+    }
+    public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise {
+        new AudioPreview(this.extensionRoot, document.uri, webviewEditor, this.binarySizeStatusBarEntry);
+    }
 }
-
-
 class AudioPreview extends MediaPreview {
-
-	constructor(
-		private readonly extensionRoot: vscode.Uri,
-		resource: vscode.Uri,
-		webviewEditor: vscode.WebviewPanel,
-		binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-	) {
-		super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry);
-
-		this._register(webviewEditor.webview.onDidReceiveMessage(message => {
-			switch (message.type) {
-				case 'reopen-as-text': {
-					reopenAsText(resource, webviewEditor.viewColumn);
-					break;
-				}
-			}
-		}));
-
-		this.updateBinarySize();
-		this.render();
-		this.updateState();
-	}
-
-	protected async getWebviewContents(): Promise {
-		const version = Date.now().toString();
-		const settings = {
-			src: await this.getResourcePath(this.webviewEditor, this.resource, version),
-		};
-
-		const nonce = getNonce();
-
-		const cspSource = this.webviewEditor.webview.cspSource;
-		return /* html */`
+    constructor(private readonly extensionRoot: vscode.Uri, resource: vscode.Uri, webviewEditor: vscode.WebviewPanel, binarySizeStatusBarEntry: BinarySizeStatusBarEntry) {
+        super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry);
+        this._register(webviewEditor.webview.onDidReceiveMessage(message => {
+            switch (message.type) {
+                case 'reopen-as-text': {
+                    reopenAsText(resource, webviewEditor.viewColumn);
+                    break;
+                }
+            }
+        }));
+        this.updateBinarySize();
+        this.render();
+        this.updateState();
+    }
+    protected async getWebviewContents(): Promise {
+        const version = Date.now().toString();
+        const settings = {
+            src: await this.getResourcePath(this.webviewEditor, this.resource, version),
+        };
+        const nonce = getNonce();
+        const cspSource = this.webviewEditor.webview.cspSource;
+        return /* html */ `
 
 
 	
@@ -85,35 +63,31 @@ class AudioPreview extends MediaPreview {
 	
 
 `;
-	}
-
-	private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise {
-		if (resource.scheme === 'git') {
-			const stat = await vscode.workspace.fs.stat(resource);
-			if (stat.size === 0) {
-				// The file is stored on git lfs
-				return null;
-			}
-		}
-
-		// Avoid adding cache busting if there is already a query string
-		if (resource.query) {
-			return webviewEditor.webview.asWebviewUri(resource).toString();
-		}
-		return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
-	}
-
-	private extensionResource(...parts: string[]) {
-		return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
-	}
+    }
+    private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise {
+        if (resource.scheme === 'git') {
+            const stat = await vscode.workspace.fs.stat(resource);
+            if (stat.size === 0) {
+                // The file is stored on git lfs
+                return null;
+            }
+        }
+        // Avoid adding cache busting if there is already a query string
+        if (resource.query) {
+            return webviewEditor.webview.asWebviewUri(resource).toString();
+        }
+        return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
+    }
+    private extensionResource(...parts: string[]) {
+        return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
+    }
 }
-
 export function registerAudioPreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable {
-	const provider = new AudioPreviewProvider(context.extensionUri, binarySizeStatusBarEntry);
-	return vscode.window.registerCustomEditorProvider(AudioPreviewProvider.viewType, provider, {
-		supportsMultipleEditorsPerDocument: true,
-		webviewOptions: {
-			retainContextWhenHidden: true,
-		}
-	});
+    const provider = new AudioPreviewProvider(context.extensionUri, binarySizeStatusBarEntry);
+    return vscode.window.registerCustomEditorProvider(AudioPreviewProvider.viewType, provider, {
+        supportsMultipleEditorsPerDocument: true,
+        webviewOptions: {
+            retainContextWhenHidden: true,
+        }
+    });
 }
diff --git a/extensions/media-preview/Source/binarySizeStatusBarEntry.ts b/extensions/media-preview/Source/binarySizeStatusBarEntry.ts
index af34bf8787ec0..f658cec008485 100644
--- a/extensions/media-preview/Source/binarySizeStatusBarEntry.ts
+++ b/extensions/media-preview/Source/binarySizeStatusBarEntry.ts
@@ -2,49 +2,39 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { PreviewStatusBarEntry } from './ownedStatusBarEntry';
-
-
 class BinarySize {
-	static readonly KB = 1024;
-	static readonly MB = BinarySize.KB * BinarySize.KB;
-	static readonly GB = BinarySize.MB * BinarySize.KB;
-	static readonly TB = BinarySize.GB * BinarySize.KB;
-
-	static formatSize(size: number): string {
-		if (size < BinarySize.KB) {
-			return vscode.l10n.t("{0}B", size);
-		}
-
-		if (size < BinarySize.MB) {
-			return vscode.l10n.t("{0}KB", (size / BinarySize.KB).toFixed(2));
-		}
-
-		if (size < BinarySize.GB) {
-			return vscode.l10n.t("{0}MB", (size / BinarySize.MB).toFixed(2));
-		}
-
-		if (size < BinarySize.TB) {
-			return vscode.l10n.t("{0}GB", (size / BinarySize.GB).toFixed(2));
-		}
-
-		return vscode.l10n.t("{0}TB", (size / BinarySize.TB).toFixed(2));
-	}
+    static readonly KB = 1024;
+    static readonly MB = BinarySize.KB * BinarySize.KB;
+    static readonly GB = BinarySize.MB * BinarySize.KB;
+    static readonly TB = BinarySize.GB * BinarySize.KB;
+    static formatSize(size: number): string {
+        if (size < BinarySize.KB) {
+            return vscode.l10n.t("{0}B", size);
+        }
+        if (size < BinarySize.MB) {
+            return vscode.l10n.t("{0}KB", (size / BinarySize.KB).toFixed(2));
+        }
+        if (size < BinarySize.GB) {
+            return vscode.l10n.t("{0}MB", (size / BinarySize.MB).toFixed(2));
+        }
+        if (size < BinarySize.TB) {
+            return vscode.l10n.t("{0}GB", (size / BinarySize.GB).toFixed(2));
+        }
+        return vscode.l10n.t("{0}TB", (size / BinarySize.TB).toFixed(2));
+    }
 }
-
 export class BinarySizeStatusBarEntry extends PreviewStatusBarEntry {
-
-	constructor() {
-		super('status.imagePreview.binarySize', vscode.l10n.t("Image Binary Size"), vscode.StatusBarAlignment.Right, 100);
-	}
-
-	public show(owner: unknown, size: number | undefined) {
-		if (typeof size === 'number') {
-			super.showItem(owner, BinarySize.formatSize(size));
-		} else {
-			this.hide(owner);
-		}
-	}
+    constructor() {
+        super('status.imagePreview.binarySize', vscode.l10n.t("Image Binary Size"), vscode.StatusBarAlignment.Right, 100);
+    }
+    public show(owner: unknown, size: number | undefined) {
+        if (typeof size === 'number') {
+            super.showItem(owner, BinarySize.formatSize(size));
+        }
+        else {
+            this.hide(owner);
+        }
+    }
 }
diff --git a/extensions/media-preview/Source/extension.ts b/extensions/media-preview/Source/extension.ts
index 31370d48fc66d..64dd3d0ec4a2f 100644
--- a/extensions/media-preview/Source/extension.ts
+++ b/extensions/media-preview/Source/extension.ts
@@ -2,18 +2,15 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { registerAudioPreviewSupport } from './audioPreview';
 import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
 import { registerImagePreviewSupport } from './imagePreview';
 import { registerVideoPreviewSupport } from './videoPreview';
-
 export function activate(context: vscode.ExtensionContext) {
-	const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry();
-	context.subscriptions.push(binarySizeStatusBarEntry);
-
-	context.subscriptions.push(registerImagePreviewSupport(context, binarySizeStatusBarEntry));
-	context.subscriptions.push(registerAudioPreviewSupport(context, binarySizeStatusBarEntry));
-	context.subscriptions.push(registerVideoPreviewSupport(context, binarySizeStatusBarEntry));
+    const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry();
+    context.subscriptions.push(binarySizeStatusBarEntry);
+    context.subscriptions.push(registerImagePreviewSupport(context, binarySizeStatusBarEntry));
+    context.subscriptions.push(registerAudioPreviewSupport(context, binarySizeStatusBarEntry));
+    context.subscriptions.push(registerVideoPreviewSupport(context, binarySizeStatusBarEntry));
 }
diff --git a/extensions/media-preview/Source/imagePreview/index.ts b/extensions/media-preview/Source/imagePreview/index.ts
index e0c605c2a6e25..e1dbc8188493e 100644
--- a/extensions/media-preview/Source/imagePreview/index.ts
+++ b/extensions/media-preview/Source/imagePreview/index.ts
@@ -2,174 +2,129 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { BinarySizeStatusBarEntry } from '../binarySizeStatusBarEntry';
 import { MediaPreview, PreviewState, reopenAsText } from '../mediaPreview';
 import { escapeAttribute, getNonce } from '../util/dom';
 import { SizeStatusBarEntry } from './sizeStatusBarEntry';
 import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry';
-
-
 export class PreviewManager implements vscode.CustomReadonlyEditorProvider {
-
-	public static readonly viewType = 'imagePreview.previewEditor';
-
-	private readonly _previews = new Set();
-	private _activePreview: ImagePreview | undefined;
-
-	constructor(
-		private readonly extensionRoot: vscode.Uri,
-		private readonly sizeStatusBarEntry: SizeStatusBarEntry,
-		private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-		private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
-	) { }
-
-	public async openCustomDocument(uri: vscode.Uri) {
-		return { uri, dispose: () => { } };
-	}
-
-	public async resolveCustomEditor(
-		document: vscode.CustomDocument,
-		webviewEditor: vscode.WebviewPanel,
-	): Promise {
-		const preview = new ImagePreview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry);
-		this._previews.add(preview);
-		this.setActivePreview(preview);
-
-		webviewEditor.onDidDispose(() => { this._previews.delete(preview); });
-
-		webviewEditor.onDidChangeViewState(() => {
-			if (webviewEditor.active) {
-				this.setActivePreview(preview);
-			} else if (this._activePreview === preview && !webviewEditor.active) {
-				this.setActivePreview(undefined);
-			}
-		});
-	}
-
-	public get activePreview() { return this._activePreview; }
-
-	private setActivePreview(value: ImagePreview | undefined): void {
-		this._activePreview = value;
-	}
+    public static readonly viewType = 'imagePreview.previewEditor';
+    private readonly _previews = new Set();
+    private _activePreview: ImagePreview | undefined;
+    constructor(private readonly extensionRoot: vscode.Uri, private readonly sizeStatusBarEntry: SizeStatusBarEntry, private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry) { }
+    public async openCustomDocument(uri: vscode.Uri) {
+        return { uri, dispose: () => { } };
+    }
+    public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise {
+        const preview = new ImagePreview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry);
+        this._previews.add(preview);
+        this.setActivePreview(preview);
+        webviewEditor.onDidDispose(() => { this._previews.delete(preview); });
+        webviewEditor.onDidChangeViewState(() => {
+            if (webviewEditor.active) {
+                this.setActivePreview(preview);
+            }
+            else if (this._activePreview === preview && !webviewEditor.active) {
+                this.setActivePreview(undefined);
+            }
+        });
+    }
+    public get activePreview() { return this._activePreview; }
+    private setActivePreview(value: ImagePreview | undefined): void {
+        this._activePreview = value;
+    }
 }
-
-
 class ImagePreview extends MediaPreview {
-
-	private _imageSize: string | undefined;
-	private _imageZoom: Scale | undefined;
-
-	private readonly emptyPngDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==';
-
-	constructor(
-		private readonly extensionRoot: vscode.Uri,
-		resource: vscode.Uri,
-		webviewEditor: vscode.WebviewPanel,
-		private readonly sizeStatusBarEntry: SizeStatusBarEntry,
-		binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-		private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
-	) {
-		super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry);
-
-		this._register(webviewEditor.webview.onDidReceiveMessage(message => {
-			switch (message.type) {
-				case 'size': {
-					this._imageSize = message.value;
-					this.updateState();
-					break;
-				}
-				case 'zoom': {
-					this._imageZoom = message.value;
-					this.updateState();
-					break;
-				}
-				case 'reopen-as-text': {
-					reopenAsText(resource, webviewEditor.viewColumn);
-					break;
-				}
-			}
-		}));
-
-		this._register(zoomStatusBarEntry.onDidChangeScale(e => {
-			if (this.previewState === PreviewState.Active) {
-				this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
-			}
-		}));
-
-		this._register(webviewEditor.onDidChangeViewState(() => {
-			this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
-		}));
-
-		this._register(webviewEditor.onDidDispose(() => {
-			if (this.previewState === PreviewState.Active) {
-				this.sizeStatusBarEntry.hide(this);
-				this.zoomStatusBarEntry.hide(this);
-			}
-			this.previewState = PreviewState.Disposed;
-		}));
-
-		this.updateBinarySize();
-		this.render();
-		this.updateState();
-	}
-
-	public override dispose(): void {
-		super.dispose();
-		this.sizeStatusBarEntry.hide(this);
-		this.zoomStatusBarEntry.hide(this);
-	}
-
-	public zoomIn() {
-		if (this.previewState === PreviewState.Active) {
-			this.webviewEditor.webview.postMessage({ type: 'zoomIn' });
-		}
-	}
-
-	public zoomOut() {
-		if (this.previewState === PreviewState.Active) {
-			this.webviewEditor.webview.postMessage({ type: 'zoomOut' });
-		}
-	}
-
-	public copyImage() {
-		if (this.previewState === PreviewState.Active) {
-			this.webviewEditor.reveal();
-			this.webviewEditor.webview.postMessage({ type: 'copyImage' });
-		}
-	}
-
-	protected override updateState() {
-		super.updateState();
-
-		if (this.previewState === PreviewState.Disposed) {
-			return;
-		}
-
-		if (this.webviewEditor.active) {
-			this.sizeStatusBarEntry.show(this, this._imageSize || '');
-			this.zoomStatusBarEntry.show(this, this._imageZoom || 'fit');
-		} else {
-			this.sizeStatusBarEntry.hide(this);
-			this.zoomStatusBarEntry.hide(this);
-		}
-	}
-	protected override async render(): Promise {
-		await super.render();
-		this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
-	}
-
-	protected override async getWebviewContents(): Promise {
-		const version = Date.now().toString();
-		const settings = {
-			src: await this.getResourcePath(this.webviewEditor, this.resource, version),
-		};
-
-		const nonce = getNonce();
-
-		const cspSource = this.webviewEditor.webview.cspSource;
-		return /* html */`
+    private _imageSize: string | undefined;
+    private _imageZoom: Scale | undefined;
+    private readonly emptyPngDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==';
+    constructor(private readonly extensionRoot: vscode.Uri, resource: vscode.Uri, webviewEditor: vscode.WebviewPanel, private readonly sizeStatusBarEntry: SizeStatusBarEntry, binarySizeStatusBarEntry: BinarySizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry) {
+        super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry);
+        this._register(webviewEditor.webview.onDidReceiveMessage(message => {
+            switch (message.type) {
+                case 'size': {
+                    this._imageSize = message.value;
+                    this.updateState();
+                    break;
+                }
+                case 'zoom': {
+                    this._imageZoom = message.value;
+                    this.updateState();
+                    break;
+                }
+                case 'reopen-as-text': {
+                    reopenAsText(resource, webviewEditor.viewColumn);
+                    break;
+                }
+            }
+        }));
+        this._register(zoomStatusBarEntry.onDidChangeScale(e => {
+            if (this.previewState === PreviewState.Active) {
+                this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
+            }
+        }));
+        this._register(webviewEditor.onDidChangeViewState(() => {
+            this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
+        }));
+        this._register(webviewEditor.onDidDispose(() => {
+            if (this.previewState === PreviewState.Active) {
+                this.sizeStatusBarEntry.hide(this);
+                this.zoomStatusBarEntry.hide(this);
+            }
+            this.previewState = PreviewState.Disposed;
+        }));
+        this.updateBinarySize();
+        this.render();
+        this.updateState();
+    }
+    public override dispose(): void {
+        super.dispose();
+        this.sizeStatusBarEntry.hide(this);
+        this.zoomStatusBarEntry.hide(this);
+    }
+    public zoomIn() {
+        if (this.previewState === PreviewState.Active) {
+            this.webviewEditor.webview.postMessage({ type: 'zoomIn' });
+        }
+    }
+    public zoomOut() {
+        if (this.previewState === PreviewState.Active) {
+            this.webviewEditor.webview.postMessage({ type: 'zoomOut' });
+        }
+    }
+    public copyImage() {
+        if (this.previewState === PreviewState.Active) {
+            this.webviewEditor.reveal();
+            this.webviewEditor.webview.postMessage({ type: 'copyImage' });
+        }
+    }
+    protected override updateState() {
+        super.updateState();
+        if (this.previewState === PreviewState.Disposed) {
+            return;
+        }
+        if (this.webviewEditor.active) {
+            this.sizeStatusBarEntry.show(this, this._imageSize || '');
+            this.zoomStatusBarEntry.show(this, this._imageZoom || 'fit');
+        }
+        else {
+            this.sizeStatusBarEntry.hide(this);
+            this.zoomStatusBarEntry.hide(this);
+        }
+    }
+    protected override async render(): Promise {
+        await super.render();
+        this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
+    }
+    protected override async getWebviewContents(): Promise {
+        const version = Date.now().toString();
+        const settings = {
+            src: await this.getResourcePath(this.webviewEditor, this.resource, version),
+        };
+        const nonce = getNonce();
+        const cspSource = this.webviewEditor.webview.cspSource;
+        return /* html */ `
 
 
 	
@@ -194,55 +149,42 @@ class ImagePreview extends MediaPreview {
 	
 
 `;
-	}
-
-	private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise {
-		if (resource.scheme === 'git') {
-			const stat = await vscode.workspace.fs.stat(resource);
-			if (stat.size === 0) {
-				return this.emptyPngDataUri;
-			}
-		}
-
-		// Avoid adding cache busting if there is already a query string
-		if (resource.query) {
-			return webviewEditor.webview.asWebviewUri(resource).toString();
-		}
-		return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
-	}
-
-	private extensionResource(...parts: string[]) {
-		return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
-	}
+    }
+    private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise {
+        if (resource.scheme === 'git') {
+            const stat = await vscode.workspace.fs.stat(resource);
+            if (stat.size === 0) {
+                return this.emptyPngDataUri;
+            }
+        }
+        // Avoid adding cache busting if there is already a query string
+        if (resource.query) {
+            return webviewEditor.webview.asWebviewUri(resource).toString();
+        }
+        return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
+    }
+    private extensionResource(...parts: string[]) {
+        return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
+    }
 }
-
-
 export function registerImagePreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable {
-	const disposables: vscode.Disposable[] = [];
-
-	const sizeStatusBarEntry = new SizeStatusBarEntry();
-	disposables.push(sizeStatusBarEntry);
-
-	const zoomStatusBarEntry = new ZoomStatusBarEntry();
-	disposables.push(zoomStatusBarEntry);
-
-	const previewManager = new PreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
-
-	disposables.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, {
-		supportsMultipleEditorsPerDocument: true,
-	}));
-
-	disposables.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => {
-		previewManager.activePreview?.zoomIn();
-	}));
-
-	disposables.push(vscode.commands.registerCommand('imagePreview.zoomOut', () => {
-		previewManager.activePreview?.zoomOut();
-	}));
-
-	disposables.push(vscode.commands.registerCommand('imagePreview.copyImage', () => {
-		previewManager.activePreview?.copyImage();
-	}));
-
-	return vscode.Disposable.from(...disposables);
+    const disposables: vscode.Disposable[] = [];
+    const sizeStatusBarEntry = new SizeStatusBarEntry();
+    disposables.push(sizeStatusBarEntry);
+    const zoomStatusBarEntry = new ZoomStatusBarEntry();
+    disposables.push(zoomStatusBarEntry);
+    const previewManager = new PreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
+    disposables.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, {
+        supportsMultipleEditorsPerDocument: true,
+    }));
+    disposables.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => {
+        previewManager.activePreview?.zoomIn();
+    }));
+    disposables.push(vscode.commands.registerCommand('imagePreview.zoomOut', () => {
+        previewManager.activePreview?.zoomOut();
+    }));
+    disposables.push(vscode.commands.registerCommand('imagePreview.copyImage', () => {
+        previewManager.activePreview?.copyImage();
+    }));
+    return vscode.Disposable.from(...disposables);
 }
diff --git a/extensions/media-preview/Source/imagePreview/sizeStatusBarEntry.ts b/extensions/media-preview/Source/imagePreview/sizeStatusBarEntry.ts
index cdd3b15c76eee..4a51e5d431c1d 100644
--- a/extensions/media-preview/Source/imagePreview/sizeStatusBarEntry.ts
+++ b/extensions/media-preview/Source/imagePreview/sizeStatusBarEntry.ts
@@ -2,18 +2,13 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { PreviewStatusBarEntry } from '../ownedStatusBarEntry';
-
-
 export class SizeStatusBarEntry extends PreviewStatusBarEntry {
-
-	constructor() {
-		super('status.imagePreview.size', vscode.l10n.t("Image Size"), vscode.StatusBarAlignment.Right, 101 /* to the left of editor status (100) */);
-	}
-
-	public show(owner: unknown, text: string) {
-		this.showItem(owner, text);
-	}
+    constructor() {
+        super('status.imagePreview.size', vscode.l10n.t("Image Size"), vscode.StatusBarAlignment.Right, 101 /* to the left of editor status (100) */);
+    }
+    public show(owner: unknown, text: string) {
+        this.showItem(owner, text);
+    }
 }
diff --git a/extensions/media-preview/Source/imagePreview/zoomStatusBarEntry.ts b/extensions/media-preview/Source/imagePreview/zoomStatusBarEntry.ts
index 84c4f00c507e3..aef327dff6b0c 100644
--- a/extensions/media-preview/Source/imagePreview/zoomStatusBarEntry.ts
+++ b/extensions/media-preview/Source/imagePreview/zoomStatusBarEntry.ts
@@ -2,50 +2,41 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { PreviewStatusBarEntry as OwnedStatusBarEntry } from '../ownedStatusBarEntry';
-
-
 const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel';
-
 export type Scale = number | 'fit';
-
 export class ZoomStatusBarEntry extends OwnedStatusBarEntry {
-
-	private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>());
-	public readonly onDidChangeScale = this._onDidChangeScale.event;
-
-	constructor() {
-		super('status.imagePreview.zoom', vscode.l10n.t("Image Zoom"), vscode.StatusBarAlignment.Right, 102 /* to the left of editor size entry (101) */);
-
-		this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => {
-			type MyPickItem = vscode.QuickPickItem & { scale: Scale };
-
-			const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit'];
-			const options = scales.map((scale): MyPickItem => ({
-				label: this.zoomLabel(scale),
-				scale
-			}));
-
-			const pick = await vscode.window.showQuickPick(options, {
-				placeHolder: vscode.l10n.t("Select zoom level")
-			});
-			if (pick) {
-				this._onDidChangeScale.fire({ scale: pick.scale });
-			}
-		}));
-
-		this.entry.command = selectZoomLevelCommandId;
-	}
-
-	public show(owner: unknown, scale: Scale) {
-		this.showItem(owner, this.zoomLabel(scale));
-	}
-
-	private zoomLabel(scale: Scale): string {
-		return scale === 'fit'
-			? vscode.l10n.t("Whole Image")
-			: `${Math.round(scale * 100)}%`;
-	}
+    private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{
+        scale: Scale;
+    }>());
+    public readonly onDidChangeScale = this._onDidChangeScale.event;
+    constructor() {
+        super('status.imagePreview.zoom', vscode.l10n.t("Image Zoom"), vscode.StatusBarAlignment.Right, 102 /* to the left of editor size entry (101) */);
+        this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => {
+            type MyPickItem = vscode.QuickPickItem & {
+                scale: Scale;
+            };
+            const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit'];
+            const options = scales.map((scale): MyPickItem => ({
+                label: this.zoomLabel(scale),
+                scale
+            }));
+            const pick = await vscode.window.showQuickPick(options, {
+                placeHolder: vscode.l10n.t("Select zoom level")
+            });
+            if (pick) {
+                this._onDidChangeScale.fire({ scale: pick.scale });
+            }
+        }));
+        this.entry.command = selectZoomLevelCommandId;
+    }
+    public show(owner: unknown, scale: Scale) {
+        this.showItem(owner, this.zoomLabel(scale));
+    }
+    private zoomLabel(scale: Scale): string {
+        return scale === 'fit'
+            ? vscode.l10n.t("Whole Image")
+            : `${Math.round(scale * 100)}%`;
+    }
 }
diff --git a/extensions/media-preview/Source/mediaPreview.ts b/extensions/media-preview/Source/mediaPreview.ts
index 26d1e25dbaae4..7cdc22cd45a31 100644
--- a/extensions/media-preview/Source/mediaPreview.ts
+++ b/extensions/media-preview/Source/mediaPreview.ts
@@ -2,106 +2,83 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Utils } from 'vscode-uri';
 import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
 import { Disposable } from './util/dispose';
-
 export function reopenAsText(resource: vscode.Uri, viewColumn: vscode.ViewColumn | undefined) {
-	vscode.commands.executeCommand('vscode.openWith', resource, 'default', viewColumn);
+    vscode.commands.executeCommand('vscode.openWith', resource, 'default', viewColumn);
 }
-
 export const enum PreviewState {
-	Disposed,
-	Visible,
-	Active,
+    Disposed,
+    Visible,
+    Active
 }
-
 export abstract class MediaPreview extends Disposable {
-
-	protected previewState = PreviewState.Visible;
-	private _binarySize: number | undefined;
-
-	constructor(
-		extensionRoot: vscode.Uri,
-		protected readonly resource: vscode.Uri,
-		protected readonly webviewEditor: vscode.WebviewPanel,
-		private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-	) {
-		super();
-
-		webviewEditor.webview.options = {
-			enableScripts: true,
-			enableForms: false,
-			localResourceRoots: [
-				Utils.dirname(resource),
-				extensionRoot,
-			]
-		};
-
-		this._register(webviewEditor.onDidChangeViewState(() => {
-			this.updateState();
-		}));
-
-		this._register(webviewEditor.onDidDispose(() => {
-			this.previewState = PreviewState.Disposed;
-			this.dispose();
-		}));
-
-		const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*')));
-		this._register(watcher.onDidChange(e => {
-			if (e.toString() === this.resource.toString()) {
-				this.updateBinarySize();
-				this.render();
-			}
-		}));
-
-		this._register(watcher.onDidDelete(e => {
-			if (e.toString() === this.resource.toString()) {
-				this.webviewEditor.dispose();
-			}
-		}));
-	}
-
-	public override dispose() {
-		super.dispose();
-		this.binarySizeStatusBarEntry.hide(this);
-	}
-
-	protected updateBinarySize() {
-		vscode.workspace.fs.stat(this.resource).then(({ size }) => {
-			this._binarySize = size;
-			this.updateState();
-		});
-	}
-
-	protected async render() {
-		if (this.previewState === PreviewState.Disposed) {
-			return;
-		}
-
-		const content = await this.getWebviewContents();
-		if (this.previewState as PreviewState === PreviewState.Disposed) {
-			return;
-		}
-
-		this.webviewEditor.webview.html = content;
-	}
-
-	protected abstract getWebviewContents(): Promise;
-
-	protected updateState() {
-		if (this.previewState === PreviewState.Disposed) {
-			return;
-		}
-
-		if (this.webviewEditor.active) {
-			this.previewState = PreviewState.Active;
-			this.binarySizeStatusBarEntry.show(this, this._binarySize);
-		} else {
-			this.binarySizeStatusBarEntry.hide(this);
-			this.previewState = PreviewState.Visible;
-		}
-	}
+    protected previewState = PreviewState.Visible;
+    private _binarySize: number | undefined;
+    constructor(extensionRoot: vscode.Uri, protected readonly resource: vscode.Uri, protected readonly webviewEditor: vscode.WebviewPanel, private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry) {
+        super();
+        webviewEditor.webview.options = {
+            enableScripts: true,
+            enableForms: false,
+            localResourceRoots: [
+                Utils.dirname(resource),
+                extensionRoot,
+            ]
+        };
+        this._register(webviewEditor.onDidChangeViewState(() => {
+            this.updateState();
+        }));
+        this._register(webviewEditor.onDidDispose(() => {
+            this.previewState = PreviewState.Disposed;
+            this.dispose();
+        }));
+        const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*')));
+        this._register(watcher.onDidChange(e => {
+            if (e.toString() === this.resource.toString()) {
+                this.updateBinarySize();
+                this.render();
+            }
+        }));
+        this._register(watcher.onDidDelete(e => {
+            if (e.toString() === this.resource.toString()) {
+                this.webviewEditor.dispose();
+            }
+        }));
+    }
+    public override dispose() {
+        super.dispose();
+        this.binarySizeStatusBarEntry.hide(this);
+    }
+    protected updateBinarySize() {
+        vscode.workspace.fs.stat(this.resource).then(({ size }) => {
+            this._binarySize = size;
+            this.updateState();
+        });
+    }
+    protected async render() {
+        if (this.previewState === PreviewState.Disposed) {
+            return;
+        }
+        const content = await this.getWebviewContents();
+        if (this.previewState as PreviewState === PreviewState.Disposed) {
+            return;
+        }
+        this.webviewEditor.webview.html = content;
+    }
+    protected abstract getWebviewContents(): Promise;
+    protected updateState() {
+        if (this.previewState === PreviewState.Disposed) {
+            return;
+        }
+        if (this.webviewEditor.active) {
+            this.previewState = PreviewState.Active;
+            this.binarySizeStatusBarEntry.show(this, this._binarySize);
+        }
+        else {
+            this.binarySizeStatusBarEntry.hide(this);
+            this.previewState = PreviewState.Visible;
+        }
+    }
 }
diff --git a/extensions/media-preview/Source/ownedStatusBarEntry.ts b/extensions/media-preview/Source/ownedStatusBarEntry.ts
index fff43b572baaf..df86becd3cdc7 100644
--- a/extensions/media-preview/Source/ownedStatusBarEntry.ts
+++ b/extensions/media-preview/Source/ownedStatusBarEntry.ts
@@ -2,31 +2,25 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Disposable } from './util/dispose';
-
 export abstract class PreviewStatusBarEntry extends Disposable {
-	private _showOwner: unknown | undefined;
-
-	protected readonly entry: vscode.StatusBarItem;
-
-	constructor(id: string, name: string, alignment: vscode.StatusBarAlignment, priority: number) {
-		super();
-		this.entry = this._register(vscode.window.createStatusBarItem(id, alignment, priority));
-		this.entry.name = name;
-	}
-
-	protected showItem(owner: unknown, text: string) {
-		this._showOwner = owner;
-		this.entry.text = text;
-		this.entry.show();
-	}
-
-	public hide(owner: unknown) {
-		if (owner === this._showOwner) {
-			this.entry.hide();
-			this._showOwner = undefined;
-		}
-	}
+    private _showOwner: unknown | undefined;
+    protected readonly entry: vscode.StatusBarItem;
+    constructor(id: string, name: string, alignment: vscode.StatusBarAlignment, priority: number) {
+        super();
+        this.entry = this._register(vscode.window.createStatusBarItem(id, alignment, priority));
+        this.entry.name = name;
+    }
+    protected showItem(owner: unknown, text: string) {
+        this._showOwner = owner;
+        this.entry.text = text;
+        this.entry.show();
+    }
+    public hide(owner: unknown) {
+        if (owner === this._showOwner) {
+            this.entry.hide();
+            this._showOwner = undefined;
+        }
+    }
 }
diff --git a/extensions/media-preview/Source/util/dispose.ts b/extensions/media-preview/Source/util/dispose.ts
index 548094c28e5f5..c86ab13a05cf4 100644
--- a/extensions/media-preview/Source/util/dispose.ts
+++ b/extensions/media-preview/Source/util/dispose.ts
@@ -2,41 +2,35 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export function disposeAll(disposables: vscode.Disposable[]) {
-	while (disposables.length) {
-		const item = disposables.pop();
-		if (item) {
-			item.dispose();
-		}
-	}
+    while (disposables.length) {
+        const item = disposables.pop();
+        if (item) {
+            item.dispose();
+        }
+    }
 }
-
 export abstract class Disposable {
-	private _isDisposed = false;
-
-	protected _disposables: vscode.Disposable[] = [];
-
-	public dispose(): any {
-		if (this._isDisposed) {
-			return;
-		}
-		this._isDisposed = true;
-		disposeAll(this._disposables);
-	}
-
-	protected _register(value: T): T {
-		if (this._isDisposed) {
-			value.dispose();
-		} else {
-			this._disposables.push(value);
-		}
-		return value;
-	}
-
-	protected get isDisposed() {
-		return this._isDisposed;
-	}
-}
\ No newline at end of file
+    private _isDisposed = false;
+    protected _disposables: vscode.Disposable[] = [];
+    public dispose(): any {
+        if (this._isDisposed) {
+            return;
+        }
+        this._isDisposed = true;
+        disposeAll(this._disposables);
+    }
+    protected _register(value: T): T {
+        if (this._isDisposed) {
+            value.dispose();
+        }
+        else {
+            this._disposables.push(value);
+        }
+        return value;
+    }
+    protected get isDisposed() {
+        return this._isDisposed;
+    }
+}
diff --git a/extensions/media-preview/Source/util/dom.ts b/extensions/media-preview/Source/util/dom.ts
index 0f6c00da9daf7..93f100fc12417 100644
--- a/extensions/media-preview/Source/util/dom.ts
+++ b/extensions/media-preview/Source/util/dom.ts
@@ -3,16 +3,14 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import * as vscode from 'vscode';
-
 export function escapeAttribute(value: string | vscode.Uri): string {
-	return value.toString().replace(/"/g, '"');
+    return value.toString().replace(/"/g, '"');
 }
-
 export function getNonce() {
-	let text = '';
-	const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-	for (let i = 0; i < 64; i++) {
-		text += possible.charAt(Math.floor(Math.random() * possible.length));
-	}
-	return text;
+    let text = '';
+    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+    for (let i = 0; i < 64; i++) {
+        text += possible.charAt(Math.floor(Math.random() * possible.length));
+    }
+    return text;
 }
diff --git a/extensions/media-preview/Source/videoPreview.ts b/extensions/media-preview/Source/videoPreview.ts
index efc6be76a4fcb..91e5fddf7ee66 100644
--- a/extensions/media-preview/Source/videoPreview.ts
+++ b/extensions/media-preview/Source/videoPreview.ts
@@ -2,69 +2,46 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
 import { MediaPreview, reopenAsText } from './mediaPreview';
 import { escapeAttribute, getNonce } from './util/dom';
-
-
 class VideoPreviewProvider implements vscode.CustomReadonlyEditorProvider {
-
-	public static readonly viewType = 'vscode.videoPreview';
-
-	constructor(
-		private readonly extensionRoot: vscode.Uri,
-		private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-	) { }
-
-	public async openCustomDocument(uri: vscode.Uri) {
-		return { uri, dispose: () => { } };
-	}
-
-	public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise {
-		new VideoPreview(this.extensionRoot, document.uri, webviewEditor, this.binarySizeStatusBarEntry);
-	}
+    public static readonly viewType = 'vscode.videoPreview';
+    constructor(private readonly extensionRoot: vscode.Uri, private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry) { }
+    public async openCustomDocument(uri: vscode.Uri) {
+        return { uri, dispose: () => { } };
+    }
+    public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise {
+        new VideoPreview(this.extensionRoot, document.uri, webviewEditor, this.binarySizeStatusBarEntry);
+    }
 }
-
-
 class VideoPreview extends MediaPreview {
-
-	constructor(
-		private readonly extensionRoot: vscode.Uri,
-		resource: vscode.Uri,
-		webviewEditor: vscode.WebviewPanel,
-		binarySizeStatusBarEntry: BinarySizeStatusBarEntry,
-	) {
-		super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry);
-
-		this._register(webviewEditor.webview.onDidReceiveMessage(message => {
-			switch (message.type) {
-				case 'reopen-as-text': {
-					reopenAsText(resource, webviewEditor.viewColumn);
-					break;
-				}
-			}
-		}));
-
-		this.updateBinarySize();
-		this.render();
-		this.updateState();
-	}
-
-	protected async getWebviewContents(): Promise {
-		const version = Date.now().toString();
-		const configurations = vscode.workspace.getConfiguration('mediaPreview.video');
-		const settings = {
-			src: await this.getResourcePath(this.webviewEditor, this.resource, version),
-			autoplay: configurations.get('autoPlay'),
-			loop: configurations.get('loop'),
-		};
-
-		const nonce = getNonce();
-
-		const cspSource = this.webviewEditor.webview.cspSource;
-		return /* html */`
+    constructor(private readonly extensionRoot: vscode.Uri, resource: vscode.Uri, webviewEditor: vscode.WebviewPanel, binarySizeStatusBarEntry: BinarySizeStatusBarEntry) {
+        super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry);
+        this._register(webviewEditor.webview.onDidReceiveMessage(message => {
+            switch (message.type) {
+                case 'reopen-as-text': {
+                    reopenAsText(resource, webviewEditor.viewColumn);
+                    break;
+                }
+            }
+        }));
+        this.updateBinarySize();
+        this.render();
+        this.updateState();
+    }
+    protected async getWebviewContents(): Promise {
+        const version = Date.now().toString();
+        const configurations = vscode.workspace.getConfiguration('mediaPreview.video');
+        const settings = {
+            src: await this.getResourcePath(this.webviewEditor, this.resource, version),
+            autoplay: configurations.get('autoPlay'),
+            loop: configurations.get('loop'),
+        };
+        const nonce = getNonce();
+        const cspSource = this.webviewEditor.webview.cspSource;
+        return /* html */ `
 
 
 	
@@ -89,35 +66,31 @@ class VideoPreview extends MediaPreview {
 	
 
 `;
-	}
-
-	private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise {
-		if (resource.scheme === 'git') {
-			const stat = await vscode.workspace.fs.stat(resource);
-			if (stat.size === 0) {
-				// The file is stored on git lfs
-				return null;
-			}
-		}
-
-		// Avoid adding cache busting if there is already a query string
-		if (resource.query) {
-			return webviewEditor.webview.asWebviewUri(resource).toString();
-		}
-		return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
-	}
-
-	private extensionResource(...parts: string[]) {
-		return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
-	}
+    }
+    private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise {
+        if (resource.scheme === 'git') {
+            const stat = await vscode.workspace.fs.stat(resource);
+            if (stat.size === 0) {
+                // The file is stored on git lfs
+                return null;
+            }
+        }
+        // Avoid adding cache busting if there is already a query string
+        if (resource.query) {
+            return webviewEditor.webview.asWebviewUri(resource).toString();
+        }
+        return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString();
+    }
+    private extensionResource(...parts: string[]) {
+        return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts));
+    }
 }
-
 export function registerVideoPreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable {
-	const provider = new VideoPreviewProvider(context.extensionUri, binarySizeStatusBarEntry);
-	return vscode.window.registerCustomEditorProvider(VideoPreviewProvider.viewType, provider, {
-		supportsMultipleEditorsPerDocument: true,
-		webviewOptions: {
-			retainContextWhenHidden: true,
-		}
-	});
+    const provider = new VideoPreviewProvider(context.extensionUri, binarySizeStatusBarEntry);
+    return vscode.window.registerCustomEditorProvider(VideoPreviewProvider.viewType, provider, {
+        supportsMultipleEditorsPerDocument: true,
+        webviewOptions: {
+            retainContextWhenHidden: true,
+        }
+    });
 }
diff --git a/extensions/merge-conflict/Source/codelensProvider.ts b/extensions/merge-conflict/Source/codelensProvider.ts
index 73eeddfb109f2..427dcda8024cc 100644
--- a/extensions/merge-conflict/Source/codelensProvider.ts
+++ b/extensions/merge-conflict/Source/codelensProvider.ts
@@ -2,107 +2,80 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as interfaces from './interfaces';
-
 export default class MergeConflictCodeLensProvider implements vscode.CodeLensProvider, vscode.Disposable {
-	private codeLensRegistrationHandle?: vscode.Disposable | null;
-	private config?: interfaces.IExtensionConfiguration;
-	private tracker: interfaces.IDocumentMergeConflictTracker;
-
-	constructor(trackerService: interfaces.IDocumentMergeConflictTrackerService) {
-		this.tracker = trackerService.createTracker('codelens');
-	}
-
-	begin(config: interfaces.IExtensionConfiguration) {
-		this.config = config;
-
-		if (this.config.enableCodeLens) {
-			this.registerCodeLensProvider();
-		}
-	}
-
-	configurationUpdated(updatedConfig: interfaces.IExtensionConfiguration) {
-
-		if (updatedConfig.enableCodeLens === false && this.codeLensRegistrationHandle) {
-			this.codeLensRegistrationHandle.dispose();
-			this.codeLensRegistrationHandle = null;
-		}
-		else if (updatedConfig.enableCodeLens === true && !this.codeLensRegistrationHandle) {
-			this.registerCodeLensProvider();
-		}
-
-		this.config = updatedConfig;
-	}
-
-
-	dispose() {
-		if (this.codeLensRegistrationHandle) {
-			this.codeLensRegistrationHandle.dispose();
-			this.codeLensRegistrationHandle = null;
-		}
-	}
-
-	async provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): Promise {
-
-		if (!this.config || !this.config.enableCodeLens) {
-			return null;
-		}
-
-		const conflicts = await this.tracker.getConflicts(document);
-		const conflictsCount = conflicts?.length ?? 0;
-		vscode.commands.executeCommand('setContext', 'mergeConflictsCount', conflictsCount);
-
-		if (!conflictsCount) {
-			return null;
-		}
-
-		const items: vscode.CodeLens[] = [];
-
-		conflicts.forEach(conflict => {
-			const acceptCurrentCommand: vscode.Command = {
-				command: 'merge-conflict.accept.current',
-				title: vscode.l10n.t("Accept Current Change"),
-				arguments: ['known-conflict', conflict]
-			};
-
-			const acceptIncomingCommand: vscode.Command = {
-				command: 'merge-conflict.accept.incoming',
-				title: vscode.l10n.t("Accept Incoming Change"),
-				arguments: ['known-conflict', conflict]
-			};
-
-			const acceptBothCommand: vscode.Command = {
-				command: 'merge-conflict.accept.both',
-				title: vscode.l10n.t("Accept Both Changes"),
-				arguments: ['known-conflict', conflict]
-			};
-
-			const diffCommand: vscode.Command = {
-				command: 'merge-conflict.compare',
-				title: vscode.l10n.t("Compare Changes"),
-				arguments: [conflict]
-			};
-
-			const range = document.lineAt(conflict.range.start.line).range;
-			items.push(
-				new vscode.CodeLens(range, acceptCurrentCommand),
-				new vscode.CodeLens(range, acceptIncomingCommand),
-				new vscode.CodeLens(range, acceptBothCommand),
-				new vscode.CodeLens(range, diffCommand)
-			);
-		});
-
-		return items;
-	}
-
-	private registerCodeLensProvider() {
-		this.codeLensRegistrationHandle = vscode.languages.registerCodeLensProvider([
-			{ scheme: 'file' },
-			{ scheme: 'vscode-vfs' },
-			{ scheme: 'untitled' },
-			{ scheme: 'vscode-userdata' },
-		], this);
-	}
+    private codeLensRegistrationHandle?: vscode.Disposable | null;
+    private config?: interfaces.IExtensionConfiguration;
+    private tracker: interfaces.IDocumentMergeConflictTracker;
+    constructor(trackerService: interfaces.IDocumentMergeConflictTrackerService) {
+        this.tracker = trackerService.createTracker('codelens');
+    }
+    begin(config: interfaces.IExtensionConfiguration) {
+        this.config = config;
+        if (this.config.enableCodeLens) {
+            this.registerCodeLensProvider();
+        }
+    }
+    configurationUpdated(updatedConfig: interfaces.IExtensionConfiguration) {
+        if (updatedConfig.enableCodeLens === false && this.codeLensRegistrationHandle) {
+            this.codeLensRegistrationHandle.dispose();
+            this.codeLensRegistrationHandle = null;
+        }
+        else if (updatedConfig.enableCodeLens === true && !this.codeLensRegistrationHandle) {
+            this.registerCodeLensProvider();
+        }
+        this.config = updatedConfig;
+    }
+    dispose() {
+        if (this.codeLensRegistrationHandle) {
+            this.codeLensRegistrationHandle.dispose();
+            this.codeLensRegistrationHandle = null;
+        }
+    }
+    async provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): Promise {
+        if (!this.config || !this.config.enableCodeLens) {
+            return null;
+        }
+        const conflicts = await this.tracker.getConflicts(document);
+        const conflictsCount = conflicts?.length ?? 0;
+        vscode.commands.executeCommand('setContext', 'mergeConflictsCount', conflictsCount);
+        if (!conflictsCount) {
+            return null;
+        }
+        const items: vscode.CodeLens[] = [];
+        conflicts.forEach(conflict => {
+            const acceptCurrentCommand: vscode.Command = {
+                command: 'merge-conflict.accept.current',
+                title: vscode.l10n.t("Accept Current Change"),
+                arguments: ['known-conflict', conflict]
+            };
+            const acceptIncomingCommand: vscode.Command = {
+                command: 'merge-conflict.accept.incoming',
+                title: vscode.l10n.t("Accept Incoming Change"),
+                arguments: ['known-conflict', conflict]
+            };
+            const acceptBothCommand: vscode.Command = {
+                command: 'merge-conflict.accept.both',
+                title: vscode.l10n.t("Accept Both Changes"),
+                arguments: ['known-conflict', conflict]
+            };
+            const diffCommand: vscode.Command = {
+                command: 'merge-conflict.compare',
+                title: vscode.l10n.t("Compare Changes"),
+                arguments: [conflict]
+            };
+            const range = document.lineAt(conflict.range.start.line).range;
+            items.push(new vscode.CodeLens(range, acceptCurrentCommand), new vscode.CodeLens(range, acceptIncomingCommand), new vscode.CodeLens(range, acceptBothCommand), new vscode.CodeLens(range, diffCommand));
+        });
+        return items;
+    }
+    private registerCodeLensProvider() {
+        this.codeLensRegistrationHandle = vscode.languages.registerCodeLensProvider([
+            { scheme: 'file' },
+            { scheme: 'vscode-vfs' },
+            { scheme: 'untitled' },
+            { scheme: 'vscode-userdata' },
+        ], this);
+    }
 }
diff --git a/extensions/merge-conflict/Source/commandHandler.ts b/extensions/merge-conflict/Source/commandHandler.ts
index a2b940b24c601..73a2d8a74938c 100644
--- a/extensions/merge-conflict/Source/commandHandler.ts
+++ b/extensions/merge-conflict/Source/commandHandler.ts
@@ -5,365 +5,286 @@
 import * as vscode from 'vscode';
 import * as interfaces from './interfaces';
 import ContentProvider from './contentProvider';
-
 interface IDocumentMergeConflictNavigationResults {
-	canNavigate: boolean;
-	conflict?: interfaces.IDocumentMergeConflict;
+    canNavigate: boolean;
+    conflict?: interfaces.IDocumentMergeConflict;
 }
-
 enum NavigationDirection {
-	Forwards,
-	Backwards
+    Forwards,
+    Backwards
 }
-
 export default class CommandHandler implements vscode.Disposable {
-
-	private disposables: vscode.Disposable[] = [];
-	private tracker: interfaces.IDocumentMergeConflictTracker;
-
-	constructor(trackerService: interfaces.IDocumentMergeConflictTrackerService) {
-		this.tracker = trackerService.createTracker('commands');
-	}
-
-	begin() {
-		this.disposables.push(
-			this.registerTextEditorCommand('merge-conflict.accept.current', this.acceptCurrent),
-			this.registerTextEditorCommand('merge-conflict.accept.incoming', this.acceptIncoming),
-			this.registerTextEditorCommand('merge-conflict.accept.selection', this.acceptSelection),
-			this.registerTextEditorCommand('merge-conflict.accept.both', this.acceptBoth),
-			this.registerTextEditorCommand('merge-conflict.accept.all-current', this.acceptAllCurrent, this.acceptAllCurrentResources),
-			this.registerTextEditorCommand('merge-conflict.accept.all-incoming', this.acceptAllIncoming, this.acceptAllIncomingResources),
-			this.registerTextEditorCommand('merge-conflict.accept.all-both', this.acceptAllBoth),
-			this.registerTextEditorCommand('merge-conflict.next', this.navigateNext),
-			this.registerTextEditorCommand('merge-conflict.previous', this.navigatePrevious),
-			this.registerTextEditorCommand('merge-conflict.compare', this.compare)
-		);
-	}
-
-	private registerTextEditorCommand(command: string, cb: (editor: vscode.TextEditor, ...args: any[]) => Promise, resourceCB?: (uris: vscode.Uri[]) => Promise) {
-		return vscode.commands.registerCommand(command, (...args) => {
-			if (resourceCB && args.length && args.every(arg => arg && arg.resourceUri)) {
-				return resourceCB.call(this, args.map(arg => arg.resourceUri));
-			}
-			const editor = vscode.window.activeTextEditor;
-			return editor && cb.call(this, editor, ...args);
-		});
-	}
-
-	acceptCurrent(editor: vscode.TextEditor, ...args: any[]): Promise {
-		return this.accept(interfaces.CommitType.Current, editor, ...args);
-	}
-
-	acceptIncoming(editor: vscode.TextEditor, ...args: any[]): Promise {
-		return this.accept(interfaces.CommitType.Incoming, editor, ...args);
-	}
-
-	acceptBoth(editor: vscode.TextEditor, ...args: any[]): Promise {
-		return this.accept(interfaces.CommitType.Both, editor, ...args);
-	}
-
-	acceptAllCurrent(editor: vscode.TextEditor): Promise {
-		return this.acceptAll(interfaces.CommitType.Current, editor);
-	}
-
-	acceptAllIncoming(editor: vscode.TextEditor): Promise {
-		return this.acceptAll(interfaces.CommitType.Incoming, editor);
-	}
-
-	acceptAllCurrentResources(resources: vscode.Uri[]): Promise {
-		return this.acceptAllResources(interfaces.CommitType.Current, resources);
-	}
-
-	acceptAllIncomingResources(resources: vscode.Uri[]): Promise {
-		return this.acceptAllResources(interfaces.CommitType.Incoming, resources);
-	}
-
-	acceptAllBoth(editor: vscode.TextEditor): Promise {
-		return this.acceptAll(interfaces.CommitType.Both, editor);
-	}
-
-	async compare(editor: vscode.TextEditor, conflict: interfaces.IDocumentMergeConflict | null) {
-
-		// No conflict, command executed from command palette
-		if (!conflict) {
-			conflict = await this.findConflictContainingSelection(editor);
-
-			// Still failed to find conflict, warn the user and exit
-			if (!conflict) {
-				vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
-				return;
-			}
-		}
-
-		const conflicts = await this.tracker.getConflicts(editor.document);
-
-		// Still failed to find conflict, warn the user and exit
-		if (!conflicts) {
-			vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
-			return;
-		}
-
-		const scheme = editor.document.uri.scheme;
-		let range = conflict.current.content;
-		const leftRanges = conflicts.map(conflict => [conflict.current.content, conflict.range]);
-		const rightRanges = conflicts.map(conflict => [conflict.incoming.content, conflict.range]);
-
-		const leftUri = editor.document.uri.with({
-			scheme: ContentProvider.scheme,
-			query: JSON.stringify({ scheme, range: range, ranges: leftRanges })
-		});
-
-
-		range = conflict.incoming.content;
-		const rightUri = leftUri.with({ query: JSON.stringify({ scheme, ranges: rightRanges }) });
-
-		let mergeConflictLineOffsets = 0;
-		for (const nextconflict of conflicts) {
-			if (nextconflict.range.isEqual(conflict.range)) {
-				break;
-			} else {
-				mergeConflictLineOffsets += (nextconflict.range.end.line - nextconflict.range.start.line) - (nextconflict.incoming.content.end.line - nextconflict.incoming.content.start.line);
-			}
-		}
-		const selection = new vscode.Range(
-			conflict.range.start.line - mergeConflictLineOffsets, conflict.range.start.character,
-			conflict.range.start.line - mergeConflictLineOffsets, conflict.range.start.character
-		);
-
-		const docPath = editor.document.uri.path;
-		const fileName = docPath.substring(docPath.lastIndexOf('/') + 1); // avoid NodeJS path to keep browser webpack small
-		const title = vscode.l10n.t("{0}: Current Changes ↔ Incoming Changes", fileName);
-		const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict');
-		const openToTheSide = mergeConflictConfig.get('diffViewPosition');
-		const opts: vscode.TextDocumentShowOptions = {
-			viewColumn: openToTheSide === 'Beside' ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active,
-			selection
-		};
-
-		if (openToTheSide === 'Below') {
-			await vscode.commands.executeCommand('workbench.action.newGroupBelow');
-		}
-
-		await vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title, opts);
-	}
-
-	navigateNext(editor: vscode.TextEditor): Promise {
-		return this.navigate(editor, NavigationDirection.Forwards);
-	}
-
-	navigatePrevious(editor: vscode.TextEditor): Promise {
-		return this.navigate(editor, NavigationDirection.Backwards);
-	}
-
-	async acceptSelection(editor: vscode.TextEditor): Promise {
-		const conflict = await this.findConflictContainingSelection(editor);
-
-		if (!conflict) {
-			vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
-			return;
-		}
-
-		let typeToAccept: interfaces.CommitType;
-		let tokenAfterCurrentBlock: vscode.Range = conflict.splitter;
-
-		if (conflict.commonAncestors.length > 0) {
-			tokenAfterCurrentBlock = conflict.commonAncestors[0].header;
-		}
-
-		// Figure out if the cursor is in current or incoming, we do this by seeing if
-		// the active position is before or after the range of the splitter or common
-		// ancestors marker. We can use this trick as the previous check in
-		// findConflictByActiveSelection will ensure it's within the conflict range, so
-		// we don't falsely identify "current" or "incoming" if outside of a conflict range.
-		if (editor.selection.active.isBefore(tokenAfterCurrentBlock.start)) {
-			typeToAccept = interfaces.CommitType.Current;
-		}
-		else if (editor.selection.active.isAfter(conflict.splitter.end)) {
-			typeToAccept = interfaces.CommitType.Incoming;
-		}
-		else if (editor.selection.active.isBefore(conflict.splitter.start)) {
-			vscode.window.showWarningMessage(vscode.l10n.t('Editor cursor is within the common ancestors block, please move it to either the "current" or "incoming" block'));
-			return;
-		}
-		else {
-			vscode.window.showWarningMessage(vscode.l10n.t('Editor cursor is within the merge conflict splitter, please move it to either the "current" or "incoming" block'));
-			return;
-		}
-
-		this.tracker.forget(editor.document);
-		conflict.commitEdit(typeToAccept, editor);
-	}
-
-	dispose() {
-		this.disposables.forEach(disposable => disposable.dispose());
-		this.disposables = [];
-	}
-
-	private async navigate(editor: vscode.TextEditor, direction: NavigationDirection): Promise {
-		const navigationResult = await this.findConflictForNavigation(editor, direction);
-
-		if (!navigationResult) {
-			// Check for autoNavigateNextConflict, if it's enabled(which indicating no conflict remain), then do not show warning
-			const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict');
-			if (mergeConflictConfig.get('autoNavigateNextConflict.enabled')) {
-				return;
-			}
-			vscode.window.showWarningMessage(vscode.l10n.t("No merge conflicts found in this file"));
-			return;
-		}
-		else if (!navigationResult.canNavigate) {
-			vscode.window.showWarningMessage(vscode.l10n.t("No other merge conflicts within this file"));
-			return;
-		}
-		else if (!navigationResult.conflict) {
-			// TODO: Show error message?
-			return;
-		}
-
-		// Move the selection to the first line of the conflict
-		editor.selection = new vscode.Selection(navigationResult.conflict.range.start, navigationResult.conflict.range.start);
-		editor.revealRange(navigationResult.conflict.range, vscode.TextEditorRevealType.Default);
-	}
-
-	private async accept(type: interfaces.CommitType, editor: vscode.TextEditor, ...args: any[]): Promise {
-
-		let conflict: interfaces.IDocumentMergeConflict | null;
-
-		// If launched with known context, take the conflict from that
-		if (args[0] === 'known-conflict') {
-			conflict = args[1];
-		}
-		else {
-			// Attempt to find a conflict that matches the current cursor position
-			conflict = await this.findConflictContainingSelection(editor);
-		}
-
-		if (!conflict) {
-			vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
-			return;
-		}
-
-		// Tracker can forget as we know we are going to do an edit
-		this.tracker.forget(editor.document);
-		conflict.commitEdit(type, editor);
-
-		// navigate to the next merge conflict
-		const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict');
-		if (mergeConflictConfig.get('autoNavigateNextConflict.enabled')) {
-			this.navigateNext(editor);
-		}
-
-	}
-
-	private async acceptAll(type: interfaces.CommitType, editor: vscode.TextEditor): Promise {
-		const conflicts = await this.tracker.getConflicts(editor.document);
-
-		if (!conflicts || conflicts.length === 0) {
-			vscode.window.showWarningMessage(vscode.l10n.t("No merge conflicts found in this file"));
-			return;
-		}
-
-		// For get the current state of the document, as we know we are doing to do a large edit
-		this.tracker.forget(editor.document);
-
-		// Apply all changes as one edit
-		await editor.edit((edit) => conflicts.forEach(conflict => {
-			conflict.applyEdit(type, editor.document, edit);
-		}));
-	}
-
-	private async acceptAllResources(type: interfaces.CommitType, resources: vscode.Uri[]): Promise {
-		const documents = await Promise.all(resources.map(resource => vscode.workspace.openTextDocument(resource)));
-		const edit = new vscode.WorkspaceEdit();
-		for (const document of documents) {
-			const conflicts = await this.tracker.getConflicts(document);
-
-			if (!conflicts || conflicts.length === 0) {
-				continue;
-			}
-
-			// For get the current state of the document, as we know we are doing to do a large edit
-			this.tracker.forget(document);
-
-			// Apply all changes as one edit
-			conflicts.forEach(conflict => {
-				conflict.applyEdit(type, document, { replace: (range, newText) => edit.replace(document.uri, range, newText) });
-			});
-		}
-		vscode.workspace.applyEdit(edit);
-	}
-
-	private async findConflictContainingSelection(editor: vscode.TextEditor, conflicts?: interfaces.IDocumentMergeConflict[]): Promise {
-
-		if (!conflicts) {
-			conflicts = await this.tracker.getConflicts(editor.document);
-		}
-
-		if (!conflicts || conflicts.length === 0) {
-			return null;
-		}
-
-		for (const conflict of conflicts) {
-			if (conflict.range.contains(editor.selection.active)) {
-				return conflict;
-			}
-		}
-
-		return null;
-	}
-
-	private async findConflictForNavigation(editor: vscode.TextEditor, direction: NavigationDirection, conflicts?: interfaces.IDocumentMergeConflict[]): Promise {
-		if (!conflicts) {
-			conflicts = await this.tracker.getConflicts(editor.document);
-		}
-
-		if (!conflicts || conflicts.length === 0) {
-			return null;
-		}
-
-		const selection = editor.selection.active;
-		if (conflicts.length === 1) {
-			if (conflicts[0].range.contains(selection)) {
-				return {
-					canNavigate: false
-				};
-			}
-
-			return {
-				canNavigate: true,
-				conflict: conflicts[0]
-			};
-		}
-
-		let predicate: (_conflict: any) => boolean;
-		let fallback: () => interfaces.IDocumentMergeConflict;
-		let scanOrder: interfaces.IDocumentMergeConflict[];
-
-		if (direction === NavigationDirection.Forwards) {
-			predicate = (conflict) => selection.isBefore(conflict.range.start);
-			fallback = () => conflicts![0];
-			scanOrder = conflicts;
-		} else if (direction === NavigationDirection.Backwards) {
-			predicate = (conflict) => selection.isAfter(conflict.range.start);
-			fallback = () => conflicts![conflicts!.length - 1];
-			scanOrder = conflicts.slice().reverse();
-		} else {
-			throw new Error(`Unsupported direction ${direction}`);
-		}
-
-		for (const conflict of scanOrder) {
-			if (predicate(conflict) && !conflict.range.contains(selection)) {
-				return {
-					canNavigate: true,
-					conflict: conflict
-				};
-			}
-		}
-
-		// Went all the way to the end, return the head
-		return {
-			canNavigate: true,
-			conflict: fallback()
-		};
-	}
+    private disposables: vscode.Disposable[] = [];
+    private tracker: interfaces.IDocumentMergeConflictTracker;
+    constructor(trackerService: interfaces.IDocumentMergeConflictTrackerService) {
+        this.tracker = trackerService.createTracker('commands');
+    }
+    begin() {
+        this.disposables.push(this.registerTextEditorCommand('merge-conflict.accept.current', this.acceptCurrent), this.registerTextEditorCommand('merge-conflict.accept.incoming', this.acceptIncoming), this.registerTextEditorCommand('merge-conflict.accept.selection', this.acceptSelection), this.registerTextEditorCommand('merge-conflict.accept.both', this.acceptBoth), this.registerTextEditorCommand('merge-conflict.accept.all-current', this.acceptAllCurrent, this.acceptAllCurrentResources), this.registerTextEditorCommand('merge-conflict.accept.all-incoming', this.acceptAllIncoming, this.acceptAllIncomingResources), this.registerTextEditorCommand('merge-conflict.accept.all-both', this.acceptAllBoth), this.registerTextEditorCommand('merge-conflict.next', this.navigateNext), this.registerTextEditorCommand('merge-conflict.previous', this.navigatePrevious), this.registerTextEditorCommand('merge-conflict.compare', this.compare));
+    }
+    private registerTextEditorCommand(command: string, cb: (editor: vscode.TextEditor, ...args: any[]) => Promise, resourceCB?: (uris: vscode.Uri[]) => Promise) {
+        return vscode.commands.registerCommand(command, (...args) => {
+            if (resourceCB && args.length && args.every(arg => arg && arg.resourceUri)) {
+                return resourceCB.call(this, args.map(arg => arg.resourceUri));
+            }
+            const editor = vscode.window.activeTextEditor;
+            return editor && cb.call(this, editor, ...args);
+        });
+    }
+    acceptCurrent(editor: vscode.TextEditor, ...args: any[]): Promise {
+        return this.accept(interfaces.CommitType.Current, editor, ...args);
+    }
+    acceptIncoming(editor: vscode.TextEditor, ...args: any[]): Promise {
+        return this.accept(interfaces.CommitType.Incoming, editor, ...args);
+    }
+    acceptBoth(editor: vscode.TextEditor, ...args: any[]): Promise {
+        return this.accept(interfaces.CommitType.Both, editor, ...args);
+    }
+    acceptAllCurrent(editor: vscode.TextEditor): Promise {
+        return this.acceptAll(interfaces.CommitType.Current, editor);
+    }
+    acceptAllIncoming(editor: vscode.TextEditor): Promise {
+        return this.acceptAll(interfaces.CommitType.Incoming, editor);
+    }
+    acceptAllCurrentResources(resources: vscode.Uri[]): Promise {
+        return this.acceptAllResources(interfaces.CommitType.Current, resources);
+    }
+    acceptAllIncomingResources(resources: vscode.Uri[]): Promise {
+        return this.acceptAllResources(interfaces.CommitType.Incoming, resources);
+    }
+    acceptAllBoth(editor: vscode.TextEditor): Promise {
+        return this.acceptAll(interfaces.CommitType.Both, editor);
+    }
+    async compare(editor: vscode.TextEditor, conflict: interfaces.IDocumentMergeConflict | null) {
+        // No conflict, command executed from command palette
+        if (!conflict) {
+            conflict = await this.findConflictContainingSelection(editor);
+            // Still failed to find conflict, warn the user and exit
+            if (!conflict) {
+                vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
+                return;
+            }
+        }
+        const conflicts = await this.tracker.getConflicts(editor.document);
+        // Still failed to find conflict, warn the user and exit
+        if (!conflicts) {
+            vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
+            return;
+        }
+        const scheme = editor.document.uri.scheme;
+        let range = conflict.current.content;
+        const leftRanges = conflicts.map(conflict => [conflict.current.content, conflict.range]);
+        const rightRanges = conflicts.map(conflict => [conflict.incoming.content, conflict.range]);
+        const leftUri = editor.document.uri.with({
+            scheme: ContentProvider.scheme,
+            query: JSON.stringify({ scheme, range: range, ranges: leftRanges })
+        });
+        range = conflict.incoming.content;
+        const rightUri = leftUri.with({ query: JSON.stringify({ scheme, ranges: rightRanges }) });
+        let mergeConflictLineOffsets = 0;
+        for (const nextconflict of conflicts) {
+            if (nextconflict.range.isEqual(conflict.range)) {
+                break;
+            }
+            else {
+                mergeConflictLineOffsets += (nextconflict.range.end.line - nextconflict.range.start.line) - (nextconflict.incoming.content.end.line - nextconflict.incoming.content.start.line);
+            }
+        }
+        const selection = new vscode.Range(conflict.range.start.line - mergeConflictLineOffsets, conflict.range.start.character, conflict.range.start.line - mergeConflictLineOffsets, conflict.range.start.character);
+        const docPath = editor.document.uri.path;
+        const fileName = docPath.substring(docPath.lastIndexOf('/') + 1); // avoid NodeJS path to keep browser webpack small
+        const title = vscode.l10n.t("{0}: Current Changes ↔ Incoming Changes", fileName);
+        const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict');
+        const openToTheSide = mergeConflictConfig.get('diffViewPosition');
+        const opts: vscode.TextDocumentShowOptions = {
+            viewColumn: openToTheSide === 'Beside' ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active,
+            selection
+        };
+        if (openToTheSide === 'Below') {
+            await vscode.commands.executeCommand('workbench.action.newGroupBelow');
+        }
+        await vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title, opts);
+    }
+    navigateNext(editor: vscode.TextEditor): Promise {
+        return this.navigate(editor, NavigationDirection.Forwards);
+    }
+    navigatePrevious(editor: vscode.TextEditor): Promise {
+        return this.navigate(editor, NavigationDirection.Backwards);
+    }
+    async acceptSelection(editor: vscode.TextEditor): Promise {
+        const conflict = await this.findConflictContainingSelection(editor);
+        if (!conflict) {
+            vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
+            return;
+        }
+        let typeToAccept: interfaces.CommitType;
+        let tokenAfterCurrentBlock: vscode.Range = conflict.splitter;
+        if (conflict.commonAncestors.length > 0) {
+            tokenAfterCurrentBlock = conflict.commonAncestors[0].header;
+        }
+        // Figure out if the cursor is in current or incoming, we do this by seeing if
+        // the active position is before or after the range of the splitter or common
+        // ancestors marker. We can use this trick as the previous check in
+        // findConflictByActiveSelection will ensure it's within the conflict range, so
+        // we don't falsely identify "current" or "incoming" if outside of a conflict range.
+        if (editor.selection.active.isBefore(tokenAfterCurrentBlock.start)) {
+            typeToAccept = interfaces.CommitType.Current;
+        }
+        else if (editor.selection.active.isAfter(conflict.splitter.end)) {
+            typeToAccept = interfaces.CommitType.Incoming;
+        }
+        else if (editor.selection.active.isBefore(conflict.splitter.start)) {
+            vscode.window.showWarningMessage(vscode.l10n.t('Editor cursor is within the common ancestors block, please move it to either the "current" or "incoming" block'));
+            return;
+        }
+        else {
+            vscode.window.showWarningMessage(vscode.l10n.t('Editor cursor is within the merge conflict splitter, please move it to either the "current" or "incoming" block'));
+            return;
+        }
+        this.tracker.forget(editor.document);
+        conflict.commitEdit(typeToAccept, editor);
+    }
+    dispose() {
+        this.disposables.forEach(disposable => disposable.dispose());
+        this.disposables = [];
+    }
+    private async navigate(editor: vscode.TextEditor, direction: NavigationDirection): Promise {
+        const navigationResult = await this.findConflictForNavigation(editor, direction);
+        if (!navigationResult) {
+            // Check for autoNavigateNextConflict, if it's enabled(which indicating no conflict remain), then do not show warning
+            const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict');
+            if (mergeConflictConfig.get('autoNavigateNextConflict.enabled')) {
+                return;
+            }
+            vscode.window.showWarningMessage(vscode.l10n.t("No merge conflicts found in this file"));
+            return;
+        }
+        else if (!navigationResult.canNavigate) {
+            vscode.window.showWarningMessage(vscode.l10n.t("No other merge conflicts within this file"));
+            return;
+        }
+        else if (!navigationResult.conflict) {
+            // TODO: Show error message?
+            return;
+        }
+        // Move the selection to the first line of the conflict
+        editor.selection = new vscode.Selection(navigationResult.conflict.range.start, navigationResult.conflict.range.start);
+        editor.revealRange(navigationResult.conflict.range, vscode.TextEditorRevealType.Default);
+    }
+    private async accept(type: interfaces.CommitType, editor: vscode.TextEditor, ...args: any[]): Promise {
+        let conflict: interfaces.IDocumentMergeConflict | null;
+        // If launched with known context, take the conflict from that
+        if (args[0] === 'known-conflict') {
+            conflict = args[1];
+        }
+        else {
+            // Attempt to find a conflict that matches the current cursor position
+            conflict = await this.findConflictContainingSelection(editor);
+        }
+        if (!conflict) {
+            vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict"));
+            return;
+        }
+        // Tracker can forget as we know we are going to do an edit
+        this.tracker.forget(editor.document);
+        conflict.commitEdit(type, editor);
+        // navigate to the next merge conflict
+        const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict');
+        if (mergeConflictConfig.get('autoNavigateNextConflict.enabled')) {
+            this.navigateNext(editor);
+        }
+    }
+    private async acceptAll(type: interfaces.CommitType, editor: vscode.TextEditor): Promise {
+        const conflicts = await this.tracker.getConflicts(editor.document);
+        if (!conflicts || conflicts.length === 0) {
+            vscode.window.showWarningMessage(vscode.l10n.t("No merge conflicts found in this file"));
+            return;
+        }
+        // For get the current state of the document, as we know we are doing to do a large edit
+        this.tracker.forget(editor.document);
+        // Apply all changes as one edit
+        await editor.edit((edit) => conflicts.forEach(conflict => {
+            conflict.applyEdit(type, editor.document, edit);
+        }));
+    }
+    private async acceptAllResources(type: interfaces.CommitType, resources: vscode.Uri[]): Promise {
+        const documents = await Promise.all(resources.map(resource => vscode.workspace.openTextDocument(resource)));
+        const edit = new vscode.WorkspaceEdit();
+        for (const document of documents) {
+            const conflicts = await this.tracker.getConflicts(document);
+            if (!conflicts || conflicts.length === 0) {
+                continue;
+            }
+            // For get the current state of the document, as we know we are doing to do a large edit
+            this.tracker.forget(document);
+            // Apply all changes as one edit
+            conflicts.forEach(conflict => {
+                conflict.applyEdit(type, document, { replace: (range, newText) => edit.replace(document.uri, range, newText) });
+            });
+        }
+        vscode.workspace.applyEdit(edit);
+    }
+    private async findConflictContainingSelection(editor: vscode.TextEditor, conflicts?: interfaces.IDocumentMergeConflict[]): Promise {
+        if (!conflicts) {
+            conflicts = await this.tracker.getConflicts(editor.document);
+        }
+        if (!conflicts || conflicts.length === 0) {
+            return null;
+        }
+        for (const conflict of conflicts) {
+            if (conflict.range.contains(editor.selection.active)) {
+                return conflict;
+            }
+        }
+        return null;
+    }
+    private async findConflictForNavigation(editor: vscode.TextEditor, direction: NavigationDirection, conflicts?: interfaces.IDocumentMergeConflict[]): Promise {
+        if (!conflicts) {
+            conflicts = await this.tracker.getConflicts(editor.document);
+        }
+        if (!conflicts || conflicts.length === 0) {
+            return null;
+        }
+        const selection = editor.selection.active;
+        if (conflicts.length === 1) {
+            if (conflicts[0].range.contains(selection)) {
+                return {
+                    canNavigate: false
+                };
+            }
+            return {
+                canNavigate: true,
+                conflict: conflicts[0]
+            };
+        }
+        let predicate: (_conflict: any) => boolean;
+        let fallback: () => interfaces.IDocumentMergeConflict;
+        let scanOrder: interfaces.IDocumentMergeConflict[];
+        if (direction === NavigationDirection.Forwards) {
+            predicate = (conflict) => selection.isBefore(conflict.range.start);
+            fallback = () => conflicts![0];
+            scanOrder = conflicts;
+        }
+        else if (direction === NavigationDirection.Backwards) {
+            predicate = (conflict) => selection.isAfter(conflict.range.start);
+            fallback = () => conflicts![conflicts!.length - 1];
+            scanOrder = conflicts.slice().reverse();
+        }
+        else {
+            throw new Error(`Unsupported direction ${direction}`);
+        }
+        for (const conflict of scanOrder) {
+            if (predicate(conflict) && !conflict.range.contains(selection)) {
+                return {
+                    canNavigate: true,
+                    conflict: conflict
+                };
+            }
+        }
+        // Went all the way to the end, return the head
+        return {
+            canNavigate: true,
+            conflict: fallback()
+        };
+    }
 }
diff --git a/extensions/merge-conflict/Source/contentProvider.ts b/extensions/merge-conflict/Source/contentProvider.ts
index 155bac08d4065..d851c24330e12 100644
--- a/extensions/merge-conflict/Source/contentProvider.ts
+++ b/extensions/merge-conflict/Source/contentProvider.ts
@@ -2,53 +2,50 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export default class MergeConflictContentProvider implements vscode.TextDocumentContentProvider, vscode.Disposable {
-
-	static scheme = 'merge-conflict.conflict-diff';
-
-	constructor(private context: vscode.ExtensionContext) {
-	}
-
-	begin() {
-		this.context.subscriptions.push(
-			vscode.workspace.registerTextDocumentContentProvider(MergeConflictContentProvider.scheme, this)
-		);
-	}
-
-	dispose() {
-	}
-
-	async provideTextDocumentContent(uri: vscode.Uri): Promise {
-		try {
-			const { scheme, ranges } = JSON.parse(uri.query) as { scheme: string; ranges: [{ line: number; character: number }[], { line: number; character: number }[]][] };
-
-			// complete diff
-			const document = await vscode.workspace.openTextDocument(uri.with({ scheme, query: '' }));
-
-			let text = '';
-			let lastPosition = new vscode.Position(0, 0);
-
-			ranges.forEach(rangeObj => {
-				const [conflictRange, fullRange] = rangeObj;
-				const [start, end] = conflictRange;
-				const [fullStart, fullEnd] = fullRange;
-
-				text += document.getText(new vscode.Range(lastPosition.line, lastPosition.character, fullStart.line, fullStart.character));
-				text += document.getText(new vscode.Range(start.line, start.character, end.line, end.character));
-				lastPosition = new vscode.Position(fullEnd.line, fullEnd.character);
-			});
-
-			const documentEnd = document.lineAt(document.lineCount - 1).range.end;
-			text += document.getText(new vscode.Range(lastPosition.line, lastPosition.character, documentEnd.line, documentEnd.character));
-
-			return text;
-		}
-		catch (ex) {
-			await vscode.window.showErrorMessage('Unable to show comparison');
-			return null;
-		}
-	}
-}
\ No newline at end of file
+    static scheme = 'merge-conflict.conflict-diff';
+    constructor(private context: vscode.ExtensionContext) {
+    }
+    begin() {
+        this.context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(MergeConflictContentProvider.scheme, this));
+    }
+    dispose() {
+    }
+    async provideTextDocumentContent(uri: vscode.Uri): Promise {
+        try {
+            const { scheme, ranges } = JSON.parse(uri.query) as {
+                scheme: string;
+                ranges: [
+                    {
+                        line: number;
+                        character: number;
+                    }[],
+                    {
+                        line: number;
+                        character: number;
+                    }[]
+                ][];
+            };
+            // complete diff
+            const document = await vscode.workspace.openTextDocument(uri.with({ scheme, query: '' }));
+            let text = '';
+            let lastPosition = new vscode.Position(0, 0);
+            ranges.forEach(rangeObj => {
+                const [conflictRange, fullRange] = rangeObj;
+                const [start, end] = conflictRange;
+                const [fullStart, fullEnd] = fullRange;
+                text += document.getText(new vscode.Range(lastPosition.line, lastPosition.character, fullStart.line, fullStart.character));
+                text += document.getText(new vscode.Range(start.line, start.character, end.line, end.character));
+                lastPosition = new vscode.Position(fullEnd.line, fullEnd.character);
+            });
+            const documentEnd = document.lineAt(document.lineCount - 1).range.end;
+            text += document.getText(new vscode.Range(lastPosition.line, lastPosition.character, documentEnd.line, documentEnd.character));
+            return text;
+        }
+        catch (ex) {
+            await vscode.window.showErrorMessage('Unable to show comparison');
+            return null;
+        }
+    }
+}
diff --git a/extensions/merge-conflict/Source/delayer.ts b/extensions/merge-conflict/Source/delayer.ts
index 8609ff6abe42c..e9e38dba05d89 100644
--- a/extensions/merge-conflict/Source/delayer.ts
+++ b/extensions/merge-conflict/Source/delayer.ts
@@ -2,78 +2,66 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export interface ITask {
-	(): T;
+    (): T;
 }
-
 export class Delayer {
-
-	public defaultDelay: number;
-	private timeout: any; // Timer
-	private completionPromise: Promise | null;
-	private onSuccess: ((value: T | PromiseLike | undefined) => void) | null;
-	private task: ITask | null;
-
-	constructor(defaultDelay: number) {
-		this.defaultDelay = defaultDelay;
-		this.timeout = null;
-		this.completionPromise = null;
-		this.onSuccess = null;
-		this.task = null;
-	}
-
-	public trigger(task: ITask, delay: number = this.defaultDelay): Promise {
-		this.task = task;
-		if (delay >= 0) {
-			this.cancelTimeout();
-		}
-
-		if (!this.completionPromise) {
-			this.completionPromise = new Promise((resolve) => {
-				this.onSuccess = resolve;
-			}).then(() => {
-				this.completionPromise = null;
-				this.onSuccess = null;
-				const result = this.task!();
-				this.task = null;
-				return result;
-			});
-		}
-
-		if (delay >= 0 || this.timeout === null) {
-			this.timeout = setTimeout(() => {
-				this.timeout = null;
-				this.onSuccess!(undefined);
-			}, delay >= 0 ? delay : this.defaultDelay);
-		}
-
-		return this.completionPromise;
-	}
-
-	public forceDelivery(): Promise | null {
-		if (!this.completionPromise) {
-			return null;
-		}
-		this.cancelTimeout();
-		const result = this.completionPromise;
-		this.onSuccess!(undefined);
-		return result;
-	}
-
-	public isTriggered(): boolean {
-		return this.timeout !== null;
-	}
-
-	public cancel(): void {
-		this.cancelTimeout();
-		this.completionPromise = null;
-	}
-
-	private cancelTimeout(): void {
-		if (this.timeout !== null) {
-			clearTimeout(this.timeout);
-			this.timeout = null;
-		}
-	}
+    public defaultDelay: number;
+    private timeout: any; // Timer
+    private completionPromise: Promise | null;
+    private onSuccess: ((value: T | PromiseLike | undefined) => void) | null;
+    private task: ITask | null;
+    constructor(defaultDelay: number) {
+        this.defaultDelay = defaultDelay;
+        this.timeout = null;
+        this.completionPromise = null;
+        this.onSuccess = null;
+        this.task = null;
+    }
+    public trigger(task: ITask, delay: number = this.defaultDelay): Promise {
+        this.task = task;
+        if (delay >= 0) {
+            this.cancelTimeout();
+        }
+        if (!this.completionPromise) {
+            this.completionPromise = new Promise((resolve) => {
+                this.onSuccess = resolve;
+            }).then(() => {
+                this.completionPromise = null;
+                this.onSuccess = null;
+                const result = this.task!();
+                this.task = null;
+                return result;
+            });
+        }
+        if (delay >= 0 || this.timeout === null) {
+            this.timeout = setTimeout(() => {
+                this.timeout = null;
+                this.onSuccess!(undefined);
+            }, delay >= 0 ? delay : this.defaultDelay);
+        }
+        return this.completionPromise;
+    }
+    public forceDelivery(): Promise | null {
+        if (!this.completionPromise) {
+            return null;
+        }
+        this.cancelTimeout();
+        const result = this.completionPromise;
+        this.onSuccess!(undefined);
+        return result;
+    }
+    public isTriggered(): boolean {
+        return this.timeout !== null;
+    }
+    public cancel(): void {
+        this.cancelTimeout();
+        this.completionPromise = null;
+    }
+    private cancelTimeout(): void {
+        if (this.timeout !== null) {
+            clearTimeout(this.timeout);
+            this.timeout = null;
+        }
+    }
 }
diff --git a/extensions/merge-conflict/Source/documentMergeConflict.ts b/extensions/merge-conflict/Source/documentMergeConflict.ts
index 8b78ad88493cb..2e349350f672e 100644
--- a/extensions/merge-conflict/Source/documentMergeConflict.ts
+++ b/extensions/merge-conflict/Source/documentMergeConflict.ts
@@ -5,99 +5,88 @@
 import * as interfaces from './interfaces';
 import * as vscode from 'vscode';
 import type TelemetryReporter from '@vscode/extension-telemetry';
-
 export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict {
-
-	public range: vscode.Range;
-	public current: interfaces.IMergeRegion;
-	public incoming: interfaces.IMergeRegion;
-	public commonAncestors: interfaces.IMergeRegion[];
-	public splitter: vscode.Range;
-	private applied = false;
-
-	constructor(descriptor: interfaces.IDocumentMergeConflictDescriptor, private readonly telemetryReporter: TelemetryReporter) {
-		this.range = descriptor.range;
-		this.current = descriptor.current;
-		this.incoming = descriptor.incoming;
-		this.commonAncestors = descriptor.commonAncestors;
-		this.splitter = descriptor.splitter;
-	}
-
-	public commitEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable {
-		function commitTypeToString(type: interfaces.CommitType): string {
-			switch (type) {
-				case interfaces.CommitType.Current:
-					return 'current';
-				case interfaces.CommitType.Incoming:
-					return 'incoming';
-				case interfaces.CommitType.Both:
-					return 'both';
-			}
-		}
-
-		/* __GDPR__
-			"mergeMarkers.accept" : {
-				"owner": "hediet",
-				"comment": "Used to understand how the inline merge editor experience is used.",
-				"resolution": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Indicates how the merge conflict was resolved by the user" }
-			}
-		*/
-		this.telemetryReporter.sendTelemetryEvent('mergeMarkers.accept', { resolution: commitTypeToString(type) });
-
-		if (edit) {
-
-			this.applyEdit(type, editor.document, edit);
-			return Promise.resolve(true);
-		}
-
-		return editor.edit((edit) => this.applyEdit(type, editor.document, edit));
-	}
-
-	public applyEdit(type: interfaces.CommitType, document: vscode.TextDocument, edit: { replace(range: vscode.Range, newText: string): void }): void {
-		if (this.applied) {
-			return;
-		}
-		this.applied = true;
-
-		// Each conflict is a set of ranges as follows, note placements or newlines
-		// which may not in spans
-		// [ Conflict Range             -- (Entire content below)
-		//   [ Current Header ]\n       -- >>>>> Header
-		//   [ Current Content ]        -- (content)
-		//   [ Splitter ]\n             -- =====
-		//   [ Incoming Content ]       -- (content)
-		//   [ Incoming Header ]\n      -- <<<<< Incoming
-		// ]
-		if (type === interfaces.CommitType.Current) {
-			// Replace [ Conflict Range ] with [ Current Content ]
-			const content = document.getText(this.current.content);
-			this.replaceRangeWithContent(content, edit);
-		}
-		else if (type === interfaces.CommitType.Incoming) {
-			const content = document.getText(this.incoming.content);
-			this.replaceRangeWithContent(content, edit);
-		}
-		else if (type === interfaces.CommitType.Both) {
-			// Replace [ Conflict Range ] with [ Current Content ] + \n + [ Incoming Content ]
-
-			const currentContent = document.getText(this.current.content);
-			const incomingContent = document.getText(this.incoming.content);
-
-			edit.replace(this.range, currentContent.concat(incomingContent));
-		}
-	}
-
-	private replaceRangeWithContent(content: string, edit: { replace(range: vscode.Range, newText: string): void }) {
-		if (this.isNewlineOnly(content)) {
-			edit.replace(this.range, '');
-			return;
-		}
-
-		// Replace [ Conflict Range ] with [ Current Content ]
-		edit.replace(this.range, content);
-	}
-
-	private isNewlineOnly(text: string) {
-		return text === '\n' || text === '\r\n';
-	}
+    public range: vscode.Range;
+    public current: interfaces.IMergeRegion;
+    public incoming: interfaces.IMergeRegion;
+    public commonAncestors: interfaces.IMergeRegion[];
+    public splitter: vscode.Range;
+    private applied = false;
+    constructor(descriptor: interfaces.IDocumentMergeConflictDescriptor, private readonly telemetryReporter: TelemetryReporter) {
+        this.range = descriptor.range;
+        this.current = descriptor.current;
+        this.incoming = descriptor.incoming;
+        this.commonAncestors = descriptor.commonAncestors;
+        this.splitter = descriptor.splitter;
+    }
+    public commitEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable {
+        function commitTypeToString(type: interfaces.CommitType): string {
+            switch (type) {
+                case interfaces.CommitType.Current:
+                    return 'current';
+                case interfaces.CommitType.Incoming:
+                    return 'incoming';
+                case interfaces.CommitType.Both:
+                    return 'both';
+            }
+        }
+        /* __GDPR__
+            "mergeMarkers.accept" : {
+                "owner": "hediet",
+                "comment": "Used to understand how the inline merge editor experience is used.",
+                "resolution": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Indicates how the merge conflict was resolved by the user" }
+            }
+        */
+        this.telemetryReporter.sendTelemetryEvent('mergeMarkers.accept', { resolution: commitTypeToString(type) });
+        if (edit) {
+            this.applyEdit(type, editor.document, edit);
+            return Promise.resolve(true);
+        }
+        return editor.edit((edit) => this.applyEdit(type, editor.document, edit));
+    }
+    public applyEdit(type: interfaces.CommitType, document: vscode.TextDocument, edit: {
+        replace(range: vscode.Range, newText: string): void;
+    }): void {
+        if (this.applied) {
+            return;
+        }
+        this.applied = true;
+        // Each conflict is a set of ranges as follows, note placements or newlines
+        // which may not in spans
+        // [ Conflict Range             -- (Entire content below)
+        //   [ Current Header ]\n       -- >>>>> Header
+        //   [ Current Content ]        -- (content)
+        //   [ Splitter ]\n             -- =====
+        //   [ Incoming Content ]       -- (content)
+        //   [ Incoming Header ]\n      -- <<<<< Incoming
+        // ]
+        if (type === interfaces.CommitType.Current) {
+            // Replace [ Conflict Range ] with [ Current Content ]
+            const content = document.getText(this.current.content);
+            this.replaceRangeWithContent(content, edit);
+        }
+        else if (type === interfaces.CommitType.Incoming) {
+            const content = document.getText(this.incoming.content);
+            this.replaceRangeWithContent(content, edit);
+        }
+        else if (type === interfaces.CommitType.Both) {
+            // Replace [ Conflict Range ] with [ Current Content ] + \n + [ Incoming Content ]
+            const currentContent = document.getText(this.current.content);
+            const incomingContent = document.getText(this.incoming.content);
+            edit.replace(this.range, currentContent.concat(incomingContent));
+        }
+    }
+    private replaceRangeWithContent(content: string, edit: {
+        replace(range: vscode.Range, newText: string): void;
+    }) {
+        if (this.isNewlineOnly(content)) {
+            edit.replace(this.range, '');
+            return;
+        }
+        // Replace [ Conflict Range ] with [ Current Content ]
+        edit.replace(this.range, content);
+    }
+    private isNewlineOnly(text: string) {
+        return text === '\n' || text === '\r\n';
+    }
 }
diff --git a/extensions/merge-conflict/Source/documentTracker.ts b/extensions/merge-conflict/Source/documentTracker.ts
index cd11886821e64..1c07d6508a0bd 100644
--- a/extensions/merge-conflict/Source/documentTracker.ts
+++ b/extensions/merge-conflict/Source/documentTracker.ts
@@ -2,154 +2,118 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { MergeConflictParser } from './mergeConflictParser';
 import * as interfaces from './interfaces';
 import { Delayer } from './delayer';
 import TelemetryReporter from '@vscode/extension-telemetry';
-
 class ScanTask {
-	public origins: Set = new Set();
-	public delayTask: Delayer;
-
-	constructor(delayTime: number, initialOrigin: string) {
-		this.origins.add(initialOrigin);
-		this.delayTask = new Delayer(delayTime);
-	}
-
-	public addOrigin(name: string): void {
-		this.origins.add(name);
-	}
-
-	public hasOrigin(name: string): boolean {
-		return this.origins.has(name);
-	}
+    public origins: Set = new Set();
+    public delayTask: Delayer;
+    constructor(delayTime: number, initialOrigin: string) {
+        this.origins.add(initialOrigin);
+        this.delayTask = new Delayer(delayTime);
+    }
+    public addOrigin(name: string): void {
+        this.origins.add(name);
+    }
+    public hasOrigin(name: string): boolean {
+        return this.origins.has(name);
+    }
 }
-
 class OriginDocumentMergeConflictTracker implements interfaces.IDocumentMergeConflictTracker {
-	constructor(private parent: DocumentMergeConflictTracker, private origin: string) {
-	}
-
-	getConflicts(document: vscode.TextDocument): PromiseLike {
-		return this.parent.getConflicts(document, this.origin);
-	}
-
-	isPending(document: vscode.TextDocument): boolean {
-		return this.parent.isPending(document, this.origin);
-	}
-
-	forget(document: vscode.TextDocument) {
-		this.parent.forget(document);
-	}
+    constructor(private parent: DocumentMergeConflictTracker, private origin: string) {
+    }
+    getConflicts(document: vscode.TextDocument): PromiseLike {
+        return this.parent.getConflicts(document, this.origin);
+    }
+    isPending(document: vscode.TextDocument): boolean {
+        return this.parent.isPending(document, this.origin);
+    }
+    forget(document: vscode.TextDocument) {
+        this.parent.forget(document);
+    }
 }
-
 export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTrackerService {
-	private cache: Map = new Map();
-	private delayExpireTime: number = 0;
-
-	constructor(private readonly telemetryReporter: TelemetryReporter) { }
-
-	getConflicts(document: vscode.TextDocument, origin: string): PromiseLike {
-		// Attempt from cache
-
-		const key = this.getCacheKey(document);
-
-		if (!key) {
-			// Document doesn't have a uri, can't cache it, so return
-			return Promise.resolve(this.getConflictsOrEmpty(document, [origin]));
-		}
-
-		let cacheItem = this.cache.get(key);
-		if (!cacheItem) {
-			cacheItem = new ScanTask(this.delayExpireTime, origin);
-			this.cache.set(key, cacheItem);
-		}
-		else {
-			cacheItem.addOrigin(origin);
-		}
-
-		return cacheItem.delayTask.trigger(() => {
-			const conflicts = this.getConflictsOrEmpty(document, Array.from(cacheItem!.origins));
-
-			this.cache?.delete(key!);
-
-			return conflicts;
-		});
-	}
-
-	isPending(document: vscode.TextDocument, origin: string): boolean {
-		if (!document) {
-			return false;
-		}
-
-		const key = this.getCacheKey(document);
-		if (!key) {
-			return false;
-		}
-
-		const task = this.cache.get(key);
-		if (!task) {
-			return false;
-		}
-
-		return task.hasOrigin(origin);
-	}
-
-	createTracker(origin: string): interfaces.IDocumentMergeConflictTracker {
-		return new OriginDocumentMergeConflictTracker(this, origin);
-	}
-
-	forget(document: vscode.TextDocument) {
-		const key = this.getCacheKey(document);
-
-		if (key) {
-			this.cache.delete(key);
-		}
-	}
-
-	dispose() {
-		this.cache.clear();
-	}
-
-	private readonly seenDocumentsWithConflicts = new Set();
-
-	private getConflictsOrEmpty(document: vscode.TextDocument, _origins: string[]): interfaces.IDocumentMergeConflict[] {
-		const containsConflict = MergeConflictParser.containsConflict(document);
-
-		if (!containsConflict) {
-			return [];
-		}
-
-		const conflicts = MergeConflictParser.scanDocument(document, this.telemetryReporter);
-
-		const key = document.uri.toString();
-		// Don't report telemetry for the same document twice. This is an approximation, but good enough.
-		// Otherwise redo/undo could trigger this event multiple times.
-		if (!this.seenDocumentsWithConflicts.has(key)) {
-			this.seenDocumentsWithConflicts.add(key);
-
-			/* __GDPR__
-				"mergeMarkers.documentWithConflictMarkersOpened" : {
-					"owner": "hediet",
-					"comment": "Used to determine how many documents with conflicts are opened.",
-					"conflictCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of conflict counts" }
-				}
-			*/
-			this.telemetryReporter.sendTelemetryEvent('mergeMarkers.documentWithConflictMarkersOpened', {}, {
-				conflictCount: conflicts.length,
-			});
-		}
-
-		return conflicts;
-	}
-
-	private getCacheKey(document: vscode.TextDocument): string | null {
-		if (document.uri) {
-			return document.uri.toString();
-		}
-
-		return null;
-	}
+    private cache: Map = new Map();
+    private delayExpireTime: number = 0;
+    constructor(private readonly telemetryReporter: TelemetryReporter) { }
+    getConflicts(document: vscode.TextDocument, origin: string): PromiseLike {
+        // Attempt from cache
+        const key = this.getCacheKey(document);
+        if (!key) {
+            // Document doesn't have a uri, can't cache it, so return
+            return Promise.resolve(this.getConflictsOrEmpty(document, [origin]));
+        }
+        let cacheItem = this.cache.get(key);
+        if (!cacheItem) {
+            cacheItem = new ScanTask(this.delayExpireTime, origin);
+            this.cache.set(key, cacheItem);
+        }
+        else {
+            cacheItem.addOrigin(origin);
+        }
+        return cacheItem.delayTask.trigger(() => {
+            const conflicts = this.getConflictsOrEmpty(document, Array.from(cacheItem!.origins));
+            this.cache?.delete(key!);
+            return conflicts;
+        });
+    }
+    isPending(document: vscode.TextDocument, origin: string): boolean {
+        if (!document) {
+            return false;
+        }
+        const key = this.getCacheKey(document);
+        if (!key) {
+            return false;
+        }
+        const task = this.cache.get(key);
+        if (!task) {
+            return false;
+        }
+        return task.hasOrigin(origin);
+    }
+    createTracker(origin: string): interfaces.IDocumentMergeConflictTracker {
+        return new OriginDocumentMergeConflictTracker(this, origin);
+    }
+    forget(document: vscode.TextDocument) {
+        const key = this.getCacheKey(document);
+        if (key) {
+            this.cache.delete(key);
+        }
+    }
+    dispose() {
+        this.cache.clear();
+    }
+    private readonly seenDocumentsWithConflicts = new Set();
+    private getConflictsOrEmpty(document: vscode.TextDocument, _origins: string[]): interfaces.IDocumentMergeConflict[] {
+        const containsConflict = MergeConflictParser.containsConflict(document);
+        if (!containsConflict) {
+            return [];
+        }
+        const conflicts = MergeConflictParser.scanDocument(document, this.telemetryReporter);
+        const key = document.uri.toString();
+        // Don't report telemetry for the same document twice. This is an approximation, but good enough.
+        // Otherwise redo/undo could trigger this event multiple times.
+        if (!this.seenDocumentsWithConflicts.has(key)) {
+            this.seenDocumentsWithConflicts.add(key);
+            /* __GDPR__
+                "mergeMarkers.documentWithConflictMarkersOpened" : {
+                    "owner": "hediet",
+                    "comment": "Used to determine how many documents with conflicts are opened.",
+                    "conflictCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of conflict counts" }
+                }
+            */
+            this.telemetryReporter.sendTelemetryEvent('mergeMarkers.documentWithConflictMarkersOpened', {}, {
+                conflictCount: conflicts.length,
+            });
+        }
+        return conflicts;
+    }
+    private getCacheKey(document: vscode.TextDocument): string | null {
+        if (document.uri) {
+            return document.uri.toString();
+        }
+        return null;
+    }
 }
-
diff --git a/extensions/merge-conflict/Source/interfaces.ts b/extensions/merge-conflict/Source/interfaces.ts
index 0943c26354187..9f09c3d605a64 100644
--- a/extensions/merge-conflict/Source/interfaces.ts
+++ b/extensions/merge-conflict/Source/interfaces.ts
@@ -3,46 +3,41 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import * as vscode from 'vscode';
-
 export interface IMergeRegion {
-	name: string;
-	header: vscode.Range;
-	content: vscode.Range;
-	decoratorContent: vscode.Range;
+    name: string;
+    header: vscode.Range;
+    content: vscode.Range;
+    decoratorContent: vscode.Range;
 }
-
 export const enum CommitType {
-	Current,
-	Incoming,
-	Both
+    Current,
+    Incoming,
+    Both
 }
-
 export interface IExtensionConfiguration {
-	enableCodeLens: boolean;
-	enableDecorations: boolean;
-	enableEditorOverview: boolean;
+    enableCodeLens: boolean;
+    enableDecorations: boolean;
+    enableEditorOverview: boolean;
 }
-
 export interface IDocumentMergeConflict extends IDocumentMergeConflictDescriptor {
-	commitEdit(type: CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable;
-	applyEdit(type: CommitType, document: vscode.TextDocument, edit: { replace(range: vscode.Range, newText: string): void }): void;
+    commitEdit(type: CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable;
+    applyEdit(type: CommitType, document: vscode.TextDocument, edit: {
+        replace(range: vscode.Range, newText: string): void;
+    }): void;
 }
-
 export interface IDocumentMergeConflictDescriptor {
-	range: vscode.Range;
-	current: IMergeRegion;
-	incoming: IMergeRegion;
-	commonAncestors: IMergeRegion[];
-	splitter: vscode.Range;
+    range: vscode.Range;
+    current: IMergeRegion;
+    incoming: IMergeRegion;
+    commonAncestors: IMergeRegion[];
+    splitter: vscode.Range;
 }
-
 export interface IDocumentMergeConflictTracker {
-	getConflicts(document: vscode.TextDocument): PromiseLike;
-	isPending(document: vscode.TextDocument): boolean;
-	forget(document: vscode.TextDocument): void;
+    getConflicts(document: vscode.TextDocument): PromiseLike;
+    isPending(document: vscode.TextDocument): boolean;
+    forget(document: vscode.TextDocument): void;
 }
-
 export interface IDocumentMergeConflictTrackerService {
-	createTracker(origin: string): IDocumentMergeConflictTracker;
-	forget(document: vscode.TextDocument): void;
+    createTracker(origin: string): IDocumentMergeConflictTracker;
+    forget(document: vscode.TextDocument): void;
 }
diff --git a/extensions/merge-conflict/Source/mergeConflictMain.ts b/extensions/merge-conflict/Source/mergeConflictMain.ts
index 33c996955dbee..9e58ae8c1ad90 100644
--- a/extensions/merge-conflict/Source/mergeConflictMain.ts
+++ b/extensions/merge-conflict/Source/mergeConflictMain.ts
@@ -2,17 +2,13 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import MergeConflictServices from './services';
-
 export function activate(context: vscode.ExtensionContext) {
-	// Register disposables
-	const services = new MergeConflictServices(context);
-	services.begin();
-	context.subscriptions.push(services);
+    // Register disposables
+    const services = new MergeConflictServices(context);
+    services.begin();
+    context.subscriptions.push(services);
 }
-
 export function deactivate() {
 }
-
diff --git a/extensions/merge-conflict/Source/mergeConflictParser.ts b/extensions/merge-conflict/Source/mergeConflictParser.ts
index 6b651fd5e03e4..14bdbcbaefc77 100644
--- a/extensions/merge-conflict/Source/mergeConflictParser.ts
+++ b/extensions/merge-conflict/Source/mergeConflictParser.ts
@@ -6,164 +6,128 @@ import * as vscode from 'vscode';
 import * as interfaces from './interfaces';
 import { DocumentMergeConflict } from './documentMergeConflict';
 import TelemetryReporter from '@vscode/extension-telemetry';
-
 const startHeaderMarker = '<<<<<<<';
 const commonAncestorsMarker = '|||||||';
 const splitterMarker = '=======';
 const endFooterMarker = '>>>>>>>';
-
 interface IScanMergedConflict {
-	startHeader: vscode.TextLine;
-	commonAncestors: vscode.TextLine[];
-	splitter?: vscode.TextLine;
-	endFooter?: vscode.TextLine;
+    startHeader: vscode.TextLine;
+    commonAncestors: vscode.TextLine[];
+    splitter?: vscode.TextLine;
+    endFooter?: vscode.TextLine;
 }
-
 export class MergeConflictParser {
-
-	static scanDocument(document: vscode.TextDocument, telemetryReporter: TelemetryReporter): interfaces.IDocumentMergeConflict[] {
-
-		// Scan each line in the document, we already know there is at least a <<<<<<< and
-		// >>>>>> marker within the document, we need to group these into conflict ranges.
-		// We initially build a scan match, that references the lines of the header, splitter
-		// and footer. This is then converted into a full descriptor containing all required
-		// ranges.
-
-		let currentConflict: IScanMergedConflict | null = null;
-		const conflictDescriptors: interfaces.IDocumentMergeConflictDescriptor[] = [];
-
-		for (let i = 0; i < document.lineCount; i++) {
-			const line = document.lineAt(i);
-
-			// Ignore empty lines
-			if (!line || line.isEmptyOrWhitespace) {
-				continue;
-			}
-
-			// Is this a start line? <<<<<<<
-			if (line.text.startsWith(startHeaderMarker)) {
-				if (currentConflict !== null) {
-					// Error, we should not see a startMarker before we've seen an endMarker
-					currentConflict = null;
-
-					// Give up parsing, anything matched up this to this point will be decorated
-					// anything after will not
-					break;
-				}
-
-				// Create a new conflict starting at this line
-				currentConflict = { startHeader: line, commonAncestors: [] };
-			}
-			// Are we within a conflict block and is this a common ancestors marker? |||||||
-			else if (currentConflict && !currentConflict.splitter && line.text.startsWith(commonAncestorsMarker)) {
-				currentConflict.commonAncestors.push(line);
-			}
-			// Are we within a conflict block and is this a splitter? =======
-			else if (currentConflict && !currentConflict.splitter && line.text === splitterMarker) {
-				currentConflict.splitter = line;
-			}
-			// Are we within a conflict block and is this a footer? >>>>>>>
-			else if (currentConflict && line.text.startsWith(endFooterMarker)) {
-				currentConflict.endFooter = line;
-
-				// Create a full descriptor from the lines that we matched. This can return
-				// null if the descriptor could not be completed.
-				const completeDescriptor = MergeConflictParser.scanItemTolMergeConflictDescriptor(document, currentConflict);
-
-				if (completeDescriptor !== null) {
-					conflictDescriptors.push(completeDescriptor);
-				}
-
-				// Reset the current conflict to be empty, so we can match the next
-				// starting header marker.
-				currentConflict = null;
-			}
-		}
-
-		return conflictDescriptors
-			.filter(Boolean)
-			.map(descriptor => new DocumentMergeConflict(descriptor, telemetryReporter));
-	}
-
-	private static scanItemTolMergeConflictDescriptor(document: vscode.TextDocument, scanned: IScanMergedConflict): interfaces.IDocumentMergeConflictDescriptor | null {
-		// Validate we have all the required lines within the scan item.
-		if (!scanned.startHeader || !scanned.splitter || !scanned.endFooter) {
-			return null;
-		}
-
-		const tokenAfterCurrentBlock: vscode.TextLine = scanned.commonAncestors[0] || scanned.splitter;
-
-		// Assume that descriptor.current.header, descriptor.incoming.header and descriptor.splitter
-		// have valid ranges, fill in content and total ranges from these parts.
-		// NOTE: We need to shift the decorator range back one character so the splitter does not end up with
-		// two decoration colors (current and splitter), if we take the new line from the content into account
-		// the decorator will wrap to the next line.
-		return {
-			current: {
-				header: scanned.startHeader.range,
-				decoratorContent: new vscode.Range(
-					scanned.startHeader.rangeIncludingLineBreak.end,
-					MergeConflictParser.shiftBackOneCharacter(document, tokenAfterCurrentBlock.range.start, scanned.startHeader.rangeIncludingLineBreak.end)),
-				// Current content is range between header (shifted for linebreak) and splitter or common ancestors mark start
-				content: new vscode.Range(
-					scanned.startHeader.rangeIncludingLineBreak.end,
-					tokenAfterCurrentBlock.range.start),
-				name: scanned.startHeader.text.substring(startHeaderMarker.length + 1)
-			},
-			commonAncestors: scanned.commonAncestors.map((currentTokenLine, index, commonAncestors) => {
-				const nextTokenLine = commonAncestors[index + 1] || scanned.splitter;
-				return {
-					header: currentTokenLine.range,
-					decoratorContent: new vscode.Range(
-						currentTokenLine.rangeIncludingLineBreak.end,
-						MergeConflictParser.shiftBackOneCharacter(document, nextTokenLine.range.start, currentTokenLine.rangeIncludingLineBreak.end)),
-					// Each common ancestors block is range between one common ancestors token
-					// (shifted for linebreak) and start of next common ancestors token or splitter
-					content: new vscode.Range(
-						currentTokenLine.rangeIncludingLineBreak.end,
-						nextTokenLine.range.start),
-					name: currentTokenLine.text.substring(commonAncestorsMarker.length + 1)
-				};
-			}),
-			splitter: scanned.splitter.range,
-			incoming: {
-				header: scanned.endFooter.range,
-				decoratorContent: new vscode.Range(
-					scanned.splitter.rangeIncludingLineBreak.end,
-					MergeConflictParser.shiftBackOneCharacter(document, scanned.endFooter.range.start, scanned.splitter.rangeIncludingLineBreak.end)),
-				// Incoming content is range between splitter (shifted for linebreak) and footer start
-				content: new vscode.Range(
-					scanned.splitter.rangeIncludingLineBreak.end,
-					scanned.endFooter.range.start),
-				name: scanned.endFooter.text.substring(endFooterMarker.length + 1)
-			},
-			// Entire range is between current header start and incoming header end (including line break)
-			range: new vscode.Range(scanned.startHeader.range.start, scanned.endFooter.rangeIncludingLineBreak.end)
-		};
-	}
-
-	static containsConflict(document: vscode.TextDocument): boolean {
-		if (!document) {
-			return false;
-		}
-
-		const text = document.getText();
-		return text.includes(startHeaderMarker) && text.includes(endFooterMarker);
-	}
-
-	private static shiftBackOneCharacter(document: vscode.TextDocument, range: vscode.Position, unlessEqual: vscode.Position): vscode.Position {
-		if (range.isEqual(unlessEqual)) {
-			return range;
-		}
-
-		let line = range.line;
-		let character = range.character - 1;
-
-		if (character < 0) {
-			line--;
-			character = document.lineAt(line).range.end.character;
-		}
-
-		return new vscode.Position(line, character);
-	}
+    static scanDocument(document: vscode.TextDocument, telemetryReporter: TelemetryReporter): interfaces.IDocumentMergeConflict[] {
+        // Scan each line in the document, we already know there is at least a <<<<<<< and
+        // >>>>>> marker within the document, we need to group these into conflict ranges.
+        // We initially build a scan match, that references the lines of the header, splitter
+        // and footer. This is then converted into a full descriptor containing all required
+        // ranges.
+        let currentConflict: IScanMergedConflict | null = null;
+        const conflictDescriptors: interfaces.IDocumentMergeConflictDescriptor[] = [];
+        for (let i = 0; i < document.lineCount; i++) {
+            const line = document.lineAt(i);
+            // Ignore empty lines
+            if (!line || line.isEmptyOrWhitespace) {
+                continue;
+            }
+            // Is this a start line? <<<<<<<
+            if (line.text.startsWith(startHeaderMarker)) {
+                if (currentConflict !== null) {
+                    // Error, we should not see a startMarker before we've seen an endMarker
+                    currentConflict = null;
+                    // Give up parsing, anything matched up this to this point will be decorated
+                    // anything after will not
+                    break;
+                }
+                // Create a new conflict starting at this line
+                currentConflict = { startHeader: line, commonAncestors: [] };
+            }
+            // Are we within a conflict block and is this a common ancestors marker? |||||||
+            else if (currentConflict && !currentConflict.splitter && line.text.startsWith(commonAncestorsMarker)) {
+                currentConflict.commonAncestors.push(line);
+            }
+            // Are we within a conflict block and is this a splitter? =======
+            else if (currentConflict && !currentConflict.splitter && line.text === splitterMarker) {
+                currentConflict.splitter = line;
+            }
+            // Are we within a conflict block and is this a footer? >>>>>>>
+            else if (currentConflict && line.text.startsWith(endFooterMarker)) {
+                currentConflict.endFooter = line;
+                // Create a full descriptor from the lines that we matched. This can return
+                // null if the descriptor could not be completed.
+                const completeDescriptor = MergeConflictParser.scanItemTolMergeConflictDescriptor(document, currentConflict);
+                if (completeDescriptor !== null) {
+                    conflictDescriptors.push(completeDescriptor);
+                }
+                // Reset the current conflict to be empty, so we can match the next
+                // starting header marker.
+                currentConflict = null;
+            }
+        }
+        return conflictDescriptors
+            .filter(Boolean)
+            .map(descriptor => new DocumentMergeConflict(descriptor, telemetryReporter));
+    }
+    private static scanItemTolMergeConflictDescriptor(document: vscode.TextDocument, scanned: IScanMergedConflict): interfaces.IDocumentMergeConflictDescriptor | null {
+        // Validate we have all the required lines within the scan item.
+        if (!scanned.startHeader || !scanned.splitter || !scanned.endFooter) {
+            return null;
+        }
+        const tokenAfterCurrentBlock: vscode.TextLine = scanned.commonAncestors[0] || scanned.splitter;
+        // Assume that descriptor.current.header, descriptor.incoming.header and descriptor.splitter
+        // have valid ranges, fill in content and total ranges from these parts.
+        // NOTE: We need to shift the decorator range back one character so the splitter does not end up with
+        // two decoration colors (current and splitter), if we take the new line from the content into account
+        // the decorator will wrap to the next line.
+        return {
+            current: {
+                header: scanned.startHeader.range,
+                decoratorContent: new vscode.Range(scanned.startHeader.rangeIncludingLineBreak.end, MergeConflictParser.shiftBackOneCharacter(document, tokenAfterCurrentBlock.range.start, scanned.startHeader.rangeIncludingLineBreak.end)),
+                // Current content is range between header (shifted for linebreak) and splitter or common ancestors mark start
+                content: new vscode.Range(scanned.startHeader.rangeIncludingLineBreak.end, tokenAfterCurrentBlock.range.start),
+                name: scanned.startHeader.text.substring(startHeaderMarker.length + 1)
+            },
+            commonAncestors: scanned.commonAncestors.map((currentTokenLine, index, commonAncestors) => {
+                const nextTokenLine = commonAncestors[index + 1] || scanned.splitter;
+                return {
+                    header: currentTokenLine.range,
+                    decoratorContent: new vscode.Range(currentTokenLine.rangeIncludingLineBreak.end, MergeConflictParser.shiftBackOneCharacter(document, nextTokenLine.range.start, currentTokenLine.rangeIncludingLineBreak.end)),
+                    // Each common ancestors block is range between one common ancestors token
+                    // (shifted for linebreak) and start of next common ancestors token or splitter
+                    content: new vscode.Range(currentTokenLine.rangeIncludingLineBreak.end, nextTokenLine.range.start),
+                    name: currentTokenLine.text.substring(commonAncestorsMarker.length + 1)
+                };
+            }),
+            splitter: scanned.splitter.range,
+            incoming: {
+                header: scanned.endFooter.range,
+                decoratorContent: new vscode.Range(scanned.splitter.rangeIncludingLineBreak.end, MergeConflictParser.shiftBackOneCharacter(document, scanned.endFooter.range.start, scanned.splitter.rangeIncludingLineBreak.end)),
+                // Incoming content is range between splitter (shifted for linebreak) and footer start
+                content: new vscode.Range(scanned.splitter.rangeIncludingLineBreak.end, scanned.endFooter.range.start),
+                name: scanned.endFooter.text.substring(endFooterMarker.length + 1)
+            },
+            // Entire range is between current header start and incoming header end (including line break)
+            range: new vscode.Range(scanned.startHeader.range.start, scanned.endFooter.rangeIncludingLineBreak.end)
+        };
+    }
+    static containsConflict(document: vscode.TextDocument): boolean {
+        if (!document) {
+            return false;
+        }
+        const text = document.getText();
+        return text.includes(startHeaderMarker) && text.includes(endFooterMarker);
+    }
+    private static shiftBackOneCharacter(document: vscode.TextDocument, range: vscode.Position, unlessEqual: vscode.Position): vscode.Position {
+        if (range.isEqual(unlessEqual)) {
+            return range;
+        }
+        let line = range.line;
+        let character = range.character - 1;
+        if (character < 0) {
+            line--;
+            character = document.lineAt(line).range.end.character;
+        }
+        return new vscode.Position(line, character);
+    }
 }
diff --git a/extensions/merge-conflict/Source/mergeDecorator.ts b/extensions/merge-conflict/Source/mergeDecorator.ts
index 6c70fa79fae1a..a4fbb1dc43ad5 100644
--- a/extensions/merge-conflict/Source/mergeDecorator.ts
+++ b/extensions/merge-conflict/Source/mergeDecorator.ts
@@ -4,248 +4,199 @@
  *--------------------------------------------------------------------------------------------*/
 import * as vscode from 'vscode';
 import * as interfaces from './interfaces';
-
-
 export default class MergeDecorator implements vscode.Disposable {
-
-	private decorations: { [key: string]: vscode.TextEditorDecorationType } = {};
-
-	private decorationUsesWholeLine: boolean = true; // Useful for debugging, set to false to see exact match ranges
-
-	private config?: interfaces.IExtensionConfiguration;
-	private tracker: interfaces.IDocumentMergeConflictTracker;
-	private updating = new Map();
-
-	constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) {
-		this.tracker = trackerService.createTracker('decorator');
-	}
-
-	begin(config: interfaces.IExtensionConfiguration) {
-		this.config = config;
-		this.registerDecorationTypes(config);
-
-		// Check if we already have a set of active windows, attempt to track these.
-		vscode.window.visibleTextEditors.forEach(e => this.applyDecorations(e));
-
-		vscode.workspace.onDidOpenTextDocument(event => {
-			this.applyDecorationsFromEvent(event);
-		}, null, this.context.subscriptions);
-
-		vscode.workspace.onDidChangeTextDocument(event => {
-			this.applyDecorationsFromEvent(event.document);
-		}, null, this.context.subscriptions);
-
-		vscode.window.onDidChangeVisibleTextEditors((e) => {
-			// Any of which could be new (not just the active one).
-			e.forEach(e => this.applyDecorations(e));
-		}, null, this.context.subscriptions);
-	}
-
-	configurationUpdated(config: interfaces.IExtensionConfiguration) {
-		this.config = config;
-		this.registerDecorationTypes(config);
-
-		// Re-apply the decoration
-		vscode.window.visibleTextEditors.forEach(e => {
-			this.removeDecorations(e);
-			this.applyDecorations(e);
-		});
-	}
-
-	private registerDecorationTypes(config: interfaces.IExtensionConfiguration) {
-
-		// Dispose of existing decorations
-		Object.keys(this.decorations).forEach(k => this.decorations[k].dispose());
-		this.decorations = {};
-
-		// None of our features are enabled
-		if (!config.enableDecorations || !config.enableEditorOverview) {
-			return;
-		}
-
-		// Create decorators
-		if (config.enableDecorations || config.enableEditorOverview) {
-			this.decorations['current.content'] = vscode.window.createTextEditorDecorationType(
-				this.generateBlockRenderOptions('merge.currentContentBackground', 'editorOverviewRuler.currentContentForeground', config)
-			);
-
-			this.decorations['incoming.content'] = vscode.window.createTextEditorDecorationType(
-				this.generateBlockRenderOptions('merge.incomingContentBackground', 'editorOverviewRuler.incomingContentForeground', config)
-			);
-
-			this.decorations['commonAncestors.content'] = vscode.window.createTextEditorDecorationType(
-				this.generateBlockRenderOptions('merge.commonContentBackground', 'editorOverviewRuler.commonContentForeground', config)
-			);
-		}
-
-		if (config.enableDecorations) {
-			this.decorations['current.header'] = vscode.window.createTextEditorDecorationType({
-				isWholeLine: this.decorationUsesWholeLine,
-				backgroundColor: new vscode.ThemeColor('merge.currentHeaderBackground'),
-				color: new vscode.ThemeColor('editor.foreground'),
-				outlineStyle: 'solid',
-				outlineWidth: '1pt',
-				outlineColor: new vscode.ThemeColor('merge.border'),
-				after: {
-					contentText: ' ' + vscode.l10n.t("(Current Change)"),
-					color: new vscode.ThemeColor('descriptionForeground')
-				}
-			});
-
-			this.decorations['commonAncestors.header'] = vscode.window.createTextEditorDecorationType({
-				isWholeLine: this.decorationUsesWholeLine,
-				backgroundColor: new vscode.ThemeColor('merge.commonHeaderBackground'),
-				color: new vscode.ThemeColor('editor.foreground'),
-				outlineStyle: 'solid',
-				outlineWidth: '1pt',
-				outlineColor: new vscode.ThemeColor('merge.border')
-			});
-
-			this.decorations['splitter'] = vscode.window.createTextEditorDecorationType({
-				color: new vscode.ThemeColor('editor.foreground'),
-				outlineStyle: 'solid',
-				outlineWidth: '1pt',
-				outlineColor: new vscode.ThemeColor('merge.border'),
-				isWholeLine: this.decorationUsesWholeLine,
-			});
-
-			this.decorations['incoming.header'] = vscode.window.createTextEditorDecorationType({
-				backgroundColor: new vscode.ThemeColor('merge.incomingHeaderBackground'),
-				color: new vscode.ThemeColor('editor.foreground'),
-				outlineStyle: 'solid',
-				outlineWidth: '1pt',
-				outlineColor: new vscode.ThemeColor('merge.border'),
-				isWholeLine: this.decorationUsesWholeLine,
-				after: {
-					contentText: ' ' + vscode.l10n.t("(Incoming Change)"),
-					color: new vscode.ThemeColor('descriptionForeground')
-				}
-			});
-		}
-	}
-
-	dispose() {
-
-		// TODO: Replace with Map
-		Object.keys(this.decorations).forEach(name => {
-			this.decorations[name].dispose();
-		});
-
-		this.decorations = {};
-	}
-
-	private generateBlockRenderOptions(backgroundColor: string, overviewRulerColor: string, config: interfaces.IExtensionConfiguration): vscode.DecorationRenderOptions {
-
-		const renderOptions: vscode.DecorationRenderOptions = {};
-
-		if (config.enableDecorations) {
-			renderOptions.backgroundColor = new vscode.ThemeColor(backgroundColor);
-			renderOptions.isWholeLine = this.decorationUsesWholeLine;
-		}
-
-		if (config.enableEditorOverview) {
-			renderOptions.overviewRulerColor = new vscode.ThemeColor(overviewRulerColor);
-			renderOptions.overviewRulerLane = vscode.OverviewRulerLane.Full;
-		}
-
-		return renderOptions;
-	}
-
-	private applyDecorationsFromEvent(eventDocument: vscode.TextDocument) {
-		for (const editor of vscode.window.visibleTextEditors) {
-			if (editor.document === eventDocument) {
-				// Attempt to apply
-				this.applyDecorations(editor);
-			}
-		}
-	}
-
-	private async applyDecorations(editor: vscode.TextEditor) {
-		if (!editor || !editor.document) { return; }
-
-		if (!this.config || (!this.config.enableDecorations && !this.config.enableEditorOverview)) {
-			return;
-		}
-
-		// If we have a pending scan from the same origin, exit early. (Cannot use this.tracker.isPending() because decorations are per editor.)
-		if (this.updating.get(editor)) {
-			return;
-		}
-
-		try {
-			this.updating.set(editor, true);
-
-			const conflicts = await this.tracker.getConflicts(editor.document);
-			if (vscode.window.visibleTextEditors.indexOf(editor) === -1) {
-				return;
-			}
-
-			if (conflicts.length === 0) {
-				this.removeDecorations(editor);
-				return;
-			}
-
-			// Store decorations keyed by the type of decoration, set decoration wants a "style"
-			// to go with it, which will match this key (see constructor);
-			const matchDecorations: { [key: string]: vscode.Range[] } = {};
-
-			const pushDecoration = (key: string, d: vscode.Range) => {
-				matchDecorations[key] = matchDecorations[key] || [];
-				matchDecorations[key].push(d);
-			};
-
-			conflicts.forEach(conflict => {
-				// TODO, this could be more effective, just call getMatchPositions once with a map of decoration to position
-				if (!conflict.current.decoratorContent.isEmpty) {
-					pushDecoration('current.content', conflict.current.decoratorContent);
-				}
-				if (!conflict.incoming.decoratorContent.isEmpty) {
-					pushDecoration('incoming.content', conflict.incoming.decoratorContent);
-				}
-
-				conflict.commonAncestors.forEach(commonAncestorsRegion => {
-					if (!commonAncestorsRegion.decoratorContent.isEmpty) {
-						pushDecoration('commonAncestors.content', commonAncestorsRegion.decoratorContent);
-					}
-				});
-
-				if (this.config!.enableDecorations) {
-					pushDecoration('current.header', conflict.current.header);
-					pushDecoration('splitter', conflict.splitter);
-					pushDecoration('incoming.header', conflict.incoming.header);
-
-					conflict.commonAncestors.forEach(commonAncestorsRegion => {
-						pushDecoration('commonAncestors.header', commonAncestorsRegion.header);
-					});
-				}
-			});
-
-			// For each match we've generated, apply the generated decoration with the matching decoration type to the
-			// editor instance. Keys in both matches and decorations should match.
-			Object.keys(matchDecorations).forEach(decorationKey => {
-				const decorationType = this.decorations[decorationKey];
-
-				if (decorationType) {
-					editor.setDecorations(decorationType, matchDecorations[decorationKey]);
-				}
-			});
-
-		} finally {
-			this.updating.delete(editor);
-		}
-	}
-
-	private removeDecorations(editor: vscode.TextEditor) {
-		// Remove all decorations, there might be none
-		Object.keys(this.decorations).forEach(decorationKey => {
-
-			// Race condition, while editing the settings, it's possible to
-			// generate regions before the configuration has been refreshed
-			const decorationType = this.decorations[decorationKey];
-
-			if (decorationType) {
-				editor.setDecorations(decorationType, []);
-			}
-		});
-	}
+    private decorations: {
+        [key: string]: vscode.TextEditorDecorationType;
+    } = {};
+    private decorationUsesWholeLine: boolean = true; // Useful for debugging, set to false to see exact match ranges
+    private config?: interfaces.IExtensionConfiguration;
+    private tracker: interfaces.IDocumentMergeConflictTracker;
+    private updating = new Map();
+    constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) {
+        this.tracker = trackerService.createTracker('decorator');
+    }
+    begin(config: interfaces.IExtensionConfiguration) {
+        this.config = config;
+        this.registerDecorationTypes(config);
+        // Check if we already have a set of active windows, attempt to track these.
+        vscode.window.visibleTextEditors.forEach(e => this.applyDecorations(e));
+        vscode.workspace.onDidOpenTextDocument(event => {
+            this.applyDecorationsFromEvent(event);
+        }, null, this.context.subscriptions);
+        vscode.workspace.onDidChangeTextDocument(event => {
+            this.applyDecorationsFromEvent(event.document);
+        }, null, this.context.subscriptions);
+        vscode.window.onDidChangeVisibleTextEditors((e) => {
+            // Any of which could be new (not just the active one).
+            e.forEach(e => this.applyDecorations(e));
+        }, null, this.context.subscriptions);
+    }
+    configurationUpdated(config: interfaces.IExtensionConfiguration) {
+        this.config = config;
+        this.registerDecorationTypes(config);
+        // Re-apply the decoration
+        vscode.window.visibleTextEditors.forEach(e => {
+            this.removeDecorations(e);
+            this.applyDecorations(e);
+        });
+    }
+    private registerDecorationTypes(config: interfaces.IExtensionConfiguration) {
+        // Dispose of existing decorations
+        Object.keys(this.decorations).forEach(k => this.decorations[k].dispose());
+        this.decorations = {};
+        // None of our features are enabled
+        if (!config.enableDecorations || !config.enableEditorOverview) {
+            return;
+        }
+        // Create decorators
+        if (config.enableDecorations || config.enableEditorOverview) {
+            this.decorations['current.content'] = vscode.window.createTextEditorDecorationType(this.generateBlockRenderOptions('merge.currentContentBackground', 'editorOverviewRuler.currentContentForeground', config));
+            this.decorations['incoming.content'] = vscode.window.createTextEditorDecorationType(this.generateBlockRenderOptions('merge.incomingContentBackground', 'editorOverviewRuler.incomingContentForeground', config));
+            this.decorations['commonAncestors.content'] = vscode.window.createTextEditorDecorationType(this.generateBlockRenderOptions('merge.commonContentBackground', 'editorOverviewRuler.commonContentForeground', config));
+        }
+        if (config.enableDecorations) {
+            this.decorations['current.header'] = vscode.window.createTextEditorDecorationType({
+                isWholeLine: this.decorationUsesWholeLine,
+                backgroundColor: new vscode.ThemeColor('merge.currentHeaderBackground'),
+                color: new vscode.ThemeColor('editor.foreground'),
+                outlineStyle: 'solid',
+                outlineWidth: '1pt',
+                outlineColor: new vscode.ThemeColor('merge.border'),
+                after: {
+                    contentText: ' ' + vscode.l10n.t("(Current Change)"),
+                    color: new vscode.ThemeColor('descriptionForeground')
+                }
+            });
+            this.decorations['commonAncestors.header'] = vscode.window.createTextEditorDecorationType({
+                isWholeLine: this.decorationUsesWholeLine,
+                backgroundColor: new vscode.ThemeColor('merge.commonHeaderBackground'),
+                color: new vscode.ThemeColor('editor.foreground'),
+                outlineStyle: 'solid',
+                outlineWidth: '1pt',
+                outlineColor: new vscode.ThemeColor('merge.border')
+            });
+            this.decorations['splitter'] = vscode.window.createTextEditorDecorationType({
+                color: new vscode.ThemeColor('editor.foreground'),
+                outlineStyle: 'solid',
+                outlineWidth: '1pt',
+                outlineColor: new vscode.ThemeColor('merge.border'),
+                isWholeLine: this.decorationUsesWholeLine,
+            });
+            this.decorations['incoming.header'] = vscode.window.createTextEditorDecorationType({
+                backgroundColor: new vscode.ThemeColor('merge.incomingHeaderBackground'),
+                color: new vscode.ThemeColor('editor.foreground'),
+                outlineStyle: 'solid',
+                outlineWidth: '1pt',
+                outlineColor: new vscode.ThemeColor('merge.border'),
+                isWholeLine: this.decorationUsesWholeLine,
+                after: {
+                    contentText: ' ' + vscode.l10n.t("(Incoming Change)"),
+                    color: new vscode.ThemeColor('descriptionForeground')
+                }
+            });
+        }
+    }
+    dispose() {
+        // TODO: Replace with Map
+        Object.keys(this.decorations).forEach(name => {
+            this.decorations[name].dispose();
+        });
+        this.decorations = {};
+    }
+    private generateBlockRenderOptions(backgroundColor: string, overviewRulerColor: string, config: interfaces.IExtensionConfiguration): vscode.DecorationRenderOptions {
+        const renderOptions: vscode.DecorationRenderOptions = {};
+        if (config.enableDecorations) {
+            renderOptions.backgroundColor = new vscode.ThemeColor(backgroundColor);
+            renderOptions.isWholeLine = this.decorationUsesWholeLine;
+        }
+        if (config.enableEditorOverview) {
+            renderOptions.overviewRulerColor = new vscode.ThemeColor(overviewRulerColor);
+            renderOptions.overviewRulerLane = vscode.OverviewRulerLane.Full;
+        }
+        return renderOptions;
+    }
+    private applyDecorationsFromEvent(eventDocument: vscode.TextDocument) {
+        for (const editor of vscode.window.visibleTextEditors) {
+            if (editor.document === eventDocument) {
+                // Attempt to apply
+                this.applyDecorations(editor);
+            }
+        }
+    }
+    private async applyDecorations(editor: vscode.TextEditor) {
+        if (!editor || !editor.document) {
+            return;
+        }
+        if (!this.config || (!this.config.enableDecorations && !this.config.enableEditorOverview)) {
+            return;
+        }
+        // If we have a pending scan from the same origin, exit early. (Cannot use this.tracker.isPending() because decorations are per editor.)
+        if (this.updating.get(editor)) {
+            return;
+        }
+        try {
+            this.updating.set(editor, true);
+            const conflicts = await this.tracker.getConflicts(editor.document);
+            if (vscode.window.visibleTextEditors.indexOf(editor) === -1) {
+                return;
+            }
+            if (conflicts.length === 0) {
+                this.removeDecorations(editor);
+                return;
+            }
+            // Store decorations keyed by the type of decoration, set decoration wants a "style"
+            // to go with it, which will match this key (see constructor);
+            const matchDecorations: {
+                [key: string]: vscode.Range[];
+            } = {};
+            const pushDecoration = (key: string, d: vscode.Range) => {
+                matchDecorations[key] = matchDecorations[key] || [];
+                matchDecorations[key].push(d);
+            };
+            conflicts.forEach(conflict => {
+                // TODO, this could be more effective, just call getMatchPositions once with a map of decoration to position
+                if (!conflict.current.decoratorContent.isEmpty) {
+                    pushDecoration('current.content', conflict.current.decoratorContent);
+                }
+                if (!conflict.incoming.decoratorContent.isEmpty) {
+                    pushDecoration('incoming.content', conflict.incoming.decoratorContent);
+                }
+                conflict.commonAncestors.forEach(commonAncestorsRegion => {
+                    if (!commonAncestorsRegion.decoratorContent.isEmpty) {
+                        pushDecoration('commonAncestors.content', commonAncestorsRegion.decoratorContent);
+                    }
+                });
+                if (this.config!.enableDecorations) {
+                    pushDecoration('current.header', conflict.current.header);
+                    pushDecoration('splitter', conflict.splitter);
+                    pushDecoration('incoming.header', conflict.incoming.header);
+                    conflict.commonAncestors.forEach(commonAncestorsRegion => {
+                        pushDecoration('commonAncestors.header', commonAncestorsRegion.header);
+                    });
+                }
+            });
+            // For each match we've generated, apply the generated decoration with the matching decoration type to the
+            // editor instance. Keys in both matches and decorations should match.
+            Object.keys(matchDecorations).forEach(decorationKey => {
+                const decorationType = this.decorations[decorationKey];
+                if (decorationType) {
+                    editor.setDecorations(decorationType, matchDecorations[decorationKey]);
+                }
+            });
+        }
+        finally {
+            this.updating.delete(editor);
+        }
+    }
+    private removeDecorations(editor: vscode.TextEditor) {
+        // Remove all decorations, there might be none
+        Object.keys(this.decorations).forEach(decorationKey => {
+            // Race condition, while editing the settings, it's possible to
+            // generate regions before the configuration has been refreshed
+            const decorationType = this.decorations[decorationKey];
+            if (decorationType) {
+                editor.setDecorations(decorationType, []);
+            }
+        });
+    }
 }
diff --git a/extensions/merge-conflict/Source/services.ts b/extensions/merge-conflict/Source/services.ts
index 273d13d35f724..f7769016e3574 100644
--- a/extensions/merge-conflict/Source/services.ts
+++ b/extensions/merge-conflict/Source/services.ts
@@ -10,62 +10,46 @@ import ContentProvider from './contentProvider';
 import Decorator from './mergeDecorator';
 import * as interfaces from './interfaces';
 import TelemetryReporter from '@vscode/extension-telemetry';
-
 const ConfigurationSectionName = 'merge-conflict';
-
 export default class ServiceWrapper implements vscode.Disposable {
-
-	private services: vscode.Disposable[] = [];
-	private telemetryReporter: TelemetryReporter;
-
-	constructor(private context: vscode.ExtensionContext) {
-		const { aiKey } = context.extension.packageJSON as { aiKey: string };
-		this.telemetryReporter = new TelemetryReporter(aiKey);
-		context.subscriptions.push(this.telemetryReporter);
-	}
-
-	begin() {
-
-		const configuration = this.createExtensionConfiguration();
-		const documentTracker = new DocumentTracker(this.telemetryReporter);
-
-		this.services.push(
-			documentTracker,
-			new CommandHandler(documentTracker),
-			new CodeLensProvider(documentTracker),
-			new ContentProvider(this.context),
-			new Decorator(this.context, documentTracker),
-		);
-
-		this.services.forEach((service: any) => {
-			if (service.begin && service.begin instanceof Function) {
-				service.begin(configuration);
-			}
-		});
-
-		vscode.workspace.onDidChangeConfiguration(() => {
-			this.services.forEach((service: any) => {
-				if (service.configurationUpdated && service.configurationUpdated instanceof Function) {
-					service.configurationUpdated(this.createExtensionConfiguration());
-				}
-			});
-		});
-	}
-
-	createExtensionConfiguration(): interfaces.IExtensionConfiguration {
-		const workspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationSectionName);
-		const codeLensEnabled: boolean = workspaceConfiguration.get('codeLens.enabled', true);
-		const decoratorsEnabled: boolean = workspaceConfiguration.get('decorators.enabled', true);
-
-		return {
-			enableCodeLens: codeLensEnabled,
-			enableDecorations: decoratorsEnabled,
-			enableEditorOverview: decoratorsEnabled
-		};
-	}
-
-	dispose() {
-		this.services.forEach(disposable => disposable.dispose());
-		this.services = [];
-	}
+    private services: vscode.Disposable[] = [];
+    private telemetryReporter: TelemetryReporter;
+    constructor(private context: vscode.ExtensionContext) {
+        const { aiKey } = context.extension.packageJSON as {
+            aiKey: string;
+        };
+        this.telemetryReporter = new TelemetryReporter(aiKey);
+        context.subscriptions.push(this.telemetryReporter);
+    }
+    begin() {
+        const configuration = this.createExtensionConfiguration();
+        const documentTracker = new DocumentTracker(this.telemetryReporter);
+        this.services.push(documentTracker, new CommandHandler(documentTracker), new CodeLensProvider(documentTracker), new ContentProvider(this.context), new Decorator(this.context, documentTracker));
+        this.services.forEach((service: any) => {
+            if (service.begin && service.begin instanceof Function) {
+                service.begin(configuration);
+            }
+        });
+        vscode.workspace.onDidChangeConfiguration(() => {
+            this.services.forEach((service: any) => {
+                if (service.configurationUpdated && service.configurationUpdated instanceof Function) {
+                    service.configurationUpdated(this.createExtensionConfiguration());
+                }
+            });
+        });
+    }
+    createExtensionConfiguration(): interfaces.IExtensionConfiguration {
+        const workspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationSectionName);
+        const codeLensEnabled: boolean = workspaceConfiguration.get('codeLens.enabled', true);
+        const decoratorsEnabled: boolean = workspaceConfiguration.get('decorators.enabled', true);
+        return {
+            enableCodeLens: codeLensEnabled,
+            enableDecorations: decoratorsEnabled,
+            enableEditorOverview: decoratorsEnabled
+        };
+    }
+    dispose() {
+        this.services.forEach(disposable => disposable.dispose());
+        this.services = [];
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/AADHelper.ts b/extensions/microsoft-authentication/Source/AADHelper.ts
index 9722145dd0376..5c168205641fa 100644
--- a/extensions/microsoft-authentication/Source/AADHelper.ts
+++ b/extensions/microsoft-authentication/Source/AADHelper.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as path from 'path';
 import { isSupportedEnvironment } from './common/uri';
@@ -15,965 +14,867 @@ import fetch from './node/fetch';
 import { UriEventHandler } from './UriEventHandler';
 import TelemetryReporter from '@vscode/extension-telemetry';
 import { Environment } from '@azure/ms-rest-azure-env';
-
 const redirectUrl = 'https://vscode.dev/redirect';
 const defaultActiveDirectoryEndpointUrl = Environment.AzureCloud.activeDirectoryEndpointUrl;
 const DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56';
 const DEFAULT_TENANT = 'organizations';
 const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad';
 const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a';
-
 const enum MicrosoftAccountType {
-	AAD = 'aad',
-	MSA = 'msa',
-	Unknown = 'unknown'
+    AAD = 'aad',
+    MSA = 'msa',
+    Unknown = 'unknown'
 }
-
 interface IToken {
-	accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined
-	idToken?: string; // depending on the scopes can be either supplied or empty
-
-	expiresIn?: number; // How long access token is valid, in seconds
-	expiresAt?: number; // UNIX epoch time at which token will expire
-	refreshToken: string;
-
-	account: {
-		label: string;
-		id: string;
-		type: MicrosoftAccountType;
-	};
-	scope: string;
-	sessionId: string; // The account id + the scope
+    accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined
+    idToken?: string; // depending on the scopes can be either supplied or empty
+    expiresIn?: number; // How long access token is valid, in seconds
+    expiresAt?: number; // UNIX epoch time at which token will expire
+    refreshToken: string;
+    account: {
+        label: string;
+        id: string;
+        type: MicrosoftAccountType;
+    };
+    scope: string;
+    sessionId: string; // The account id + the scope
 }
-
 export interface IStoredSession {
-	id: string;
-	refreshToken: string;
-	scope: string; // Scopes are alphabetized and joined with a space
-	account: {
-		label: string;
-		id: string;
-	};
-	endpoint: string | undefined;
+    id: string;
+    refreshToken: string;
+    scope: string; // Scopes are alphabetized and joined with a space
+    account: {
+        label: string;
+        id: string;
+    };
+    endpoint: string | undefined;
 }
-
 export interface ITokenResponse {
-	access_token: string;
-	expires_in: number;
-	ext_expires_in: number;
-	refresh_token: string;
-	scope: string;
-	token_type: string;
-	id_token?: string;
+    access_token: string;
+    expires_in: number;
+    ext_expires_in: number;
+    refresh_token: string;
+    scope: string;
+    token_type: string;
+    id_token?: string;
 }
-
 export interface IMicrosoftTokens {
-	accessToken: string;
-	idToken?: string;
+    accessToken: string;
+    idToken?: string;
 }
-
 interface IScopeData {
-	originalScopes?: string[];
-	scopes: string[];
-	scopeStr: string;
-	scopesToSend: string;
-	clientId: string;
-	tenant: string;
+    originalScopes?: string[];
+    scopes: string[];
+    scopeStr: string;
+    scopesToSend: string;
+    clientId: string;
+    tenant: string;
 }
-
 export const REFRESH_NETWORK_FAILURE = 'Network failure';
-
 export class AzureActiveDirectoryService {
-	// For details on why this is set to 2/3... see https://github.com/microsoft/vscode/issues/133201#issuecomment-966668197
-	private static REFRESH_TIMEOUT_MODIFIER = 1000 * 2 / 3;
-	private static POLLING_CONSTANT = 1000 * 60 * 30;
-
-	private _tokens: IToken[] = [];
-	private _refreshTimeouts: Map = new Map();
-	private _sessionChangeEmitter: vscode.EventEmitter = new vscode.EventEmitter();
-
-	// Used to keep track of current requests when not using the local server approach.
-	private _pendingNonces = new Map();
-	private _codeExchangePromises = new Map>();
-	private _codeVerfifiers = new Map();
-
-	// Used to keep track of tokens that we need to store but can't because we aren't the focused window.
-	private _pendingTokensToStore: Map = new Map();
-
-	// Used to sequence requests to the same scope.
-	private _sequencer = new SequencerByKey();
-
-	constructor(
-		private readonly _logger: vscode.LogOutputChannel,
-		_context: vscode.ExtensionContext,
-		private readonly _uriHandler: UriEventHandler,
-		private readonly _tokenStorage: BetterTokenStorage,
-		private readonly _telemetryReporter: TelemetryReporter,
-		private readonly _env: Environment
-	) {
-		_context.subscriptions.push(this._tokenStorage.onDidChangeInOtherWindow((e) => this.checkForUpdates(e)));
-		_context.subscriptions.push(vscode.window.onDidChangeWindowState(async (e) => e.focused && await this.storePendingTokens()));
-
-		// In the event that a window isn't focused for a long time, we should still try to store the tokens at some point.
-		const timer = new IntervalTimer();
-		timer.cancelAndSet(
-			() => !vscode.window.state.focused && this.storePendingTokens(),
-			// 5 hours + random extra 0-30 seconds so that each window doesn't try to store at the same time
-			(18000000) + Math.floor(Math.random() * 30000));
-		_context.subscriptions.push(timer);
-	}
-
-	public async initialize(): Promise {
-		this._logger.trace('Reading sessions from secret storage...');
-		const sessions = await this._tokenStorage.getAll(item => this.sessionMatchesEndpoint(item));
-		this._logger.trace(`Got ${sessions.length} stored sessions`);
-
-		const refreshes = sessions.map(async session => {
-			this._logger.trace(`[${session.scope}] '${session.id}' Read stored session`);
-			const scopes = session.scope.split(' ');
-			const scopeData: IScopeData = {
-				scopes,
-				scopeStr: session.scope,
-				// filter our special scopes
-				scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
-				clientId: this.getClientId(scopes),
-				tenant: this.getTenantId(scopes),
-			};
-			try {
-				await this.refreshToken(session.refreshToken, scopeData, session.id);
-			} catch (e) {
-				// If we aren't connected to the internet, then wait and try to refresh again later.
-				if (e.message === REFRESH_NETWORK_FAILURE) {
-					this._tokens.push({
-						accessToken: undefined,
-						refreshToken: session.refreshToken,
-						account: {
-							...session.account,
-							type: MicrosoftAccountType.Unknown
-						},
-						scope: session.scope,
-						sessionId: session.id
-					});
-				} else {
-					vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.'));
-					this._logger.error(e);
-					await this.removeSessionByIToken({
-						accessToken: undefined,
-						refreshToken: session.refreshToken,
-						account: {
-							...session.account,
-							type: MicrosoftAccountType.Unknown
-						},
-						scope: session.scope,
-						sessionId: session.id
-					});
-				}
-			}
-		});
-
-		const result = await Promise.allSettled(refreshes);
-		for (const res of result) {
-			if (res.status === 'rejected') {
-				this._logger.error(`Failed to initialize stored data: ${res.reason}`);
-				this.clearSessions();
-				break;
-			}
-		}
-
-		for (const token of this._tokens) {
-			/* __GDPR__
-				"login" : {
-					"owner": "TylerLeonhardt",
-					"comment": "Used to determine the usage of the Microsoft Auth Provider.",
-					"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." },
-					"accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." }
-				}
-			*/
-			this._telemetryReporter.sendTelemetryEvent('account', {
-				// Get rid of guids from telemetry.
-				scopes: JSON.stringify(token.scope.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}').split(' ')),
-				accountType: token.account.type
-			});
-		}
-	}
-
-	//#region session operations
-
-	public get onDidChangeSessions(): vscode.Event {
-		return this._sessionChangeEmitter.event;
-	}
-
-	public getSessions(scopes?: string[], account?: vscode.AuthenticationSessionAccountInformation): Promise {
-		if (!scopes) {
-			this._logger.info('Getting sessions for all scopes...');
-			const sessions = this._tokens
-				.filter(token => !account?.label || token.account.label === account.label)
-				.map(token => this.convertToSessionSync(token));
-			this._logger.info(`Got ${sessions.length} sessions for all scopes${account ? ` for account '${account.label}'` : ''}...`);
-			return Promise.resolve(sessions);
-		}
-
-		let modifiedScopes = [...scopes];
-		if (!modifiedScopes.includes('openid')) {
-			modifiedScopes.push('openid');
-		}
-		if (!modifiedScopes.includes('email')) {
-			modifiedScopes.push('email');
-		}
-		if (!modifiedScopes.includes('profile')) {
-			modifiedScopes.push('profile');
-		}
-		if (!modifiedScopes.includes('offline_access')) {
-			modifiedScopes.push('offline_access');
-		}
-		modifiedScopes = modifiedScopes.sort();
-
-		const modifiedScopesStr = modifiedScopes.join(' ');
-		const clientId = this.getClientId(scopes);
-		const scopeData: IScopeData = {
-			clientId,
-			originalScopes: scopes,
-			scopes: modifiedScopes,
-			scopeStr: modifiedScopesStr,
-			// filter our special scopes
-			scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
-			tenant: this.getTenantId(scopes),
-		};
-
-		this._logger.trace(`[${scopeData.scopeStr}] Queued getting sessions` + account ? ` for ${account?.label}` : '');
-		return this._sequencer.queue(modifiedScopesStr, () => this.doGetSessions(scopeData, account));
-	}
-
-	private async doGetSessions(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise {
-		this._logger.info(`[${scopeData.scopeStr}] Getting sessions` + account ? ` for ${account?.label}` : '');
-
-		const matchingTokens = this._tokens
-			.filter(token => token.scope === scopeData.scopeStr)
-			.filter(token => !account?.label || token.account.label === account.label);
-		// If we still don't have a matching token try to get a new token from an existing token by using
-		// the refreshToken. This is documented here:
-		// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token
-		// "Refresh tokens are valid for all permissions that your client has already received consent for."
-		if (!matchingTokens.length) {
-			// Get a token with the correct client id and account.
-			let token: IToken | undefined;
-			for (const t of this._tokens) {
-				// No refresh token, so we can't make a new token from this session
-				if (!t.refreshToken) {
-					continue;
-				}
-				// Need to make sure the account matches if we were provided one
-				if (account?.label && t.account.label !== account.label) {
-					continue;
-				}
-				// If the client id is the default client id, then check for the absence of the VSCODE_CLIENT_ID scope
-				if (scopeData.clientId === DEFAULT_CLIENT_ID && !t.scope.includes('VSCODE_CLIENT_ID')) {
-					token = t;
-					break;
-				}
-				// If the client id is not the default client id, then check for the matching VSCODE_CLIENT_ID scope
-				if (scopeData.clientId !== DEFAULT_CLIENT_ID && t.scope.includes(`VSCODE_CLIENT_ID:${scopeData.clientId}`)) {
-					token = t;
-					break;
-				}
-			}
-
-			if (token) {
-				this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Found a matching token with a different scopes '${token.scope}'. Attempting to get a new session using the existing session.`);
-				try {
-					const itoken = await this.doRefreshToken(token.refreshToken, scopeData);
-					this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(itoken)], removed: [], changed: [] });
-					matchingTokens.push(itoken);
-				} catch (err) {
-					this._logger.error(`[${scopeData.scopeStr}] Attempted to get a new session using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`);
-				}
-			}
-		}
-
-		this._logger.info(`[${scopeData.scopeStr}] Got ${matchingTokens.length} sessions`);
-		const results = await Promise.allSettled(matchingTokens.map(token => this.convertToSession(token, scopeData)));
-		return results
-			.filter(result => result.status === 'fulfilled')
-			.map(result => (result as PromiseFulfilledResult).value);
-	}
-
-	public createSession(scopes: string[], account?: vscode.AuthenticationSessionAccountInformation): Promise {
-		let modifiedScopes = [...scopes];
-		if (!modifiedScopes.includes('openid')) {
-			modifiedScopes.push('openid');
-		}
-		if (!modifiedScopes.includes('email')) {
-			modifiedScopes.push('email');
-		}
-		if (!modifiedScopes.includes('profile')) {
-			modifiedScopes.push('profile');
-		}
-		if (!modifiedScopes.includes('offline_access')) {
-			modifiedScopes.push('offline_access');
-		}
-		modifiedScopes = modifiedScopes.sort();
-		const scopeData: IScopeData = {
-			originalScopes: scopes,
-			scopes: modifiedScopes,
-			scopeStr: modifiedScopes.join(' '),
-			// filter our special scopes
-			scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
-			clientId: this.getClientId(scopes),
-			tenant: this.getTenantId(scopes),
-		};
-
-		this._logger.trace(`[${scopeData.scopeStr}] Queued creating session`);
-		return this._sequencer.queue(scopeData.scopeStr, () => this.doCreateSession(scopeData, account));
-	}
-
-	private async doCreateSession(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise {
-		this._logger.info(`[${scopeData.scopeStr}] Creating session` + account ? ` for ${account?.label}` : '');
-
-		const runsRemote = vscode.env.remoteName !== undefined;
-		const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web;
-
-		if (runsServerless && this._env.activeDirectoryEndpointUrl !== defaultActiveDirectoryEndpointUrl) {
-			throw new Error('Sign in to non-public clouds is not supported on the web.');
-		}
-
-		return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Signing in to your account...'), cancellable: true }, async (_progress, token) => {
-			if (runsRemote || runsServerless) {
-				return await this.createSessionWithoutLocalServer(scopeData, account?.label, token);
-			}
-
-			try {
-				return await this.createSessionWithLocalServer(scopeData, account?.label, token);
-			} catch (e) {
-				this._logger.error(`[${scopeData.scopeStr}] Error creating session: ${e}`);
-
-				// If the error was about starting the server, try directly hitting the login endpoint instead
-				if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') {
-					return this.createSessionWithoutLocalServer(scopeData, account?.label, token);
-				}
-
-				throw e;
-			}
-		});
-	}
-
-	private async createSessionWithLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise {
-		this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with local server`);
-		const codeVerifier = generateCodeVerifier();
-		const codeChallenge = await generateCodeChallenge(codeVerifier);
-		const qs = new URLSearchParams({
-			response_type: 'code',
-			response_mode: 'query',
-			client_id: scopeData.clientId,
-			redirect_uri: redirectUrl,
-			scope: scopeData.scopesToSend,
-			code_challenge_method: 'S256',
-			code_challenge: codeChallenge,
-		});
-		if (loginHint) {
-			qs.set('login_hint', loginHint);
-		} else {
-			qs.set('prompt', 'select_account');
-		}
-		const loginUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize?${qs.toString()}`, this._env.activeDirectoryEndpointUrl).toString();
-		const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl);
-		await server.start();
-
-		let codeToExchange;
-		try {
-			vscode.env.openExternal(vscode.Uri.parse(`http://127.0.0.1:${server.port}/signin?nonce=${encodeURIComponent(server.nonce)}`));
-			const { code } = await raceCancellationAndTimeoutError(server.waitForOAuthResponse(), token, 1000 * 60 * 5); // 5 minutes
-			codeToExchange = code;
-		} finally {
-			setTimeout(() => {
-				void server.stop();
-			}, 5000);
-		}
-
-		const session = await this.exchangeCodeForSession(codeToExchange, codeVerifier, scopeData);
-		this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Sending change event for added session`);
-		this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
-		this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`);
-		return session;
-	}
-
-	private async createSessionWithoutLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise {
-		this._logger.trace(`[${scopeData.scopeStr}] Starting login flow without local server`);
-		let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`));
-		const nonce = generateCodeVerifier();
-		const callbackQuery = new URLSearchParams(callbackUri.query);
-		callbackQuery.set('nonce', encodeURIComponent(nonce));
-		callbackUri = callbackUri.with({
-			query: callbackQuery.toString()
-		});
-		const state = encodeURIComponent(callbackUri.toString(true));
-		const codeVerifier = generateCodeVerifier();
-		const codeChallenge = await generateCodeChallenge(codeVerifier);
-		const signInUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize`, this._env.activeDirectoryEndpointUrl);
-		const qs = new URLSearchParams({
-			response_type: 'code',
-			client_id: encodeURIComponent(scopeData.clientId),
-			response_mode: 'query',
-			redirect_uri: redirectUrl,
-			state,
-			scope: scopeData.scopesToSend,
-			code_challenge_method: 'S256',
-			code_challenge: codeChallenge,
-		});
-		if (loginHint) {
-			qs.append('login_hint', loginHint);
-		} else {
-			qs.append('prompt', 'select_account');
-		}
-		signInUrl.search = qs.toString();
-		const uri = vscode.Uri.parse(signInUrl.toString());
-		vscode.env.openExternal(uri);
-
-
-		const existingNonces = this._pendingNonces.get(scopeData.scopeStr) || [];
-		this._pendingNonces.set(scopeData.scopeStr, [...existingNonces, nonce]);
-
-		// Register a single listener for the URI callback, in case the user starts the login process multiple times
-		// before completing it.
-		let existingPromise = this._codeExchangePromises.get(scopeData.scopeStr);
-		let inputBox: vscode.InputBox | undefined;
-		if (!existingPromise) {
-			if (isSupportedEnvironment(callbackUri)) {
-				existingPromise = this.handleCodeResponse(scopeData);
-			} else {
-				inputBox = vscode.window.createInputBox();
-				existingPromise = this.handleCodeInputBox(inputBox, codeVerifier, scopeData);
-			}
-			this._codeExchangePromises.set(scopeData.scopeStr, existingPromise);
-		}
-
-		this._codeVerfifiers.set(nonce, codeVerifier);
-
-		return await raceCancellationAndTimeoutError(existingPromise, token, 1000 * 60 * 5) // 5 minutes
-			.finally(() => {
-				this._pendingNonces.delete(scopeData.scopeStr);
-				this._codeExchangePromises.delete(scopeData.scopeStr);
-				this._codeVerfifiers.delete(nonce);
-				inputBox?.dispose();
-			});
-	}
-
-	public async removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise {
-		const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId);
-		if (tokenIndex === -1) {
-			this._logger.warn(`'${sessionId}' Session not found to remove`);
-			return Promise.resolve(undefined);
-		}
-
-		const token = this._tokens.splice(tokenIndex, 1)[0];
-		this._logger.trace(`[${token.scope}] '${sessionId}' Queued removing session`);
-		return this._sequencer.queue(token.scope, () => this.removeSessionByIToken(token, writeToDisk));
-	}
-
-	public async clearSessions() {
-		this._logger.trace('Logging out of all sessions');
-		this._tokens = [];
-		await this._tokenStorage.deleteAll(item => this.sessionMatchesEndpoint(item));
-
-		this._refreshTimeouts.forEach(timeout => {
-			clearTimeout(timeout);
-		});
-
-		this._refreshTimeouts.clear();
-		this._logger.trace('All sessions logged out');
-	}
-
-	private async removeSessionByIToken(token: IToken, writeToDisk: boolean = true): Promise {
-		this._logger.info(`[${token.scope}] '${token.sessionId}' Logging out of session`);
-		this.removeSessionTimeout(token.sessionId);
-
-		if (writeToDisk) {
-			await this._tokenStorage.delete(token.sessionId);
-		}
-
-		const tokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId);
-		if (tokenIndex !== -1) {
-			this._tokens.splice(tokenIndex, 1);
-		}
-
-		const session = this.convertToSessionSync(token);
-		this._logger.trace(`[${token.scope}] '${token.sessionId}' Sending change event for session that was removed`);
-		this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] });
-		this._logger.info(`[${token.scope}] '${token.sessionId}' Logged out of session successfully!`);
-		return session;
-	}
-
-	//#endregion
-
-	//#region timeout
-
-	private setSessionTimeout(sessionId: string, refreshToken: string, scopeData: IScopeData, timeout: number) {
-		this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Setting refresh timeout for ${timeout} milliseconds`);
-		this.removeSessionTimeout(sessionId);
-		this._refreshTimeouts.set(sessionId, setTimeout(async () => {
-			try {
-				const refreshedToken = await this.refreshToken(refreshToken, scopeData, sessionId);
-				this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Sending change event for session that was refreshed`);
-				this._sessionChangeEmitter.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
-				this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' refresh timeout complete`);
-			} catch (e) {
-				if (e.message !== REFRESH_NETWORK_FAILURE) {
-					vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.'));
-					await this.removeSessionById(sessionId);
-				}
-			}
-		}, timeout));
-	}
-
-	private removeSessionTimeout(sessionId: string): void {
-		const timeout = this._refreshTimeouts.get(sessionId);
-		if (timeout) {
-			clearTimeout(timeout);
-			this._refreshTimeouts.delete(sessionId);
-		}
-	}
-
-	//#endregion
-
-	//#region convert operations
-
-	private convertToTokenSync(json: ITokenResponse, scopeData: IScopeData, existingId?: string): IToken {
-		let claims = undefined;
-		this._logger.trace(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse token response.`);
-
-		try {
-			if (json.id_token) {
-				claims = JSON.parse(base64Decode(json.id_token.split('.')[1]));
-			} else {
-				this._logger.warn(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse access_token instead since no id_token was included in the response.`);
-				claims = JSON.parse(base64Decode(json.access_token.split('.')[1]));
-			}
-		} catch (e) {
-			throw e;
-		}
-
-		const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd ?? ''))}`;
-		const sessionId = existingId || `${id}/${randomUUID()}`;
-		this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Token response parsed successfully.`);
-		return {
-			expiresIn: json.expires_in,
-			expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined,
-			accessToken: json.access_token,
-			idToken: json.id_token,
-			refreshToken: json.refresh_token,
-			scope: scopeData.scopeStr,
-			sessionId,
-			account: {
-				label: claims.preferred_username ?? claims.email ?? claims.unique_name ?? 'user@example.com',
-				id,
-				type: claims.tid === MSA_TID || claims.tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD
-			}
-		};
-	}
-
-	/**
-	 * Return a session object without checking for expiry and potentially refreshing.
-	 * @param token The token information.
-	 */
-	private convertToSessionSync(token: IToken): vscode.AuthenticationSession {
-		return {
-			id: token.sessionId,
-			accessToken: token.accessToken!,
-			idToken: token.idToken,
-			account: token.account,
-			scopes: token.scope.split(' ')
-		};
-	}
-
-	private async convertToSession(token: IToken, scopeData: IScopeData): Promise {
-		if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) {
-			this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token available from cache${token.expiresAt ? `, expires in ${token.expiresAt - Date.now()} milliseconds` : ''}.`);
-			return {
-				id: token.sessionId,
-				accessToken: token.accessToken,
-				idToken: token.idToken,
-				account: token.account,
-				scopes: scopeData.originalScopes ?? scopeData.scopes
-			};
-		}
-
-		try {
-			this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token expired or unavailable, trying refresh`);
-			const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId);
-			if (refreshedToken.accessToken) {
-				return {
-					id: token.sessionId,
-					accessToken: refreshedToken.accessToken,
-					idToken: refreshedToken.idToken,
-					account: token.account,
-					// We always prefer the original scopes requested since that array is used as a key in the AuthService
-					scopes: scopeData.originalScopes ?? scopeData.scopes
-				};
-			} else {
-				throw new Error();
-			}
-		} catch (e) {
-			throw new Error('Unavailable due to network problems');
-		}
-	}
-
-	//#endregion
-
-	//#region refresh logic
-
-	private refreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise {
-		this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Queued refreshing token`);
-		return this._sequencer.queue(scopeData.scopeStr, () => this.doRefreshToken(refreshToken, scopeData, sessionId));
-	}
-
-	private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise {
-		this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token`);
-		const postData = new URLSearchParams({
-			refresh_token: refreshToken,
-			client_id: scopeData.clientId,
-			grant_type: 'refresh_token',
-			scope: scopeData.scopesToSend
-		}).toString();
-
-		try {
-			const json = await this.fetchTokenResponse(postData, scopeData);
-			const token = this.convertToTokenSync(json, scopeData, sessionId);
-			if (token.expiresIn) {
-				this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER);
-			}
-			this.setToken(token, scopeData);
-			this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token refresh success`);
-			return token;
-		} catch (e) {
-			if (e.message === REFRESH_NETWORK_FAILURE) {
-				// We were unable to refresh because of a network failure (i.e. the user lost internet access).
-				// so set up a timeout to try again later. We only do this if we have a session id to reference later.
-				if (sessionId) {
-					this.setSessionTimeout(sessionId, refreshToken, scopeData, AzureActiveDirectoryService.POLLING_CONSTANT);
-				}
-				throw e;
-			}
-			this._logger.error(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token failed: ${e.message}`);
-			throw e;
-		}
-	}
-
-	//#endregion
-
-	//#region scope parsers
-
-	private getClientId(scopes: string[]) {
-		return scopes.reduce((prev, current) => {
-			if (current.startsWith('VSCODE_CLIENT_ID:')) {
-				return current.split('VSCODE_CLIENT_ID:')[1];
-			}
-			return prev;
-		}, undefined) ?? DEFAULT_CLIENT_ID;
-	}
-
-	private getTenantId(scopes: string[]) {
-		return scopes.reduce((prev, current) => {
-			if (current.startsWith('VSCODE_TENANT:')) {
-				return current.split('VSCODE_TENANT:')[1];
-			}
-			return prev;
-		}, undefined) ?? DEFAULT_TENANT;
-	}
-
-	//#endregion
-
-	//#region oauth flow
-
-	private async handleCodeResponse(scopeData: IScopeData): Promise {
-		let uriEventListener: vscode.Disposable;
-		return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => {
-			uriEventListener = this._uriHandler.event(async (uri: vscode.Uri) => {
-				try {
-					const query = new URLSearchParams(uri.query);
-					let code = query.get('code');
-					let nonce = query.get('nonce');
-					if (Array.isArray(code)) {
-						code = code[0];
-					}
-					if (!code) {
-						throw new Error('No code included in query');
-					}
-					if (Array.isArray(nonce)) {
-						nonce = nonce[0];
-					}
-					if (!nonce) {
-						throw new Error('No nonce included in query');
-					}
-
-					const acceptedStates = this._pendingNonces.get(scopeData.scopeStr) || [];
-					// Workaround double encoding issues of state in web
-					if (!acceptedStates.includes(nonce) && !acceptedStates.includes(decodeURIComponent(nonce))) {
-						throw new Error('Nonce does not match.');
-					}
-
-					const verifier = this._codeVerfifiers.get(nonce) ?? this._codeVerfifiers.get(decodeURIComponent(nonce));
-					if (!verifier) {
-						throw new Error('No available code verifier');
-					}
-
-					const session = await this.exchangeCodeForSession(code, verifier, scopeData);
-					this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
-					this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`);
-					resolve(session);
-				} catch (err) {
-					reject(err);
-				}
-			});
-		}).then(result => {
-			uriEventListener.dispose();
-			return result;
-		}).catch(err => {
-			uriEventListener.dispose();
-			throw err;
-		});
-	}
-
-	private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise {
-		this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with input box`);
-		inputBox.ignoreFocusOut = true;
-		inputBox.title = vscode.l10n.t('Microsoft Authentication');
-		inputBox.prompt = vscode.l10n.t('Provide the authorization code to complete the sign in flow.');
-		inputBox.placeholder = vscode.l10n.t('Paste authorization code here...');
-		return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => {
-			inputBox.show();
-			inputBox.onDidAccept(async () => {
-				const code = inputBox.value;
-				if (code) {
-					inputBox.dispose();
-					const session = await this.exchangeCodeForSession(code, verifier, scopeData);
-					this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' sending session changed event because session was added.`);
-					this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
-					this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`);
-					resolve(session);
-				}
-			});
-			inputBox.onDidHide(() => {
-				if (!inputBox.value) {
-					inputBox.dispose();
-					reject('Cancelled');
-				}
-			});
-		});
-	}
-
-	private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise {
-		this._logger.trace(`[${scopeData.scopeStr}] Exchanging login code for session`);
-		let token: IToken | undefined;
-		try {
-			const postData = new URLSearchParams({
-				grant_type: 'authorization_code',
-				code: code,
-				client_id: scopeData.clientId,
-				scope: scopeData.scopesToSend,
-				code_verifier: codeVerifier,
-				redirect_uri: redirectUrl
-			}).toString();
-
-			const json = await this.fetchTokenResponse(postData, scopeData);
-			this._logger.trace(`[${scopeData.scopeStr}] Exchanging code for token succeeded!`);
-			token = this.convertToTokenSync(json, scopeData);
-		} catch (e) {
-			this._logger.error(`[${scopeData.scopeStr}] Error exchanging code for token: ${e}`);
-			throw e;
-		}
-
-		if (token.expiresIn) {
-			this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER);
-		}
-		this.setToken(token, scopeData);
-		this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Exchanging login code for session succeeded!`);
-		return await this.convertToSession(token, scopeData);
-	}
-
-	private async fetchTokenResponse(postData: string, scopeData: IScopeData): Promise {
-		let endpointUrl: string;
-		if (this._env.activeDirectoryEndpointUrl !== defaultActiveDirectoryEndpointUrl) {
-			// If this is for sovereign clouds, don't try using the proxy endpoint, which supports only public cloud
-			endpointUrl = this._env.activeDirectoryEndpointUrl;
-		} else {
-			const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
-			endpointUrl = proxyEndpoints?.microsoft || this._env.activeDirectoryEndpointUrl;
-		}
-		const endpoint = new URL(`${scopeData.tenant}/oauth2/v2.0/token`, endpointUrl);
-
-		let attempts = 0;
-		while (attempts <= 3) {
-			attempts++;
-			let result;
-			let errorMessage: string | undefined;
-			try {
-				result = await fetch(endpoint.toString(), {
-					method: 'POST',
-					headers: {
-						'Content-Type': 'application/x-www-form-urlencoded'
-					},
-					body: postData
-				});
-			} catch (e) {
-				errorMessage = e.message ?? e;
-			}
-
-			if (!result || result.status > 499) {
-				if (attempts > 3) {
-					this._logger.error(`[${scopeData.scopeStr}] Fetching token failed: ${result ? await result.text() : errorMessage}`);
-					break;
-				}
-				// Exponential backoff
-				await new Promise(resolve => setTimeout(resolve, 5 * attempts * attempts * 1000));
-				continue;
-			} else if (!result.ok) {
-				// For 4XX errors, the user may actually have an expired token or have changed
-				// their password recently which is throwing a 4XX. For this, we throw an error
-				// so that the user can be prompted to sign in again.
-				throw new Error(await result.text());
-			}
-
-			return await result.json() as ITokenResponse;
-		}
-
-		throw new Error(REFRESH_NETWORK_FAILURE);
-	}
-
-	//#endregion
-
-	//#region storage operations
-
-	private setToken(token: IToken, scopeData: IScopeData): void {
-		this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Setting token`);
-
-		const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId);
-		if (existingTokenIndex > -1) {
-			this._tokens.splice(existingTokenIndex, 1, token);
-		} else {
-			this._tokens.push(token);
-		}
-
-		// Don't await because setting the token is only useful for any new windows that open.
-		void this.storeToken(token, scopeData);
-	}
-
-	private async storeToken(token: IToken, scopeData: IScopeData): Promise {
-		if (!vscode.window.state.focused) {
-			if (this._pendingTokensToStore.has(token.sessionId)) {
-				this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, replacing token to be stored`);
-			} else {
-				this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, pending storage of token`);
-			}
-			this._pendingTokensToStore.set(token.sessionId, token);
-			return;
-		}
-
-		await this._tokenStorage.store(token.sessionId, {
-			id: token.sessionId,
-			refreshToken: token.refreshToken,
-			scope: token.scope,
-			account: token.account,
-			endpoint: this._env.activeDirectoryEndpointUrl,
-		});
-		this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Stored token`);
-	}
-
-	private async storePendingTokens(): Promise {
-		if (this._pendingTokensToStore.size === 0) {
-			this._logger.trace('No pending tokens to store');
-			return;
-		}
-
-		const tokens = [...this._pendingTokensToStore.values()];
-		this._pendingTokensToStore.clear();
-
-		this._logger.trace(`Storing ${tokens.length} pending tokens...`);
-		await Promise.allSettled(tokens.map(async token => {
-			this._logger.trace(`[${token.scope}] '${token.sessionId}' Storing pending token`);
-			await this._tokenStorage.store(token.sessionId, {
-				id: token.sessionId,
-				refreshToken: token.refreshToken,
-				scope: token.scope,
-				account: token.account,
-				endpoint: this._env.activeDirectoryEndpointUrl,
-			});
-			this._logger.trace(`[${token.scope}] '${token.sessionId}' Stored pending token`);
-		}));
-		this._logger.trace('Done storing pending tokens');
-	}
-
-	private async checkForUpdates(e: IDidChangeInOtherWindowEvent): Promise {
-		for (const key of e.added) {
-			const session = await this._tokenStorage.get(key);
-			if (!session) {
-				this._logger.error('session not found that was apparently just added');
-				continue;
-			}
-
-			if (!this.sessionMatchesEndpoint(session)) {
-				// If the session wasn't made for this login endpoint, ignore this update
-				continue;
-			}
-
-			const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id);
-			if (!matchesExisting && session.refreshToken) {
-				try {
-					const scopes = session.scope.split(' ');
-					const scopeData: IScopeData = {
-						scopes,
-						scopeStr: session.scope,
-						// filter our special scopes
-						scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
-						clientId: this.getClientId(scopes),
-						tenant: this.getTenantId(scopes),
-					};
-					this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Session added in another window`);
-					const token = await this.refreshToken(session.refreshToken, scopeData, session.id);
-					this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Sending change event for session that was added`);
-					this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] });
-					this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Session added in another window added here`);
-					continue;
-				} catch (e) {
-					// Network failures will automatically retry on next poll.
-					if (e.message !== REFRESH_NETWORK_FAILURE) {
-						vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.'));
-						await this.removeSessionById(session.id);
-					}
-					continue;
-				}
-			}
-		}
-
-		for (const { value } of e.removed) {
-			this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window`);
-			if (!this.sessionMatchesEndpoint(value)) {
-				// If the session wasn't made for this login endpoint, ignore this update
-				this._logger.trace(`[${value.scope}] '${value.id}' Session doesn't match endpoint. Skipping...`);
-				continue;
-			}
-
-			await this.removeSessionById(value.id, false);
-			this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window removed here`);
-		}
-
-		// NOTE: We don't need to handle changed sessions because all that really would give us is a new refresh token
-		// because access tokens are not stored in Secret Storage due to their short lifespan. This new refresh token
-		// is not useful in this window because we really only care about the lifetime of the _access_ token which we
-		// are already managing (see usages of `setSessionTimeout`).
-		// However, in order to minimize the amount of times we store tokens, if a token was stored via another window,
-		// we cancel any pending token storage operations.
-		for (const sessionId of e.updated) {
-			if (this._pendingTokensToStore.delete(sessionId)) {
-				this._logger.trace(`'${sessionId}' Cancelled pending token storage because token was updated in another window`);
-			}
-		}
-	}
-
-	private sessionMatchesEndpoint(session: IStoredSession): boolean {
-		// For older sessions with no endpoint set, it can be assumed to be the default endpoint
-		session.endpoint ||= defaultActiveDirectoryEndpointUrl;
-
-		return session.endpoint === this._env.activeDirectoryEndpointUrl;
-	}
-
-	//#endregion
+    // For details on why this is set to 2/3... see https://github.com/microsoft/vscode/issues/133201#issuecomment-966668197
+    private static REFRESH_TIMEOUT_MODIFIER = 1000 * 2 / 3;
+    private static POLLING_CONSTANT = 1000 * 60 * 30;
+    private _tokens: IToken[] = [];
+    private _refreshTimeouts: Map = new Map();
+    private _sessionChangeEmitter: vscode.EventEmitter = new vscode.EventEmitter();
+    // Used to keep track of current requests when not using the local server approach.
+    private _pendingNonces = new Map();
+    private _codeExchangePromises = new Map>();
+    private _codeVerfifiers = new Map();
+    // Used to keep track of tokens that we need to store but can't because we aren't the focused window.
+    private _pendingTokensToStore: Map = new Map();
+    // Used to sequence requests to the same scope.
+    private _sequencer = new SequencerByKey();
+    constructor(private readonly _logger: vscode.LogOutputChannel, _context: vscode.ExtensionContext, private readonly _uriHandler: UriEventHandler, private readonly _tokenStorage: BetterTokenStorage, private readonly _telemetryReporter: TelemetryReporter, private readonly _env: Environment) {
+        _context.subscriptions.push(this._tokenStorage.onDidChangeInOtherWindow((e) => this.checkForUpdates(e)));
+        _context.subscriptions.push(vscode.window.onDidChangeWindowState(async (e) => e.focused && await this.storePendingTokens()));
+        // In the event that a window isn't focused for a long time, we should still try to store the tokens at some point.
+        const timer = new IntervalTimer();
+        timer.cancelAndSet(() => !vscode.window.state.focused && this.storePendingTokens(), 
+        // 5 hours + random extra 0-30 seconds so that each window doesn't try to store at the same time
+        (18000000) + Math.floor(Math.random() * 30000));
+        _context.subscriptions.push(timer);
+    }
+    public async initialize(): Promise {
+        this._logger.trace('Reading sessions from secret storage...');
+        const sessions = await this._tokenStorage.getAll(item => this.sessionMatchesEndpoint(item));
+        this._logger.trace(`Got ${sessions.length} stored sessions`);
+        const refreshes = sessions.map(async (session) => {
+            this._logger.trace(`[${session.scope}] '${session.id}' Read stored session`);
+            const scopes = session.scope.split(' ');
+            const scopeData: IScopeData = {
+                scopes,
+                scopeStr: session.scope,
+                // filter our special scopes
+                scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
+                clientId: this.getClientId(scopes),
+                tenant: this.getTenantId(scopes),
+            };
+            try {
+                await this.refreshToken(session.refreshToken, scopeData, session.id);
+            }
+            catch (e) {
+                // If we aren't connected to the internet, then wait and try to refresh again later.
+                if (e.message === REFRESH_NETWORK_FAILURE) {
+                    this._tokens.push({
+                        accessToken: undefined,
+                        refreshToken: session.refreshToken,
+                        account: {
+                            ...session.account,
+                            type: MicrosoftAccountType.Unknown
+                        },
+                        scope: session.scope,
+                        sessionId: session.id
+                    });
+                }
+                else {
+                    vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.'));
+                    this._logger.error(e);
+                    await this.removeSessionByIToken({
+                        accessToken: undefined,
+                        refreshToken: session.refreshToken,
+                        account: {
+                            ...session.account,
+                            type: MicrosoftAccountType.Unknown
+                        },
+                        scope: session.scope,
+                        sessionId: session.id
+                    });
+                }
+            }
+        });
+        const result = await Promise.allSettled(refreshes);
+        for (const res of result) {
+            if (res.status === 'rejected') {
+                this._logger.error(`Failed to initialize stored data: ${res.reason}`);
+                this.clearSessions();
+                break;
+            }
+        }
+        for (const token of this._tokens) {
+            /* __GDPR__
+                "login" : {
+                    "owner": "TylerLeonhardt",
+                    "comment": "Used to determine the usage of the Microsoft Auth Provider.",
+                    "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." },
+                    "accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." }
+                }
+            */
+            this._telemetryReporter.sendTelemetryEvent('account', {
+                // Get rid of guids from telemetry.
+                scopes: JSON.stringify(token.scope.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}').split(' ')),
+                accountType: token.account.type
+            });
+        }
+    }
+    //#region session operations
+    public get onDidChangeSessions(): vscode.Event {
+        return this._sessionChangeEmitter.event;
+    }
+    public getSessions(scopes?: string[], account?: vscode.AuthenticationSessionAccountInformation): Promise {
+        if (!scopes) {
+            this._logger.info('Getting sessions for all scopes...');
+            const sessions = this._tokens
+                .filter(token => !account?.label || token.account.label === account.label)
+                .map(token => this.convertToSessionSync(token));
+            this._logger.info(`Got ${sessions.length} sessions for all scopes${account ? ` for account '${account.label}'` : ''}...`);
+            return Promise.resolve(sessions);
+        }
+        let modifiedScopes = [...scopes];
+        if (!modifiedScopes.includes('openid')) {
+            modifiedScopes.push('openid');
+        }
+        if (!modifiedScopes.includes('email')) {
+            modifiedScopes.push('email');
+        }
+        if (!modifiedScopes.includes('profile')) {
+            modifiedScopes.push('profile');
+        }
+        if (!modifiedScopes.includes('offline_access')) {
+            modifiedScopes.push('offline_access');
+        }
+        modifiedScopes = modifiedScopes.sort();
+        const modifiedScopesStr = modifiedScopes.join(' ');
+        const clientId = this.getClientId(scopes);
+        const scopeData: IScopeData = {
+            clientId,
+            originalScopes: scopes,
+            scopes: modifiedScopes,
+            scopeStr: modifiedScopesStr,
+            // filter our special scopes
+            scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
+            tenant: this.getTenantId(scopes),
+        };
+        this._logger.trace(`[${scopeData.scopeStr}] Queued getting sessions` + account ? ` for ${account?.label}` : '');
+        return this._sequencer.queue(modifiedScopesStr, () => this.doGetSessions(scopeData, account));
+    }
+    private async doGetSessions(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise {
+        this._logger.info(`[${scopeData.scopeStr}] Getting sessions` + account ? ` for ${account?.label}` : '');
+        const matchingTokens = this._tokens
+            .filter(token => token.scope === scopeData.scopeStr)
+            .filter(token => !account?.label || token.account.label === account.label);
+        // If we still don't have a matching token try to get a new token from an existing token by using
+        // the refreshToken. This is documented here:
+        // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token
+        // "Refresh tokens are valid for all permissions that your client has already received consent for."
+        if (!matchingTokens.length) {
+            // Get a token with the correct client id and account.
+            let token: IToken | undefined;
+            for (const t of this._tokens) {
+                // No refresh token, so we can't make a new token from this session
+                if (!t.refreshToken) {
+                    continue;
+                }
+                // Need to make sure the account matches if we were provided one
+                if (account?.label && t.account.label !== account.label) {
+                    continue;
+                }
+                // If the client id is the default client id, then check for the absence of the VSCODE_CLIENT_ID scope
+                if (scopeData.clientId === DEFAULT_CLIENT_ID && !t.scope.includes('VSCODE_CLIENT_ID')) {
+                    token = t;
+                    break;
+                }
+                // If the client id is not the default client id, then check for the matching VSCODE_CLIENT_ID scope
+                if (scopeData.clientId !== DEFAULT_CLIENT_ID && t.scope.includes(`VSCODE_CLIENT_ID:${scopeData.clientId}`)) {
+                    token = t;
+                    break;
+                }
+            }
+            if (token) {
+                this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Found a matching token with a different scopes '${token.scope}'. Attempting to get a new session using the existing session.`);
+                try {
+                    const itoken = await this.doRefreshToken(token.refreshToken, scopeData);
+                    this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(itoken)], removed: [], changed: [] });
+                    matchingTokens.push(itoken);
+                }
+                catch (err) {
+                    this._logger.error(`[${scopeData.scopeStr}] Attempted to get a new session using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`);
+                }
+            }
+        }
+        this._logger.info(`[${scopeData.scopeStr}] Got ${matchingTokens.length} sessions`);
+        const results = await Promise.allSettled(matchingTokens.map(token => this.convertToSession(token, scopeData)));
+        return results
+            .filter(result => result.status === 'fulfilled')
+            .map(result => (result as PromiseFulfilledResult).value);
+    }
+    public createSession(scopes: string[], account?: vscode.AuthenticationSessionAccountInformation): Promise {
+        let modifiedScopes = [...scopes];
+        if (!modifiedScopes.includes('openid')) {
+            modifiedScopes.push('openid');
+        }
+        if (!modifiedScopes.includes('email')) {
+            modifiedScopes.push('email');
+        }
+        if (!modifiedScopes.includes('profile')) {
+            modifiedScopes.push('profile');
+        }
+        if (!modifiedScopes.includes('offline_access')) {
+            modifiedScopes.push('offline_access');
+        }
+        modifiedScopes = modifiedScopes.sort();
+        const scopeData: IScopeData = {
+            originalScopes: scopes,
+            scopes: modifiedScopes,
+            scopeStr: modifiedScopes.join(' '),
+            // filter our special scopes
+            scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
+            clientId: this.getClientId(scopes),
+            tenant: this.getTenantId(scopes),
+        };
+        this._logger.trace(`[${scopeData.scopeStr}] Queued creating session`);
+        return this._sequencer.queue(scopeData.scopeStr, () => this.doCreateSession(scopeData, account));
+    }
+    private async doCreateSession(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise {
+        this._logger.info(`[${scopeData.scopeStr}] Creating session` + account ? ` for ${account?.label}` : '');
+        const runsRemote = vscode.env.remoteName !== undefined;
+        const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web;
+        if (runsServerless && this._env.activeDirectoryEndpointUrl !== defaultActiveDirectoryEndpointUrl) {
+            throw new Error('Sign in to non-public clouds is not supported on the web.');
+        }
+        return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Signing in to your account...'), cancellable: true }, async (_progress, token) => {
+            if (runsRemote || runsServerless) {
+                return await this.createSessionWithoutLocalServer(scopeData, account?.label, token);
+            }
+            try {
+                return await this.createSessionWithLocalServer(scopeData, account?.label, token);
+            }
+            catch (e) {
+                this._logger.error(`[${scopeData.scopeStr}] Error creating session: ${e}`);
+                // If the error was about starting the server, try directly hitting the login endpoint instead
+                if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') {
+                    return this.createSessionWithoutLocalServer(scopeData, account?.label, token);
+                }
+                throw e;
+            }
+        });
+    }
+    private async createSessionWithLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise {
+        this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with local server`);
+        const codeVerifier = generateCodeVerifier();
+        const codeChallenge = await generateCodeChallenge(codeVerifier);
+        const qs = new URLSearchParams({
+            response_type: 'code',
+            response_mode: 'query',
+            client_id: scopeData.clientId,
+            redirect_uri: redirectUrl,
+            scope: scopeData.scopesToSend,
+            code_challenge_method: 'S256',
+            code_challenge: codeChallenge,
+        });
+        if (loginHint) {
+            qs.set('login_hint', loginHint);
+        }
+        else {
+            qs.set('prompt', 'select_account');
+        }
+        const loginUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize?${qs.toString()}`, this._env.activeDirectoryEndpointUrl).toString();
+        const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl);
+        await server.start();
+        let codeToExchange;
+        try {
+            vscode.env.openExternal(vscode.Uri.parse(`http://127.0.0.1:${server.port}/signin?nonce=${encodeURIComponent(server.nonce)}`));
+            const { code } = await raceCancellationAndTimeoutError(server.waitForOAuthResponse(), token, 1000 * 60 * 5); // 5 minutes
+            codeToExchange = code;
+        }
+        finally {
+            setTimeout(() => {
+                void server.stop();
+            }, 5000);
+        }
+        const session = await this.exchangeCodeForSession(codeToExchange, codeVerifier, scopeData);
+        this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Sending change event for added session`);
+        this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
+        this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`);
+        return session;
+    }
+    private async createSessionWithoutLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise {
+        this._logger.trace(`[${scopeData.scopeStr}] Starting login flow without local server`);
+        let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`));
+        const nonce = generateCodeVerifier();
+        const callbackQuery = new URLSearchParams(callbackUri.query);
+        callbackQuery.set('nonce', encodeURIComponent(nonce));
+        callbackUri = callbackUri.with({
+            query: callbackQuery.toString()
+        });
+        const state = encodeURIComponent(callbackUri.toString(true));
+        const codeVerifier = generateCodeVerifier();
+        const codeChallenge = await generateCodeChallenge(codeVerifier);
+        const signInUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize`, this._env.activeDirectoryEndpointUrl);
+        const qs = new URLSearchParams({
+            response_type: 'code',
+            client_id: encodeURIComponent(scopeData.clientId),
+            response_mode: 'query',
+            redirect_uri: redirectUrl,
+            state,
+            scope: scopeData.scopesToSend,
+            code_challenge_method: 'S256',
+            code_challenge: codeChallenge,
+        });
+        if (loginHint) {
+            qs.append('login_hint', loginHint);
+        }
+        else {
+            qs.append('prompt', 'select_account');
+        }
+        signInUrl.search = qs.toString();
+        const uri = vscode.Uri.parse(signInUrl.toString());
+        vscode.env.openExternal(uri);
+        const existingNonces = this._pendingNonces.get(scopeData.scopeStr) || [];
+        this._pendingNonces.set(scopeData.scopeStr, [...existingNonces, nonce]);
+        // Register a single listener for the URI callback, in case the user starts the login process multiple times
+        // before completing it.
+        let existingPromise = this._codeExchangePromises.get(scopeData.scopeStr);
+        let inputBox: vscode.InputBox | undefined;
+        if (!existingPromise) {
+            if (isSupportedEnvironment(callbackUri)) {
+                existingPromise = this.handleCodeResponse(scopeData);
+            }
+            else {
+                inputBox = vscode.window.createInputBox();
+                existingPromise = this.handleCodeInputBox(inputBox, codeVerifier, scopeData);
+            }
+            this._codeExchangePromises.set(scopeData.scopeStr, existingPromise);
+        }
+        this._codeVerfifiers.set(nonce, codeVerifier);
+        return await raceCancellationAndTimeoutError(existingPromise, token, 1000 * 60 * 5) // 5 minutes
+            .finally(() => {
+            this._pendingNonces.delete(scopeData.scopeStr);
+            this._codeExchangePromises.delete(scopeData.scopeStr);
+            this._codeVerfifiers.delete(nonce);
+            inputBox?.dispose();
+        });
+    }
+    public async removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise {
+        const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId);
+        if (tokenIndex === -1) {
+            this._logger.warn(`'${sessionId}' Session not found to remove`);
+            return Promise.resolve(undefined);
+        }
+        const token = this._tokens.splice(tokenIndex, 1)[0];
+        this._logger.trace(`[${token.scope}] '${sessionId}' Queued removing session`);
+        return this._sequencer.queue(token.scope, () => this.removeSessionByIToken(token, writeToDisk));
+    }
+    public async clearSessions() {
+        this._logger.trace('Logging out of all sessions');
+        this._tokens = [];
+        await this._tokenStorage.deleteAll(item => this.sessionMatchesEndpoint(item));
+        this._refreshTimeouts.forEach(timeout => {
+            clearTimeout(timeout);
+        });
+        this._refreshTimeouts.clear();
+        this._logger.trace('All sessions logged out');
+    }
+    private async removeSessionByIToken(token: IToken, writeToDisk: boolean = true): Promise {
+        this._logger.info(`[${token.scope}] '${token.sessionId}' Logging out of session`);
+        this.removeSessionTimeout(token.sessionId);
+        if (writeToDisk) {
+            await this._tokenStorage.delete(token.sessionId);
+        }
+        const tokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId);
+        if (tokenIndex !== -1) {
+            this._tokens.splice(tokenIndex, 1);
+        }
+        const session = this.convertToSessionSync(token);
+        this._logger.trace(`[${token.scope}] '${token.sessionId}' Sending change event for session that was removed`);
+        this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] });
+        this._logger.info(`[${token.scope}] '${token.sessionId}' Logged out of session successfully!`);
+        return session;
+    }
+    //#endregion
+    //#region timeout
+    private setSessionTimeout(sessionId: string, refreshToken: string, scopeData: IScopeData, timeout: number) {
+        this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Setting refresh timeout for ${timeout} milliseconds`);
+        this.removeSessionTimeout(sessionId);
+        this._refreshTimeouts.set(sessionId, setTimeout(async () => {
+            try {
+                const refreshedToken = await this.refreshToken(refreshToken, scopeData, sessionId);
+                this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Sending change event for session that was refreshed`);
+                this._sessionChangeEmitter.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
+                this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' refresh timeout complete`);
+            }
+            catch (e) {
+                if (e.message !== REFRESH_NETWORK_FAILURE) {
+                    vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.'));
+                    await this.removeSessionById(sessionId);
+                }
+            }
+        }, timeout));
+    }
+    private removeSessionTimeout(sessionId: string): void {
+        const timeout = this._refreshTimeouts.get(sessionId);
+        if (timeout) {
+            clearTimeout(timeout);
+            this._refreshTimeouts.delete(sessionId);
+        }
+    }
+    //#endregion
+    //#region convert operations
+    private convertToTokenSync(json: ITokenResponse, scopeData: IScopeData, existingId?: string): IToken {
+        let claims = undefined;
+        this._logger.trace(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse token response.`);
+        try {
+            if (json.id_token) {
+                claims = JSON.parse(base64Decode(json.id_token.split('.')[1]));
+            }
+            else {
+                this._logger.warn(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse access_token instead since no id_token was included in the response.`);
+                claims = JSON.parse(base64Decode(json.access_token.split('.')[1]));
+            }
+        }
+        catch (e) {
+            throw e;
+        }
+        const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd ?? ''))}`;
+        const sessionId = existingId || `${id}/${randomUUID()}`;
+        this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Token response parsed successfully.`);
+        return {
+            expiresIn: json.expires_in,
+            expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined,
+            accessToken: json.access_token,
+            idToken: json.id_token,
+            refreshToken: json.refresh_token,
+            scope: scopeData.scopeStr,
+            sessionId,
+            account: {
+                label: claims.preferred_username ?? claims.email ?? claims.unique_name ?? 'user@example.com',
+                id,
+                type: claims.tid === MSA_TID || claims.tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD
+            }
+        };
+    }
+    /**
+     * Return a session object without checking for expiry and potentially refreshing.
+     * @param token The token information.
+     */
+    private convertToSessionSync(token: IToken): vscode.AuthenticationSession {
+        return {
+            id: token.sessionId,
+            accessToken: token.accessToken!,
+            idToken: token.idToken,
+            account: token.account,
+            scopes: token.scope.split(' ')
+        };
+    }
+    private async convertToSession(token: IToken, scopeData: IScopeData): Promise {
+        if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) {
+            this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token available from cache${token.expiresAt ? `, expires in ${token.expiresAt - Date.now()} milliseconds` : ''}.`);
+            return {
+                id: token.sessionId,
+                accessToken: token.accessToken,
+                idToken: token.idToken,
+                account: token.account,
+                scopes: scopeData.originalScopes ?? scopeData.scopes
+            };
+        }
+        try {
+            this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token expired or unavailable, trying refresh`);
+            const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId);
+            if (refreshedToken.accessToken) {
+                return {
+                    id: token.sessionId,
+                    accessToken: refreshedToken.accessToken,
+                    idToken: refreshedToken.idToken,
+                    account: token.account,
+                    // We always prefer the original scopes requested since that array is used as a key in the AuthService
+                    scopes: scopeData.originalScopes ?? scopeData.scopes
+                };
+            }
+            else {
+                throw new Error();
+            }
+        }
+        catch (e) {
+            throw new Error('Unavailable due to network problems');
+        }
+    }
+    //#endregion
+    //#region refresh logic
+    private refreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise {
+        this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Queued refreshing token`);
+        return this._sequencer.queue(scopeData.scopeStr, () => this.doRefreshToken(refreshToken, scopeData, sessionId));
+    }
+    private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise {
+        this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token`);
+        const postData = new URLSearchParams({
+            refresh_token: refreshToken,
+            client_id: scopeData.clientId,
+            grant_type: 'refresh_token',
+            scope: scopeData.scopesToSend
+        }).toString();
+        try {
+            const json = await this.fetchTokenResponse(postData, scopeData);
+            const token = this.convertToTokenSync(json, scopeData, sessionId);
+            if (token.expiresIn) {
+                this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER);
+            }
+            this.setToken(token, scopeData);
+            this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token refresh success`);
+            return token;
+        }
+        catch (e) {
+            if (e.message === REFRESH_NETWORK_FAILURE) {
+                // We were unable to refresh because of a network failure (i.e. the user lost internet access).
+                // so set up a timeout to try again later. We only do this if we have a session id to reference later.
+                if (sessionId) {
+                    this.setSessionTimeout(sessionId, refreshToken, scopeData, AzureActiveDirectoryService.POLLING_CONSTANT);
+                }
+                throw e;
+            }
+            this._logger.error(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token failed: ${e.message}`);
+            throw e;
+        }
+    }
+    //#endregion
+    //#region scope parsers
+    private getClientId(scopes: string[]) {
+        return scopes.reduce((prev, current) => {
+            if (current.startsWith('VSCODE_CLIENT_ID:')) {
+                return current.split('VSCODE_CLIENT_ID:')[1];
+            }
+            return prev;
+        }, undefined) ?? DEFAULT_CLIENT_ID;
+    }
+    private getTenantId(scopes: string[]) {
+        return scopes.reduce((prev, current) => {
+            if (current.startsWith('VSCODE_TENANT:')) {
+                return current.split('VSCODE_TENANT:')[1];
+            }
+            return prev;
+        }, undefined) ?? DEFAULT_TENANT;
+    }
+    //#endregion
+    //#region oauth flow
+    private async handleCodeResponse(scopeData: IScopeData): Promise {
+        let uriEventListener: vscode.Disposable;
+        return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => {
+            uriEventListener = this._uriHandler.event(async (uri: vscode.Uri) => {
+                try {
+                    const query = new URLSearchParams(uri.query);
+                    let code = query.get('code');
+                    let nonce = query.get('nonce');
+                    if (Array.isArray(code)) {
+                        code = code[0];
+                    }
+                    if (!code) {
+                        throw new Error('No code included in query');
+                    }
+                    if (Array.isArray(nonce)) {
+                        nonce = nonce[0];
+                    }
+                    if (!nonce) {
+                        throw new Error('No nonce included in query');
+                    }
+                    const acceptedStates = this._pendingNonces.get(scopeData.scopeStr) || [];
+                    // Workaround double encoding issues of state in web
+                    if (!acceptedStates.includes(nonce) && !acceptedStates.includes(decodeURIComponent(nonce))) {
+                        throw new Error('Nonce does not match.');
+                    }
+                    const verifier = this._codeVerfifiers.get(nonce) ?? this._codeVerfifiers.get(decodeURIComponent(nonce));
+                    if (!verifier) {
+                        throw new Error('No available code verifier');
+                    }
+                    const session = await this.exchangeCodeForSession(code, verifier, scopeData);
+                    this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
+                    this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`);
+                    resolve(session);
+                }
+                catch (err) {
+                    reject(err);
+                }
+            });
+        }).then(result => {
+            uriEventListener.dispose();
+            return result;
+        }).catch(err => {
+            uriEventListener.dispose();
+            throw err;
+        });
+    }
+    private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise {
+        this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with input box`);
+        inputBox.ignoreFocusOut = true;
+        inputBox.title = vscode.l10n.t('Microsoft Authentication');
+        inputBox.prompt = vscode.l10n.t('Provide the authorization code to complete the sign in flow.');
+        inputBox.placeholder = vscode.l10n.t('Paste authorization code here...');
+        return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => {
+            inputBox.show();
+            inputBox.onDidAccept(async () => {
+                const code = inputBox.value;
+                if (code) {
+                    inputBox.dispose();
+                    const session = await this.exchangeCodeForSession(code, verifier, scopeData);
+                    this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' sending session changed event because session was added.`);
+                    this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] });
+                    this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`);
+                    resolve(session);
+                }
+            });
+            inputBox.onDidHide(() => {
+                if (!inputBox.value) {
+                    inputBox.dispose();
+                    reject('Cancelled');
+                }
+            });
+        });
+    }
+    private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise {
+        this._logger.trace(`[${scopeData.scopeStr}] Exchanging login code for session`);
+        let token: IToken | undefined;
+        try {
+            const postData = new URLSearchParams({
+                grant_type: 'authorization_code',
+                code: code,
+                client_id: scopeData.clientId,
+                scope: scopeData.scopesToSend,
+                code_verifier: codeVerifier,
+                redirect_uri: redirectUrl
+            }).toString();
+            const json = await this.fetchTokenResponse(postData, scopeData);
+            this._logger.trace(`[${scopeData.scopeStr}] Exchanging code for token succeeded!`);
+            token = this.convertToTokenSync(json, scopeData);
+        }
+        catch (e) {
+            this._logger.error(`[${scopeData.scopeStr}] Error exchanging code for token: ${e}`);
+            throw e;
+        }
+        if (token.expiresIn) {
+            this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER);
+        }
+        this.setToken(token, scopeData);
+        this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Exchanging login code for session succeeded!`);
+        return await this.convertToSession(token, scopeData);
+    }
+    private async fetchTokenResponse(postData: string, scopeData: IScopeData): Promise {
+        let endpointUrl: string;
+        if (this._env.activeDirectoryEndpointUrl !== defaultActiveDirectoryEndpointUrl) {
+            // If this is for sovereign clouds, don't try using the proxy endpoint, which supports only public cloud
+            endpointUrl = this._env.activeDirectoryEndpointUrl;
+        }
+        else {
+            const proxyEndpoints: {
+                [providerId: string]: string;
+            } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
+            endpointUrl = proxyEndpoints?.microsoft || this._env.activeDirectoryEndpointUrl;
+        }
+        const endpoint = new URL(`${scopeData.tenant}/oauth2/v2.0/token`, endpointUrl);
+        let attempts = 0;
+        while (attempts <= 3) {
+            attempts++;
+            let result;
+            let errorMessage: string | undefined;
+            try {
+                result = await fetch(endpoint.toString(), {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/x-www-form-urlencoded'
+                    },
+                    body: postData
+                });
+            }
+            catch (e) {
+                errorMessage = e.message ?? e;
+            }
+            if (!result || result.status > 499) {
+                if (attempts > 3) {
+                    this._logger.error(`[${scopeData.scopeStr}] Fetching token failed: ${result ? await result.text() : errorMessage}`);
+                    break;
+                }
+                // Exponential backoff
+                await new Promise(resolve => setTimeout(resolve, 5 * attempts * attempts * 1000));
+                continue;
+            }
+            else if (!result.ok) {
+                // For 4XX errors, the user may actually have an expired token or have changed
+                // their password recently which is throwing a 4XX. For this, we throw an error
+                // so that the user can be prompted to sign in again.
+                throw new Error(await result.text());
+            }
+            return await result.json() as ITokenResponse;
+        }
+        throw new Error(REFRESH_NETWORK_FAILURE);
+    }
+    //#endregion
+    //#region storage operations
+    private setToken(token: IToken, scopeData: IScopeData): void {
+        this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Setting token`);
+        const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId);
+        if (existingTokenIndex > -1) {
+            this._tokens.splice(existingTokenIndex, 1, token);
+        }
+        else {
+            this._tokens.push(token);
+        }
+        // Don't await because setting the token is only useful for any new windows that open.
+        void this.storeToken(token, scopeData);
+    }
+    private async storeToken(token: IToken, scopeData: IScopeData): Promise {
+        if (!vscode.window.state.focused) {
+            if (this._pendingTokensToStore.has(token.sessionId)) {
+                this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, replacing token to be stored`);
+            }
+            else {
+                this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, pending storage of token`);
+            }
+            this._pendingTokensToStore.set(token.sessionId, token);
+            return;
+        }
+        await this._tokenStorage.store(token.sessionId, {
+            id: token.sessionId,
+            refreshToken: token.refreshToken,
+            scope: token.scope,
+            account: token.account,
+            endpoint: this._env.activeDirectoryEndpointUrl,
+        });
+        this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Stored token`);
+    }
+    private async storePendingTokens(): Promise {
+        if (this._pendingTokensToStore.size === 0) {
+            this._logger.trace('No pending tokens to store');
+            return;
+        }
+        const tokens = [...this._pendingTokensToStore.values()];
+        this._pendingTokensToStore.clear();
+        this._logger.trace(`Storing ${tokens.length} pending tokens...`);
+        await Promise.allSettled(tokens.map(async (token) => {
+            this._logger.trace(`[${token.scope}] '${token.sessionId}' Storing pending token`);
+            await this._tokenStorage.store(token.sessionId, {
+                id: token.sessionId,
+                refreshToken: token.refreshToken,
+                scope: token.scope,
+                account: token.account,
+                endpoint: this._env.activeDirectoryEndpointUrl,
+            });
+            this._logger.trace(`[${token.scope}] '${token.sessionId}' Stored pending token`);
+        }));
+        this._logger.trace('Done storing pending tokens');
+    }
+    private async checkForUpdates(e: IDidChangeInOtherWindowEvent): Promise {
+        for (const key of e.added) {
+            const session = await this._tokenStorage.get(key);
+            if (!session) {
+                this._logger.error('session not found that was apparently just added');
+                continue;
+            }
+            if (!this.sessionMatchesEndpoint(session)) {
+                // If the session wasn't made for this login endpoint, ignore this update
+                continue;
+            }
+            const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id);
+            if (!matchesExisting && session.refreshToken) {
+                try {
+                    const scopes = session.scope.split(' ');
+                    const scopeData: IScopeData = {
+                        scopes,
+                        scopeStr: session.scope,
+                        // filter our special scopes
+                        scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
+                        clientId: this.getClientId(scopes),
+                        tenant: this.getTenantId(scopes),
+                    };
+                    this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Session added in another window`);
+                    const token = await this.refreshToken(session.refreshToken, scopeData, session.id);
+                    this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Sending change event for session that was added`);
+                    this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] });
+                    this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Session added in another window added here`);
+                    continue;
+                }
+                catch (e) {
+                    // Network failures will automatically retry on next poll.
+                    if (e.message !== REFRESH_NETWORK_FAILURE) {
+                        vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.'));
+                        await this.removeSessionById(session.id);
+                    }
+                    continue;
+                }
+            }
+        }
+        for (const { value } of e.removed) {
+            this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window`);
+            if (!this.sessionMatchesEndpoint(value)) {
+                // If the session wasn't made for this login endpoint, ignore this update
+                this._logger.trace(`[${value.scope}] '${value.id}' Session doesn't match endpoint. Skipping...`);
+                continue;
+            }
+            await this.removeSessionById(value.id, false);
+            this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window removed here`);
+        }
+        // NOTE: We don't need to handle changed sessions because all that really would give us is a new refresh token
+        // because access tokens are not stored in Secret Storage due to their short lifespan. This new refresh token
+        // is not useful in this window because we really only care about the lifetime of the _access_ token which we
+        // are already managing (see usages of `setSessionTimeout`).
+        // However, in order to minimize the amount of times we store tokens, if a token was stored via another window,
+        // we cancel any pending token storage operations.
+        for (const sessionId of e.updated) {
+            if (this._pendingTokensToStore.delete(sessionId)) {
+                this._logger.trace(`'${sessionId}' Cancelled pending token storage because token was updated in another window`);
+            }
+        }
+    }
+    private sessionMatchesEndpoint(session: IStoredSession): boolean {
+        // For older sessions with no endpoint set, it can be assumed to be the default endpoint
+        session.endpoint ||= defaultActiveDirectoryEndpointUrl;
+        return session.endpoint === this._env.activeDirectoryEndpointUrl;
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/UriEventHandler.ts b/extensions/microsoft-authentication/Source/UriEventHandler.ts
index f525912fa51e8..98ac0bfb5aeaf 100644
--- a/extensions/microsoft-authentication/Source/UriEventHandler.ts
+++ b/extensions/microsoft-authentication/Source/UriEventHandler.ts
@@ -2,18 +2,14 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler {
-	private _disposable = vscode.window.registerUriHandler(this);
-
-	handleUri(uri: vscode.Uri) {
-		this.fire(uri);
-	}
-
-	override dispose(): void {
-		super.dispose();
-		this._disposable.dispose();
-	}
+    private _disposable = vscode.window.registerUriHandler(this);
+    handleUri(uri: vscode.Uri) {
+        this.fire(uri);
+    }
+    override dispose(): void {
+        super.dispose();
+        this._disposable.dispose();
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/betterSecretStorage.ts b/extensions/microsoft-authentication/Source/betterSecretStorage.ts
index 3cc854064b595..ec1ae9cef76da 100644
--- a/extensions/microsoft-authentication/Source/betterSecretStorage.ts
+++ b/extensions/microsoft-authentication/Source/betterSecretStorage.ts
@@ -2,247 +2,222 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import Logger from './logger';
 import { Event, EventEmitter, ExtensionContext, SecretStorage, SecretStorageChangeEvent } from 'vscode';
-
 export interface IDidChangeInOtherWindowEvent {
-	added: string[];
-	updated: string[];
-	removed: Array<{ key: string; value: T }>;
+    added: string[];
+    updated: string[];
+    removed: Array<{
+        key: string;
+        value: T;
+    }>;
 }
-
 export class BetterTokenStorage {
-	// set before and after _tokensPromise is set so getTokens can handle multiple operations.
-	private _operationInProgress = false;
-	// the current state. Don't use this directly and call getTokens() so that you ensure you
-	// have awaited for all operations.
-	private _tokensPromise: Promise> = Promise.resolve(new Map());
-
-	// The vscode SecretStorage instance for this extension.
-	private readonly _secretStorage: SecretStorage;
-
-	private _didChangeInOtherWindow = new EventEmitter>();
-	public onDidChangeInOtherWindow: Event> = this._didChangeInOtherWindow.event;
-
-	/**
-	 *
-	 * @param keylistKey The key in the secret storage that will hold the list of keys associated with this instance of BetterTokenStorage
-	 * @param context the vscode Context used to register disposables and retreive the vscode.SecretStorage for this instance of VS Code
-	 */
-	constructor(private keylistKey: string, context: ExtensionContext) {
-		this._secretStorage = context.secrets;
-		context.subscriptions.push(context.secrets.onDidChange((e) => this.handleSecretChange(e)));
-		this.initialize();
-	}
-
-	private initialize(): void {
-		this._operationInProgress = true;
-		this._tokensPromise = new Promise((resolve, _) => {
-			this._secretStorage.get(this.keylistKey).then(
-				keyListStr => {
-					if (!keyListStr) {
-						resolve(new Map());
-						return;
-					}
-
-					const keyList: Array = JSON.parse(keyListStr);
-					// Gather promises that contain key value pairs our of secret storage
-					const promises = keyList.map(key => new Promise<{ key: string; value: string | undefined }>((res, rej) => {
-						this._secretStorage.get(key).then((value) => {
-							res({ key, value });
-						}, rej);
-					}));
-					Promise.allSettled(promises).then((results => {
-						const tokens = new Map();
-						results.forEach(p => {
-							if (p.status === 'fulfilled' && p.value.value) {
-								const secret = this.parseSecret(p.value.value);
-								tokens.set(p.value.key, secret);
-							} else if (p.status === 'rejected') {
-								Logger.error(p.reason);
-							} else {
-								Logger.error('Key was not found in SecretStorage.');
-							}
-						});
-						resolve(tokens);
-					}));
-				},
-				err => {
-					Logger.error(err);
-					resolve(new Map());
-				});
-		});
-		this._operationInProgress = false;
-	}
-
-	async get(key: string): Promise {
-		const tokens = await this.getTokens();
-		return tokens.get(key);
-	}
-
-	async getAll(predicate?: (item: T) => boolean): Promise {
-		const tokens = await this.getTokens();
-		const values = new Array();
-		for (const [_, value] of tokens) {
-			if (!predicate || predicate(value)) {
-				values.push(value);
-			}
-		}
-		return values;
-	}
-
-	async store(key: string, value: T): Promise {
-		const tokens = await this.getTokens();
-
-		const isAddition = !tokens.has(key);
-		tokens.set(key, value);
-		const valueStr = this.serializeSecret(value);
-		this._operationInProgress = true;
-		this._tokensPromise = new Promise((resolve, _) => {
-			const promises = [this._secretStorage.store(key, valueStr)];
-
-			// if we are adding a secret we need to update the keylist too
-			if (isAddition) {
-				promises.push(this.updateKeyList(tokens));
-			}
-
-			Promise.allSettled(promises).then(results => {
-				results.forEach(r => {
-					if (r.status === 'rejected') {
-						Logger.error(r.reason);
-					}
-				});
-				resolve(tokens);
-			});
-		});
-		this._operationInProgress = false;
-	}
-
-	async delete(key: string): Promise {
-		const tokens = await this.getTokens();
-		if (!tokens.has(key)) {
-			return;
-		}
-		tokens.delete(key);
-
-		this._operationInProgress = true;
-		this._tokensPromise = new Promise((resolve, _) => {
-			Promise.allSettled([
-				this._secretStorage.delete(key),
-				this.updateKeyList(tokens)
-			]).then(results => {
-				results.forEach(r => {
-					if (r.status === 'rejected') {
-						Logger.error(r.reason);
-					}
-				});
-				resolve(tokens);
-			});
-		});
-		this._operationInProgress = false;
-	}
-
-	async deleteAll(predicate?: (item: T) => boolean): Promise {
-		const tokens = await this.getTokens();
-		const promises = [];
-		for (const [key, value] of tokens) {
-			if (!predicate || predicate(value)) {
-				promises.push(this.delete(key));
-			}
-		}
-		await Promise.all(promises);
-	}
-
-	private async updateKeyList(tokens: Map) {
-		const keyList = [];
-		for (const [key] of tokens) {
-			keyList.push(key);
-		}
-
-		const keyListStr = JSON.stringify(keyList);
-		await this._secretStorage.store(this.keylistKey, keyListStr);
-	}
-
-	protected parseSecret(secret: string): T {
-		return JSON.parse(secret);
-	}
-
-	protected serializeSecret(secret: T): string {
-		return JSON.stringify(secret);
-	}
-
-	// This is the one way to get tokens to ensure all other operations that
-	// came before you have been processed.
-	private async getTokens(): Promise> {
-		let tokens;
-		do {
-			tokens = await this._tokensPromise;
-		} while (this._operationInProgress);
-		return tokens;
-	}
-
-	// This is a crucial function that handles whether or not the token has changed in
-	// a different window of VS Code and sends the necessary event if it has.
-	// Scenarios this should cover:
-	// * Added in another window
-	// * Updated in another window
-	// * Deleted in another window
-	// * Added in this window
-	// * Updated in this window
-	// * Deleted in this window
-	private async handleSecretChange(e: SecretStorageChangeEvent) {
-		const key = e.key;
-
-		// The KeyList is only a list of keys to aid initial start up of VS Code to know which
-		// Keys are associated with this handler.
-		if (key === this.keylistKey) {
-			return;
-		}
-		const tokens = await this.getTokens();
-
-		this._operationInProgress = true;
-		this._tokensPromise = new Promise((resolve, _) => {
-			this._secretStorage.get(key).then(
-				storageSecretStr => {
-					if (!storageSecretStr) {
-						// true -> secret was deleted in another window
-						// false -> secret was deleted in this window
-						if (tokens.has(key)) {
-							const value = tokens.get(key)!;
-							tokens.delete(key);
-							this._didChangeInOtherWindow.fire({ added: [], updated: [], removed: [{ key, value }] });
-						}
-						return tokens;
-					}
-
-					const storageSecret = this.parseSecret(storageSecretStr);
-					const cachedSecret = tokens.get(key);
-
-					if (!cachedSecret) {
-						// token was added in another window
-						tokens.set(key, storageSecret);
-						this._didChangeInOtherWindow.fire({ added: [key], updated: [], removed: [] });
-						return tokens;
-					}
-
-					const cachedSecretStr = this.serializeSecret(cachedSecret);
-					if (storageSecretStr !== cachedSecretStr) {
-						// token was updated in another window
-						tokens.set(key, storageSecret);
-						this._didChangeInOtherWindow.fire({ added: [], updated: [key], removed: [] });
-					}
-
-					// what's in our token cache and what's in storage must be the same
-					// which means this should cover the last two scenarios of
-					// Added in this window & Updated in this window.
-					return tokens;
-				},
-				err => {
-					Logger.error(err);
-					return tokens;
-				}).then(resolve);
-		});
-		this._operationInProgress = false;
-	}
+    // set before and after _tokensPromise is set so getTokens can handle multiple operations.
+    private _operationInProgress = false;
+    // the current state. Don't use this directly and call getTokens() so that you ensure you
+    // have awaited for all operations.
+    private _tokensPromise: Promise> = Promise.resolve(new Map());
+    // The vscode SecretStorage instance for this extension.
+    private readonly _secretStorage: SecretStorage;
+    private _didChangeInOtherWindow = new EventEmitter>();
+    public onDidChangeInOtherWindow: Event> = this._didChangeInOtherWindow.event;
+    /**
+     *
+     * @param keylistKey The key in the secret storage that will hold the list of keys associated with this instance of BetterTokenStorage
+     * @param context the vscode Context used to register disposables and retreive the vscode.SecretStorage for this instance of VS Code
+     */
+    constructor(private keylistKey: string, context: ExtensionContext) {
+        this._secretStorage = context.secrets;
+        context.subscriptions.push(context.secrets.onDidChange((e) => this.handleSecretChange(e)));
+        this.initialize();
+    }
+    private initialize(): void {
+        this._operationInProgress = true;
+        this._tokensPromise = new Promise((resolve, _) => {
+            this._secretStorage.get(this.keylistKey).then(keyListStr => {
+                if (!keyListStr) {
+                    resolve(new Map());
+                    return;
+                }
+                const keyList: Array = JSON.parse(keyListStr);
+                // Gather promises that contain key value pairs our of secret storage
+                const promises = keyList.map(key => new Promise<{
+                    key: string;
+                    value: string | undefined;
+                }>((res, rej) => {
+                    this._secretStorage.get(key).then((value) => {
+                        res({ key, value });
+                    }, rej);
+                }));
+                Promise.allSettled(promises).then((results => {
+                    const tokens = new Map();
+                    results.forEach(p => {
+                        if (p.status === 'fulfilled' && p.value.value) {
+                            const secret = this.parseSecret(p.value.value);
+                            tokens.set(p.value.key, secret);
+                        }
+                        else if (p.status === 'rejected') {
+                            Logger.error(p.reason);
+                        }
+                        else {
+                            Logger.error('Key was not found in SecretStorage.');
+                        }
+                    });
+                    resolve(tokens);
+                }));
+            }, err => {
+                Logger.error(err);
+                resolve(new Map());
+            });
+        });
+        this._operationInProgress = false;
+    }
+    async get(key: string): Promise {
+        const tokens = await this.getTokens();
+        return tokens.get(key);
+    }
+    async getAll(predicate?: (item: T) => boolean): Promise {
+        const tokens = await this.getTokens();
+        const values = new Array();
+        for (const [_, value] of tokens) {
+            if (!predicate || predicate(value)) {
+                values.push(value);
+            }
+        }
+        return values;
+    }
+    async store(key: string, value: T): Promise {
+        const tokens = await this.getTokens();
+        const isAddition = !tokens.has(key);
+        tokens.set(key, value);
+        const valueStr = this.serializeSecret(value);
+        this._operationInProgress = true;
+        this._tokensPromise = new Promise((resolve, _) => {
+            const promises = [this._secretStorage.store(key, valueStr)];
+            // if we are adding a secret we need to update the keylist too
+            if (isAddition) {
+                promises.push(this.updateKeyList(tokens));
+            }
+            Promise.allSettled(promises).then(results => {
+                results.forEach(r => {
+                    if (r.status === 'rejected') {
+                        Logger.error(r.reason);
+                    }
+                });
+                resolve(tokens);
+            });
+        });
+        this._operationInProgress = false;
+    }
+    async delete(key: string): Promise {
+        const tokens = await this.getTokens();
+        if (!tokens.has(key)) {
+            return;
+        }
+        tokens.delete(key);
+        this._operationInProgress = true;
+        this._tokensPromise = new Promise((resolve, _) => {
+            Promise.allSettled([
+                this._secretStorage.delete(key),
+                this.updateKeyList(tokens)
+            ]).then(results => {
+                results.forEach(r => {
+                    if (r.status === 'rejected') {
+                        Logger.error(r.reason);
+                    }
+                });
+                resolve(tokens);
+            });
+        });
+        this._operationInProgress = false;
+    }
+    async deleteAll(predicate?: (item: T) => boolean): Promise {
+        const tokens = await this.getTokens();
+        const promises = [];
+        for (const [key, value] of tokens) {
+            if (!predicate || predicate(value)) {
+                promises.push(this.delete(key));
+            }
+        }
+        await Promise.all(promises);
+    }
+    private async updateKeyList(tokens: Map) {
+        const keyList = [];
+        for (const [key] of tokens) {
+            keyList.push(key);
+        }
+        const keyListStr = JSON.stringify(keyList);
+        await this._secretStorage.store(this.keylistKey, keyListStr);
+    }
+    protected parseSecret(secret: string): T {
+        return JSON.parse(secret);
+    }
+    protected serializeSecret(secret: T): string {
+        return JSON.stringify(secret);
+    }
+    // This is the one way to get tokens to ensure all other operations that
+    // came before you have been processed.
+    private async getTokens(): Promise> {
+        let tokens;
+        do {
+            tokens = await this._tokensPromise;
+        } while (this._operationInProgress);
+        return tokens;
+    }
+    // This is a crucial function that handles whether or not the token has changed in
+    // a different window of VS Code and sends the necessary event if it has.
+    // Scenarios this should cover:
+    // * Added in another window
+    // * Updated in another window
+    // * Deleted in another window
+    // * Added in this window
+    // * Updated in this window
+    // * Deleted in this window
+    private async handleSecretChange(e: SecretStorageChangeEvent) {
+        const key = e.key;
+        // The KeyList is only a list of keys to aid initial start up of VS Code to know which
+        // Keys are associated with this handler.
+        if (key === this.keylistKey) {
+            return;
+        }
+        const tokens = await this.getTokens();
+        this._operationInProgress = true;
+        this._tokensPromise = new Promise((resolve, _) => {
+            this._secretStorage.get(key).then(storageSecretStr => {
+                if (!storageSecretStr) {
+                    // true -> secret was deleted in another window
+                    // false -> secret was deleted in this window
+                    if (tokens.has(key)) {
+                        const value = tokens.get(key)!;
+                        tokens.delete(key);
+                        this._didChangeInOtherWindow.fire({ added: [], updated: [], removed: [{ key, value }] });
+                    }
+                    return tokens;
+                }
+                const storageSecret = this.parseSecret(storageSecretStr);
+                const cachedSecret = tokens.get(key);
+                if (!cachedSecret) {
+                    // token was added in another window
+                    tokens.set(key, storageSecret);
+                    this._didChangeInOtherWindow.fire({ added: [key], updated: [], removed: [] });
+                    return tokens;
+                }
+                const cachedSecretStr = this.serializeSecret(cachedSecret);
+                if (storageSecretStr !== cachedSecretStr) {
+                    // token was updated in another window
+                    tokens.set(key, storageSecret);
+                    this._didChangeInOtherWindow.fire({ added: [], updated: [key], removed: [] });
+                }
+                // what's in our token cache and what's in storage must be the same
+                // which means this should cover the last two scenarios of
+                // Added in this window & Updated in this window.
+                return tokens;
+            }, err => {
+                Logger.error(err);
+                return tokens;
+            }).then(resolve);
+        });
+        this._operationInProgress = false;
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/browser/authProvider.ts b/extensions/microsoft-authentication/Source/browser/authProvider.ts
index 3b4da5b18fa6a..ed975c6b8a03e 100644
--- a/extensions/microsoft-authentication/Source/browser/authProvider.ts
+++ b/extensions/microsoft-authentication/Source/browser/authProvider.ts
@@ -2,28 +2,23 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationSession, EventEmitter } from 'vscode';
-
 export class MsalAuthProvider implements AuthenticationProvider {
-	private _onDidChangeSessions = new EventEmitter();
-	onDidChangeSessions = this._onDidChangeSessions.event;
-
-	initialize(): Thenable {
-		throw new Error('Method not implemented.');
-	}
-
-	getSessions(): Thenable {
-		throw new Error('Method not implemented.');
-	}
-	createSession(): Thenable {
-		throw new Error('Method not implemented.');
-	}
-	removeSession(): Thenable {
-		throw new Error('Method not implemented.');
-	}
-
-	dispose() {
-		this._onDidChangeSessions.dispose();
-	}
+    private _onDidChangeSessions = new EventEmitter();
+    onDidChangeSessions = this._onDidChangeSessions.event;
+    initialize(): Thenable {
+        throw new Error('Method not implemented.');
+    }
+    getSessions(): Thenable {
+        throw new Error('Method not implemented.');
+    }
+    createSession(): Thenable {
+        throw new Error('Method not implemented.');
+    }
+    removeSession(): Thenable {
+        throw new Error('Method not implemented.');
+    }
+    dispose() {
+        this._onDidChangeSessions.dispose();
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/browser/authServer.ts b/extensions/microsoft-authentication/Source/browser/authServer.ts
index 60b53c713a85e..568ea72a2d3d9 100644
--- a/extensions/microsoft-authentication/Source/browser/authServer.ts
+++ b/extensions/microsoft-authentication/Source/browser/authServer.ts
@@ -2,11 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function startServer(_: any): any {
-	throw new Error('Not implemented');
+    throw new Error('Not implemented');
 }
-
 export function createServer(_: any): any {
-	throw new Error('Not implemented');
+    throw new Error('Not implemented');
 }
diff --git a/extensions/microsoft-authentication/Source/browser/buffer.ts b/extensions/microsoft-authentication/Source/browser/buffer.ts
index 794bb19f57937..3f324c8d958bc 100644
--- a/extensions/microsoft-authentication/Source/browser/buffer.ts
+++ b/extensions/microsoft-authentication/Source/browser/buffer.ts
@@ -2,16 +2,14 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function base64Encode(text: string): string {
-	return btoa(text);
+    return btoa(text);
 }
-
 export function base64Decode(text: string): string {
-	// modification of https://stackoverflow.com/a/38552302
-	const replacedCharacters = text.replace(/-/g, '+').replace(/_/g, '/');
-	const decodedText = decodeURIComponent(atob(replacedCharacters).split('').map(function (c) {
-		return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
-	}).join(''));
-	return decodedText;
+    // modification of https://stackoverflow.com/a/38552302
+    const replacedCharacters = text.replace(/-/g, '+').replace(/_/g, '/');
+    const decodedText = decodeURIComponent(atob(replacedCharacters).split('').map(function (c) {
+        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+    }).join(''));
+    return decodedText;
 }
diff --git a/extensions/microsoft-authentication/Source/browser/fetch.ts b/extensions/microsoft-authentication/Source/browser/fetch.ts
index c61281ca8f882..6da5e5d5063af 100644
--- a/extensions/microsoft-authentication/Source/browser/fetch.ts
+++ b/extensions/microsoft-authentication/Source/browser/fetch.ts
@@ -2,5 +2,4 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export default fetch;
diff --git a/extensions/microsoft-authentication/Source/common/async.ts b/extensions/microsoft-authentication/Source/common/async.ts
index 094861518fc61..06dc23e0a798c 100644
--- a/extensions/microsoft-authentication/Source/common/async.ts
+++ b/extensions/microsoft-authentication/Source/common/async.ts
@@ -2,232 +2,193 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { CancellationError, CancellationToken, Disposable, Event, EventEmitter } from 'vscode';
-
 /**
  * Can be passed into the Delayed to defer using a microtask
  */
 export const MicrotaskDelay = Symbol('MicrotaskDelay');
-
 export class SequencerByKey {
-
-	private promiseMap = new Map>();
-
-	queue(key: TKey, promiseTask: () => Promise): Promise {
-		const runningPromise = this.promiseMap.get(key) ?? Promise.resolve();
-		const newPromise = runningPromise
-			.catch(() => { })
-			.then(promiseTask)
-			.finally(() => {
-				if (this.promiseMap.get(key) === newPromise) {
-					this.promiseMap.delete(key);
-				}
-			});
-		this.promiseMap.set(key, newPromise);
-		return newPromise;
-	}
+    private promiseMap = new Map>();
+    queue(key: TKey, promiseTask: () => Promise): Promise {
+        const runningPromise = this.promiseMap.get(key) ?? Promise.resolve();
+        const newPromise = runningPromise
+            .catch(() => { })
+            .then(promiseTask)
+            .finally(() => {
+            if (this.promiseMap.get(key) === newPromise) {
+                this.promiseMap.delete(key);
+            }
+        });
+        this.promiseMap.set(key, newPromise);
+        return newPromise;
+    }
 }
-
 export class IntervalTimer extends Disposable {
-
-	private _token: any;
-
-	constructor() {
-		super(() => this.cancel());
-		this._token = -1;
-	}
-
-	cancel(): void {
-		if (this._token !== -1) {
-			clearInterval(this._token);
-			this._token = -1;
-		}
-	}
-
-	cancelAndSet(runner: () => void, interval: number): void {
-		this.cancel();
-		this._token = setInterval(() => {
-			runner();
-		}, interval);
-	}
+    private _token: any;
+    constructor() {
+        super(() => this.cancel());
+        this._token = -1;
+    }
+    cancel(): void {
+        if (this._token !== -1) {
+            clearInterval(this._token);
+            this._token = -1;
+        }
+    }
+    cancelAndSet(runner: () => void, interval: number): void {
+        this.cancel();
+        this._token = setInterval(() => {
+            runner();
+        }, interval);
+    }
 }
-
 /**
  * Returns a promise that rejects with an {@CancellationError} as soon as the passed token is cancelled.
  * @see {@link raceCancellation}
  */
 export function raceCancellationError(promise: Promise, token: CancellationToken): Promise {
-	return new Promise((resolve, reject) => {
-		const ref = token.onCancellationRequested(() => {
-			ref.dispose();
-			reject(new CancellationError());
-		});
-		promise.then(resolve, reject).finally(() => ref.dispose());
-	});
+    return new Promise((resolve, reject) => {
+        const ref = token.onCancellationRequested(() => {
+            ref.dispose();
+            reject(new CancellationError());
+        });
+        promise.then(resolve, reject).finally(() => ref.dispose());
+    });
 }
-
 export class TimeoutError extends Error {
-	constructor() {
-		super('Timed out');
-	}
+    constructor() {
+        super('Timed out');
+    }
 }
-
 export function raceTimeoutError(promise: Promise, timeout: number): Promise {
-	return new Promise((resolve, reject) => {
-		const ref = setTimeout(() => {
-			reject(new CancellationError());
-		}, timeout);
-		promise.then(resolve, reject).finally(() => clearTimeout(ref));
-	});
+    return new Promise((resolve, reject) => {
+        const ref = setTimeout(() => {
+            reject(new CancellationError());
+        }, timeout);
+        promise.then(resolve, reject).finally(() => clearTimeout(ref));
+    });
 }
-
 export function raceCancellationAndTimeoutError(promise: Promise, token: CancellationToken, timeout: number): Promise {
-	return raceCancellationError(raceTimeoutError(promise, timeout), token);
+    return raceCancellationError(raceTimeoutError(promise, timeout), token);
 }
-
 interface ILimitedTaskFactory {
-	factory: () => Promise;
-	c: (value: T | Promise) => void;
-	e: (error?: unknown) => void;
+    factory: () => Promise;
+    c: (value: T | Promise) => void;
+    e: (error?: unknown) => void;
 }
-
 export interface ILimiter {
-
-	readonly size: number;
-
-	queue(factory: () => Promise): Promise;
-
-	clear(): void;
+    readonly size: number;
+    queue(factory: () => Promise): Promise;
+    clear(): void;
 }
-
 /**
  * A helper to queue N promises and run them all with a max degree of parallelism. The helper
  * ensures that at any time no more than M promises are running at the same time.
  */
 export class Limiter implements ILimiter {
-
-	private _size = 0;
-	private _isDisposed = false;
-	private runningPromises: number;
-	private readonly maxDegreeOfParalellism: number;
-	private readonly outstandingPromises: ILimitedTaskFactory[];
-	private readonly _onDrained: EventEmitter;
-
-	constructor(maxDegreeOfParalellism: number) {
-		this.maxDegreeOfParalellism = maxDegreeOfParalellism;
-		this.outstandingPromises = [];
-		this.runningPromises = 0;
-		this._onDrained = new EventEmitter();
-	}
-
-	/**
-	 *
-	 * @returns A promise that resolved when all work is done (onDrained) or when
-	 * there is nothing to do
-	 */
-	whenIdle(): Promise {
-		return this.size > 0
-			? toPromise(this.onDrained)
-			: Promise.resolve();
-	}
-
-	get onDrained(): Event {
-		return this._onDrained.event;
-	}
-
-	get size(): number {
-		return this._size;
-	}
-
-	queue(factory: () => Promise): Promise {
-		if (this._isDisposed) {
-			throw new Error('Object has been disposed');
-		}
-		this._size++;
-
-		return new Promise((c, e) => {
-			this.outstandingPromises.push({ factory, c, e });
-			this.consume();
-		});
-	}
-
-	private consume(): void {
-		while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
-			const iLimitedTask = this.outstandingPromises.shift()!;
-			this.runningPromises++;
-
-			const promise = iLimitedTask.factory();
-			promise.then(iLimitedTask.c, iLimitedTask.e);
-			promise.then(() => this.consumed(), () => this.consumed());
-		}
-	}
-
-	private consumed(): void {
-		if (this._isDisposed) {
-			return;
-		}
-		this.runningPromises--;
-		if (--this._size === 0) {
-			this._onDrained.fire();
-		}
-
-		if (this.outstandingPromises.length > 0) {
-			this.consume();
-		}
-	}
-
-	clear(): void {
-		if (this._isDisposed) {
-			throw new Error('Object has been disposed');
-		}
-		this.outstandingPromises.length = 0;
-		this._size = this.runningPromises;
-	}
-
-	dispose(): void {
-		this._isDisposed = true;
-		this.outstandingPromises.length = 0; // stop further processing
-		this._size = 0;
-		this._onDrained.dispose();
-	}
+    private _size = 0;
+    private _isDisposed = false;
+    private runningPromises: number;
+    private readonly maxDegreeOfParalellism: number;
+    private readonly outstandingPromises: ILimitedTaskFactory[];
+    private readonly _onDrained: EventEmitter;
+    constructor(maxDegreeOfParalellism: number) {
+        this.maxDegreeOfParalellism = maxDegreeOfParalellism;
+        this.outstandingPromises = [];
+        this.runningPromises = 0;
+        this._onDrained = new EventEmitter();
+    }
+    /**
+     *
+     * @returns A promise that resolved when all work is done (onDrained) or when
+     * there is nothing to do
+     */
+    whenIdle(): Promise {
+        return this.size > 0
+            ? toPromise(this.onDrained)
+            : Promise.resolve();
+    }
+    get onDrained(): Event {
+        return this._onDrained.event;
+    }
+    get size(): number {
+        return this._size;
+    }
+    queue(factory: () => Promise): Promise {
+        if (this._isDisposed) {
+            throw new Error('Object has been disposed');
+        }
+        this._size++;
+        return new Promise((c, e) => {
+            this.outstandingPromises.push({ factory, c, e });
+            this.consume();
+        });
+    }
+    private consume(): void {
+        while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
+            const iLimitedTask = this.outstandingPromises.shift()!;
+            this.runningPromises++;
+            const promise = iLimitedTask.factory();
+            promise.then(iLimitedTask.c, iLimitedTask.e);
+            promise.then(() => this.consumed(), () => this.consumed());
+        }
+    }
+    private consumed(): void {
+        if (this._isDisposed) {
+            return;
+        }
+        this.runningPromises--;
+        if (--this._size === 0) {
+            this._onDrained.fire();
+        }
+        if (this.outstandingPromises.length > 0) {
+            this.consume();
+        }
+    }
+    clear(): void {
+        if (this._isDisposed) {
+            throw new Error('Object has been disposed');
+        }
+        this.outstandingPromises.length = 0;
+        this._size = this.runningPromises;
+    }
+    dispose(): void {
+        this._isDisposed = true;
+        this.outstandingPromises.length = 0; // stop further processing
+        this._size = 0;
+        this._onDrained.dispose();
+    }
 }
-
-
 interface IScheduledLater extends Disposable {
-	isTriggered(): boolean;
+    isTriggered(): boolean;
 }
-
 const timeoutDeferred = (timeout: number, fn: () => void): IScheduledLater => {
-	let scheduled = true;
-	const handle = setTimeout(() => {
-		scheduled = false;
-		fn();
-	}, timeout);
-	return {
-		isTriggered: () => scheduled,
-		dispose: () => {
-			clearTimeout(handle);
-			scheduled = false;
-		},
-	};
+    let scheduled = true;
+    const handle = setTimeout(() => {
+        scheduled = false;
+        fn();
+    }, timeout);
+    return {
+        isTriggered: () => scheduled,
+        dispose: () => {
+            clearTimeout(handle);
+            scheduled = false;
+        },
+    };
 };
-
 const microtaskDeferred = (fn: () => void): IScheduledLater => {
-	let scheduled = true;
-	queueMicrotask(() => {
-		if (scheduled) {
-			scheduled = false;
-			fn();
-		}
-	});
-
-	return {
-		isTriggered: () => scheduled,
-		dispose: () => { scheduled = false; },
-	};
+    let scheduled = true;
+    queueMicrotask(() => {
+        if (scheduled) {
+            scheduled = false;
+            fn();
+        }
+    });
+    return {
+        isTriggered: () => scheduled,
+        dispose: () => { scheduled = false; },
+    };
 };
-
 /**
  * A helper to delay (debounce) execution of a task that is being requested often.
  *
@@ -252,74 +213,61 @@ const microtaskDeferred = (fn: () => void): IScheduledLater => {
  * 		}
  */
 export class Delayer implements Disposable {
-
-	private deferred: IScheduledLater | null;
-	private completionPromise: Promise | null;
-	private doResolve: ((value?: any | Promise) => void) | null;
-	private doReject: ((err: any) => void) | null;
-	private task: (() => T | Promise) | null;
-
-	constructor(public defaultDelay: number | typeof MicrotaskDelay) {
-		this.deferred = null;
-		this.completionPromise = null;
-		this.doResolve = null;
-		this.doReject = null;
-		this.task = null;
-	}
-
-	trigger(task: () => T | Promise, delay = this.defaultDelay): Promise {
-		this.task = task;
-		this.cancelTimeout();
-
-		if (!this.completionPromise) {
-			this.completionPromise = new Promise((resolve, reject) => {
-				this.doResolve = resolve;
-				this.doReject = reject;
-			}).then(() => {
-				this.completionPromise = null;
-				this.doResolve = null;
-				if (this.task) {
-					const task = this.task;
-					this.task = null;
-					return task();
-				}
-				return undefined;
-			});
-		}
-
-		const fn = () => {
-			this.deferred = null;
-			this.doResolve?.(null);
-		};
-
-		this.deferred = delay === MicrotaskDelay ? microtaskDeferred(fn) : timeoutDeferred(delay, fn);
-
-		return this.completionPromise;
-	}
-
-	isTriggered(): boolean {
-		return !!this.deferred?.isTriggered();
-	}
-
-	cancel(): void {
-		this.cancelTimeout();
-
-		if (this.completionPromise) {
-			this.doReject?.(new CancellationError());
-			this.completionPromise = null;
-		}
-	}
-
-	private cancelTimeout(): void {
-		this.deferred?.dispose();
-		this.deferred = null;
-	}
-
-	dispose(): void {
-		this.cancel();
-	}
+    private deferred: IScheduledLater | null;
+    private completionPromise: Promise | null;
+    private doResolve: ((value?: any | Promise) => void) | null;
+    private doReject: ((err: any) => void) | null;
+    private task: (() => T | Promise) | null;
+    constructor(public defaultDelay: number | typeof MicrotaskDelay) {
+        this.deferred = null;
+        this.completionPromise = null;
+        this.doResolve = null;
+        this.doReject = null;
+        this.task = null;
+    }
+    trigger(task: () => T | Promise, delay = this.defaultDelay): Promise {
+        this.task = task;
+        this.cancelTimeout();
+        if (!this.completionPromise) {
+            this.completionPromise = new Promise((resolve, reject) => {
+                this.doResolve = resolve;
+                this.doReject = reject;
+            }).then(() => {
+                this.completionPromise = null;
+                this.doResolve = null;
+                if (this.task) {
+                    const task = this.task;
+                    this.task = null;
+                    return task();
+                }
+                return undefined;
+            });
+        }
+        const fn = () => {
+            this.deferred = null;
+            this.doResolve?.(null);
+        };
+        this.deferred = delay === MicrotaskDelay ? microtaskDeferred(fn) : timeoutDeferred(delay, fn);
+        return this.completionPromise;
+    }
+    isTriggered(): boolean {
+        return !!this.deferred?.isTriggered();
+    }
+    cancel(): void {
+        this.cancelTimeout();
+        if (this.completionPromise) {
+            this.doReject?.(new CancellationError());
+            this.completionPromise = null;
+        }
+    }
+    private cancelTimeout(): void {
+        this.deferred?.dispose();
+        this.deferred = null;
+    }
+    dispose(): void {
+        this.cancel();
+    }
 }
-
 /**
  * A helper to prevent accumulation of sequential async tasks.
  *
@@ -347,69 +295,54 @@ export class Delayer implements Disposable {
  * 		}
  */
 export class Throttler implements Disposable {
-
-	private activePromise: Promise | null;
-	private queuedPromise: Promise | null;
-	private queuedPromiseFactory: (() => Promise) | null;
-
-	private isDisposed = false;
-
-	constructor() {
-		this.activePromise = null;
-		this.queuedPromise = null;
-		this.queuedPromiseFactory = null;
-	}
-
-	queue(promiseFactory: () => Promise): Promise {
-		if (this.isDisposed) {
-			return Promise.reject(new Error('Throttler is disposed'));
-		}
-
-		if (this.activePromise) {
-			this.queuedPromiseFactory = promiseFactory;
-
-			if (!this.queuedPromise) {
-				const onComplete = () => {
-					this.queuedPromise = null;
-
-					if (this.isDisposed) {
-						return;
-					}
-
-					const result = this.queue(this.queuedPromiseFactory!);
-					this.queuedPromiseFactory = null;
-
-					return result;
-				};
-
-				this.queuedPromise = new Promise(resolve => {
-					this.activePromise!.then(onComplete, onComplete).then(resolve);
-				});
-			}
-
-			return new Promise((resolve, reject) => {
-				this.queuedPromise!.then(resolve, reject);
-			});
-		}
-
-		this.activePromise = promiseFactory();
-
-		return new Promise((resolve, reject) => {
-			this.activePromise!.then((result: T) => {
-				this.activePromise = null;
-				resolve(result);
-			}, (err: unknown) => {
-				this.activePromise = null;
-				reject(err);
-			});
-		});
-	}
-
-	dispose(): void {
-		this.isDisposed = true;
-	}
+    private activePromise: Promise | null;
+    private queuedPromise: Promise | null;
+    private queuedPromiseFactory: (() => Promise) | null;
+    private isDisposed = false;
+    constructor() {
+        this.activePromise = null;
+        this.queuedPromise = null;
+        this.queuedPromiseFactory = null;
+    }
+    queue(promiseFactory: () => Promise): Promise {
+        if (this.isDisposed) {
+            return Promise.reject(new Error('Throttler is disposed'));
+        }
+        if (this.activePromise) {
+            this.queuedPromiseFactory = promiseFactory;
+            if (!this.queuedPromise) {
+                const onComplete = () => {
+                    this.queuedPromise = null;
+                    if (this.isDisposed) {
+                        return;
+                    }
+                    const result = this.queue(this.queuedPromiseFactory!);
+                    this.queuedPromiseFactory = null;
+                    return result;
+                };
+                this.queuedPromise = new Promise(resolve => {
+                    this.activePromise!.then(onComplete, onComplete).then(resolve);
+                });
+            }
+            return new Promise((resolve, reject) => {
+                this.queuedPromise!.then(resolve, reject);
+            });
+        }
+        this.activePromise = promiseFactory();
+        return new Promise((resolve, reject) => {
+            this.activePromise!.then((result: T) => {
+                this.activePromise = null;
+                resolve(result);
+            }, (err: unknown) => {
+                this.activePromise = null;
+                reject(err);
+            });
+        });
+    }
+    dispose(): void {
+        this.isDisposed = true;
+    }
 }
-
 /**
  * A helper to delay execution of a task that is being requested often, while
  * preventing accumulation of consecutive executions, while the task runs.
@@ -420,138 +353,120 @@ export class Throttler implements Disposable {
  * do one more trip to deliver the letters that have accumulated while he was out.
  */
 export class ThrottledDelayer {
-
-	private delayer: Delayer>;
-	private throttler: Throttler;
-
-	constructor(defaultDelay: number) {
-		this.delayer = new Delayer(defaultDelay);
-		this.throttler = new Throttler();
-	}
-
-	trigger(promiseFactory: () => Promise, delay?: number): Promise {
-		return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise;
-	}
-
-	isTriggered(): boolean {
-		return this.delayer.isTriggered();
-	}
-
-	cancel(): void {
-		this.delayer.cancel();
-	}
-
-	dispose(): void {
-		this.delayer.dispose();
-		this.throttler.dispose();
-	}
+    private delayer: Delayer>;
+    private throttler: Throttler;
+    constructor(defaultDelay: number) {
+        this.delayer = new Delayer(defaultDelay);
+        this.throttler = new Throttler();
+    }
+    trigger(promiseFactory: () => Promise, delay?: number): Promise {
+        return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise;
+    }
+    isTriggered(): boolean {
+        return this.delayer.isTriggered();
+    }
+    cancel(): void {
+        this.delayer.cancel();
+    }
+    dispose(): void {
+        this.delayer.dispose();
+        this.throttler.dispose();
+    }
 }
-
 /**
  * A queue is handles one promise at a time and guarantees that at any time only one promise is executing.
  */
 export class Queue extends Limiter {
-
-	constructor() {
-		super(1);
-	}
+    constructor() {
+        super(1);
+    }
 }
-
 /**
  * Given an event, returns another event which only fires once.
  *
  * @param event The event source for the new event.
  */
 export function once(event: Event): Event {
-	return (listener, thisArgs = null, disposables?) => {
-		// we need this, in case the event fires during the listener call
-		let didFire = false;
-		let result: Disposable | undefined = undefined;
-		result = event(e => {
-			if (didFire) {
-				return;
-			} else if (result) {
-				result.dispose();
-			} else {
-				didFire = true;
-			}
-
-			return listener.call(thisArgs, e);
-		}, null, disposables);
-
-		if (didFire) {
-			result.dispose();
-		}
-
-		return result;
-	};
+    return (listener, thisArgs = null, disposables?) => {
+        // we need this, in case the event fires during the listener call
+        let didFire = false;
+        let result: Disposable | undefined = undefined;
+        result = event(e => {
+            if (didFire) {
+                return;
+            }
+            else if (result) {
+                result.dispose();
+            }
+            else {
+                didFire = true;
+            }
+            return listener.call(thisArgs, e);
+        }, null, disposables);
+        if (didFire) {
+            result.dispose();
+        }
+        return result;
+    };
 }
-
 /**
  * Creates a promise out of an event, using the {@link Event.once} helper.
  */
 export function toPromise(event: Event): Promise {
-	return new Promise(resolve => once(event)(resolve));
+    return new Promise(resolve => once(event)(resolve));
 }
-
 export type ValueCallback = (value: T | Promise) => void;
-
 const enum DeferredOutcome {
-	Resolved,
-	Rejected
+    Resolved,
+    Rejected
 }
-
 /**
  * Creates a promise whose resolution or rejection can be controlled imperatively.
  */
 export class DeferredPromise {
-
-	private completeCallback!: ValueCallback;
-	private errorCallback!: (err: unknown) => void;
-	private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };
-
-	public get isRejected() {
-		return this.outcome?.outcome === DeferredOutcome.Rejected;
-	}
-
-	public get isResolved() {
-		return this.outcome?.outcome === DeferredOutcome.Resolved;
-	}
-
-	public get isSettled() {
-		return !!this.outcome;
-	}
-
-	public get value() {
-		return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
-	}
-
-	public readonly p: Promise;
-
-	constructor() {
-		this.p = new Promise((c, e) => {
-			this.completeCallback = c;
-			this.errorCallback = e;
-		});
-	}
-
-	public complete(value: T) {
-		return new Promise(resolve => {
-			this.completeCallback(value);
-			this.outcome = { outcome: DeferredOutcome.Resolved, value };
-			resolve();
-		});
-	}
-
-	public error(err: unknown) {
-		return new Promise(resolve => {
-			this.errorCallback(err);
-			this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
-			resolve();
-		});
-	}
-
-	public cancel() {
-		return this.error(new CancellationError());
-	}
+    private completeCallback!: ValueCallback;
+    private errorCallback!: (err: unknown) => void;
+    private outcome?: {
+        outcome: DeferredOutcome.Rejected;
+        value: any;
+    } | {
+        outcome: DeferredOutcome.Resolved;
+        value: T;
+    };
+    public get isRejected() {
+        return this.outcome?.outcome === DeferredOutcome.Rejected;
+    }
+    public get isResolved() {
+        return this.outcome?.outcome === DeferredOutcome.Resolved;
+    }
+    public get isSettled() {
+        return !!this.outcome;
+    }
+    public get value() {
+        return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
+    }
+    public readonly p: Promise;
+    constructor() {
+        this.p = new Promise((c, e) => {
+            this.completeCallback = c;
+            this.errorCallback = e;
+        });
+    }
+    public complete(value: T) {
+        return new Promise(resolve => {
+            this.completeCallback(value);
+            this.outcome = { outcome: DeferredOutcome.Resolved, value };
+            resolve();
+        });
+    }
+    public error(err: unknown) {
+        return new Promise(resolve => {
+            this.errorCallback(err);
+            this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
+            resolve();
+        });
+    }
+    public cancel() {
+        return this.error(new CancellationError());
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/cachePlugin.ts b/extensions/microsoft-authentication/Source/common/cachePlugin.ts
index 91b4f0ee6a8e1..29a8261ba734d 100644
--- a/extensions/microsoft-authentication/Source/common/cachePlugin.ts
+++ b/extensions/microsoft-authentication/Source/common/cachePlugin.ts
@@ -2,54 +2,39 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { ICachePlugin, TokenCacheContext } from '@azure/msal-node';
 import { Disposable, EventEmitter, SecretStorage } from 'vscode';
-
 export class SecretStorageCachePlugin implements ICachePlugin {
-	private readonly _onDidChange: EventEmitter = new EventEmitter();
-	readonly onDidChange = this._onDidChange.event;
-
-	private _disposable: Disposable;
-
-	private _value: string | undefined;
-
-	constructor(
-		private readonly _secretStorage: SecretStorage,
-		private readonly _key: string
-	) {
-		this._disposable = Disposable.from(
-			this._onDidChange,
-			this._registerChangeHandler()
-		);
-	}
-
-	private _registerChangeHandler() {
-		return this._secretStorage.onDidChange(e => {
-			if (e.key === this._key) {
-				this._onDidChange.fire();
-			}
-		});
-	}
-
-	async beforeCacheAccess(tokenCacheContext: TokenCacheContext): Promise {
-		const data = await this._secretStorage.get(this._key);
-		this._value = data;
-		if (data) {
-			tokenCacheContext.tokenCache.deserialize(data);
-		}
-	}
-
-	async afterCacheAccess(tokenCacheContext: TokenCacheContext): Promise {
-		if (tokenCacheContext.cacheHasChanged) {
-			const value = tokenCacheContext.tokenCache.serialize();
-			if (value !== this._value) {
-				await this._secretStorage.store(this._key, value);
-			}
-		}
-	}
-
-	dispose() {
-		this._disposable.dispose();
-	}
+    private readonly _onDidChange: EventEmitter = new EventEmitter();
+    readonly onDidChange = this._onDidChange.event;
+    private _disposable: Disposable;
+    private _value: string | undefined;
+    constructor(private readonly _secretStorage: SecretStorage, private readonly _key: string) {
+        this._disposable = Disposable.from(this._onDidChange, this._registerChangeHandler());
+    }
+    private _registerChangeHandler() {
+        return this._secretStorage.onDidChange(e => {
+            if (e.key === this._key) {
+                this._onDidChange.fire();
+            }
+        });
+    }
+    async beforeCacheAccess(tokenCacheContext: TokenCacheContext): Promise {
+        const data = await this._secretStorage.get(this._key);
+        this._value = data;
+        if (data) {
+            tokenCacheContext.tokenCache.deserialize(data);
+        }
+    }
+    async afterCacheAccess(tokenCacheContext: TokenCacheContext): Promise {
+        if (tokenCacheContext.cacheHasChanged) {
+            const value = tokenCacheContext.tokenCache.serialize();
+            if (value !== this._value) {
+                await this._secretStorage.store(this._key, value);
+            }
+        }
+    }
+    dispose() {
+        this._disposable.dispose();
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/event.ts b/extensions/microsoft-authentication/Source/common/event.ts
index 1df992cf8bbfa..49f78914f3d69 100644
--- a/extensions/microsoft-authentication/Source/common/event.ts
+++ b/extensions/microsoft-authentication/Source/common/event.ts
@@ -3,7 +3,6 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import { Event } from 'vscode';
-
 /**
  * The EventBufferer is useful in situations in which you want
  * to delay firing your events during some code.
@@ -25,83 +24,79 @@ import { Event } from 'vscode';
  * ```
  */
 export class EventBufferer {
-
-	private data: { buffers: Function[] }[] = [];
-
-	wrapEvent(event: Event): Event;
-	wrapEvent(event: Event, reduce: (last: T | undefined, event: T) => T): Event;
-	wrapEvent(event: Event, reduce: (last: O | undefined, event: T) => O, initial: O): Event;
-	wrapEvent(event: Event, reduce?: (last: T | O | undefined, event: T) => T | O, initial?: O): Event {
-		return (listener, thisArgs?, disposables?) => {
-			return event(i => {
-				const data = this.data[this.data.length - 1];
-
-				// Non-reduce scenario
-				if (!reduce) {
-					// Buffering case
-					if (data) {
-						data.buffers.push(() => listener.call(thisArgs, i));
-					} else {
-						// Not buffering case
-						listener.call(thisArgs, i);
-					}
-					return;
-				}
-
-				// Reduce scenario
-				const reduceData = data as typeof data & {
-					/**
-					 * The accumulated items that will be reduced.
-					 */
-					items?: T[];
-					/**
-					 * The reduced result cached to be shared with other listeners.
-					 */
-					reducedResult?: T | O;
-				};
-
-				// Not buffering case
-				if (!reduceData) {
-					// TODO: Is there a way to cache this reduce call for all listeners?
-					listener.call(thisArgs, reduce(initial, i));
-					return;
-				}
-
-				// Buffering case
-				reduceData.items ??= [];
-				reduceData.items.push(i);
-				if (reduceData.buffers.length === 0) {
-					// Include a single buffered function that will reduce all events when we're done buffering events
-					data.buffers.push(() => {
-						// cache the reduced result so that the value can be shared across all listeners
-						reduceData.reducedResult ??= initial
-							? reduceData.items!.reduce(reduce as (last: O | undefined, event: T) => O, initial)
-							: reduceData.items!.reduce(reduce as (last: T | undefined, event: T) => T);
-						listener.call(thisArgs, reduceData.reducedResult);
-					});
-				}
-			}, undefined, disposables);
-		};
-	}
-
-	bufferEvents(fn: () => R): R {
-		const data = { buffers: new Array() };
-		this.data.push(data);
-		const r = fn();
-		this.data.pop();
-		data.buffers.forEach(flush => flush());
-		return r;
-	}
-
-	async bufferEventsAsync(fn: () => Promise): Promise {
-		const data = { buffers: new Array() };
-		this.data.push(data);
-		try {
-			const r = await fn();
-			return r;
-		} finally {
-			this.data.pop();
-			data.buffers.forEach(flush => flush());
-		}
-	}
+    private data: {
+        buffers: Function[];
+    }[] = [];
+    wrapEvent(event: Event): Event;
+    wrapEvent(event: Event, reduce: (last: T | undefined, event: T) => T): Event;
+    wrapEvent(event: Event, reduce: (last: O | undefined, event: T) => O, initial: O): Event;
+    wrapEvent(event: Event, reduce?: (last: T | O | undefined, event: T) => T | O, initial?: O): Event {
+        return (listener, thisArgs?, disposables?) => {
+            return event(i => {
+                const data = this.data[this.data.length - 1];
+                // Non-reduce scenario
+                if (!reduce) {
+                    // Buffering case
+                    if (data) {
+                        data.buffers.push(() => listener.call(thisArgs, i));
+                    }
+                    else {
+                        // Not buffering case
+                        listener.call(thisArgs, i);
+                    }
+                    return;
+                }
+                // Reduce scenario
+                const reduceData = data as typeof data & {
+                    /**
+                     * The accumulated items that will be reduced.
+                     */
+                    items?: T[];
+                    /**
+                     * The reduced result cached to be shared with other listeners.
+                     */
+                    reducedResult?: T | O;
+                };
+                // Not buffering case
+                if (!reduceData) {
+                    // TODO: Is there a way to cache this reduce call for all listeners?
+                    listener.call(thisArgs, reduce(initial, i));
+                    return;
+                }
+                // Buffering case
+                reduceData.items ??= [];
+                reduceData.items.push(i);
+                if (reduceData.buffers.length === 0) {
+                    // Include a single buffered function that will reduce all events when we're done buffering events
+                    data.buffers.push(() => {
+                        // cache the reduced result so that the value can be shared across all listeners
+                        reduceData.reducedResult ??= initial
+                            ? reduceData.items!.reduce(reduce as (last: O | undefined, event: T) => O, initial)
+                            : reduceData.items!.reduce(reduce as (last: T | undefined, event: T) => T);
+                        listener.call(thisArgs, reduceData.reducedResult);
+                    });
+                }
+            }, undefined, disposables);
+        };
+    }
+    bufferEvents(fn: () => R): R {
+        const data = { buffers: new Array() };
+        this.data.push(data);
+        const r = fn();
+        this.data.pop();
+        data.buffers.forEach(flush => flush());
+        return r;
+    }
+    async bufferEventsAsync(fn: () => Promise): Promise {
+        const data = { buffers: new Array() };
+        this.data.push(data);
+        try {
+            const r = await fn();
+            return r;
+        }
+        finally {
+            this.data.pop();
+            data.buffers.forEach(flush => flush());
+        }
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/experimentation.ts b/extensions/microsoft-authentication/Source/common/experimentation.ts
index dd383c4f040d4..8deabc963533f 100644
--- a/extensions/microsoft-authentication/Source/common/experimentation.ts
+++ b/extensions/microsoft-authentication/Source/common/experimentation.ts
@@ -4,23 +4,11 @@
  *--------------------------------------------------------------------------------------------*/
 import * as vscode from 'vscode';
 import { getExperimentationService, IExperimentationService, IExperimentationTelemetry, TargetPopulation } from 'vscode-tas-client';
-
-export async function createExperimentationService(
-	context: vscode.ExtensionContext,
-	experimentationTelemetry: IExperimentationTelemetry,
-	isPreRelease: boolean,
-): Promise {
-	const id = context.extension.id;
-	const version = context.extension.packageJSON['version'];
-
-	const service = getExperimentationService(
-		id,
-		version,
-		isPreRelease ? TargetPopulation.Insiders : TargetPopulation.Public,
-		experimentationTelemetry,
-		context.globalState,
-	) as unknown as IExperimentationService;
-	await service.initializePromise;
-	await service.initialFetch;
-	return service;
+export async function createExperimentationService(context: vscode.ExtensionContext, experimentationTelemetry: IExperimentationTelemetry, isPreRelease: boolean): Promise {
+    const id = context.extension.id;
+    const version = context.extension.packageJSON['version'];
+    const service = getExperimentationService(id, version, isPreRelease ? TargetPopulation.Insiders : TargetPopulation.Public, experimentationTelemetry, context.globalState) as unknown as IExperimentationService;
+    await service.initializePromise;
+    await service.initialFetch;
+    return service;
 }
diff --git a/extensions/microsoft-authentication/Source/common/loggerOptions.ts b/extensions/microsoft-authentication/Source/common/loggerOptions.ts
index 86443c0281f0a..70abc9a6039e1 100644
--- a/extensions/microsoft-authentication/Source/common/loggerOptions.ts
+++ b/extensions/microsoft-authentication/Source/common/loggerOptions.ts
@@ -2,60 +2,53 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { LogLevel as MsalLogLevel } from '@azure/msal-node';
 import { env, LogLevel, LogOutputChannel } from 'vscode';
-
 export class MsalLoggerOptions {
-	piiLoggingEnabled = false;
-
-	constructor(private readonly _output: LogOutputChannel) { }
-
-	get logLevel(): MsalLogLevel {
-		return this._toMsalLogLevel(env.logLevel);
-	}
-
-	loggerCallback(level: MsalLogLevel, message: string, containsPii: boolean): void {
-		if (containsPii) {
-			return;
-		}
-
-		switch (level) {
-			case MsalLogLevel.Error:
-				this._output.error(message);
-				return;
-			case MsalLogLevel.Warning:
-				this._output.warn(message);
-				return;
-			case MsalLogLevel.Info:
-				this._output.info(message);
-				return;
-			case MsalLogLevel.Verbose:
-				this._output.debug(message);
-				return;
-			case MsalLogLevel.Trace:
-				this._output.trace(message);
-				return;
-			default:
-				this._output.info(message);
-				return;
-		}
-	}
-
-	private _toMsalLogLevel(logLevel: LogLevel): MsalLogLevel {
-		switch (logLevel) {
-			case LogLevel.Trace:
-				return MsalLogLevel.Trace;
-			case LogLevel.Debug:
-				return MsalLogLevel.Verbose;
-			case LogLevel.Info:
-				return MsalLogLevel.Info;
-			case LogLevel.Warning:
-				return MsalLogLevel.Warning;
-			case LogLevel.Error:
-				return MsalLogLevel.Error;
-			default:
-				return MsalLogLevel.Info;
-		}
-	}
+    piiLoggingEnabled = false;
+    constructor(private readonly _output: LogOutputChannel) { }
+    get logLevel(): MsalLogLevel {
+        return this._toMsalLogLevel(env.logLevel);
+    }
+    loggerCallback(level: MsalLogLevel, message: string, containsPii: boolean): void {
+        if (containsPii) {
+            return;
+        }
+        switch (level) {
+            case MsalLogLevel.Error:
+                this._output.error(message);
+                return;
+            case MsalLogLevel.Warning:
+                this._output.warn(message);
+                return;
+            case MsalLogLevel.Info:
+                this._output.info(message);
+                return;
+            case MsalLogLevel.Verbose:
+                this._output.debug(message);
+                return;
+            case MsalLogLevel.Trace:
+                this._output.trace(message);
+                return;
+            default:
+                this._output.info(message);
+                return;
+        }
+    }
+    private _toMsalLogLevel(logLevel: LogLevel): MsalLogLevel {
+        switch (logLevel) {
+            case LogLevel.Trace:
+                return MsalLogLevel.Trace;
+            case LogLevel.Debug:
+                return MsalLogLevel.Verbose;
+            case LogLevel.Info:
+                return MsalLogLevel.Info;
+            case LogLevel.Warning:
+                return MsalLogLevel.Warning;
+            case LogLevel.Error:
+                return MsalLogLevel.Error;
+            default:
+                return MsalLogLevel.Info;
+        }
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/loopbackClientAndOpener.ts b/extensions/microsoft-authentication/Source/common/loopbackClientAndOpener.ts
index 3fbb034003790..63180689cb313 100644
--- a/extensions/microsoft-authentication/Source/common/loopbackClientAndOpener.ts
+++ b/extensions/microsoft-authentication/Source/common/loopbackClientAndOpener.ts
@@ -2,51 +2,38 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import type { ILoopbackClient, ServerAuthorizationCodeResponse } from '@azure/msal-node';
 import type { UriEventHandler } from '../UriEventHandler';
 import { env, LogOutputChannel, Uri } from 'vscode';
 import { toPromise } from './async';
-
 export interface ILoopbackClientAndOpener extends ILoopbackClient {
-	openBrowser(url: string): Promise;
+    openBrowser(url: string): Promise;
 }
-
 export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener {
-	constructor(
-		private readonly _uriHandler: UriEventHandler,
-		private readonly _redirectUri: string,
-		private readonly _logger: LogOutputChannel
-	) { }
-
-	async listenForAuthCode(): Promise {
-		const url = await toPromise(this._uriHandler.event);
-		this._logger.debug(`Received URL event. Authority: ${url.authority}`);
-		const result = new URL(url.toString(true));
-
-		return {
-			code: result.searchParams.get('code') ?? undefined,
-			state: result.searchParams.get('state') ?? undefined,
-			error: result.searchParams.get('error') ?? undefined,
-			error_description: result.searchParams.get('error_description') ?? undefined,
-			error_uri: result.searchParams.get('error_uri') ?? undefined,
-		};
-	}
-
-	getRedirectUri(): string {
-		// We always return the constant redirect URL because
-		// it will handle redirecting back to the extension
-		return this._redirectUri;
-	}
-
-	closeServer(): void {
-		// No-op
-	}
-
-	async openBrowser(url: string): Promise {
-		const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`));
-
-		const uri = Uri.parse(url + `&state=${encodeURI(callbackUri.toString(true))}`);
-		await env.openExternal(uri);
-	}
+    constructor(private readonly _uriHandler: UriEventHandler, private readonly _redirectUri: string, private readonly _logger: LogOutputChannel) { }
+    async listenForAuthCode(): Promise {
+        const url = await toPromise(this._uriHandler.event);
+        this._logger.debug(`Received URL event. Authority: ${url.authority}`);
+        const result = new URL(url.toString(true));
+        return {
+            code: result.searchParams.get('code') ?? undefined,
+            state: result.searchParams.get('state') ?? undefined,
+            error: result.searchParams.get('error') ?? undefined,
+            error_description: result.searchParams.get('error_description') ?? undefined,
+            error_uri: result.searchParams.get('error_uri') ?? undefined,
+        };
+    }
+    getRedirectUri(): string {
+        // We always return the constant redirect URL because
+        // it will handle redirecting back to the extension
+        return this._redirectUri;
+    }
+    closeServer(): void {
+        // No-op
+    }
+    async openBrowser(url: string): Promise {
+        const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`));
+        const uri = Uri.parse(url + `&state=${encodeURI(callbackUri.toString(true))}`);
+        await env.openExternal(uri);
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/publicClientCache.ts b/extensions/microsoft-authentication/Source/common/publicClientCache.ts
index 925a4d1a88c3d..a672e12da62dd 100644
--- a/extensions/microsoft-authentication/Source/common/publicClientCache.ts
+++ b/extensions/microsoft-authentication/Source/common/publicClientCache.ts
@@ -4,21 +4,27 @@
  *--------------------------------------------------------------------------------------------*/
 import type { AccountInfo, AuthenticationResult, InteractiveRequest, SilentFlowRequest } from '@azure/msal-node';
 import type { Disposable, Event } from 'vscode';
-
 export interface ICachedPublicClientApplication extends Disposable {
-	initialize(): Promise;
-	onDidAccountsChange: Event<{ added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }>;
-	onDidRemoveLastAccount: Event;
-	acquireTokenSilent(request: SilentFlowRequest): Promise;
-	acquireTokenInteractive(request: InteractiveRequest): Promise;
-	removeAccount(account: AccountInfo): Promise;
-	accounts: AccountInfo[];
-	clientId: string;
-	authority: string;
+    initialize(): Promise;
+    onDidAccountsChange: Event<{
+        added: AccountInfo[];
+        changed: AccountInfo[];
+        deleted: AccountInfo[];
+    }>;
+    onDidRemoveLastAccount: Event;
+    acquireTokenSilent(request: SilentFlowRequest): Promise;
+    acquireTokenInteractive(request: InteractiveRequest): Promise;
+    removeAccount(account: AccountInfo): Promise;
+    accounts: AccountInfo[];
+    clientId: string;
+    authority: string;
 }
-
 export interface ICachedPublicClientApplicationManager {
-	onDidAccountsChange: Event<{ added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }>;
-	getOrCreate(clientId: string, authority: string): Promise;
-	getAll(): ICachedPublicClientApplication[];
+    onDidAccountsChange: Event<{
+        added: AccountInfo[];
+        changed: AccountInfo[];
+        deleted: AccountInfo[];
+    }>;
+    getOrCreate(clientId: string, authority: string): Promise;
+    getAll(): ICachedPublicClientApplication[];
 }
diff --git a/extensions/microsoft-authentication/Source/common/scopeData.ts b/extensions/microsoft-authentication/Source/common/scopeData.ts
index 4432abfed435a..61c9ea05a1b6d 100644
--- a/extensions/microsoft-authentication/Source/common/scopeData.ts
+++ b/extensions/microsoft-authentication/Source/common/scopeData.ts
@@ -2,84 +2,70 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 const DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56';
 const DEFAULT_TENANT = 'organizations';
-
 const OIDC_SCOPES = ['openid', 'email', 'profile', 'offline_access'];
 const GRAPH_TACK_ON_SCOPE = 'User.Read';
-
 export class ScopeData {
-
-	/**
-	 * The full list of scopes including:
-	 * * the original scopes passed to the constructor
-	 * * internal VS Code scopes (e.g. `VSCODE_CLIENT_ID:...`)
-	 * * the default scopes (`openid`, `email`, `profile`, `offline_access`)
-	 */
-	readonly allScopes: string[];
-
-	/**
-	 * The full list of scopes as a space-separated string. For logging.
-	 */
-	readonly scopeStr: string;
-
-	/**
-	 * The list of scopes to send to the token endpoint. This is the same as `scopes` but without the internal VS Code scopes.
-	 */
-	readonly scopesToSend: string[];
-
-	/**
-	 * The client ID to use for the token request. This is the value of the `VSCODE_CLIENT_ID:...` scope if present, otherwise the default client ID.
-	 */
-	readonly clientId: string;
-
-	/**
-	 * The tenant ID to use for the token request. This is the value of the `VSCODE_TENANT:...` scope if present, otherwise the default tenant ID.
-	 */
-	readonly tenant: string;
-
-	constructor(readonly originalScopes: readonly string[] = []) {
-		const modifiedScopes = [...originalScopes];
-		modifiedScopes.sort();
-		this.allScopes = modifiedScopes;
-		this.scopeStr = modifiedScopes.join(' ');
-		this.scopesToSend = this.getScopesToSend(modifiedScopes);
-		this.clientId = this.getClientId(this.allScopes);
-		this.tenant = this.getTenantId(this.allScopes);
-	}
-
-	private getClientId(scopes: string[]) {
-		return scopes.reduce((prev, current) => {
-			if (current.startsWith('VSCODE_CLIENT_ID:')) {
-				return current.split('VSCODE_CLIENT_ID:')[1];
-			}
-			return prev;
-		}, undefined) ?? DEFAULT_CLIENT_ID;
-	}
-
-	private getTenantId(scopes: string[]) {
-		return scopes.reduce((prev, current) => {
-			if (current.startsWith('VSCODE_TENANT:')) {
-				return current.split('VSCODE_TENANT:')[1];
-			}
-			return prev;
-		}, undefined) ?? DEFAULT_TENANT;
-	}
-
-	private getScopesToSend(scopes: string[]) {
-		const scopesToSend = scopes.filter(s => !s.startsWith('VSCODE_'));
-
-		const set = new Set(scopesToSend);
-		for (const scope of OIDC_SCOPES) {
-			set.delete(scope);
-		}
-
-		// If we only had OIDC scopes, we need to add a tack-on scope to make the request valid
-		// by forcing Identity into treating this as a Graph token request.
-		if (!set.size) {
-			scopesToSend.push(GRAPH_TACK_ON_SCOPE);
-		}
-		return scopesToSend;
-	}
+    /**
+     * The full list of scopes including:
+     * * the original scopes passed to the constructor
+     * * internal VS Code scopes (e.g. `VSCODE_CLIENT_ID:...`)
+     * * the default scopes (`openid`, `email`, `profile`, `offline_access`)
+     */
+    readonly allScopes: string[];
+    /**
+     * The full list of scopes as a space-separated string. For logging.
+     */
+    readonly scopeStr: string;
+    /**
+     * The list of scopes to send to the token endpoint. This is the same as `scopes` but without the internal VS Code scopes.
+     */
+    readonly scopesToSend: string[];
+    /**
+     * The client ID to use for the token request. This is the value of the `VSCODE_CLIENT_ID:...` scope if present, otherwise the default client ID.
+     */
+    readonly clientId: string;
+    /**
+     * The tenant ID to use for the token request. This is the value of the `VSCODE_TENANT:...` scope if present, otherwise the default tenant ID.
+     */
+    readonly tenant: string;
+    constructor(readonly originalScopes: readonly string[] = []) {
+        const modifiedScopes = [...originalScopes];
+        modifiedScopes.sort();
+        this.allScopes = modifiedScopes;
+        this.scopeStr = modifiedScopes.join(' ');
+        this.scopesToSend = this.getScopesToSend(modifiedScopes);
+        this.clientId = this.getClientId(this.allScopes);
+        this.tenant = this.getTenantId(this.allScopes);
+    }
+    private getClientId(scopes: string[]) {
+        return scopes.reduce((prev, current) => {
+            if (current.startsWith('VSCODE_CLIENT_ID:')) {
+                return current.split('VSCODE_CLIENT_ID:')[1];
+            }
+            return prev;
+        }, undefined) ?? DEFAULT_CLIENT_ID;
+    }
+    private getTenantId(scopes: string[]) {
+        return scopes.reduce((prev, current) => {
+            if (current.startsWith('VSCODE_TENANT:')) {
+                return current.split('VSCODE_TENANT:')[1];
+            }
+            return prev;
+        }, undefined) ?? DEFAULT_TENANT;
+    }
+    private getScopesToSend(scopes: string[]) {
+        const scopesToSend = scopes.filter(s => !s.startsWith('VSCODE_'));
+        const set = new Set(scopesToSend);
+        for (const scope of OIDC_SCOPES) {
+            set.delete(scope);
+        }
+        // If we only had OIDC scopes, we need to add a tack-on scope to make the request valid
+        // by forcing Identity into treating this as a Graph token request.
+        if (!set.size) {
+            scopesToSend.push(GRAPH_TACK_ON_SCOPE);
+        }
+        return scopesToSend;
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/telemetryReporter.ts b/extensions/microsoft-authentication/Source/common/telemetryReporter.ts
index 25ac2623282a7..39112c772ba39 100644
--- a/extensions/microsoft-authentication/Source/common/telemetryReporter.ts
+++ b/extensions/microsoft-authentication/Source/common/telemetryReporter.ts
@@ -2,127 +2,115 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import TelemetryReporter, { TelemetryEventProperties } from '@vscode/extension-telemetry';
 import { IExperimentationTelemetry } from 'vscode-tas-client';
-
 export const enum MicrosoftAccountType {
-	AAD = 'aad',
-	MSA = 'msa',
-	Unknown = 'unknown'
+    AAD = 'aad',
+    MSA = 'msa',
+    Unknown = 'unknown'
 }
-
 export class MicrosoftAuthenticationTelemetryReporter implements IExperimentationTelemetry {
-	private sharedProperties: Record = {};
-	protected _telemetryReporter: TelemetryReporter;
-	constructor(aiKey: string) {
-		this._telemetryReporter = new TelemetryReporter(aiKey);
-	}
-
-	get telemetryReporter(): TelemetryReporter {
-		return this._telemetryReporter;
-	}
-
-	setSharedProperty(name: string, value: string): void {
-		this.sharedProperties[name] = value;
-	}
-
-	postEvent(eventName: string, props: Map): void {
-		const eventProperties: TelemetryEventProperties = { ...this.sharedProperties, ...Object.fromEntries(props) };
-		this._telemetryReporter.sendTelemetryEvent(
-			eventName,
-			eventProperties
-		);
-	}
-
-	sendLoginEvent(scopes: readonly string[]): void {
-		/* __GDPR__
-			"login" : {
-				"owner": "TylerLeonhardt",
-				"comment": "Used to determine the usage of the Microsoft Auth Provider.",
-				"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
-			}
-		*/
-		this._telemetryReporter.sendTelemetryEvent('login', {
-			// Get rid of guids from telemetry.
-			scopes: JSON.stringify(this._scrubGuids(scopes)),
-		});
-	}
-	sendLoginFailedEvent(): void {
-		/* __GDPR__
-			"loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
-		*/
-		this._telemetryReporter.sendTelemetryEvent('loginFailed');
-	}
-	sendLogoutEvent(): void {
-		/* __GDPR__
-			"logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
-		*/
-		this._telemetryReporter.sendTelemetryEvent('logout');
-	}
-	sendLogoutFailedEvent(): void {
-		/* __GDPR__
-			"logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
-		*/
-		this._telemetryReporter.sendTelemetryEvent('logoutFailed');
-	}
-	/**
-	 * Sends an event for an account type available at startup.
-	 * @param scopes The scopes for the session
-	 * @param accountType The account type for the session
-	 * @todo Remove the scopes since we really don't care about them.
-	 */
-	sendAccountEvent(scopes: string[], accountType: MicrosoftAccountType): void {
-		/* __GDPR__
-			"login" : {
-				"owner": "TylerLeonhardt",
-				"comment": "Used to determine the usage of the Microsoft Auth Provider.",
-				"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." },
-				"accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." }
-			}
-		*/
-		this._telemetryReporter.sendTelemetryEvent('account', {
-			// Get rid of guids from telemetry.
-			scopes: JSON.stringify(this._scrubGuids(scopes)),
-			accountType
-		});
-	}
-
-	protected _scrubGuids(scopes: readonly string[]): string[] {
-		return scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'));
-	}
+    private sharedProperties: Record = {};
+    protected _telemetryReporter: TelemetryReporter;
+    constructor(aiKey: string) {
+        this._telemetryReporter = new TelemetryReporter(aiKey);
+    }
+    get telemetryReporter(): TelemetryReporter {
+        return this._telemetryReporter;
+    }
+    setSharedProperty(name: string, value: string): void {
+        this.sharedProperties[name] = value;
+    }
+    postEvent(eventName: string, props: Map): void {
+        const eventProperties: TelemetryEventProperties = { ...this.sharedProperties, ...Object.fromEntries(props) };
+        this._telemetryReporter.sendTelemetryEvent(eventName, eventProperties);
+    }
+    sendLoginEvent(scopes: readonly string[]): void {
+        /* __GDPR__
+            "login" : {
+                "owner": "TylerLeonhardt",
+                "comment": "Used to determine the usage of the Microsoft Auth Provider.",
+                "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
+            }
+        */
+        this._telemetryReporter.sendTelemetryEvent('login', {
+            // Get rid of guids from telemetry.
+            scopes: JSON.stringify(this._scrubGuids(scopes)),
+        });
+    }
+    sendLoginFailedEvent(): void {
+        /* __GDPR__
+            "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
+        */
+        this._telemetryReporter.sendTelemetryEvent('loginFailed');
+    }
+    sendLogoutEvent(): void {
+        /* __GDPR__
+            "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
+        */
+        this._telemetryReporter.sendTelemetryEvent('logout');
+    }
+    sendLogoutFailedEvent(): void {
+        /* __GDPR__
+            "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
+        */
+        this._telemetryReporter.sendTelemetryEvent('logoutFailed');
+    }
+    /**
+     * Sends an event for an account type available at startup.
+     * @param scopes The scopes for the session
+     * @param accountType The account type for the session
+     * @todo Remove the scopes since we really don't care about them.
+     */
+    sendAccountEvent(scopes: string[], accountType: MicrosoftAccountType): void {
+        /* __GDPR__
+            "login" : {
+                "owner": "TylerLeonhardt",
+                "comment": "Used to determine the usage of the Microsoft Auth Provider.",
+                "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." },
+                "accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." }
+            }
+        */
+        this._telemetryReporter.sendTelemetryEvent('account', {
+            // Get rid of guids from telemetry.
+            scopes: JSON.stringify(this._scrubGuids(scopes)),
+            accountType
+        });
+    }
+    protected _scrubGuids(scopes: readonly string[]): string[] {
+        return scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'));
+    }
 }
-
 export class MicrosoftSovereignCloudAuthenticationTelemetryReporter extends MicrosoftAuthenticationTelemetryReporter {
-	override sendLoginEvent(scopes: string[]): void {
-		/* __GDPR__
-			"loginMicrosoftSovereignCloud" : {
-				"owner": "TylerLeonhardt",
-				"comment": "Used to determine the usage of the Microsoft Auth Provider.",
-				"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
-			}
-		*/
-		this._telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', {
-			// Get rid of guids from telemetry.
-			scopes: JSON.stringify(this._scrubGuids(scopes)),
-		});
-	}
-	override sendLoginFailedEvent(): void {
-		/* __GDPR__
-			"loginMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
-		*/
-		this._telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed');
-	}
-	override sendLogoutEvent(): void {
-		/* __GDPR__
-			"logoutMicrosoftSovereignCloud" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
-		*/
-		this._telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud');
-	}
-	override sendLogoutFailedEvent(): void {
-		/* __GDPR__
-			"logoutMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
-		*/
-		this._telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed');
-	}
+    override sendLoginEvent(scopes: string[]): void {
+        /* __GDPR__
+            "loginMicrosoftSovereignCloud" : {
+                "owner": "TylerLeonhardt",
+                "comment": "Used to determine the usage of the Microsoft Auth Provider.",
+                "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
+            }
+        */
+        this._telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', {
+            // Get rid of guids from telemetry.
+            scopes: JSON.stringify(this._scrubGuids(scopes)),
+        });
+    }
+    override sendLoginFailedEvent(): void {
+        /* __GDPR__
+            "loginMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
+        */
+        this._telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed');
+    }
+    override sendLogoutEvent(): void {
+        /* __GDPR__
+            "logoutMicrosoftSovereignCloud" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
+        */
+        this._telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud');
+    }
+    override sendLogoutFailedEvent(): void {
+        /* __GDPR__
+            "logoutMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
+        */
+        this._telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed');
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/common/uri.ts b/extensions/microsoft-authentication/Source/common/uri.ts
index 7382cc2f4f269..fb4985626922e 100644
--- a/extensions/microsoft-authentication/Source/common/uri.ts
+++ b/extensions/microsoft-authentication/Source/common/uri.ts
@@ -3,35 +3,31 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import { env, UIKind, Uri } from 'vscode';
-
 const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1'];
 function isLocalhost(uri: Uri): boolean {
-	if (!/^https?$/i.test(uri.scheme)) {
-		return false;
-	}
-	const host = uri.authority.split(':')[0];
-	return LOCALHOST_ADDRESSES.indexOf(host) >= 0;
+    if (!/^https?$/i.test(uri.scheme)) {
+        return false;
+    }
+    const host = uri.authority.split(':')[0];
+    return LOCALHOST_ADDRESSES.indexOf(host) >= 0;
 }
-
 export function isSupportedEnvironment(uri: Uri): boolean {
-	if (env.uiKind === UIKind.Desktop) {
-		return true;
-	}
-	// local development (localhost:* or 127.0.0.1:*)
-	if (isLocalhost(uri)) {
-		return true;
-	}
-	// At this point we should only ever see https
-	if (uri.scheme !== 'https') {
-		return false;
-	}
-
-	return (
-		// vscode.dev & insiders.vscode.dev
-		/(?:^|\.)vscode\.dev$/.test(uri.authority) ||
-		// github.dev & codespaces
-		/(?:^|\.)github\.dev$/.test(uri.authority) ||
-		// github.dev/codespaces local setup (github.localhost)
-		/(?:^|\.)github\.localhost$/.test(uri.authority)
-	);
+    if (env.uiKind === UIKind.Desktop) {
+        return true;
+    }
+    // local development (localhost:* or 127.0.0.1:*)
+    if (isLocalhost(uri)) {
+        return true;
+    }
+    // At this point we should only ever see https
+    if (uri.scheme !== 'https') {
+        return false;
+    }
+    return (
+    // vscode.dev & insiders.vscode.dev
+    /(?:^|\.)vscode\.dev$/.test(uri.authority) ||
+        // github.dev & codespaces
+        /(?:^|\.)github\.dev$/.test(uri.authority) ||
+        // github.dev/codespaces local setup (github.localhost)
+        /(?:^|\.)github\.localhost$/.test(uri.authority));
 }
diff --git a/extensions/microsoft-authentication/Source/cryptoUtils.ts b/extensions/microsoft-authentication/Source/cryptoUtils.ts
index e608a81fc99a0..5e94167b4f8c8 100644
--- a/extensions/microsoft-authentication/Source/cryptoUtils.ts
+++ b/extensions/microsoft-authentication/Source/cryptoUtils.ts
@@ -3,42 +3,36 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import { base64Encode } from './node/buffer';
-
 export function randomUUID() {
-	return crypto.randomUUID();
+    return crypto.randomUUID();
 }
-
 function dec2hex(dec: number): string {
-	return ('0' + dec.toString(16)).slice(-2);
+    return ('0' + dec.toString(16)).slice(-2);
 }
-
 export function generateCodeVerifier(): string {
-	const array = new Uint32Array(56 / 2);
-	crypto.getRandomValues(array);
-	return Array.from(array, dec2hex).join('');
+    const array = new Uint32Array(56 / 2);
+    crypto.getRandomValues(array);
+    return Array.from(array, dec2hex).join('');
 }
-
 function sha256(plain: string | undefined) {
-	const encoder = new TextEncoder();
-	const data = encoder.encode(plain);
-	return crypto.subtle.digest('SHA-256', data);
+    const encoder = new TextEncoder();
+    const data = encoder.encode(plain);
+    return crypto.subtle.digest('SHA-256', data);
 }
-
 function base64urlencode(a: ArrayBuffer) {
-	let str = '';
-	const bytes = new Uint8Array(a);
-	const len = bytes.byteLength;
-	for (let i = 0; i < len; i++) {
-		str += String.fromCharCode(bytes[i]);
-	}
-	return base64Encode(str)
-		.replace(/\+/g, '-')
-		.replace(/\//g, '_')
-		.replace(/=+$/, '');
+    let str = '';
+    const bytes = new Uint8Array(a);
+    const len = bytes.byteLength;
+    for (let i = 0; i < len; i++) {
+        str += String.fromCharCode(bytes[i]);
+    }
+    return base64Encode(str)
+        .replace(/\+/g, '-')
+        .replace(/\//g, '_')
+        .replace(/=+$/, '');
 }
-
 export async function generateCodeChallenge(v: string) {
-	const hashed = await sha256(v);
-	const base64encoded = base64urlencode(hashed);
-	return base64encoded;
+    const hashed = await sha256(v);
+    const base64encoded = base64urlencode(hashed);
+    return base64encoded;
 }
diff --git a/extensions/microsoft-authentication/Source/extension.ts b/extensions/microsoft-authentication/Source/extension.ts
index 3f9b5d3a4d1c6..aaa36bf76c4fe 100644
--- a/extensions/microsoft-authentication/Source/extension.ts
+++ b/extensions/microsoft-authentication/Source/extension.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { commands, env, ExtensionContext, l10n, window, workspace } from 'vscode';
 import * as extensionV1 from './extensionV1';
 import * as extensionV2 from './extensionV2';
@@ -10,76 +9,62 @@ import { createExperimentationService } from './common/experimentation';
 import { MicrosoftAuthenticationTelemetryReporter } from './common/telemetryReporter';
 import { IExperimentationService } from 'vscode-tas-client';
 import Logger from './logger';
-
 function shouldUseMsal(expService: IExperimentationService): boolean {
-	// First check if there is a setting value to allow user to override the default
-	const inspect = workspace.getConfiguration('microsoft').inspect('useMsal');
-	if (inspect?.workspaceFolderValue !== undefined) {
-		Logger.debug(`Acquired MSAL enablement value from 'workspaceFolderValue'. Value: ${inspect.workspaceFolderValue}`);
-		return inspect.workspaceFolderValue;
-	}
-	if (inspect?.workspaceValue !== undefined) {
-		Logger.debug(`Acquired MSAL enablement value from 'workspaceValue'. Value: ${inspect.workspaceValue}`);
-		return inspect.workspaceValue;
-	}
-	if (inspect?.globalValue !== undefined) {
-		Logger.debug(`Acquired MSAL enablement value from 'globalValue'. Value: ${inspect.globalValue}`);
-		return inspect.globalValue;
-	}
-
-	// Then check if the experiment value
-	const expValue = expService.getTreatmentVariable('vscode', 'microsoft.useMsal');
-	if (expValue !== undefined) {
-		Logger.debug(`Acquired MSAL enablement value from 'exp'. Value: ${expValue}`);
-		return expValue;
-	}
-
-	Logger.debug('Acquired MSAL enablement value from default. Value: false');
-	// If no setting or experiment value is found, default to false
-	return false;
+    // First check if there is a setting value to allow user to override the default
+    const inspect = workspace.getConfiguration('microsoft').inspect('useMsal');
+    if (inspect?.workspaceFolderValue !== undefined) {
+        Logger.debug(`Acquired MSAL enablement value from 'workspaceFolderValue'. Value: ${inspect.workspaceFolderValue}`);
+        return inspect.workspaceFolderValue;
+    }
+    if (inspect?.workspaceValue !== undefined) {
+        Logger.debug(`Acquired MSAL enablement value from 'workspaceValue'. Value: ${inspect.workspaceValue}`);
+        return inspect.workspaceValue;
+    }
+    if (inspect?.globalValue !== undefined) {
+        Logger.debug(`Acquired MSAL enablement value from 'globalValue'. Value: ${inspect.globalValue}`);
+        return inspect.globalValue;
+    }
+    // Then check if the experiment value
+    const expValue = expService.getTreatmentVariable('vscode', 'microsoft.useMsal');
+    if (expValue !== undefined) {
+        Logger.debug(`Acquired MSAL enablement value from 'exp'. Value: ${expValue}`);
+        return expValue;
+    }
+    Logger.debug('Acquired MSAL enablement value from default. Value: false');
+    // If no setting or experiment value is found, default to false
+    return false;
 }
 let useMsal: boolean | undefined;
-
 export async function activate(context: ExtensionContext) {
-	const mainTelemetryReporter = new MicrosoftAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey);
-	const expService = await createExperimentationService(
-		context,
-		mainTelemetryReporter,
-		env.uriScheme !== 'vscode', // isPreRelease
-	);
-	useMsal = shouldUseMsal(expService);
-
-	context.subscriptions.push(workspace.onDidChangeConfiguration(async e => {
-		if (!e.affectsConfiguration('microsoft.useMsal') || useMsal === shouldUseMsal(expService)) {
-			return;
-		}
-
-		const reload = l10n.t('Reload');
-		const result = await window.showInformationMessage(
-			'Reload required',
-			{
-				modal: true,
-				detail: l10n.t('Microsoft Account configuration has been changed.'),
-			},
-			reload
-		);
-
-		if (result === reload) {
-			commands.executeCommand('workbench.action.reloadWindow');
-		}
-	}));
-	// Only activate the new extension if we are not running in a browser environment
-	if (useMsal && typeof navigator === 'undefined') {
-		await extensionV2.activate(context, mainTelemetryReporter);
-	} else {
-		await extensionV1.activate(context, mainTelemetryReporter.telemetryReporter);
-	}
+    const mainTelemetryReporter = new MicrosoftAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey);
+    const expService = await createExperimentationService(context, mainTelemetryReporter, env.uriScheme !== 'vscode');
+    useMsal = shouldUseMsal(expService);
+    context.subscriptions.push(workspace.onDidChangeConfiguration(async (e) => {
+        if (!e.affectsConfiguration('microsoft.useMsal') || useMsal === shouldUseMsal(expService)) {
+            return;
+        }
+        const reload = l10n.t('Reload');
+        const result = await window.showInformationMessage('Reload required', {
+            modal: true,
+            detail: l10n.t('Microsoft Account configuration has been changed.'),
+        }, reload);
+        if (result === reload) {
+            commands.executeCommand('workbench.action.reloadWindow');
+        }
+    }));
+    // Only activate the new extension if we are not running in a browser environment
+    if (useMsal && typeof navigator === 'undefined') {
+        await extensionV2.activate(context, mainTelemetryReporter);
+    }
+    else {
+        await extensionV1.activate(context, mainTelemetryReporter.telemetryReporter);
+    }
 }
-
 export function deactivate() {
-	if (useMsal) {
-		extensionV2.deactivate();
-	} else {
-		extensionV1.deactivate();
-	}
+    if (useMsal) {
+        extensionV2.deactivate();
+    }
+    else {
+        extensionV1.deactivate();
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/extensionV1.ts b/extensions/microsoft-authentication/Source/extensionV1.ts
index f785adad85ccc..bba12ed31077d 100644
--- a/extensions/microsoft-authentication/Source/extensionV1.ts
+++ b/extensions/microsoft-authentication/Source/extensionV1.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env';
 import { AzureActiveDirectoryService, IStoredSession } from './AADHelper';
@@ -10,169 +9,144 @@ import { BetterTokenStorage } from './betterSecretStorage';
 import { UriEventHandler } from './UriEventHandler';
 import TelemetryReporter from '@vscode/extension-telemetry';
 import Logger from './logger';
-
 async function initMicrosoftSovereignCloudAuthProvider(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, uriHandler: UriEventHandler, tokenStorage: BetterTokenStorage): Promise {
-	const environment = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('environment');
-	let authProviderName: string | undefined;
-	if (!environment) {
-		return undefined;
-	}
-
-	if (environment === 'custom') {
-		const customEnv = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment');
-		if (!customEnv) {
-			const res = await vscode.window.showErrorMessage(vscode.l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), vscode.l10n.t('Open settings'));
-			if (res) {
-				await vscode.commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment');
-			}
-			return undefined;
-		}
-		try {
-			Environment.add(customEnv);
-		} catch (e) {
-			const res = await vscode.window.showErrorMessage(vscode.l10n.t('Error validating custom environment setting: {0}', e.message), vscode.l10n.t('Open settings'));
-			if (res) {
-				await vscode.commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment');
-			}
-			return undefined;
-		}
-		authProviderName = customEnv.name;
-	} else {
-		authProviderName = environment;
-	}
-
-	const env = Environment.get(authProviderName);
-	if (!env) {
-		const res = await vscode.window.showErrorMessage(vscode.l10n.t('The environment `{0}` is not a valid environment.', authProviderName), vscode.l10n.t('Open settings'));
-		return undefined;
-	}
-
-	const aadService = new AzureActiveDirectoryService(
-		vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }),
-		context,
-		uriHandler,
-		tokenStorage,
-		telemetryReporter,
-		env);
-	await aadService.initialize();
-
-	const disposable = vscode.authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, {
-		onDidChangeSessions: aadService.onDidChangeSessions,
-		getSessions: (scopes: string[]) => aadService.getSessions(scopes),
-		createSession: async (scopes: string[]) => {
-			try {
-				/* __GDPR__
-					"login" : {
-						"owner": "TylerLeonhardt",
-						"comment": "Used to determine the usage of the Microsoft Sovereign Cloud Auth Provider.",
-						"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
-					}
-				*/
-				telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', {
-					// Get rid of guids from telemetry.
-					scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))),
-				});
-
-				return await aadService.createSession(scopes);
-			} catch (e) {
-				/* __GDPR__
-					"loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
-				*/
-				telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed');
-
-				throw e;
-			}
-		},
-		removeSession: async (id: string) => {
-			try {
-				/* __GDPR__
-					"logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
-				*/
-				telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud');
-
-				await aadService.removeSessionById(id);
-			} catch (e) {
-				/* __GDPR__
-					"logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
-				*/
-				telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed');
-			}
-		}
-	}, { supportsMultipleAccounts: true });
-
-	context.subscriptions.push(disposable);
-	return disposable;
+    const environment = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('environment');
+    let authProviderName: string | undefined;
+    if (!environment) {
+        return undefined;
+    }
+    if (environment === 'custom') {
+        const customEnv = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment');
+        if (!customEnv) {
+            const res = await vscode.window.showErrorMessage(vscode.l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), vscode.l10n.t('Open settings'));
+            if (res) {
+                await vscode.commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment');
+            }
+            return undefined;
+        }
+        try {
+            Environment.add(customEnv);
+        }
+        catch (e) {
+            const res = await vscode.window.showErrorMessage(vscode.l10n.t('Error validating custom environment setting: {0}', e.message), vscode.l10n.t('Open settings'));
+            if (res) {
+                await vscode.commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment');
+            }
+            return undefined;
+        }
+        authProviderName = customEnv.name;
+    }
+    else {
+        authProviderName = environment;
+    }
+    const env = Environment.get(authProviderName);
+    if (!env) {
+        const res = await vscode.window.showErrorMessage(vscode.l10n.t('The environment `{0}` is not a valid environment.', authProviderName), vscode.l10n.t('Open settings'));
+        return undefined;
+    }
+    const aadService = new AzureActiveDirectoryService(vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), context, uriHandler, tokenStorage, telemetryReporter, env);
+    await aadService.initialize();
+    const disposable = vscode.authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, {
+        onDidChangeSessions: aadService.onDidChangeSessions,
+        getSessions: (scopes: string[]) => aadService.getSessions(scopes),
+        createSession: async (scopes: string[]) => {
+            try {
+                /* __GDPR__
+                    "login" : {
+                        "owner": "TylerLeonhardt",
+                        "comment": "Used to determine the usage of the Microsoft Sovereign Cloud Auth Provider.",
+                        "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
+                    }
+                */
+                telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', {
+                    // Get rid of guids from telemetry.
+                    scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))),
+                });
+                return await aadService.createSession(scopes);
+            }
+            catch (e) {
+                /* __GDPR__
+                    "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
+                */
+                telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed');
+                throw e;
+            }
+        },
+        removeSession: async (id: string) => {
+            try {
+                /* __GDPR__
+                    "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
+                */
+                telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud');
+                await aadService.removeSessionById(id);
+            }
+            catch (e) {
+                /* __GDPR__
+                    "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
+                */
+                telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed');
+            }
+        }
+    }, { supportsMultipleAccounts: true });
+    context.subscriptions.push(disposable);
+    return disposable;
 }
-
 export async function activate(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter) {
-	const uriHandler = new UriEventHandler();
-	context.subscriptions.push(uriHandler);
-	const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context);
-
-	const loginService = new AzureActiveDirectoryService(
-		Logger,
-		context,
-		uriHandler,
-		betterSecretStorage,
-		telemetryReporter,
-		Environment.AzureCloud);
-	await loginService.initialize();
-
-	context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', {
-		onDidChangeSessions: loginService.onDidChangeSessions,
-		getSessions: (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => loginService.getSessions(scopes, options?.account),
-		createSession: async (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => {
-			try {
-				/* __GDPR__
-					"login" : {
-						"owner": "TylerLeonhardt",
-						"comment": "Used to determine the usage of the Microsoft Auth Provider.",
-						"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
-					}
-				*/
-				telemetryReporter.sendTelemetryEvent('login', {
-					// Get rid of guids from telemetry.
-					scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))),
-				});
-
-				return await loginService.createSession(scopes, options?.account);
-			} catch (e) {
-				/* __GDPR__
-					"loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
-				*/
-				telemetryReporter.sendTelemetryEvent('loginFailed');
-
-				throw e;
-			}
-		},
-		removeSession: async (id: string) => {
-			try {
-				/* __GDPR__
-					"logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
-				*/
-				telemetryReporter.sendTelemetryEvent('logout');
-
-				await loginService.removeSessionById(id);
-			} catch (e) {
-				/* __GDPR__
-					"logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
-				*/
-				telemetryReporter.sendTelemetryEvent('logoutFailed');
-			}
-		}
-	}, { supportsMultipleAccounts: true }));
-
-	let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage);
-
-	context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => {
-		if (e.affectsConfiguration('microsoft-sovereign-cloud')) {
-			microsoftSovereignCloudAuthProviderDisposable?.dispose();
-			microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage);
-		}
-	}));
-
-	return;
+    const uriHandler = new UriEventHandler();
+    context.subscriptions.push(uriHandler);
+    const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context);
+    const loginService = new AzureActiveDirectoryService(Logger, context, uriHandler, betterSecretStorage, telemetryReporter, Environment.AzureCloud);
+    await loginService.initialize();
+    context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', {
+        onDidChangeSessions: loginService.onDidChangeSessions,
+        getSessions: (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => loginService.getSessions(scopes, options?.account),
+        createSession: async (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => {
+            try {
+                /* __GDPR__
+                    "login" : {
+                        "owner": "TylerLeonhardt",
+                        "comment": "Used to determine the usage of the Microsoft Auth Provider.",
+                        "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
+                    }
+                */
+                telemetryReporter.sendTelemetryEvent('login', {
+                    // Get rid of guids from telemetry.
+                    scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))),
+                });
+                return await loginService.createSession(scopes, options?.account);
+            }
+            catch (e) {
+                /* __GDPR__
+                    "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." }
+                */
+                telemetryReporter.sendTelemetryEvent('loginFailed');
+                throw e;
+            }
+        },
+        removeSession: async (id: string) => {
+            try {
+                /* __GDPR__
+                    "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." }
+                */
+                telemetryReporter.sendTelemetryEvent('logout');
+                await loginService.removeSessionById(id);
+            }
+            catch (e) {
+                /* __GDPR__
+                    "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." }
+                */
+                telemetryReporter.sendTelemetryEvent('logoutFailed');
+            }
+        }
+    }, { supportsMultipleAccounts: true }));
+    let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage);
+    context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async (e) => {
+        if (e.affectsConfiguration('microsoft-sovereign-cloud')) {
+            microsoftSovereignCloudAuthProviderDisposable?.dispose();
+            microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage);
+        }
+    }));
+    return;
 }
-
 // this method is called when your extension is deactivated
 export function deactivate() { }
diff --git a/extensions/microsoft-authentication/Source/extensionV2.ts b/extensions/microsoft-authentication/Source/extensionV2.ts
index 9610af37977c4..54378012adff9 100644
--- a/extensions/microsoft-authentication/Source/extensionV2.ts
+++ b/extensions/microsoft-authentication/Source/extensionV2.ts
@@ -2,96 +2,65 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env';
 import Logger from './logger';
 import { MsalAuthProvider } from './node/authProvider';
 import { UriEventHandler } from './UriEventHandler';
 import { authentication, commands, ExtensionContext, l10n, window, workspace, Disposable } from 'vscode';
 import { MicrosoftAuthenticationTelemetryReporter, MicrosoftSovereignCloudAuthenticationTelemetryReporter } from './common/telemetryReporter';
-
-async function initMicrosoftSovereignCloudAuthProvider(
-	context: ExtensionContext,
-	uriHandler: UriEventHandler
-): Promise {
-	const environment = workspace.getConfiguration('microsoft-sovereign-cloud').get('environment');
-	let authProviderName: string | undefined;
-	if (!environment) {
-		return undefined;
-	}
-
-	if (environment === 'custom') {
-		const customEnv = workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment');
-		if (!customEnv) {
-			const res = await window.showErrorMessage(l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), l10n.t('Open settings'));
-			if (res) {
-				await commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment');
-			}
-			return undefined;
-		}
-		try {
-			Environment.add(customEnv);
-		} catch (e) {
-			const res = await window.showErrorMessage(l10n.t('Error validating custom environment setting: {0}', e.message), l10n.t('Open settings'));
-			if (res) {
-				await commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment');
-			}
-			return undefined;
-		}
-		authProviderName = customEnv.name;
-	} else {
-		authProviderName = environment;
-	}
-
-	const env = Environment.get(authProviderName);
-	if (!env) {
-		await window.showErrorMessage(l10n.t('The environment `{0}` is not a valid environment.', authProviderName), l10n.t('Open settings'));
-		return undefined;
-	}
-
-	const authProvider = new MsalAuthProvider(
-		context,
-		new MicrosoftSovereignCloudAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey),
-		window.createOutputChannel(l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }),
-		uriHandler,
-		env
-	);
-	await authProvider.initialize();
-	const disposable = authentication.registerAuthenticationProvider(
-		'microsoft-sovereign-cloud',
-		authProviderName,
-		authProvider,
-		{ supportsMultipleAccounts: true }
-	);
-	context.subscriptions.push(disposable);
-	return disposable;
+async function initMicrosoftSovereignCloudAuthProvider(context: ExtensionContext, uriHandler: UriEventHandler): Promise {
+    const environment = workspace.getConfiguration('microsoft-sovereign-cloud').get('environment');
+    let authProviderName: string | undefined;
+    if (!environment) {
+        return undefined;
+    }
+    if (environment === 'custom') {
+        const customEnv = workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment');
+        if (!customEnv) {
+            const res = await window.showErrorMessage(l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), l10n.t('Open settings'));
+            if (res) {
+                await commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment');
+            }
+            return undefined;
+        }
+        try {
+            Environment.add(customEnv);
+        }
+        catch (e) {
+            const res = await window.showErrorMessage(l10n.t('Error validating custom environment setting: {0}', e.message), l10n.t('Open settings'));
+            if (res) {
+                await commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment');
+            }
+            return undefined;
+        }
+        authProviderName = customEnv.name;
+    }
+    else {
+        authProviderName = environment;
+    }
+    const env = Environment.get(authProviderName);
+    if (!env) {
+        await window.showErrorMessage(l10n.t('The environment `{0}` is not a valid environment.', authProviderName), l10n.t('Open settings'));
+        return undefined;
+    }
+    const authProvider = new MsalAuthProvider(context, new MicrosoftSovereignCloudAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey), window.createOutputChannel(l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), uriHandler, env);
+    await authProvider.initialize();
+    const disposable = authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, authProvider, { supportsMultipleAccounts: true });
+    context.subscriptions.push(disposable);
+    return disposable;
 }
-
 export async function activate(context: ExtensionContext, mainTelemetryReporter: MicrosoftAuthenticationTelemetryReporter) {
-	const uriHandler = new UriEventHandler();
-	context.subscriptions.push(uriHandler);
-	const authProvider = new MsalAuthProvider(
-		context,
-		mainTelemetryReporter,
-		Logger,
-		uriHandler
-	);
-	await authProvider.initialize();
-	context.subscriptions.push(authentication.registerAuthenticationProvider(
-		'microsoft',
-		'Microsoft',
-		authProvider,
-		{ supportsMultipleAccounts: true }
-	));
-
-	let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler);
-
-	context.subscriptions.push(workspace.onDidChangeConfiguration(async e => {
-		if (e.affectsConfiguration('microsoft-sovereign-cloud')) {
-			microsoftSovereignCloudAuthProviderDisposable?.dispose();
-			microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler);
-		}
-	}));
+    const uriHandler = new UriEventHandler();
+    context.subscriptions.push(uriHandler);
+    const authProvider = new MsalAuthProvider(context, mainTelemetryReporter, Logger, uriHandler);
+    await authProvider.initialize();
+    context.subscriptions.push(authentication.registerAuthenticationProvider('microsoft', 'Microsoft', authProvider, { supportsMultipleAccounts: true }));
+    let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler);
+    context.subscriptions.push(workspace.onDidChangeConfiguration(async (e) => {
+        if (e.affectsConfiguration('microsoft-sovereign-cloud')) {
+            microsoftSovereignCloudAuthProviderDisposable?.dispose();
+            microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler);
+        }
+    }));
 }
-
 export function deactivate() { }
diff --git a/extensions/microsoft-authentication/Source/logger.ts b/extensions/microsoft-authentication/Source/logger.ts
index 38ae6c732d6df..d51b99115af51 100644
--- a/extensions/microsoft-authentication/Source/logger.ts
+++ b/extensions/microsoft-authentication/Source/logger.ts
@@ -2,8 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 const Logger = vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Authentication'), { log: true });
 export default Logger;
diff --git a/extensions/microsoft-authentication/Source/node/authProvider.ts b/extensions/microsoft-authentication/Source/node/authProvider.ts
index 0c285e0cb2ba0..b7539f730f866 100644
--- a/extensions/microsoft-authentication/Source/node/authProvider.ts
+++ b/extensions/microsoft-authentication/Source/node/authProvider.ts
@@ -13,301 +13,255 @@ import { MicrosoftAccountType, MicrosoftAuthenticationTelemetryReporter } from '
 import { loopbackTemplate } from './loopbackTemplate';
 import { ScopeData } from '../common/scopeData';
 import { EventBufferer } from '../common/event';
-
 const redirectUri = 'https://vscode.dev/redirect';
 const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad';
 const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a';
-
 export class MsalAuthProvider implements AuthenticationProvider {
-
-	private readonly _disposables: { dispose(): void }[];
-	private readonly _publicClientManager: CachedPublicClientApplicationManager;
-	private readonly _eventBufferer = new EventBufferer();
-
-	/**
-	 * Event to signal a change in authentication sessions for this provider.
-	 */
-	private readonly _onDidChangeSessionsEmitter = new EventEmitter();
-
-	/**
-	 * Event to signal a change in authentication sessions for this provider.
-	 *
-	 * NOTE: This event is handled differently in the Microsoft auth provider than "typical" auth providers. Normally,
-	 * this event would fire when the provider's sessions change... which are tied to a specific list of scopes. However,
-	 * since Microsoft identity doesn't care too much about scopes (you can mint a new token from an existing token),
-	 * we just fire this event whenever the account list changes... so essentially there is one session per account.
-	 *
-	 * This is not quite how the API should be used... but this event really is just for signaling that the account list
-	 * has changed.
-	 */
-	onDidChangeSessions = this._onDidChangeSessionsEmitter.event;
-
-	constructor(
-		context: ExtensionContext,
-		private readonly _telemetryReporter: MicrosoftAuthenticationTelemetryReporter,
-		private readonly _logger: LogOutputChannel,
-		private readonly _uriHandler: UriEventHandler,
-		private readonly _env: Environment = Environment.AzureCloud
-	) {
-		this._disposables = context.subscriptions;
-		this._publicClientManager = new CachedPublicClientApplicationManager(
-			context.globalState,
-			context.secrets,
-			this._logger,
-			this._env.name
-		);
-		const accountChangeEvent = this._eventBufferer.wrapEvent(
-			this._publicClientManager.onDidAccountsChange,
-			(last, newEvent) => {
-				if (!last) {
-					return newEvent;
-				}
-				const mergedEvent = {
-					added: [...(last.added ?? []), ...(newEvent.added ?? [])],
-					deleted: [...(last.deleted ?? []), ...(newEvent.deleted ?? [])],
-					changed: [...(last.changed ?? []), ...(newEvent.changed ?? [])]
-				};
-
-				const dedupedEvent = {
-					added: Array.from(new Map(mergedEvent.added.map(item => [item.username, item])).values()),
-					deleted: Array.from(new Map(mergedEvent.deleted.map(item => [item.username, item])).values()),
-					changed: Array.from(new Map(mergedEvent.changed.map(item => [item.username, item])).values())
-				};
-
-				return dedupedEvent;
-			},
-			{ added: new Array(), deleted: new Array(), changed: new Array() }
-		)(e => this._handleAccountChange(e));
-		this._disposables.push(
-			this._onDidChangeSessionsEmitter,
-			this._publicClientManager,
-			accountChangeEvent
-		);
-	}
-
-	async initialize(): Promise {
-		await this._eventBufferer.bufferEventsAsync(() => this._publicClientManager.initialize());
-
-		// Send telemetry for existing accounts
-		for (const cachedPca of this._publicClientManager.getAll()) {
-			for (const account of cachedPca.accounts) {
-				if (!account.idTokenClaims?.tid) {
-					continue;
-				}
-				const tid = account.idTokenClaims.tid;
-				const type = tid === MSA_TID || tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD;
-				this._telemetryReporter.sendAccountEvent([], type);
-			}
-		}
-	}
-
-	/**
-	 * See {@link onDidChangeSessions} for more information on how this is used.
-	 * @param param0 Event that contains the added and removed accounts
-	 */
-	private _handleAccountChange({ added, changed, deleted }: { added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }) {
-		this._logger.debug(`[_handleAccountChange] added: ${added.length}, changed: ${changed.length}, deleted: ${deleted.length}`);
-		this._onDidChangeSessionsEmitter.fire({
-			added: added.map(this.sessionFromAccountInfo),
-			changed: changed.map(this.sessionFromAccountInfo),
-			removed: deleted.map(this.sessionFromAccountInfo)
-		});
-	}
-
-	//#region AuthenticationProvider methods
-
-	async getSessions(scopes: string[] | undefined, options?: AuthenticationGetSessionOptions): Promise {
-		const askingForAll = scopes === undefined;
-		const scopeData = new ScopeData(scopes);
-		// Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
-		this._logger.info('[getSessions]', askingForAll ? '[all]' : `[${scopeData.scopeStr}]`, 'starting');
-
-		// This branch only gets called by Core for sign out purposes and initial population of the account menu. Since we are
-		// living in a world where a "session" from Core's perspective is an account, we return 1 session per account.
-		// See the large comment on `onDidChangeSessions` for more information.
-		if (askingForAll) {
-			const allSessionsForAccounts = new Map();
-			for (const cachedPca of this._publicClientManager.getAll()) {
-				for (const account of cachedPca.accounts) {
-					if (allSessionsForAccounts.has(account.homeAccountId)) {
-						continue;
-					}
-					allSessionsForAccounts.set(account.homeAccountId, this.sessionFromAccountInfo(account));
-				}
-			}
-			const allSessions = Array.from(allSessionsForAccounts.values());
-			this._logger.info('[getSessions] [all]', `returned ${allSessions.length} session(s)`);
-			return allSessions;
-		}
-
-		const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
-		const sessions = await this.getAllSessionsForPca(cachedPca, scopeData.originalScopes, scopeData.scopesToSend, options?.account);
-		this._logger.info(`[getSessions] [${scopeData.scopeStr}] returned ${sessions.length} session(s)`);
-		return sessions;
-
-	}
-
-	async createSession(scopes: readonly string[], options: AuthenticationProviderSessionOptions): Promise {
-		const scopeData = new ScopeData(scopes);
-		// Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
-
-		this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'starting');
-		const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
-		let result: AuthenticationResult | undefined;
-
-		try {
-			result = await cachedPca.acquireTokenInteractive({
-				openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },
-				scopes: scopeData.scopesToSend,
-				// The logic for rendering one or the other of these templates is in the
-				// template itself, so we pass the same one for both.
-				successTemplate: loopbackTemplate,
-				errorTemplate: loopbackTemplate,
-				// Pass the label of the account to the login hint so that we prefer signing in to that account
-				loginHint: options.account?.label,
-				// If we aren't logging in to a specific account, then we can use the prompt to make sure they get
-				// the option to choose a different account.
-				prompt: options.account?.label ? undefined : 'select_account'
-			});
-		} catch (e) {
-			if (e instanceof CancellationError) {
-				const yes = l10n.t('Yes');
-				const result = await window.showErrorMessage(
-					l10n.t('Having trouble logging in?'),
-					{
-						modal: true,
-						detail: l10n.t('Would you like to try a different way to sign in to your Microsoft account? ({0})', 'protocol handler')
-					},
-					yes
-				);
-				if (!result) {
-					this._telemetryReporter.sendLoginFailedEvent();
-					throw e;
-				}
-			}
-			// This error comes from the backend and is likely not due to the user's machine
-			// failing to open a port or something local that would require us to try the
-			// URL handler loopback client.
-			if (e instanceof ServerError) {
-				this._telemetryReporter.sendLoginFailedEvent();
-				throw e;
-			}
-
-			// The user wants to try the loopback client or we got an error likely due to spinning up the server
-			const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri, this._logger);
-			try {
-				result = await cachedPca.acquireTokenInteractive({
-					openBrowser: (url: string) => loopbackClient.openBrowser(url),
-					scopes: scopeData.scopesToSend,
-					loopbackClient,
-					loginHint: options.account?.label,
-					prompt: options.account?.label ? undefined : 'select_account'
-				});
-			} catch (e) {
-				this._telemetryReporter.sendLoginFailedEvent();
-				throw e;
-			}
-		}
-
-		if (!result) {
-			this._telemetryReporter.sendLoginFailedEvent();
-			throw new Error('No result returned from MSAL');
-		}
-
-		const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes);
-		this._telemetryReporter.sendLoginEvent(session.scopes);
-		this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'returned session');
-		// This is the only scenario in which we need to fire the _onDidChangeSessionsEmitter out of band...
-		// the badge flow (when the client passes no options in to getSession) will only remove a badge if a session
-		// was created that _matches the scopes_ that that badge requests. See `onDidChangeSessions` for more info.
-		// TODO: This should really be fixed in Core.
-		this._onDidChangeSessionsEmitter.fire({ added: [session], changed: [], removed: [] });
-		return session;
-	}
-
-	async removeSession(sessionId: string): Promise {
-		this._logger.info('[removeSession]', sessionId, 'starting');
-		const promises = new Array>();
-		for (const cachedPca of this._publicClientManager.getAll()) {
-			const accounts = cachedPca.accounts;
-			for (const account of accounts) {
-				if (account.homeAccountId === sessionId) {
-					this._telemetryReporter.sendLogoutEvent();
-					promises.push(cachedPca.removeAccount(account));
-					this._logger.info(`[removeSession] [${sessionId}] [${cachedPca.clientId}] [${cachedPca.authority}] removing session...`);
-				}
-			}
-		}
-		if (!promises.length) {
-			this._logger.info('[removeSession]', sessionId, 'session not found');
-			return;
-		}
-		const results = await Promise.allSettled(promises);
-		for (const result of results) {
-			if (result.status === 'rejected') {
-				this._telemetryReporter.sendLogoutFailedEvent();
-				this._logger.error('[removeSession]', sessionId, 'error removing session', result.reason);
-			}
-		}
-
-		this._logger.info('[removeSession]', sessionId, `attempted to remove ${promises.length} sessions`);
-	}
-
-	//#endregion
-
-	private async getOrCreatePublicClientApplication(clientId: string, tenant: string): Promise {
-		const authority = new URL(tenant, this._env.activeDirectoryEndpointUrl).toString();
-		return await this._publicClientManager.getOrCreate(clientId, authority);
-	}
-
-	private async getAllSessionsForPca(
-		cachedPca: ICachedPublicClientApplication,
-		originalScopes: readonly string[],
-		scopesToSend: string[],
-		accountFilter?: AuthenticationSessionAccountInformation
-	): Promise {
-		const accounts = accountFilter
-			? cachedPca.accounts.filter(a => a.homeAccountId === accountFilter.id)
-			: cachedPca.accounts;
-		const sessions: AuthenticationSession[] = [];
-		return this._eventBufferer.bufferEventsAsync(async () => {
-			for (const account of accounts) {
-				try {
-					const result = await cachedPca.acquireTokenSilent({ account, scopes: scopesToSend, redirectUri });
-					sessions.push(this.sessionFromAuthenticationResult(result, originalScopes));
-				} catch (e) {
-					// If we can't get a token silently, the account is probably in a bad state so we should skip it
-					// MSAL will log this already, so we don't need to log it again
-					continue;
-				}
-			}
-			return sessions;
-		});
-	}
-
-	private sessionFromAuthenticationResult(result: AuthenticationResult, scopes: readonly string[]): AuthenticationSession & { idToken: string } {
-		return {
-			accessToken: result.accessToken,
-			idToken: result.idToken,
-			id: result.account?.homeAccountId ?? result.uniqueId,
-			account: {
-				id: result.account?.homeAccountId ?? result.uniqueId,
-				label: result.account?.username ?? 'Unknown',
-			},
-			scopes
-		};
-	}
-
-	private sessionFromAccountInfo(account: AccountInfo): AuthenticationSession {
-		return {
-			accessToken: '1234',
-			id: account.homeAccountId,
-			scopes: [],
-			account: {
-				id: account.homeAccountId,
-				label: account.username
-			},
-			idToken: account.idToken,
-		};
-	}
+    private readonly _disposables: {
+        dispose(): void;
+    }[];
+    private readonly _publicClientManager: CachedPublicClientApplicationManager;
+    private readonly _eventBufferer = new EventBufferer();
+    /**
+     * Event to signal a change in authentication sessions for this provider.
+     */
+    private readonly _onDidChangeSessionsEmitter = new EventEmitter();
+    /**
+     * Event to signal a change in authentication sessions for this provider.
+     *
+     * NOTE: This event is handled differently in the Microsoft auth provider than "typical" auth providers. Normally,
+     * this event would fire when the provider's sessions change... which are tied to a specific list of scopes. However,
+     * since Microsoft identity doesn't care too much about scopes (you can mint a new token from an existing token),
+     * we just fire this event whenever the account list changes... so essentially there is one session per account.
+     *
+     * This is not quite how the API should be used... but this event really is just for signaling that the account list
+     * has changed.
+     */
+    onDidChangeSessions = this._onDidChangeSessionsEmitter.event;
+    constructor(context: ExtensionContext, private readonly _telemetryReporter: MicrosoftAuthenticationTelemetryReporter, private readonly _logger: LogOutputChannel, private readonly _uriHandler: UriEventHandler, private readonly _env: Environment = Environment.AzureCloud) {
+        this._disposables = context.subscriptions;
+        this._publicClientManager = new CachedPublicClientApplicationManager(context.globalState, context.secrets, this._logger, this._env.name);
+        const accountChangeEvent = this._eventBufferer.wrapEvent(this._publicClientManager.onDidAccountsChange, (last, newEvent) => {
+            if (!last) {
+                return newEvent;
+            }
+            const mergedEvent = {
+                added: [...(last.added ?? []), ...(newEvent.added ?? [])],
+                deleted: [...(last.deleted ?? []), ...(newEvent.deleted ?? [])],
+                changed: [...(last.changed ?? []), ...(newEvent.changed ?? [])]
+            };
+            const dedupedEvent = {
+                added: Array.from(new Map(mergedEvent.added.map(item => [item.username, item])).values()),
+                deleted: Array.from(new Map(mergedEvent.deleted.map(item => [item.username, item])).values()),
+                changed: Array.from(new Map(mergedEvent.changed.map(item => [item.username, item])).values())
+            };
+            return dedupedEvent;
+        }, { added: new Array(), deleted: new Array(), changed: new Array() })(e => this._handleAccountChange(e));
+        this._disposables.push(this._onDidChangeSessionsEmitter, this._publicClientManager, accountChangeEvent);
+    }
+    async initialize(): Promise {
+        await this._eventBufferer.bufferEventsAsync(() => this._publicClientManager.initialize());
+        // Send telemetry for existing accounts
+        for (const cachedPca of this._publicClientManager.getAll()) {
+            for (const account of cachedPca.accounts) {
+                if (!account.idTokenClaims?.tid) {
+                    continue;
+                }
+                const tid = account.idTokenClaims.tid;
+                const type = tid === MSA_TID || tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD;
+                this._telemetryReporter.sendAccountEvent([], type);
+            }
+        }
+    }
+    /**
+     * See {@link onDidChangeSessions} for more information on how this is used.
+     * @param param0 Event that contains the added and removed accounts
+     */
+    private _handleAccountChange({ added, changed, deleted }: {
+        added: AccountInfo[];
+        changed: AccountInfo[];
+        deleted: AccountInfo[];
+    }) {
+        this._logger.debug(`[_handleAccountChange] added: ${added.length}, changed: ${changed.length}, deleted: ${deleted.length}`);
+        this._onDidChangeSessionsEmitter.fire({
+            added: added.map(this.sessionFromAccountInfo),
+            changed: changed.map(this.sessionFromAccountInfo),
+            removed: deleted.map(this.sessionFromAccountInfo)
+        });
+    }
+    //#region AuthenticationProvider methods
+    async getSessions(scopes: string[] | undefined, options?: AuthenticationGetSessionOptions): Promise {
+        const askingForAll = scopes === undefined;
+        const scopeData = new ScopeData(scopes);
+        // Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
+        this._logger.info('[getSessions]', askingForAll ? '[all]' : `[${scopeData.scopeStr}]`, 'starting');
+        // This branch only gets called by Core for sign out purposes and initial population of the account menu. Since we are
+        // living in a world where a "session" from Core's perspective is an account, we return 1 session per account.
+        // See the large comment on `onDidChangeSessions` for more information.
+        if (askingForAll) {
+            const allSessionsForAccounts = new Map();
+            for (const cachedPca of this._publicClientManager.getAll()) {
+                for (const account of cachedPca.accounts) {
+                    if (allSessionsForAccounts.has(account.homeAccountId)) {
+                        continue;
+                    }
+                    allSessionsForAccounts.set(account.homeAccountId, this.sessionFromAccountInfo(account));
+                }
+            }
+            const allSessions = Array.from(allSessionsForAccounts.values());
+            this._logger.info('[getSessions] [all]', `returned ${allSessions.length} session(s)`);
+            return allSessions;
+        }
+        const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
+        const sessions = await this.getAllSessionsForPca(cachedPca, scopeData.originalScopes, scopeData.scopesToSend, options?.account);
+        this._logger.info(`[getSessions] [${scopeData.scopeStr}] returned ${sessions.length} session(s)`);
+        return sessions;
+    }
+    async createSession(scopes: readonly string[], options: AuthenticationProviderSessionOptions): Promise {
+        const scopeData = new ScopeData(scopes);
+        // Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
+        this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'starting');
+        const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
+        let result: AuthenticationResult | undefined;
+        try {
+            result = await cachedPca.acquireTokenInteractive({
+                openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },
+                scopes: scopeData.scopesToSend,
+                // The logic for rendering one or the other of these templates is in the
+                // template itself, so we pass the same one for both.
+                successTemplate: loopbackTemplate,
+                errorTemplate: loopbackTemplate,
+                // Pass the label of the account to the login hint so that we prefer signing in to that account
+                loginHint: options.account?.label,
+                // If we aren't logging in to a specific account, then we can use the prompt to make sure they get
+                // the option to choose a different account.
+                prompt: options.account?.label ? undefined : 'select_account'
+            });
+        }
+        catch (e) {
+            if (e instanceof CancellationError) {
+                const yes = l10n.t('Yes');
+                const result = await window.showErrorMessage(l10n.t('Having trouble logging in?'), {
+                    modal: true,
+                    detail: l10n.t('Would you like to try a different way to sign in to your Microsoft account? ({0})', 'protocol handler')
+                }, yes);
+                if (!result) {
+                    this._telemetryReporter.sendLoginFailedEvent();
+                    throw e;
+                }
+            }
+            // This error comes from the backend and is likely not due to the user's machine
+            // failing to open a port or something local that would require us to try the
+            // URL handler loopback client.
+            if (e instanceof ServerError) {
+                this._telemetryReporter.sendLoginFailedEvent();
+                throw e;
+            }
+            // The user wants to try the loopback client or we got an error likely due to spinning up the server
+            const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri, this._logger);
+            try {
+                result = await cachedPca.acquireTokenInteractive({
+                    openBrowser: (url: string) => loopbackClient.openBrowser(url),
+                    scopes: scopeData.scopesToSend,
+                    loopbackClient,
+                    loginHint: options.account?.label,
+                    prompt: options.account?.label ? undefined : 'select_account'
+                });
+            }
+            catch (e) {
+                this._telemetryReporter.sendLoginFailedEvent();
+                throw e;
+            }
+        }
+        if (!result) {
+            this._telemetryReporter.sendLoginFailedEvent();
+            throw new Error('No result returned from MSAL');
+        }
+        const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes);
+        this._telemetryReporter.sendLoginEvent(session.scopes);
+        this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'returned session');
+        // This is the only scenario in which we need to fire the _onDidChangeSessionsEmitter out of band...
+        // the badge flow (when the client passes no options in to getSession) will only remove a badge if a session
+        // was created that _matches the scopes_ that that badge requests. See `onDidChangeSessions` for more info.
+        // TODO: This should really be fixed in Core.
+        this._onDidChangeSessionsEmitter.fire({ added: [session], changed: [], removed: [] });
+        return session;
+    }
+    async removeSession(sessionId: string): Promise {
+        this._logger.info('[removeSession]', sessionId, 'starting');
+        const promises = new Array>();
+        for (const cachedPca of this._publicClientManager.getAll()) {
+            const accounts = cachedPca.accounts;
+            for (const account of accounts) {
+                if (account.homeAccountId === sessionId) {
+                    this._telemetryReporter.sendLogoutEvent();
+                    promises.push(cachedPca.removeAccount(account));
+                    this._logger.info(`[removeSession] [${sessionId}] [${cachedPca.clientId}] [${cachedPca.authority}] removing session...`);
+                }
+            }
+        }
+        if (!promises.length) {
+            this._logger.info('[removeSession]', sessionId, 'session not found');
+            return;
+        }
+        const results = await Promise.allSettled(promises);
+        for (const result of results) {
+            if (result.status === 'rejected') {
+                this._telemetryReporter.sendLogoutFailedEvent();
+                this._logger.error('[removeSession]', sessionId, 'error removing session', result.reason);
+            }
+        }
+        this._logger.info('[removeSession]', sessionId, `attempted to remove ${promises.length} sessions`);
+    }
+    //#endregion
+    private async getOrCreatePublicClientApplication(clientId: string, tenant: string): Promise {
+        const authority = new URL(tenant, this._env.activeDirectoryEndpointUrl).toString();
+        return await this._publicClientManager.getOrCreate(clientId, authority);
+    }
+    private async getAllSessionsForPca(cachedPca: ICachedPublicClientApplication, originalScopes: readonly string[], scopesToSend: string[], accountFilter?: AuthenticationSessionAccountInformation): Promise {
+        const accounts = accountFilter
+            ? cachedPca.accounts.filter(a => a.homeAccountId === accountFilter.id)
+            : cachedPca.accounts;
+        const sessions: AuthenticationSession[] = [];
+        return this._eventBufferer.bufferEventsAsync(async () => {
+            for (const account of accounts) {
+                try {
+                    const result = await cachedPca.acquireTokenSilent({ account, scopes: scopesToSend, redirectUri });
+                    sessions.push(this.sessionFromAuthenticationResult(result, originalScopes));
+                }
+                catch (e) {
+                    // If we can't get a token silently, the account is probably in a bad state so we should skip it
+                    // MSAL will log this already, so we don't need to log it again
+                    continue;
+                }
+            }
+            return sessions;
+        });
+    }
+    private sessionFromAuthenticationResult(result: AuthenticationResult, scopes: readonly string[]): AuthenticationSession & {
+        idToken: string;
+    } {
+        return {
+            accessToken: result.accessToken,
+            idToken: result.idToken,
+            id: result.account?.homeAccountId ?? result.uniqueId,
+            account: {
+                id: result.account?.homeAccountId ?? result.uniqueId,
+                label: result.account?.username ?? 'Unknown',
+            },
+            scopes
+        };
+    }
+    private sessionFromAccountInfo(account: AccountInfo): AuthenticationSession {
+        return {
+            accessToken: '1234',
+            id: account.homeAccountId,
+            scopes: [],
+            account: {
+                id: account.homeAccountId,
+                label: account.username
+            },
+            idToken: account.idToken,
+        };
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/node/authServer.ts b/extensions/microsoft-authentication/Source/node/authServer.ts
index 2d6a8d03861e7..621672c64ff69 100644
--- a/extensions/microsoft-authentication/Source/node/authServer.ts
+++ b/extensions/microsoft-authentication/Source/node/authServer.ts
@@ -7,201 +7,192 @@ import { URL } from 'url';
 import * as fs from 'fs';
 import * as path from 'path';
 import { randomBytes } from 'crypto';
-
 function sendFile(res: http.ServerResponse, filepath: string) {
-	fs.readFile(filepath, (err, body) => {
-		if (err) {
-			console.error(err);
-			res.writeHead(404);
-			res.end();
-		} else {
-			res.writeHead(200, {
-				'content-length': body.length,
-			});
-			res.end(body);
-		}
-	});
+    fs.readFile(filepath, (err, body) => {
+        if (err) {
+            console.error(err);
+            res.writeHead(404);
+            res.end();
+        }
+        else {
+            res.writeHead(200, {
+                'content-length': body.length,
+            });
+            res.end(body);
+        }
+    });
 }
-
 interface IOAuthResult {
-	code: string;
-	state: string;
+    code: string;
+    state: string;
 }
-
 interface ILoopbackServer {
-	/**
-	 * If undefined, the server is not started yet.
-	 */
-	port: number | undefined;
-
-	/**
-	 * The nonce used
-	 */
-	nonce: string;
-
-	/**
-	 * The state parameter used in the OAuth flow.
-	 */
-	state: string | undefined;
-
-	/**
-	 * Starts the server.
-	 * @returns The port to listen on.
-	 * @throws If the server fails to start.
-	 * @throws If the server is already started.
-	 */
-	start(): Promise;
-	/**
-	 * Stops the server.
-	 * @throws If the server is not started.
-	 * @throws If the server fails to stop.
-	 */
-	stop(): Promise;
-	/**
-	 * Returns a promise that resolves to the result of the OAuth flow.
-	 */
-	waitForOAuthResponse(): Promise;
+    /**
+     * If undefined, the server is not started yet.
+     */
+    port: number | undefined;
+    /**
+     * The nonce used
+     */
+    nonce: string;
+    /**
+     * The state parameter used in the OAuth flow.
+     */
+    state: string | undefined;
+    /**
+     * Starts the server.
+     * @returns The port to listen on.
+     * @throws If the server fails to start.
+     * @throws If the server is already started.
+     */
+    start(): Promise;
+    /**
+     * Stops the server.
+     * @throws If the server is not started.
+     * @throws If the server fails to stop.
+     */
+    stop(): Promise;
+    /**
+     * Returns a promise that resolves to the result of the OAuth flow.
+     */
+    waitForOAuthResponse(): Promise;
 }
-
 export class LoopbackAuthServer implements ILoopbackServer {
-	private readonly _server: http.Server;
-	private readonly _resultPromise: Promise;
-	private _startingRedirect: URL;
-
-	public nonce = randomBytes(16).toString('base64');
-	public port: number | undefined;
-
-	public set state(state: string | undefined) {
-		if (state) {
-			this._startingRedirect.searchParams.set('state', state);
-		} else {
-			this._startingRedirect.searchParams.delete('state');
-		}
-	}
-	public get state(): string | undefined {
-		return this._startingRedirect.searchParams.get('state') ?? undefined;
-	}
-
-	constructor(serveRoot: string, startingRedirect: string) {
-		if (!serveRoot) {
-			throw new Error('serveRoot must be defined');
-		}
-		if (!startingRedirect) {
-			throw new Error('startingRedirect must be defined');
-		}
-		this._startingRedirect = new URL(startingRedirect);
-		let deferred: { resolve: (result: IOAuthResult) => void; reject: (reason: any) => void };
-		this._resultPromise = new Promise((resolve, reject) => deferred = { resolve, reject });
-
-		this._server = http.createServer((req, res) => {
-			const reqUrl = new URL(req.url!, `http://${req.headers.host}`);
-			switch (reqUrl.pathname) {
-				case '/signin': {
-					const receivedNonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+');
-					if (receivedNonce !== this.nonce) {
-						res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` });
-						res.end();
-					}
-					res.writeHead(302, { location: this._startingRedirect.toString() });
-					res.end();
-					break;
-				}
-				case '/callback': {
-					const code = reqUrl.searchParams.get('code') ?? undefined;
-					const state = reqUrl.searchParams.get('state') ?? undefined;
-					const nonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+');
-					const error = reqUrl.searchParams.get('error') ?? undefined;
-					if (error) {
-						res.writeHead(302, { location: `/?error=${reqUrl.searchParams.get('error_description')}` });
-						res.end();
-						deferred.reject(new Error(error));
-						break;
-					}
-					if (!code || !state || !nonce) {
-						res.writeHead(400);
-						res.end();
-						break;
-					}
-					if (this.state !== state) {
-						res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}` });
-						res.end();
-						deferred.reject(new Error('State does not match.'));
-						break;
-					}
-					if (this.nonce !== nonce) {
-						res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` });
-						res.end();
-						deferred.reject(new Error('Nonce does not match.'));
-						break;
-					}
-					deferred.resolve({ code, state });
-					res.writeHead(302, { location: '/' });
-					res.end();
-					break;
-				}
-				// Serve the static files
-				case '/':
-					sendFile(res, path.join(serveRoot, 'index.html'));
-					break;
-				default:
-					// substring to get rid of leading '/'
-					sendFile(res, path.join(serveRoot, reqUrl.pathname.substring(1)));
-					break;
-			}
-		});
-	}
-
-	public start(): Promise {
-		return new Promise((resolve, reject) => {
-			if (this._server.listening) {
-				throw new Error('Server is already started');
-			}
-			const portTimeout = setTimeout(() => {
-				reject(new Error('Timeout waiting for port'));
-			}, 5000);
-			this._server.on('listening', () => {
-				const address = this._server.address();
-				if (typeof address === 'string') {
-					this.port = parseInt(address);
-				} else if (address instanceof Object) {
-					this.port = address.port;
-				} else {
-					throw new Error('Unable to determine port');
-				}
-
-				clearTimeout(portTimeout);
-
-				// set state which will be used to redirect back to vscode
-				this.state = `http://127.0.0.1:${this.port}/callback?nonce=${encodeURIComponent(this.nonce)}`;
-
-				resolve(this.port);
-			});
-			this._server.on('error', err => {
-				reject(new Error(`Error listening to server: ${err}`));
-			});
-			this._server.on('close', () => {
-				reject(new Error('Closed'));
-			});
-			this._server.listen(0, '127.0.0.1');
-		});
-	}
-
-	public stop(): Promise {
-		return new Promise((resolve, reject) => {
-			if (!this._server.listening) {
-				throw new Error('Server is not started');
-			}
-			this._server.close((err) => {
-				if (err) {
-					reject(err);
-				} else {
-					resolve();
-				}
-			});
-		});
-	}
-
-	public waitForOAuthResponse(): Promise {
-		return this._resultPromise;
-	}
+    private readonly _server: http.Server;
+    private readonly _resultPromise: Promise;
+    private _startingRedirect: URL;
+    public nonce = randomBytes(16).toString('base64');
+    public port: number | undefined;
+    public set state(state: string | undefined) {
+        if (state) {
+            this._startingRedirect.searchParams.set('state', state);
+        }
+        else {
+            this._startingRedirect.searchParams.delete('state');
+        }
+    }
+    public get state(): string | undefined {
+        return this._startingRedirect.searchParams.get('state') ?? undefined;
+    }
+    constructor(serveRoot: string, startingRedirect: string) {
+        if (!serveRoot) {
+            throw new Error('serveRoot must be defined');
+        }
+        if (!startingRedirect) {
+            throw new Error('startingRedirect must be defined');
+        }
+        this._startingRedirect = new URL(startingRedirect);
+        let deferred: {
+            resolve: (result: IOAuthResult) => void;
+            reject: (reason: any) => void;
+        };
+        this._resultPromise = new Promise((resolve, reject) => deferred = { resolve, reject });
+        this._server = http.createServer((req, res) => {
+            const reqUrl = new URL(req.url!, `http://${req.headers.host}`);
+            switch (reqUrl.pathname) {
+                case '/signin': {
+                    const receivedNonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+');
+                    if (receivedNonce !== this.nonce) {
+                        res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` });
+                        res.end();
+                    }
+                    res.writeHead(302, { location: this._startingRedirect.toString() });
+                    res.end();
+                    break;
+                }
+                case '/callback': {
+                    const code = reqUrl.searchParams.get('code') ?? undefined;
+                    const state = reqUrl.searchParams.get('state') ?? undefined;
+                    const nonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+');
+                    const error = reqUrl.searchParams.get('error') ?? undefined;
+                    if (error) {
+                        res.writeHead(302, { location: `/?error=${reqUrl.searchParams.get('error_description')}` });
+                        res.end();
+                        deferred.reject(new Error(error));
+                        break;
+                    }
+                    if (!code || !state || !nonce) {
+                        res.writeHead(400);
+                        res.end();
+                        break;
+                    }
+                    if (this.state !== state) {
+                        res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}` });
+                        res.end();
+                        deferred.reject(new Error('State does not match.'));
+                        break;
+                    }
+                    if (this.nonce !== nonce) {
+                        res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` });
+                        res.end();
+                        deferred.reject(new Error('Nonce does not match.'));
+                        break;
+                    }
+                    deferred.resolve({ code, state });
+                    res.writeHead(302, { location: '/' });
+                    res.end();
+                    break;
+                }
+                // Serve the static files
+                case '/':
+                    sendFile(res, path.join(serveRoot, 'index.html'));
+                    break;
+                default:
+                    // substring to get rid of leading '/'
+                    sendFile(res, path.join(serveRoot, reqUrl.pathname.substring(1)));
+                    break;
+            }
+        });
+    }
+    public start(): Promise {
+        return new Promise((resolve, reject) => {
+            if (this._server.listening) {
+                throw new Error('Server is already started');
+            }
+            const portTimeout = setTimeout(() => {
+                reject(new Error('Timeout waiting for port'));
+            }, 5000);
+            this._server.on('listening', () => {
+                const address = this._server.address();
+                if (typeof address === 'string') {
+                    this.port = parseInt(address);
+                }
+                else if (address instanceof Object) {
+                    this.port = address.port;
+                }
+                else {
+                    throw new Error('Unable to determine port');
+                }
+                clearTimeout(portTimeout);
+                // set state which will be used to redirect back to vscode
+                this.state = `http://127.0.0.1:${this.port}/callback?nonce=${encodeURIComponent(this.nonce)}`;
+                resolve(this.port);
+            });
+            this._server.on('error', err => {
+                reject(new Error(`Error listening to server: ${err}`));
+            });
+            this._server.on('close', () => {
+                reject(new Error('Closed'));
+            });
+            this._server.listen(0, '127.0.0.1');
+        });
+    }
+    public stop(): Promise {
+        return new Promise((resolve, reject) => {
+            if (!this._server.listening) {
+                throw new Error('Server is not started');
+            }
+            this._server.close((err) => {
+                if (err) {
+                    reject(err);
+                }
+                else {
+                    resolve();
+                }
+            });
+        });
+    }
+    public waitForOAuthResponse(): Promise {
+        return this._resultPromise;
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/node/buffer.ts b/extensions/microsoft-authentication/Source/node/buffer.ts
index 1ed028d4faa80..85af82ddd94c1 100644
--- a/extensions/microsoft-authentication/Source/node/buffer.ts
+++ b/extensions/microsoft-authentication/Source/node/buffer.ts
@@ -2,11 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function base64Encode(text: string): string {
-	return Buffer.from(text, 'binary').toString('base64');
+    return Buffer.from(text, 'binary').toString('base64');
 }
-
 export function base64Decode(text: string): string {
-	return Buffer.from(text, 'base64').toString('utf8');
+    return Buffer.from(text, 'base64').toString('utf8');
 }
diff --git a/extensions/microsoft-authentication/Source/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/Source/node/cachedPublicClientApplication.ts
index c679610466a24..ecab491c0c974 100644
--- a/extensions/microsoft-authentication/Source/node/cachedPublicClientApplication.ts
+++ b/extensions/microsoft-authentication/Source/node/cachedPublicClientApplication.ts
@@ -2,197 +2,150 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { PublicClientApplication, AccountInfo, Configuration, SilentFlowRequest, AuthenticationResult, InteractiveRequest, LogLevel } from '@azure/msal-node';
 import { Disposable, Memento, SecretStorage, LogOutputChannel, window, ProgressLocation, l10n, EventEmitter } from 'vscode';
 import { Delayer, raceCancellationAndTimeoutError } from '../common/async';
 import { SecretStorageCachePlugin } from '../common/cachePlugin';
 import { MsalLoggerOptions } from '../common/loggerOptions';
 import { ICachedPublicClientApplication } from '../common/publicClientCache';
-
 export class CachedPublicClientApplication implements ICachedPublicClientApplication {
-	private _pca: PublicClientApplication;
-	private _sequencer = new Sequencer();
-	private readonly _refreshDelayer = new DelayerByKey();
-
-	private _accounts: AccountInfo[] = [];
-	private readonly _disposable: Disposable;
-
-	private readonly _loggerOptions = new MsalLoggerOptions(this._logger);
-	private readonly _secretStorageCachePlugin = new SecretStorageCachePlugin(
-		this._secretStorage,
-		// Include the prefix as a differentiator to other secrets
-		`pca:${JSON.stringify({ clientId: this._clientId, authority: this._authority })}`
-	);
-	private readonly _config: Configuration = {
-		auth: { clientId: this._clientId, authority: this._authority },
-		system: {
-			loggerOptions: {
-				correlationId: `${this._clientId}] [${this._authority}`,
-				loggerCallback: (level, message, containsPii) => this._loggerOptions.loggerCallback(level, message, containsPii),
-				logLevel: LogLevel.Trace
-			}
-		},
-		cache: {
-			cachePlugin: this._secretStorageCachePlugin
-		}
-	};
-
-	/**
-	 * We keep track of the last time an account was removed so we can recreate the PCA if we detect that an account was removed.
-	 * This is due to MSAL-node not providing a way to detect when an account is removed from the cache. An internal issue has been
-	 * filed to track this. If MSAL-node ever provides a way to detect this or handle this better in the Persistant Cache Plugin,
-	 * we can remove this logic.
-	 */
-	private _lastCreated: Date;
-
-	//#region Events
-
-	private readonly _onDidAccountsChangeEmitter = new EventEmitter<{ added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }>;
-	readonly onDidAccountsChange = this._onDidAccountsChangeEmitter.event;
-
-	private readonly _onDidRemoveLastAccountEmitter = new EventEmitter();
-	readonly onDidRemoveLastAccount = this._onDidRemoveLastAccountEmitter.event;
-
-	//#endregion
-
-	constructor(
-		private readonly _clientId: string,
-		private readonly _authority: string,
-		private readonly _globalMemento: Memento,
-		private readonly _secretStorage: SecretStorage,
-		private readonly _logger: LogOutputChannel
-	) {
-		this._pca = new PublicClientApplication(this._config);
-		this._lastCreated = new Date();
-		this._disposable = Disposable.from(
-			this._registerOnSecretStorageChanged(),
-			this._onDidAccountsChangeEmitter,
-			this._onDidRemoveLastAccountEmitter
-		);
-	}
-
-	get accounts(): AccountInfo[] { return this._accounts; }
-	get clientId(): string { return this._clientId; }
-	get authority(): string { return this._authority; }
-
-	initialize(): Promise {
-		return this._update();
-	}
-
-	dispose(): void {
-		this._disposable.dispose();
-	}
-
-	async acquireTokenSilent(request: SilentFlowRequest): Promise {
-		this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] starting...`);
-		const result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(request));
-		this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got result`);
-		if (result.account && !result.fromCache) {
-			this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] firing event due to change`);
-			this._setupRefresh(result);
-			this._onDidAccountsChangeEmitter.fire({ added: [], changed: [result.account], deleted: [] });
-		}
-		return result;
-	}
-
-	async acquireTokenInteractive(request: InteractiveRequest): Promise {
-		this._logger.debug(`[acquireTokenInteractive] [${this._clientId}] [${this._authority}] [${request.scopes?.join(' ')}] loopbackClientOverride: ${request.loopbackClient ? 'true' : 'false'}`);
-		const result = await window.withProgress(
-			{
-				location: ProgressLocation.Notification,
-				cancellable: true,
-				title: l10n.t('Signing in to Microsoft...')
-			},
-			(_process, token) => raceCancellationAndTimeoutError(
-				this._pca.acquireTokenInteractive(request),
-				token,
-				1000 * 60 * 5
-			)
-		);
-		this._setupRefresh(result);
-		return result;
-	}
-
-	removeAccount(account: AccountInfo): Promise {
-		this._globalMemento.update(`lastRemoval:${this._clientId}:${this._authority}`, new Date());
-		return this._pca.getTokenCache().removeAccount(account);
-	}
-
-	private _registerOnSecretStorageChanged() {
-		return this._secretStorageCachePlugin.onDidChange(() => this._update());
-	}
-
-	private async _update() {
-		const before = this._accounts;
-		this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update before: ${before.length}`);
-		// Dates are stored as strings in the memento
-		const lastRemovalDate = this._globalMemento.get(`lastRemoval:${this._clientId}:${this._authority}`);
-		if (lastRemovalDate && this._lastCreated && Date.parse(lastRemovalDate) > this._lastCreated.getTime()) {
-			this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication removal detected... recreating PCA...`);
-			this._pca = new PublicClientApplication(this._config);
-			this._lastCreated = new Date();
-		}
-
-		const after = await this._pca.getAllAccounts();
-		this._accounts = after;
-		this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update after: ${after.length}`);
-
-		const beforeSet = new Set(before.map(b => b.homeAccountId));
-		const afterSet = new Set(after.map(a => a.homeAccountId));
-
-		const added = after.filter(a => !beforeSet.has(a.homeAccountId));
-		const deleted = before.filter(b => !afterSet.has(b.homeAccountId));
-		if (added.length > 0 || deleted.length > 0) {
-			this._onDidAccountsChangeEmitter.fire({ added, changed: [], deleted });
-			this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication accounts changed. added: ${added.length}, deleted: ${deleted.length}`);
-			if (!after.length) {
-				this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication final account deleted. Firing event.`);
-				this._onDidRemoveLastAccountEmitter.fire();
-			}
-		}
-		this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update complete`);
-	}
-
-	private _setupRefresh(result: AuthenticationResult) {
-		const on = result.refreshOn || result.expiresOn;
-		if (!result.account || !on) {
-			return;
-		}
-
-		const account = result.account;
-		const scopes = result.scopes;
-		const timeToRefresh = on.getTime() - Date.now() - 5 * 60 * 1000; // 5 minutes before expiry
-		const key = JSON.stringify({ accountId: account.homeAccountId, scopes });
-		this._logger.debug(`[_setupRefresh] [${this._clientId}] [${this._authority}] [${scopes.join(' ')}] [${account.username}] timeToRefresh: ${timeToRefresh}`);
-		this._refreshDelayer.trigger(
-			key,
-			// This may need the redirectUri when we switch to the broker
-			() => this.acquireTokenSilent({ account, scopes, redirectUri: undefined, forceRefresh: true }),
-			timeToRefresh > 0 ? timeToRefresh : 0
-		);
-	}
+    private _pca: PublicClientApplication;
+    private _sequencer = new Sequencer();
+    private readonly _refreshDelayer = new DelayerByKey();
+    private _accounts: AccountInfo[] = [];
+    private readonly _disposable: Disposable;
+    private readonly _loggerOptions = new MsalLoggerOptions(this._logger);
+    private readonly _secretStorageCachePlugin = new SecretStorageCachePlugin(this._secretStorage, 
+    // Include the prefix as a differentiator to other secrets
+    `pca:${JSON.stringify({ clientId: this._clientId, authority: this._authority })}`);
+    private readonly _config: Configuration = {
+        auth: { clientId: this._clientId, authority: this._authority },
+        system: {
+            loggerOptions: {
+                correlationId: `${this._clientId}] [${this._authority}`,
+                loggerCallback: (level, message, containsPii) => this._loggerOptions.loggerCallback(level, message, containsPii),
+                logLevel: LogLevel.Trace
+            }
+        },
+        cache: {
+            cachePlugin: this._secretStorageCachePlugin
+        }
+    };
+    /**
+     * We keep track of the last time an account was removed so we can recreate the PCA if we detect that an account was removed.
+     * This is due to MSAL-node not providing a way to detect when an account is removed from the cache. An internal issue has been
+     * filed to track this. If MSAL-node ever provides a way to detect this or handle this better in the Persistant Cache Plugin,
+     * we can remove this logic.
+     */
+    private _lastCreated: Date;
+    //#region Events
+    private readonly _onDidAccountsChangeEmitter = new EventEmitter<{
+        added: AccountInfo[];
+        changed: AccountInfo[];
+        deleted: AccountInfo[];
+    }>;
+    readonly onDidAccountsChange = this._onDidAccountsChangeEmitter.event;
+    private readonly _onDidRemoveLastAccountEmitter = new EventEmitter();
+    readonly onDidRemoveLastAccount = this._onDidRemoveLastAccountEmitter.event;
+    //#endregion
+    constructor(private readonly _clientId: string, private readonly _authority: string, private readonly _globalMemento: Memento, private readonly _secretStorage: SecretStorage, private readonly _logger: LogOutputChannel) {
+        this._pca = new PublicClientApplication(this._config);
+        this._lastCreated = new Date();
+        this._disposable = Disposable.from(this._registerOnSecretStorageChanged(), this._onDidAccountsChangeEmitter, this._onDidRemoveLastAccountEmitter);
+    }
+    get accounts(): AccountInfo[] { return this._accounts; }
+    get clientId(): string { return this._clientId; }
+    get authority(): string { return this._authority; }
+    initialize(): Promise {
+        return this._update();
+    }
+    dispose(): void {
+        this._disposable.dispose();
+    }
+    async acquireTokenSilent(request: SilentFlowRequest): Promise {
+        this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] starting...`);
+        const result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(request));
+        this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got result`);
+        if (result.account && !result.fromCache) {
+            this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] firing event due to change`);
+            this._setupRefresh(result);
+            this._onDidAccountsChangeEmitter.fire({ added: [], changed: [result.account], deleted: [] });
+        }
+        return result;
+    }
+    async acquireTokenInteractive(request: InteractiveRequest): Promise {
+        this._logger.debug(`[acquireTokenInteractive] [${this._clientId}] [${this._authority}] [${request.scopes?.join(' ')}] loopbackClientOverride: ${request.loopbackClient ? 'true' : 'false'}`);
+        const result = await window.withProgress({
+            location: ProgressLocation.Notification,
+            cancellable: true,
+            title: l10n.t('Signing in to Microsoft...')
+        }, (_process, token) => raceCancellationAndTimeoutError(this._pca.acquireTokenInteractive(request), token, 1000 * 60 * 5));
+        this._setupRefresh(result);
+        return result;
+    }
+    removeAccount(account: AccountInfo): Promise {
+        this._globalMemento.update(`lastRemoval:${this._clientId}:${this._authority}`, new Date());
+        return this._pca.getTokenCache().removeAccount(account);
+    }
+    private _registerOnSecretStorageChanged() {
+        return this._secretStorageCachePlugin.onDidChange(() => this._update());
+    }
+    private async _update() {
+        const before = this._accounts;
+        this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update before: ${before.length}`);
+        // Dates are stored as strings in the memento
+        const lastRemovalDate = this._globalMemento.get(`lastRemoval:${this._clientId}:${this._authority}`);
+        if (lastRemovalDate && this._lastCreated && Date.parse(lastRemovalDate) > this._lastCreated.getTime()) {
+            this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication removal detected... recreating PCA...`);
+            this._pca = new PublicClientApplication(this._config);
+            this._lastCreated = new Date();
+        }
+        const after = await this._pca.getAllAccounts();
+        this._accounts = after;
+        this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update after: ${after.length}`);
+        const beforeSet = new Set(before.map(b => b.homeAccountId));
+        const afterSet = new Set(after.map(a => a.homeAccountId));
+        const added = after.filter(a => !beforeSet.has(a.homeAccountId));
+        const deleted = before.filter(b => !afterSet.has(b.homeAccountId));
+        if (added.length > 0 || deleted.length > 0) {
+            this._onDidAccountsChangeEmitter.fire({ added, changed: [], deleted });
+            this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication accounts changed. added: ${added.length}, deleted: ${deleted.length}`);
+            if (!after.length) {
+                this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication final account deleted. Firing event.`);
+                this._onDidRemoveLastAccountEmitter.fire();
+            }
+        }
+        this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update complete`);
+    }
+    private _setupRefresh(result: AuthenticationResult) {
+        const on = result.refreshOn || result.expiresOn;
+        if (!result.account || !on) {
+            return;
+        }
+        const account = result.account;
+        const scopes = result.scopes;
+        const timeToRefresh = on.getTime() - Date.now() - 5 * 60 * 1000; // 5 minutes before expiry
+        const key = JSON.stringify({ accountId: account.homeAccountId, scopes });
+        this._logger.debug(`[_setupRefresh] [${this._clientId}] [${this._authority}] [${scopes.join(' ')}] [${account.username}] timeToRefresh: ${timeToRefresh}`);
+        this._refreshDelayer.trigger(key, 
+        // This may need the redirectUri when we switch to the broker
+        () => this.acquireTokenSilent({ account, scopes, redirectUri: undefined, forceRefresh: true }), timeToRefresh > 0 ? timeToRefresh : 0);
+    }
 }
-
 export class Sequencer {
-
-	private current: Promise = Promise.resolve(null);
-
-	queue(promiseTask: () => Promise): Promise {
-		return this.current = this.current.then(() => promiseTask(), () => promiseTask());
-	}
+    private current: Promise = Promise.resolve(null);
+    queue(promiseTask: () => Promise): Promise {
+        return this.current = this.current.then(() => promiseTask(), () => promiseTask());
+    }
 }
-
 class DelayerByKey {
-	private _delayers = new Map>();
-
-	trigger(key: string, fn: () => Promise, delay: number): Promise {
-		let delayer = this._delayers.get(key);
-		if (!delayer) {
-			delayer = new Delayer(delay);
-			this._delayers.set(key, delayer);
-		}
-
-		return delayer.trigger(fn, delay);
-	}
+    private _delayers = new Map>();
+    trigger(key: string, fn: () => Promise, delay: number): Promise {
+        let delayer = this._delayers.get(key);
+        if (!delayer) {
+            delayer = new Delayer(delay);
+            this._delayers.set(key, delayer);
+        }
+        return delayer.trigger(fn, delay);
+    }
 }
diff --git a/extensions/microsoft-authentication/Source/node/fetch.ts b/extensions/microsoft-authentication/Source/node/fetch.ts
index 8b12a87b087b8..2a7500326ba50 100644
--- a/extensions/microsoft-authentication/Source/node/fetch.ts
+++ b/extensions/microsoft-authentication/Source/node/fetch.ts
@@ -2,11 +2,11 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 let _fetch: typeof fetch;
 try {
-	_fetch = require('electron').net.fetch;
-} catch {
-	_fetch = fetch;
+    _fetch = require('electron').net.fetch;
+}
+catch {
+    _fetch = fetch;
 }
 export default _fetch;
diff --git a/extensions/microsoft-authentication/Source/node/publicClientCache.ts b/extensions/microsoft-authentication/Source/node/publicClientCache.ts
index fc6ce38e9757c..ae3e6b38d56bd 100644
--- a/extensions/microsoft-authentication/Source/node/publicClientCache.ts
+++ b/extensions/microsoft-authentication/Source/node/publicClientCache.ts
@@ -2,230 +2,195 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { AccountInfo } from '@azure/msal-node';
 import { SecretStorage, LogOutputChannel, Disposable, EventEmitter, Memento, Event } from 'vscode';
 import { ICachedPublicClientApplication, ICachedPublicClientApplicationManager } from '../common/publicClientCache';
 import { CachedPublicClientApplication } from './cachedPublicClientApplication';
-
 export interface IPublicClientApplicationInfo {
-	clientId: string;
-	authority: string;
+    clientId: string;
+    authority: string;
 }
-
 export class CachedPublicClientApplicationManager implements ICachedPublicClientApplicationManager {
-	// The key is the clientId and authority JSON stringified
-	private readonly _pcas = new Map();
-	private readonly _pcaDisposables = new Map();
-
-	private _disposable: Disposable;
-	private _pcasSecretStorage: PublicClientApplicationsSecretStorage;
-
-	private readonly _onDidAccountsChangeEmitter = new EventEmitter<{ added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }>();
-	readonly onDidAccountsChange = this._onDidAccountsChangeEmitter.event;
-
-	constructor(
-		private readonly _globalMemento: Memento,
-		private readonly _secretStorage: SecretStorage,
-		private readonly _logger: LogOutputChannel,
-		cloudName: string
-	) {
-		this._pcasSecretStorage = new PublicClientApplicationsSecretStorage(_secretStorage, cloudName);
-		this._disposable = Disposable.from(
-			this._pcasSecretStorage,
-			this._registerSecretStorageHandler(),
-			this._onDidAccountsChangeEmitter
-		);
-	}
-
-	private _registerSecretStorageHandler() {
-		return this._pcasSecretStorage.onDidChange(() => this._handleSecretStorageChange());
-	}
-
-	async initialize() {
-		this._logger.debug('[initialize] Initializing PublicClientApplicationManager');
-		let keys: string[] | undefined;
-		try {
-			keys = await this._pcasSecretStorage.get();
-		} catch (e) {
-			// data is corrupted
-			this._logger.error('[initialize] Error initializing PublicClientApplicationManager:', e);
-			await this._pcasSecretStorage.delete();
-		}
-		if (!keys) {
-			return;
-		}
-
-		const promises = new Array>();
-		for (const key of keys) {
-			try {
-				const { clientId, authority } = JSON.parse(key) as IPublicClientApplicationInfo;
-				// Load the PCA in memory
-				promises.push(this._doCreatePublicClientApplication(clientId, authority, key));
-			} catch (e) {
-				this._logger.error('[initialize] Error intitializing PCA:', key);
-			}
-		}
-
-		const results = await Promise.allSettled(promises);
-		let pcasChanged = false;
-		for (const result of results) {
-			if (result.status === 'rejected') {
-				this._logger.error('[initialize] Error getting PCA:', result.reason);
-			} else {
-				if (!result.value.accounts.length) {
-					pcasChanged = true;
-					const pcaKey = JSON.stringify({ clientId: result.value.clientId, authority: result.value.authority });
-					this._pcaDisposables.get(pcaKey)?.dispose();
-					this._pcaDisposables.delete(pcaKey);
-					this._pcas.delete(pcaKey);
-					this._logger.debug(`[initialize] [${result.value.clientId}] [${result.value.authority}] PCA disposed because it's empty.`);
-				}
-			}
-		}
-		if (pcasChanged) {
-			await this._storePublicClientApplications();
-		}
-		this._logger.debug('[initialize] PublicClientApplicationManager initialized');
-	}
-
-	dispose() {
-		this._disposable.dispose();
-		Disposable.from(...this._pcaDisposables.values()).dispose();
-	}
-
-	async getOrCreate(clientId: string, authority: string): Promise {
-		// Use the clientId and authority as the key
-		const pcasKey = JSON.stringify({ clientId, authority });
-		let pca = this._pcas.get(pcasKey);
-		if (pca) {
-			this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache hit`);
-			return pca;
-		}
-
-		this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache miss, creating new PCA...`);
-		pca = await this._doCreatePublicClientApplication(clientId, authority, pcasKey);
-		await this._storePublicClientApplications();
-		this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PCA created.`);
-		return pca;
-	}
-
-	private async _doCreatePublicClientApplication(clientId: string, authority: string, pcasKey: string) {
-		const pca = new CachedPublicClientApplication(clientId, authority, this._globalMemento, this._secretStorage, this._logger);
-		this._pcas.set(pcasKey, pca);
-		const disposable = Disposable.from(
-			pca,
-			pca.onDidAccountsChange(e => this._onDidAccountsChangeEmitter.fire(e)),
-			pca.onDidRemoveLastAccount(() => {
-				// The PCA has no more accounts, so we can dispose it so we're not keeping it
-				// around forever.
-				disposable.dispose();
-				this._pcaDisposables.delete(pcasKey);
-				this._pcas.delete(pcasKey);
-				this._logger.debug(`[_doCreatePublicClientApplication] [${clientId}] [${authority}] PCA disposed. Firing off storing of PCAs...`);
-				void this._storePublicClientApplications();
-			})
-		);
-		this._pcaDisposables.set(pcasKey, disposable);
-		// Intialize the PCA after the `onDidAccountsChange` is set so we get initial state.
-		await pca.initialize();
-		return pca;
-	}
-
-	getAll(): ICachedPublicClientApplication[] {
-		return Array.from(this._pcas.values());
-	}
-
-	private async _handleSecretStorageChange() {
-		this._logger.debug(`[_handleSecretStorageChange] Handling PCAs secret storage change...`);
-		let result: string[] | undefined;
-		try {
-			result = await this._pcasSecretStorage.get();
-		} catch (_e) {
-			// The data in secret storage has been corrupted somehow so
-			// we store what we have in this window
-			await this._storePublicClientApplications();
-			return;
-		}
-		if (!result) {
-			this._logger.debug(`[_handleSecretStorageChange] PCAs deleted in secret storage. Disposing all...`);
-			Disposable.from(...this._pcaDisposables.values()).dispose();
-			this._pcas.clear();
-			this._pcaDisposables.clear();
-			this._logger.debug(`[_handleSecretStorageChange] Finished PCAs secret storage change.`);
-			return;
-		}
-
-		const pcaKeysFromStorage = new Set(result);
-		// Handle the deleted ones
-		for (const pcaKey of this._pcas.keys()) {
-			if (!pcaKeysFromStorage.delete(pcaKey)) {
-				// This PCA has been removed in another window
-				this._pcaDisposables.get(pcaKey)?.dispose();
-				this._pcaDisposables.delete(pcaKey);
-				this._pcas.delete(pcaKey);
-				this._logger.debug(`[_handleSecretStorageChange] Disposed PCA that was deleted in another window: ${pcaKey}`);
-			}
-		}
-
-		// Handle the new ones
-		for (const newPca of pcaKeysFromStorage) {
-			try {
-				const { clientId, authority } = JSON.parse(newPca);
-				this._logger.debug(`[_handleSecretStorageChange] [${clientId}] [${authority}] Creating new PCA that was created in another window...`);
-				await this._doCreatePublicClientApplication(clientId, authority, newPca);
-				this._logger.debug(`[_handleSecretStorageChange] [${clientId}] [${authority}] PCA created.`);
-			} catch (_e) {
-				// This really shouldn't happen, but should we do something about this?
-				this._logger.error(`Failed to parse new PublicClientApplication: ${newPca}`);
-				continue;
-			}
-		}
-
-		this._logger.debug('[_handleSecretStorageChange] Finished handling PCAs secret storage change.');
-	}
-
-	private _storePublicClientApplications() {
-		return this._pcasSecretStorage.store(Array.from(this._pcas.keys()));
-	}
+    // The key is the clientId and authority JSON stringified
+    private readonly _pcas = new Map();
+    private readonly _pcaDisposables = new Map();
+    private _disposable: Disposable;
+    private _pcasSecretStorage: PublicClientApplicationsSecretStorage;
+    private readonly _onDidAccountsChangeEmitter = new EventEmitter<{
+        added: AccountInfo[];
+        changed: AccountInfo[];
+        deleted: AccountInfo[];
+    }>();
+    readonly onDidAccountsChange = this._onDidAccountsChangeEmitter.event;
+    constructor(private readonly _globalMemento: Memento, private readonly _secretStorage: SecretStorage, private readonly _logger: LogOutputChannel, cloudName: string) {
+        this._pcasSecretStorage = new PublicClientApplicationsSecretStorage(_secretStorage, cloudName);
+        this._disposable = Disposable.from(this._pcasSecretStorage, this._registerSecretStorageHandler(), this._onDidAccountsChangeEmitter);
+    }
+    private _registerSecretStorageHandler() {
+        return this._pcasSecretStorage.onDidChange(() => this._handleSecretStorageChange());
+    }
+    async initialize() {
+        this._logger.debug('[initialize] Initializing PublicClientApplicationManager');
+        let keys: string[] | undefined;
+        try {
+            keys = await this._pcasSecretStorage.get();
+        }
+        catch (e) {
+            // data is corrupted
+            this._logger.error('[initialize] Error initializing PublicClientApplicationManager:', e);
+            await this._pcasSecretStorage.delete();
+        }
+        if (!keys) {
+            return;
+        }
+        const promises = new Array>();
+        for (const key of keys) {
+            try {
+                const { clientId, authority } = JSON.parse(key) as IPublicClientApplicationInfo;
+                // Load the PCA in memory
+                promises.push(this._doCreatePublicClientApplication(clientId, authority, key));
+            }
+            catch (e) {
+                this._logger.error('[initialize] Error intitializing PCA:', key);
+            }
+        }
+        const results = await Promise.allSettled(promises);
+        let pcasChanged = false;
+        for (const result of results) {
+            if (result.status === 'rejected') {
+                this._logger.error('[initialize] Error getting PCA:', result.reason);
+            }
+            else {
+                if (!result.value.accounts.length) {
+                    pcasChanged = true;
+                    const pcaKey = JSON.stringify({ clientId: result.value.clientId, authority: result.value.authority });
+                    this._pcaDisposables.get(pcaKey)?.dispose();
+                    this._pcaDisposables.delete(pcaKey);
+                    this._pcas.delete(pcaKey);
+                    this._logger.debug(`[initialize] [${result.value.clientId}] [${result.value.authority}] PCA disposed because it's empty.`);
+                }
+            }
+        }
+        if (pcasChanged) {
+            await this._storePublicClientApplications();
+        }
+        this._logger.debug('[initialize] PublicClientApplicationManager initialized');
+    }
+    dispose() {
+        this._disposable.dispose();
+        Disposable.from(...this._pcaDisposables.values()).dispose();
+    }
+    async getOrCreate(clientId: string, authority: string): Promise {
+        // Use the clientId and authority as the key
+        const pcasKey = JSON.stringify({ clientId, authority });
+        let pca = this._pcas.get(pcasKey);
+        if (pca) {
+            this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache hit`);
+            return pca;
+        }
+        this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache miss, creating new PCA...`);
+        pca = await this._doCreatePublicClientApplication(clientId, authority, pcasKey);
+        await this._storePublicClientApplications();
+        this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PCA created.`);
+        return pca;
+    }
+    private async _doCreatePublicClientApplication(clientId: string, authority: string, pcasKey: string) {
+        const pca = new CachedPublicClientApplication(clientId, authority, this._globalMemento, this._secretStorage, this._logger);
+        this._pcas.set(pcasKey, pca);
+        const disposable = Disposable.from(pca, pca.onDidAccountsChange(e => this._onDidAccountsChangeEmitter.fire(e)), pca.onDidRemoveLastAccount(() => {
+            // The PCA has no more accounts, so we can dispose it so we're not keeping it
+            // around forever.
+            disposable.dispose();
+            this._pcaDisposables.delete(pcasKey);
+            this._pcas.delete(pcasKey);
+            this._logger.debug(`[_doCreatePublicClientApplication] [${clientId}] [${authority}] PCA disposed. Firing off storing of PCAs...`);
+            void this._storePublicClientApplications();
+        }));
+        this._pcaDisposables.set(pcasKey, disposable);
+        // Intialize the PCA after the `onDidAccountsChange` is set so we get initial state.
+        await pca.initialize();
+        return pca;
+    }
+    getAll(): ICachedPublicClientApplication[] {
+        return Array.from(this._pcas.values());
+    }
+    private async _handleSecretStorageChange() {
+        this._logger.debug(`[_handleSecretStorageChange] Handling PCAs secret storage change...`);
+        let result: string[] | undefined;
+        try {
+            result = await this._pcasSecretStorage.get();
+        }
+        catch (_e) {
+            // The data in secret storage has been corrupted somehow so
+            // we store what we have in this window
+            await this._storePublicClientApplications();
+            return;
+        }
+        if (!result) {
+            this._logger.debug(`[_handleSecretStorageChange] PCAs deleted in secret storage. Disposing all...`);
+            Disposable.from(...this._pcaDisposables.values()).dispose();
+            this._pcas.clear();
+            this._pcaDisposables.clear();
+            this._logger.debug(`[_handleSecretStorageChange] Finished PCAs secret storage change.`);
+            return;
+        }
+        const pcaKeysFromStorage = new Set(result);
+        // Handle the deleted ones
+        for (const pcaKey of this._pcas.keys()) {
+            if (!pcaKeysFromStorage.delete(pcaKey)) {
+                // This PCA has been removed in another window
+                this._pcaDisposables.get(pcaKey)?.dispose();
+                this._pcaDisposables.delete(pcaKey);
+                this._pcas.delete(pcaKey);
+                this._logger.debug(`[_handleSecretStorageChange] Disposed PCA that was deleted in another window: ${pcaKey}`);
+            }
+        }
+        // Handle the new ones
+        for (const newPca of pcaKeysFromStorage) {
+            try {
+                const { clientId, authority } = JSON.parse(newPca);
+                this._logger.debug(`[_handleSecretStorageChange] [${clientId}] [${authority}] Creating new PCA that was created in another window...`);
+                await this._doCreatePublicClientApplication(clientId, authority, newPca);
+                this._logger.debug(`[_handleSecretStorageChange] [${clientId}] [${authority}] PCA created.`);
+            }
+            catch (_e) {
+                // This really shouldn't happen, but should we do something about this?
+                this._logger.error(`Failed to parse new PublicClientApplication: ${newPca}`);
+                continue;
+            }
+        }
+        this._logger.debug('[_handleSecretStorageChange] Finished handling PCAs secret storage change.');
+    }
+    private _storePublicClientApplications() {
+        return this._pcasSecretStorage.store(Array.from(this._pcas.keys()));
+    }
 }
-
 class PublicClientApplicationsSecretStorage {
-	private _disposable: Disposable;
-
-	private readonly _onDidChangeEmitter = new EventEmitter;
-	readonly onDidChange: Event = this._onDidChangeEmitter.event;
-
-	private readonly _key = `publicClientApplications-${this._cloudName}`;
-
-	constructor(private readonly _secretStorage: SecretStorage, private readonly _cloudName: string) {
-		this._disposable = Disposable.from(
-			this._onDidChangeEmitter,
-			this._secretStorage.onDidChange(e => {
-				if (e.key === this._key) {
-					this._onDidChangeEmitter.fire();
-				}
-			})
-		);
-	}
-
-	async get(): Promise {
-		const value = await this._secretStorage.get(this._key);
-		if (!value) {
-			return undefined;
-		}
-		return JSON.parse(value);
-	}
-
-	store(value: string[]): Thenable {
-		return this._secretStorage.store(this._key, JSON.stringify(value));
-	}
-
-	delete(): Thenable {
-		return this._secretStorage.delete(this._key);
-	}
-
-	dispose() {
-		this._disposable.dispose();
-	}
+    private _disposable: Disposable;
+    private readonly _onDidChangeEmitter = new EventEmitter;
+    readonly onDidChange: Event = this._onDidChangeEmitter.event;
+    private readonly _key = `publicClientApplications-${this._cloudName}`;
+    constructor(private readonly _secretStorage: SecretStorage, private readonly _cloudName: string) {
+        this._disposable = Disposable.from(this._onDidChangeEmitter, this._secretStorage.onDidChange(e => {
+            if (e.key === this._key) {
+                this._onDidChangeEmitter.fire();
+            }
+        }));
+    }
+    async get(): Promise {
+        const value = await this._secretStorage.get(this._key);
+        if (!value) {
+            return undefined;
+        }
+        return JSON.parse(value);
+    }
+    store(value: string[]): Thenable {
+        return this._secretStorage.store(this._key, JSON.stringify(value));
+    }
+    delete(): Thenable {
+        return this._secretStorage.delete(this._key);
+    }
+    dispose() {
+        this._disposable.dispose();
+    }
 }
diff --git a/extensions/notebook-renderers/Source/ansi.ts b/extensions/notebook-renderers/Source/ansi.ts
index ee26d37d1f6d5..88eef60ce69a6 100644
--- a/extensions/notebook-renderers/Source/ansi.ts
+++ b/extensions/notebook-renderers/Source/ansi.ts
@@ -2,415 +2,386 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { RGBA, Color } from './color';
 import { ansiColorIdentifiers } from './colorMap';
 import { LinkOptions, linkify } from './linkify';
-
-
 export function handleANSIOutput(text: string, linkOptions: LinkOptions): HTMLSpanElement {
-
-	const root: HTMLSpanElement = document.createElement('span');
-	const textLength: number = text.length;
-
-	let styleNames: string[] = [];
-	let customFgColor: RGBA | string | undefined;
-	let customBgColor: RGBA | string | undefined;
-	let customUnderlineColor: RGBA | string | undefined;
-	let colorsInverted: boolean = false;
-	let currentPos: number = 0;
-	let buffer: string = '';
-
-	while (currentPos < textLength) {
-
-		let sequenceFound: boolean = false;
-
-		// Potentially an ANSI escape sequence.
-		// See https://www.asciitable.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code
-		if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') {
-
-			const startPos: number = currentPos;
-			currentPos += 2; // Ignore 'Esc[' as it's in every sequence.
-
-			let ansiSequence: string = '';
-
-			while (currentPos < textLength) {
-				const char: string = text.charAt(currentPos);
-				ansiSequence += char;
-
-				currentPos++;
-
-				// Look for a known sequence terminating character.
-				if (char.match(/^[ABCDHIJKfhmpsu]$/)) {
-					sequenceFound = true;
-					break;
-				}
-
-			}
-
-			if (sequenceFound) {
-
-				// Flush buffer with previous styles.
-				appendStylizedStringToContainer(root, buffer, linkOptions, styleNames, customFgColor, customBgColor, customUnderlineColor);
-
-				buffer = '';
-
-				/*
-				 * Certain ranges that are matched here do not contain real graphics rendition sequences. For
-				 * the sake of having a simpler expression, they have been included anyway.
-				 */
-				if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[0-9]|2[1-5,7-9]|[34]9|5[8,9]|1[0-9])(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) {
-
-					const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character.
-						.split(';')										   // Separate style codes.
-						.filter(elem => elem !== '')			           // Filter empty elems as '34;m' -> ['34', ''].
-						.map(elem => parseInt(elem, 10));		           // Convert to numbers.
-
-					if (styleCodes[0] === 38 || styleCodes[0] === 48 || styleCodes[0] === 58) {
-						// Advanced color code - can't be combined with formatting codes like simple colors can
-						// Ignores invalid colors and additional info beyond what is necessary
-						const colorType = (styleCodes[0] === 38) ? 'foreground' : ((styleCodes[0] === 48) ? 'background' : 'underline');
-
-						if (styleCodes[1] === 5) {
-							set8BitColor(styleCodes, colorType);
-						} else if (styleCodes[1] === 2) {
-							set24BitColor(styleCodes, colorType);
-						}
-					} else {
-						setBasicFormatters(styleCodes);
-					}
-
-				} else {
-					// Unsupported sequence so simply hide it.
-				}
-
-			} else {
-				currentPos = startPos;
-			}
-		}
-
-		if (sequenceFound === false) {
-			buffer += text.charAt(currentPos);
-			currentPos++;
-		}
-	}
-
-	// Flush remaining text buffer if not empty.
-	if (buffer) {
-		appendStylizedStringToContainer(root, buffer, linkOptions, styleNames, customFgColor, customBgColor, customUnderlineColor);
-	}
-
-	return root;
-
-	/**
-	 * Change the foreground or background color by clearing the current color
-	 * and adding the new one.
-	 * @param colorType If `'foreground'`, will change the foreground color, if
-	 * 	`'background'`, will change the background color, and if `'underline'`
-	 * will set the underline color.
-	 * @param color Color to change to. If `undefined` or not provided,
-	 * will clear current color without adding a new one.
-	 */
-	function changeColor(colorType: 'foreground' | 'background' | 'underline', color?: RGBA | string | undefined): void {
-		if (colorType === 'foreground') {
-			customFgColor = color;
-		} else if (colorType === 'background') {
-			customBgColor = color;
-		} else if (colorType === 'underline') {
-			customUnderlineColor = color;
-		}
-		styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`);
-		if (color !== undefined) {
-			styleNames.push(`code-${colorType}-colored`);
-		}
-	}
-
-	/**
-	 * Swap foreground and background colors.  Used for color inversion.  Caller should check
-	 * [] flag to make sure it is appropriate to turn ON or OFF (if it is already inverted don't call
-	 */
-	function reverseForegroundAndBackgroundColors(): void {
-		const oldFgColor: RGBA | string | undefined = customFgColor;
-		changeColor('foreground', customBgColor);
-		changeColor('background', oldFgColor);
-	}
-
-	/**
-	 * Calculate and set basic ANSI formatting. Supports ON/OFF of bold, italic, underline,
-	 * double underline,  crossed-out/strikethrough, overline, dim, blink, rapid blink,
-	 * reverse/invert video, hidden, superscript, subscript and alternate font codes,
-	 * clearing/resetting of foreground, background and underline colors,
-	 * setting normal foreground and background colors, and bright foreground and
-	 * background colors. Not to be used for codes containing advanced colors.
-	 * Will ignore invalid codes.
-	 * @param styleCodes Array of ANSI basic styling numbers, which will be
-	 * applied in order. New colors and backgrounds clear old ones; new formatting
-	 * does not.
-	 * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#SGR }
-	 */
-	function setBasicFormatters(styleCodes: number[]): void {
-		for (const code of styleCodes) {
-			switch (code) {
-				case 0: {  // reset (everything)
-					styleNames = [];
-					customFgColor = undefined;
-					customBgColor = undefined;
-					break;
-				}
-				case 1: { // bold
-					styleNames = styleNames.filter(style => style !== `code-bold`);
-					styleNames.push('code-bold');
-					break;
-				}
-				case 2: { // dim
-					styleNames = styleNames.filter(style => style !== `code-dim`);
-					styleNames.push('code-dim');
-					break;
-				}
-				case 3: { // italic
-					styleNames = styleNames.filter(style => style !== `code-italic`);
-					styleNames.push('code-italic');
-					break;
-				}
-				case 4: { // underline
-					styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
-					styleNames.push('code-underline');
-					break;
-				}
-				case 5: { // blink
-					styleNames = styleNames.filter(style => style !== `code-blink`);
-					styleNames.push('code-blink');
-					break;
-				}
-				case 6: { // rapid blink
-					styleNames = styleNames.filter(style => style !== `code-rapid-blink`);
-					styleNames.push('code-rapid-blink');
-					break;
-				}
-				case 7: { // invert foreground and background
-					if (!colorsInverted) {
-						colorsInverted = true;
-						reverseForegroundAndBackgroundColors();
-					}
-					break;
-				}
-				case 8: { // hidden
-					styleNames = styleNames.filter(style => style !== `code-hidden`);
-					styleNames.push('code-hidden');
-					break;
-				}
-				case 9: { // strike-through/crossed-out
-					styleNames = styleNames.filter(style => style !== `code-strike-through`);
-					styleNames.push('code-strike-through');
-					break;
-				}
-				case 10: { // normal default font
-					styleNames = styleNames.filter(style => !style.startsWith('code-font'));
-					break;
-				}
-				case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: { // font codes (and 20 is 'blackletter' font code)
-					styleNames = styleNames.filter(style => !style.startsWith('code-font'));
-					styleNames.push(`code-font-${code - 10}`);
-					break;
-				}
-				case 21: { // double underline
-					styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
-					styleNames.push('code-double-underline');
-					break;
-				}
-				case 22: { // normal intensity (bold off and dim off)
-					styleNames = styleNames.filter(style => (style !== `code-bold` && style !== `code-dim`));
-					break;
-				}
-				case 23: { // Neither italic or blackletter (font 10)
-					styleNames = styleNames.filter(style => (style !== `code-italic` && style !== `code-font-10`));
-					break;
-				}
-				case 24: { // not underlined (Neither singly nor doubly underlined)
-					styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
-					break;
-				}
-				case 25: { // not blinking
-					styleNames = styleNames.filter(style => (style !== `code-blink` && style !== `code-rapid-blink`));
-					break;
-				}
-				case 27: { // not reversed/inverted
-					if (colorsInverted) {
-						colorsInverted = false;
-						reverseForegroundAndBackgroundColors();
-					}
-					break;
-				}
-				case 28: { // not hidden (reveal)
-					styleNames = styleNames.filter(style => style !== `code-hidden`);
-					break;
-				}
-				case 29: { // not crossed-out
-					styleNames = styleNames.filter(style => style !== `code-strike-through`);
-					break;
-				}
-				case 53: { // overlined
-					styleNames = styleNames.filter(style => style !== `code-overline`);
-					styleNames.push('code-overline');
-					break;
-				}
-				case 55: { // not overlined
-					styleNames = styleNames.filter(style => style !== `code-overline`);
-					break;
-				}
-				case 39: {  // default foreground color
-					changeColor('foreground', undefined);
-					break;
-				}
-				case 49: {  // default background color
-					changeColor('background', undefined);
-					break;
-				}
-				case 59: {  // default underline color
-					changeColor('underline', undefined);
-					break;
-				}
-				case 73: { // superscript
-					styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
-					styleNames.push('code-superscript');
-					break;
-				}
-				case 74: { // subscript
-					styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
-					styleNames.push('code-subscript');
-					break;
-				}
-				case 75: { // neither superscript or subscript
-					styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
-					break;
-				}
-				default: {
-					setBasicColor(code);
-					break;
-				}
-			}
-		}
-	}
-
-	/**
-	 * Calculate and set styling for complicated 24-bit ANSI color codes.
-	 * @param styleCodes Full list of integer codes that make up the full ANSI
-	 * sequence, including the two defining codes and the three RGB codes.
-	 * @param colorType If `'foreground'`, will set foreground color, if
-	 * `'background'`, will set background color, and if it is `'underline'`
-	 * will set the underline color.
-	 * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit }
-	 */
-	function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
-		if (styleCodes.length >= 5 &&
-			styleCodes[2] >= 0 && styleCodes[2] <= 255 &&
-			styleCodes[3] >= 0 && styleCodes[3] <= 255 &&
-			styleCodes[4] >= 0 && styleCodes[4] <= 255) {
-			const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]);
-			changeColor(colorType, customColor);
-		}
-	}
-
-	/**
-	 * Calculate and set styling for advanced 8-bit ANSI color codes.
-	 * @param styleCodes Full list of integer codes that make up the ANSI
-	 * sequence, including the two defining codes and the one color code.
-	 * @param colorType If `'foreground'`, will set foreground color, if
-	 * `'background'`, will set background color and if it is `'underline'`
-	 * will set the underline color.
-	 * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit }
-	 */
-	function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
-		let colorNumber = styleCodes[2];
-		const color = calcANSI8bitColor(colorNumber);
-
-		if (color) {
-			changeColor(colorType, color);
-		} else if (colorNumber >= 0 && colorNumber <= 15) {
-			if (colorType === 'underline') {
-				// for underline colors we just decode the 0-15 color number to theme color, set and return
-				changeColor(colorType, ansiColorIdentifiers[colorNumber].colorValue);
-				return;
-			}
-			// Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107)
-			colorNumber += 30;
-			if (colorNumber >= 38) {
-				// Bright colors
-				colorNumber += 52;
-			}
-			if (colorType === 'background') {
-				colorNumber += 10;
-			}
-			setBasicColor(colorNumber);
-		}
-	}
-
-	/**
-	 * Calculate and set styling for basic bright and dark ANSI color codes. Uses
-	 * theme colors if available. Automatically distinguishes between foreground
-	 * and background colors; does not support color-clearing codes 39 and 49.
-	 * @param styleCode Integer color code on one of the following ranges:
-	 * [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do
-	 * nothing.
-	 */
-	function setBasicColor(styleCode: number): void {
-		// const theme = themeService.getColorTheme();
-		let colorType: 'foreground' | 'background' | undefined;
-		let colorIndex: number | undefined;
-
-		if (styleCode >= 30 && styleCode <= 37) {
-			colorIndex = styleCode - 30;
-			colorType = 'foreground';
-		} else if (styleCode >= 90 && styleCode <= 97) {
-			colorIndex = (styleCode - 90) + 8; // High-intensity (bright)
-			colorType = 'foreground';
-		} else if (styleCode >= 40 && styleCode <= 47) {
-			colorIndex = styleCode - 40;
-			colorType = 'background';
-		} else if (styleCode >= 100 && styleCode <= 107) {
-			colorIndex = (styleCode - 100) + 8; // High-intensity (bright)
-			colorType = 'background';
-		}
-
-		if (colorIndex !== undefined && colorType) {
-			changeColor(colorType, ansiColorIdentifiers[colorIndex]?.colorValue);
-		}
-	}
+    const root: HTMLSpanElement = document.createElement('span');
+    const textLength: number = text.length;
+    let styleNames: string[] = [];
+    let customFgColor: RGBA | string | undefined;
+    let customBgColor: RGBA | string | undefined;
+    let customUnderlineColor: RGBA | string | undefined;
+    let colorsInverted: boolean = false;
+    let currentPos: number = 0;
+    let buffer: string = '';
+    while (currentPos < textLength) {
+        let sequenceFound: boolean = false;
+        // Potentially an ANSI escape sequence.
+        // See https://www.asciitable.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code
+        if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') {
+            const startPos: number = currentPos;
+            currentPos += 2; // Ignore 'Esc[' as it's in every sequence.
+            let ansiSequence: string = '';
+            while (currentPos < textLength) {
+                const char: string = text.charAt(currentPos);
+                ansiSequence += char;
+                currentPos++;
+                // Look for a known sequence terminating character.
+                if (char.match(/^[ABCDHIJKfhmpsu]$/)) {
+                    sequenceFound = true;
+                    break;
+                }
+            }
+            if (sequenceFound) {
+                // Flush buffer with previous styles.
+                appendStylizedStringToContainer(root, buffer, linkOptions, styleNames, customFgColor, customBgColor, customUnderlineColor);
+                buffer = '';
+                /*
+                 * Certain ranges that are matched here do not contain real graphics rendition sequences. For
+                 * the sake of having a simpler expression, they have been included anyway.
+                 */
+                if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[0-9]|2[1-5,7-9]|[34]9|5[8,9]|1[0-9])(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) {
+                    const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character.
+                        .split(';') // Separate style codes.
+                        .filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', ''].
+                        .map(elem => parseInt(elem, 10)); // Convert to numbers.
+                    if (styleCodes[0] === 38 || styleCodes[0] === 48 || styleCodes[0] === 58) {
+                        // Advanced color code - can't be combined with formatting codes like simple colors can
+                        // Ignores invalid colors and additional info beyond what is necessary
+                        const colorType = (styleCodes[0] === 38) ? 'foreground' : ((styleCodes[0] === 48) ? 'background' : 'underline');
+                        if (styleCodes[1] === 5) {
+                            set8BitColor(styleCodes, colorType);
+                        }
+                        else if (styleCodes[1] === 2) {
+                            set24BitColor(styleCodes, colorType);
+                        }
+                    }
+                    else {
+                        setBasicFormatters(styleCodes);
+                    }
+                }
+                else {
+                    // Unsupported sequence so simply hide it.
+                }
+            }
+            else {
+                currentPos = startPos;
+            }
+        }
+        if (sequenceFound === false) {
+            buffer += text.charAt(currentPos);
+            currentPos++;
+        }
+    }
+    // Flush remaining text buffer if not empty.
+    if (buffer) {
+        appendStylizedStringToContainer(root, buffer, linkOptions, styleNames, customFgColor, customBgColor, customUnderlineColor);
+    }
+    return root;
+    /**
+     * Change the foreground or background color by clearing the current color
+     * and adding the new one.
+     * @param colorType If `'foreground'`, will change the foreground color, if
+     * 	`'background'`, will change the background color, and if `'underline'`
+     * will set the underline color.
+     * @param color Color to change to. If `undefined` or not provided,
+     * will clear current color without adding a new one.
+     */
+    function changeColor(colorType: 'foreground' | 'background' | 'underline', color?: RGBA | string | undefined): void {
+        if (colorType === 'foreground') {
+            customFgColor = color;
+        }
+        else if (colorType === 'background') {
+            customBgColor = color;
+        }
+        else if (colorType === 'underline') {
+            customUnderlineColor = color;
+        }
+        styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`);
+        if (color !== undefined) {
+            styleNames.push(`code-${colorType}-colored`);
+        }
+    }
+    /**
+     * Swap foreground and background colors.  Used for color inversion.  Caller should check
+     * [] flag to make sure it is appropriate to turn ON or OFF (if it is already inverted don't call
+     */
+    function reverseForegroundAndBackgroundColors(): void {
+        const oldFgColor: RGBA | string | undefined = customFgColor;
+        changeColor('foreground', customBgColor);
+        changeColor('background', oldFgColor);
+    }
+    /**
+     * Calculate and set basic ANSI formatting. Supports ON/OFF of bold, italic, underline,
+     * double underline,  crossed-out/strikethrough, overline, dim, blink, rapid blink,
+     * reverse/invert video, hidden, superscript, subscript and alternate font codes,
+     * clearing/resetting of foreground, background and underline colors,
+     * setting normal foreground and background colors, and bright foreground and
+     * background colors. Not to be used for codes containing advanced colors.
+     * Will ignore invalid codes.
+     * @param styleCodes Array of ANSI basic styling numbers, which will be
+     * applied in order. New colors and backgrounds clear old ones; new formatting
+     * does not.
+     * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#SGR }
+     */
+    function setBasicFormatters(styleCodes: number[]): void {
+        for (const code of styleCodes) {
+            switch (code) {
+                case 0: { // reset (everything)
+                    styleNames = [];
+                    customFgColor = undefined;
+                    customBgColor = undefined;
+                    break;
+                }
+                case 1: { // bold
+                    styleNames = styleNames.filter(style => style !== `code-bold`);
+                    styleNames.push('code-bold');
+                    break;
+                }
+                case 2: { // dim
+                    styleNames = styleNames.filter(style => style !== `code-dim`);
+                    styleNames.push('code-dim');
+                    break;
+                }
+                case 3: { // italic
+                    styleNames = styleNames.filter(style => style !== `code-italic`);
+                    styleNames.push('code-italic');
+                    break;
+                }
+                case 4: { // underline
+                    styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
+                    styleNames.push('code-underline');
+                    break;
+                }
+                case 5: { // blink
+                    styleNames = styleNames.filter(style => style !== `code-blink`);
+                    styleNames.push('code-blink');
+                    break;
+                }
+                case 6: { // rapid blink
+                    styleNames = styleNames.filter(style => style !== `code-rapid-blink`);
+                    styleNames.push('code-rapid-blink');
+                    break;
+                }
+                case 7: { // invert foreground and background
+                    if (!colorsInverted) {
+                        colorsInverted = true;
+                        reverseForegroundAndBackgroundColors();
+                    }
+                    break;
+                }
+                case 8: { // hidden
+                    styleNames = styleNames.filter(style => style !== `code-hidden`);
+                    styleNames.push('code-hidden');
+                    break;
+                }
+                case 9: { // strike-through/crossed-out
+                    styleNames = styleNames.filter(style => style !== `code-strike-through`);
+                    styleNames.push('code-strike-through');
+                    break;
+                }
+                case 10: { // normal default font
+                    styleNames = styleNames.filter(style => !style.startsWith('code-font'));
+                    break;
+                }
+                case 11:
+                case 12:
+                case 13:
+                case 14:
+                case 15:
+                case 16:
+                case 17:
+                case 18:
+                case 19:
+                case 20: { // font codes (and 20 is 'blackletter' font code)
+                    styleNames = styleNames.filter(style => !style.startsWith('code-font'));
+                    styleNames.push(`code-font-${code - 10}`);
+                    break;
+                }
+                case 21: { // double underline
+                    styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
+                    styleNames.push('code-double-underline');
+                    break;
+                }
+                case 22: { // normal intensity (bold off and dim off)
+                    styleNames = styleNames.filter(style => (style !== `code-bold` && style !== `code-dim`));
+                    break;
+                }
+                case 23: { // Neither italic or blackletter (font 10)
+                    styleNames = styleNames.filter(style => (style !== `code-italic` && style !== `code-font-10`));
+                    break;
+                }
+                case 24: { // not underlined (Neither singly nor doubly underlined)
+                    styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
+                    break;
+                }
+                case 25: { // not blinking
+                    styleNames = styleNames.filter(style => (style !== `code-blink` && style !== `code-rapid-blink`));
+                    break;
+                }
+                case 27: { // not reversed/inverted
+                    if (colorsInverted) {
+                        colorsInverted = false;
+                        reverseForegroundAndBackgroundColors();
+                    }
+                    break;
+                }
+                case 28: { // not hidden (reveal)
+                    styleNames = styleNames.filter(style => style !== `code-hidden`);
+                    break;
+                }
+                case 29: { // not crossed-out
+                    styleNames = styleNames.filter(style => style !== `code-strike-through`);
+                    break;
+                }
+                case 53: { // overlined
+                    styleNames = styleNames.filter(style => style !== `code-overline`);
+                    styleNames.push('code-overline');
+                    break;
+                }
+                case 55: { // not overlined
+                    styleNames = styleNames.filter(style => style !== `code-overline`);
+                    break;
+                }
+                case 39: { // default foreground color
+                    changeColor('foreground', undefined);
+                    break;
+                }
+                case 49: { // default background color
+                    changeColor('background', undefined);
+                    break;
+                }
+                case 59: { // default underline color
+                    changeColor('underline', undefined);
+                    break;
+                }
+                case 73: { // superscript
+                    styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
+                    styleNames.push('code-superscript');
+                    break;
+                }
+                case 74: { // subscript
+                    styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
+                    styleNames.push('code-subscript');
+                    break;
+                }
+                case 75: { // neither superscript or subscript
+                    styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
+                    break;
+                }
+                default: {
+                    setBasicColor(code);
+                    break;
+                }
+            }
+        }
+    }
+    /**
+     * Calculate and set styling for complicated 24-bit ANSI color codes.
+     * @param styleCodes Full list of integer codes that make up the full ANSI
+     * sequence, including the two defining codes and the three RGB codes.
+     * @param colorType If `'foreground'`, will set foreground color, if
+     * `'background'`, will set background color, and if it is `'underline'`
+     * will set the underline color.
+     * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit }
+     */
+    function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
+        if (styleCodes.length >= 5 &&
+            styleCodes[2] >= 0 && styleCodes[2] <= 255 &&
+            styleCodes[3] >= 0 && styleCodes[3] <= 255 &&
+            styleCodes[4] >= 0 && styleCodes[4] <= 255) {
+            const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]);
+            changeColor(colorType, customColor);
+        }
+    }
+    /**
+     * Calculate and set styling for advanced 8-bit ANSI color codes.
+     * @param styleCodes Full list of integer codes that make up the ANSI
+     * sequence, including the two defining codes and the one color code.
+     * @param colorType If `'foreground'`, will set foreground color, if
+     * `'background'`, will set background color and if it is `'underline'`
+     * will set the underline color.
+     * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit }
+     */
+    function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
+        let colorNumber = styleCodes[2];
+        const color = calcANSI8bitColor(colorNumber);
+        if (color) {
+            changeColor(colorType, color);
+        }
+        else if (colorNumber >= 0 && colorNumber <= 15) {
+            if (colorType === 'underline') {
+                // for underline colors we just decode the 0-15 color number to theme color, set and return
+                changeColor(colorType, ansiColorIdentifiers[colorNumber].colorValue);
+                return;
+            }
+            // Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107)
+            colorNumber += 30;
+            if (colorNumber >= 38) {
+                // Bright colors
+                colorNumber += 52;
+            }
+            if (colorType === 'background') {
+                colorNumber += 10;
+            }
+            setBasicColor(colorNumber);
+        }
+    }
+    /**
+     * Calculate and set styling for basic bright and dark ANSI color codes. Uses
+     * theme colors if available. Automatically distinguishes between foreground
+     * and background colors; does not support color-clearing codes 39 and 49.
+     * @param styleCode Integer color code on one of the following ranges:
+     * [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do
+     * nothing.
+     */
+    function setBasicColor(styleCode: number): void {
+        // const theme = themeService.getColorTheme();
+        let colorType: 'foreground' | 'background' | undefined;
+        let colorIndex: number | undefined;
+        if (styleCode >= 30 && styleCode <= 37) {
+            colorIndex = styleCode - 30;
+            colorType = 'foreground';
+        }
+        else if (styleCode >= 90 && styleCode <= 97) {
+            colorIndex = (styleCode - 90) + 8; // High-intensity (bright)
+            colorType = 'foreground';
+        }
+        else if (styleCode >= 40 && styleCode <= 47) {
+            colorIndex = styleCode - 40;
+            colorType = 'background';
+        }
+        else if (styleCode >= 100 && styleCode <= 107) {
+            colorIndex = (styleCode - 100) + 8; // High-intensity (bright)
+            colorType = 'background';
+        }
+        if (colorIndex !== undefined && colorType) {
+            changeColor(colorType, ansiColorIdentifiers[colorIndex]?.colorValue);
+        }
+    }
 }
-
-function appendStylizedStringToContainer(
-	root: HTMLElement,
-	stringContent: string,
-	linkOptions: LinkOptions,
-	cssClasses: string[],
-	customTextColor?: RGBA | string,
-	customBackgroundColor?: RGBA | string,
-	customUnderlineColor?: RGBA | string
-): void {
-	if (!root || !stringContent) {
-		return;
-	}
-
-	let container = document.createElement('span');
-
-	if (container.childElementCount === 0) {
-		// plain text
-		container = linkify(stringContent, linkOptions, true);
-	}
-
-	container.className = cssClasses.join(' ');
-	if (customTextColor) {
-		container.style.color = typeof customTextColor === 'string' ? customTextColor : Color.Format.CSS.formatRGB(new Color(customTextColor));
-	}
-	if (customBackgroundColor) {
-		container.style.backgroundColor = typeof customBackgroundColor === 'string' ? customBackgroundColor : Color.Format.CSS.formatRGB(new Color(customBackgroundColor));
-	}
-	if (customUnderlineColor) {
-		container.style.textDecorationColor = typeof customUnderlineColor === 'string' ? customUnderlineColor : Color.Format.CSS.formatRGB(new Color(customUnderlineColor));
-	}
-	root.appendChild(container);
+function appendStylizedStringToContainer(root: HTMLElement, stringContent: string, linkOptions: LinkOptions, cssClasses: string[], customTextColor?: RGBA | string, customBackgroundColor?: RGBA | string, customUnderlineColor?: RGBA | string): void {
+    if (!root || !stringContent) {
+        return;
+    }
+    let container = document.createElement('span');
+    if (container.childElementCount === 0) {
+        // plain text
+        container = linkify(stringContent, linkOptions, true);
+    }
+    container.className = cssClasses.join(' ');
+    if (customTextColor) {
+        container.style.color = typeof customTextColor === 'string' ? customTextColor : Color.Format.CSS.formatRGB(new Color(customTextColor));
+    }
+    if (customBackgroundColor) {
+        container.style.backgroundColor = typeof customBackgroundColor === 'string' ? customBackgroundColor : Color.Format.CSS.formatRGB(new Color(customBackgroundColor));
+    }
+    if (customUnderlineColor) {
+        container.style.textDecorationColor = typeof customUnderlineColor === 'string' ? customUnderlineColor : Color.Format.CSS.formatRGB(new Color(customUnderlineColor));
+    }
+    root.appendChild(container);
 }
-
 /**
  * Calculate the color from the color set defined in the ANSI 8-bit standard.
  * Standard and high intensity colors are not defined in the standard as specific
@@ -420,32 +391,32 @@ function appendStylizedStringToContainer(
  * desired.
  */
 export function calcANSI8bitColor(colorNumber: number): RGBA | undefined {
-	if (colorNumber % 1 !== 0) {
-		// Should be integer
-		return;
-	} if (colorNumber >= 16 && colorNumber <= 231) {
-		// Converts to one of 216 RGB colors
-		colorNumber -= 16;
-
-		let blue: number = colorNumber % 6;
-		colorNumber = (colorNumber - blue) / 6;
-		let green: number = colorNumber % 6;
-		colorNumber = (colorNumber - green) / 6;
-		let red: number = colorNumber;
-
-		// red, green, blue now range on [0, 5], need to map to [0,255]
-		const convFactor: number = 255 / 5;
-		blue = Math.round(blue * convFactor);
-		green = Math.round(green * convFactor);
-		red = Math.round(red * convFactor);
-
-		return new RGBA(red, green, blue);
-	} else if (colorNumber >= 232 && colorNumber <= 255) {
-		// Converts to a grayscale value
-		colorNumber -= 232;
-		const colorLevel: number = Math.round(colorNumber / 23 * 255);
-		return new RGBA(colorLevel, colorLevel, colorLevel);
-	} else {
-		return;
-	}
+    if (colorNumber % 1 !== 0) {
+        // Should be integer
+        return;
+    }
+    if (colorNumber >= 16 && colorNumber <= 231) {
+        // Converts to one of 216 RGB colors
+        colorNumber -= 16;
+        let blue: number = colorNumber % 6;
+        colorNumber = (colorNumber - blue) / 6;
+        let green: number = colorNumber % 6;
+        colorNumber = (colorNumber - green) / 6;
+        let red: number = colorNumber;
+        // red, green, blue now range on [0, 5], need to map to [0,255]
+        const convFactor: number = 255 / 5;
+        blue = Math.round(blue * convFactor);
+        green = Math.round(green * convFactor);
+        red = Math.round(red * convFactor);
+        return new RGBA(red, green, blue);
+    }
+    else if (colorNumber >= 232 && colorNumber <= 255) {
+        // Converts to a grayscale value
+        colorNumber -= 232;
+        const colorLevel: number = Math.round(colorNumber / 23 * 255);
+        return new RGBA(colorLevel, colorLevel, colorLevel);
+    }
+    else {
+        return;
+    }
 }
diff --git a/extensions/notebook-renderers/Source/color.ts b/extensions/notebook-renderers/Source/color.ts
index 9ad281d35c47d..940ec490362cc 100644
--- a/extensions/notebook-renderers/Source/color.ts
+++ b/extensions/notebook-renderers/Source/color.ts
@@ -2,1052 +2,952 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export const enum CharCode {
-	Null = 0,
-	/**
-	 * The `\b` character.
-	 */
-	Backspace = 8,
-	/**
-	 * The `\t` character.
-	 */
-	Tab = 9,
-	/**
-	 * The `\n` character.
-	 */
-	LineFeed = 10,
-	/**
-	 * The `\r` character.
-	 */
-	CarriageReturn = 13,
-	Space = 32,
-	/**
-	 * The `!` character.
-	 */
-	ExclamationMark = 33,
-	/**
-	 * The `"` character.
-	 */
-	DoubleQuote = 34,
-	/**
-	 * The `#` character.
-	 */
-	Hash = 35,
-	/**
-	 * The `$` character.
-	 */
-	DollarSign = 36,
-	/**
-	 * The `%` character.
-	 */
-	PercentSign = 37,
-	/**
-	 * The `&` character.
-	 */
-	Ampersand = 38,
-	/**
-	 * The `'` character.
-	 */
-	SingleQuote = 39,
-	/**
-	 * The `(` character.
-	 */
-	OpenParen = 40,
-	/**
-	 * The `)` character.
-	 */
-	CloseParen = 41,
-	/**
-	 * The `*` character.
-	 */
-	Asterisk = 42,
-	/**
-	 * The `+` character.
-	 */
-	Plus = 43,
-	/**
-	 * The `,` character.
-	 */
-	Comma = 44,
-	/**
-	 * The `-` character.
-	 */
-	Dash = 45,
-	/**
-	 * The `.` character.
-	 */
-	Period = 46,
-	/**
-	 * The `/` character.
-	 */
-	Slash = 47,
-
-	Digit0 = 48,
-	Digit1 = 49,
-	Digit2 = 50,
-	Digit3 = 51,
-	Digit4 = 52,
-	Digit5 = 53,
-	Digit6 = 54,
-	Digit7 = 55,
-	Digit8 = 56,
-	Digit9 = 57,
-
-	/**
-	 * The `:` character.
-	 */
-	Colon = 58,
-	/**
-	 * The `;` character.
-	 */
-	Semicolon = 59,
-	/**
-	 * The `<` character.
-	 */
-	LessThan = 60,
-	/**
-	 * The `=` character.
-	 */
-	Equals = 61,
-	/**
-	 * The `>` character.
-	 */
-	GreaterThan = 62,
-	/**
-	 * The `?` character.
-	 */
-	QuestionMark = 63,
-	/**
-	 * The `@` character.
-	 */
-	AtSign = 64,
-
-	A = 65,
-	B = 66,
-	C = 67,
-	D = 68,
-	E = 69,
-	F = 70,
-	G = 71,
-	H = 72,
-	I = 73,
-	J = 74,
-	K = 75,
-	L = 76,
-	M = 77,
-	N = 78,
-	O = 79,
-	P = 80,
-	Q = 81,
-	R = 82,
-	S = 83,
-	T = 84,
-	U = 85,
-	V = 86,
-	W = 87,
-	X = 88,
-	Y = 89,
-	Z = 90,
-
-	/**
-	 * The `[` character.
-	 */
-	OpenSquareBracket = 91,
-	/**
-	 * The `\` character.
-	 */
-	Backslash = 92,
-	/**
-	 * The `]` character.
-	 */
-	CloseSquareBracket = 93,
-	/**
-	 * The `^` character.
-	 */
-	Caret = 94,
-	/**
-	 * The `_` character.
-	 */
-	Underline = 95,
-	/**
-	 * The ``(`)`` character.
-	 */
-	BackTick = 96,
-
-	a = 97,
-	b = 98,
-	c = 99,
-	d = 100,
-	e = 101,
-	f = 102,
-	g = 103,
-	h = 104,
-	i = 105,
-	j = 106,
-	k = 107,
-	l = 108,
-	m = 109,
-	n = 110,
-	o = 111,
-	p = 112,
-	q = 113,
-	r = 114,
-	s = 115,
-	t = 116,
-	u = 117,
-	v = 118,
-	w = 119,
-	x = 120,
-	y = 121,
-	z = 122,
-
-	/**
-	 * The `{` character.
-	 */
-	OpenCurlyBrace = 123,
-	/**
-	 * The `|` character.
-	 */
-	Pipe = 124,
-	/**
-	 * The `}` character.
-	 */
-	CloseCurlyBrace = 125,
-	/**
-	 * The `~` character.
-	 */
-	Tilde = 126,
-
-	U_Combining_Grave_Accent = 0x0300,								//	U+0300	Combining Grave Accent
-	U_Combining_Acute_Accent = 0x0301,								//	U+0301	Combining Acute Accent
-	U_Combining_Circumflex_Accent = 0x0302,							//	U+0302	Combining Circumflex Accent
-	U_Combining_Tilde = 0x0303,										//	U+0303	Combining Tilde
-	U_Combining_Macron = 0x0304,									//	U+0304	Combining Macron
-	U_Combining_Overline = 0x0305,									//	U+0305	Combining Overline
-	U_Combining_Breve = 0x0306,										//	U+0306	Combining Breve
-	U_Combining_Dot_Above = 0x0307,									//	U+0307	Combining Dot Above
-	U_Combining_Diaeresis = 0x0308,									//	U+0308	Combining Diaeresis
-	U_Combining_Hook_Above = 0x0309,								//	U+0309	Combining Hook Above
-	U_Combining_Ring_Above = 0x030A,								//	U+030A	Combining Ring Above
-	U_Combining_Double_Acute_Accent = 0x030B,						//	U+030B	Combining Double Acute Accent
-	U_Combining_Caron = 0x030C,										//	U+030C	Combining Caron
-	U_Combining_Vertical_Line_Above = 0x030D,						//	U+030D	Combining Vertical Line Above
-	U_Combining_Double_Vertical_Line_Above = 0x030E,				//	U+030E	Combining Double Vertical Line Above
-	U_Combining_Double_Grave_Accent = 0x030F,						//	U+030F	Combining Double Grave Accent
-	U_Combining_Candrabindu = 0x0310,								//	U+0310	Combining Candrabindu
-	U_Combining_Inverted_Breve = 0x0311,							//	U+0311	Combining Inverted Breve
-	U_Combining_Turned_Comma_Above = 0x0312,						//	U+0312	Combining Turned Comma Above
-	U_Combining_Comma_Above = 0x0313,								//	U+0313	Combining Comma Above
-	U_Combining_Reversed_Comma_Above = 0x0314,						//	U+0314	Combining Reversed Comma Above
-	U_Combining_Comma_Above_Right = 0x0315,							//	U+0315	Combining Comma Above Right
-	U_Combining_Grave_Accent_Below = 0x0316,						//	U+0316	Combining Grave Accent Below
-	U_Combining_Acute_Accent_Below = 0x0317,						//	U+0317	Combining Acute Accent Below
-	U_Combining_Left_Tack_Below = 0x0318,							//	U+0318	Combining Left Tack Below
-	U_Combining_Right_Tack_Below = 0x0319,							//	U+0319	Combining Right Tack Below
-	U_Combining_Left_Angle_Above = 0x031A,							//	U+031A	Combining Left Angle Above
-	U_Combining_Horn = 0x031B,										//	U+031B	Combining Horn
-	U_Combining_Left_Half_Ring_Below = 0x031C,						//	U+031C	Combining Left Half Ring Below
-	U_Combining_Up_Tack_Below = 0x031D,								//	U+031D	Combining Up Tack Below
-	U_Combining_Down_Tack_Below = 0x031E,							//	U+031E	Combining Down Tack Below
-	U_Combining_Plus_Sign_Below = 0x031F,							//	U+031F	Combining Plus Sign Below
-	U_Combining_Minus_Sign_Below = 0x0320,							//	U+0320	Combining Minus Sign Below
-	U_Combining_Palatalized_Hook_Below = 0x0321,					//	U+0321	Combining Palatalized Hook Below
-	U_Combining_Retroflex_Hook_Below = 0x0322,						//	U+0322	Combining Retroflex Hook Below
-	U_Combining_Dot_Below = 0x0323,									//	U+0323	Combining Dot Below
-	U_Combining_Diaeresis_Below = 0x0324,							//	U+0324	Combining Diaeresis Below
-	U_Combining_Ring_Below = 0x0325,								//	U+0325	Combining Ring Below
-	U_Combining_Comma_Below = 0x0326,								//	U+0326	Combining Comma Below
-	U_Combining_Cedilla = 0x0327,									//	U+0327	Combining Cedilla
-	U_Combining_Ogonek = 0x0328,									//	U+0328	Combining Ogonek
-	U_Combining_Vertical_Line_Below = 0x0329,						//	U+0329	Combining Vertical Line Below
-	U_Combining_Bridge_Below = 0x032A,								//	U+032A	Combining Bridge Below
-	U_Combining_Inverted_Double_Arch_Below = 0x032B,				//	U+032B	Combining Inverted Double Arch Below
-	U_Combining_Caron_Below = 0x032C,								//	U+032C	Combining Caron Below
-	U_Combining_Circumflex_Accent_Below = 0x032D,					//	U+032D	Combining Circumflex Accent Below
-	U_Combining_Breve_Below = 0x032E,								//	U+032E	Combining Breve Below
-	U_Combining_Inverted_Breve_Below = 0x032F,						//	U+032F	Combining Inverted Breve Below
-	U_Combining_Tilde_Below = 0x0330,								//	U+0330	Combining Tilde Below
-	U_Combining_Macron_Below = 0x0331,								//	U+0331	Combining Macron Below
-	U_Combining_Low_Line = 0x0332,									//	U+0332	Combining Low Line
-	U_Combining_Double_Low_Line = 0x0333,							//	U+0333	Combining Double Low Line
-	U_Combining_Tilde_Overlay = 0x0334,								//	U+0334	Combining Tilde Overlay
-	U_Combining_Short_Stroke_Overlay = 0x0335,						//	U+0335	Combining Short Stroke Overlay
-	U_Combining_Long_Stroke_Overlay = 0x0336,						//	U+0336	Combining Long Stroke Overlay
-	U_Combining_Short_Solidus_Overlay = 0x0337,						//	U+0337	Combining Short Solidus Overlay
-	U_Combining_Long_Solidus_Overlay = 0x0338,						//	U+0338	Combining Long Solidus Overlay
-	U_Combining_Right_Half_Ring_Below = 0x0339,						//	U+0339	Combining Right Half Ring Below
-	U_Combining_Inverted_Bridge_Below = 0x033A,						//	U+033A	Combining Inverted Bridge Below
-	U_Combining_Square_Below = 0x033B,								//	U+033B	Combining Square Below
-	U_Combining_Seagull_Below = 0x033C,								//	U+033C	Combining Seagull Below
-	U_Combining_X_Above = 0x033D,									//	U+033D	Combining X Above
-	U_Combining_Vertical_Tilde = 0x033E,							//	U+033E	Combining Vertical Tilde
-	U_Combining_Double_Overline = 0x033F,							//	U+033F	Combining Double Overline
-	U_Combining_Grave_Tone_Mark = 0x0340,							//	U+0340	Combining Grave Tone Mark
-	U_Combining_Acute_Tone_Mark = 0x0341,							//	U+0341	Combining Acute Tone Mark
-	U_Combining_Greek_Perispomeni = 0x0342,							//	U+0342	Combining Greek Perispomeni
-	U_Combining_Greek_Koronis = 0x0343,								//	U+0343	Combining Greek Koronis
-	U_Combining_Greek_Dialytika_Tonos = 0x0344,						//	U+0344	Combining Greek Dialytika Tonos
-	U_Combining_Greek_Ypogegrammeni = 0x0345,						//	U+0345	Combining Greek Ypogegrammeni
-	U_Combining_Bridge_Above = 0x0346,								//	U+0346	Combining Bridge Above
-	U_Combining_Equals_Sign_Below = 0x0347,							//	U+0347	Combining Equals Sign Below
-	U_Combining_Double_Vertical_Line_Below = 0x0348,				//	U+0348	Combining Double Vertical Line Below
-	U_Combining_Left_Angle_Below = 0x0349,							//	U+0349	Combining Left Angle Below
-	U_Combining_Not_Tilde_Above = 0x034A,							//	U+034A	Combining Not Tilde Above
-	U_Combining_Homothetic_Above = 0x034B,							//	U+034B	Combining Homothetic Above
-	U_Combining_Almost_Equal_To_Above = 0x034C,						//	U+034C	Combining Almost Equal To Above
-	U_Combining_Left_Right_Arrow_Below = 0x034D,					//	U+034D	Combining Left Right Arrow Below
-	U_Combining_Upwards_Arrow_Below = 0x034E,						//	U+034E	Combining Upwards Arrow Below
-	U_Combining_Grapheme_Joiner = 0x034F,							//	U+034F	Combining Grapheme Joiner
-	U_Combining_Right_Arrowhead_Above = 0x0350,						//	U+0350	Combining Right Arrowhead Above
-	U_Combining_Left_Half_Ring_Above = 0x0351,						//	U+0351	Combining Left Half Ring Above
-	U_Combining_Fermata = 0x0352,									//	U+0352	Combining Fermata
-	U_Combining_X_Below = 0x0353,									//	U+0353	Combining X Below
-	U_Combining_Left_Arrowhead_Below = 0x0354,						//	U+0354	Combining Left Arrowhead Below
-	U_Combining_Right_Arrowhead_Below = 0x0355,						//	U+0355	Combining Right Arrowhead Below
-	U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356,	//	U+0356	Combining Right Arrowhead And Up Arrowhead Below
-	U_Combining_Right_Half_Ring_Above = 0x0357,						//	U+0357	Combining Right Half Ring Above
-	U_Combining_Dot_Above_Right = 0x0358,							//	U+0358	Combining Dot Above Right
-	U_Combining_Asterisk_Below = 0x0359,							//	U+0359	Combining Asterisk Below
-	U_Combining_Double_Ring_Below = 0x035A,							//	U+035A	Combining Double Ring Below
-	U_Combining_Zigzag_Above = 0x035B,								//	U+035B	Combining Zigzag Above
-	U_Combining_Double_Breve_Below = 0x035C,						//	U+035C	Combining Double Breve Below
-	U_Combining_Double_Breve = 0x035D,								//	U+035D	Combining Double Breve
-	U_Combining_Double_Macron = 0x035E,								//	U+035E	Combining Double Macron
-	U_Combining_Double_Macron_Below = 0x035F,						//	U+035F	Combining Double Macron Below
-	U_Combining_Double_Tilde = 0x0360,								//	U+0360	Combining Double Tilde
-	U_Combining_Double_Inverted_Breve = 0x0361,						//	U+0361	Combining Double Inverted Breve
-	U_Combining_Double_Rightwards_Arrow_Below = 0x0362,				//	U+0362	Combining Double Rightwards Arrow Below
-	U_Combining_Latin_Small_Letter_A = 0x0363, 						//	U+0363	Combining Latin Small Letter A
-	U_Combining_Latin_Small_Letter_E = 0x0364, 						//	U+0364	Combining Latin Small Letter E
-	U_Combining_Latin_Small_Letter_I = 0x0365, 						//	U+0365	Combining Latin Small Letter I
-	U_Combining_Latin_Small_Letter_O = 0x0366, 						//	U+0366	Combining Latin Small Letter O
-	U_Combining_Latin_Small_Letter_U = 0x0367, 						//	U+0367	Combining Latin Small Letter U
-	U_Combining_Latin_Small_Letter_C = 0x0368, 						//	U+0368	Combining Latin Small Letter C
-	U_Combining_Latin_Small_Letter_D = 0x0369, 						//	U+0369	Combining Latin Small Letter D
-	U_Combining_Latin_Small_Letter_H = 0x036A, 						//	U+036A	Combining Latin Small Letter H
-	U_Combining_Latin_Small_Letter_M = 0x036B, 						//	U+036B	Combining Latin Small Letter M
-	U_Combining_Latin_Small_Letter_R = 0x036C, 						//	U+036C	Combining Latin Small Letter R
-	U_Combining_Latin_Small_Letter_T = 0x036D, 						//	U+036D	Combining Latin Small Letter T
-	U_Combining_Latin_Small_Letter_V = 0x036E, 						//	U+036E	Combining Latin Small Letter V
-	U_Combining_Latin_Small_Letter_X = 0x036F, 						//	U+036F	Combining Latin Small Letter X
-
-	/**
-	 * Unicode Character 'LINE SEPARATOR' (U+2028)
-	 * http://www.fileformat.info/info/unicode/char/2028/index.htm
-	 */
-	LINE_SEPARATOR = 0x2028,
-	/**
-	 * Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
-	 * http://www.fileformat.info/info/unicode/char/2029/index.htm
-	 */
-	PARAGRAPH_SEPARATOR = 0x2029,
-	/**
-	 * Unicode Character 'NEXT LINE' (U+0085)
-	 * http://www.fileformat.info/info/unicode/char/0085/index.htm
-	 */
-	NEXT_LINE = 0x0085,
-
-	// http://www.fileformat.info/info/unicode/category/Sk/list.htm
-	U_CIRCUMFLEX = 0x005E,									// U+005E	CIRCUMFLEX
-	U_GRAVE_ACCENT = 0x0060,								// U+0060	GRAVE ACCENT
-	U_DIAERESIS = 0x00A8,									// U+00A8	DIAERESIS
-	U_MACRON = 0x00AF,										// U+00AF	MACRON
-	U_ACUTE_ACCENT = 0x00B4,								// U+00B4	ACUTE ACCENT
-	U_CEDILLA = 0x00B8,										// U+00B8	CEDILLA
-	U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2,				// U+02C2	MODIFIER LETTER LEFT ARROWHEAD
-	U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3,				// U+02C3	MODIFIER LETTER RIGHT ARROWHEAD
-	U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4,				// U+02C4	MODIFIER LETTER UP ARROWHEAD
-	U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5,				// U+02C5	MODIFIER LETTER DOWN ARROWHEAD
-	U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2,		// U+02D2	MODIFIER LETTER CENTRED RIGHT HALF RING
-	U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3,		// U+02D3	MODIFIER LETTER CENTRED LEFT HALF RING
-	U_MODIFIER_LETTER_UP_TACK = 0x02D4,						// U+02D4	MODIFIER LETTER UP TACK
-	U_MODIFIER_LETTER_DOWN_TACK = 0x02D5,					// U+02D5	MODIFIER LETTER DOWN TACK
-	U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6,					// U+02D6	MODIFIER LETTER PLUS SIGN
-	U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7,					// U+02D7	MODIFIER LETTER MINUS SIGN
-	U_BREVE = 0x02D8,										// U+02D8	BREVE
-	U_DOT_ABOVE = 0x02D9,									// U+02D9	DOT ABOVE
-	U_RING_ABOVE = 0x02DA,									// U+02DA	RING ABOVE
-	U_OGONEK = 0x02DB,										// U+02DB	OGONEK
-	U_SMALL_TILDE = 0x02DC,									// U+02DC	SMALL TILDE
-	U_DOUBLE_ACUTE_ACCENT = 0x02DD,							// U+02DD	DOUBLE ACUTE ACCENT
-	U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE,					// U+02DE	MODIFIER LETTER RHOTIC HOOK
-	U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF,				// U+02DF	MODIFIER LETTER CROSS ACCENT
-	U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5,			// U+02E5	MODIFIER LETTER EXTRA-HIGH TONE BAR
-	U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6,				// U+02E6	MODIFIER LETTER HIGH TONE BAR
-	U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7,				// U+02E7	MODIFIER LETTER MID TONE BAR
-	U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8,				// U+02E8	MODIFIER LETTER LOW TONE BAR
-	U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9,			// U+02E9	MODIFIER LETTER EXTRA-LOW TONE BAR
-	U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA,		// U+02EA	MODIFIER LETTER YIN DEPARTING TONE MARK
-	U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB,	// U+02EB	MODIFIER LETTER YANG DEPARTING TONE MARK
-	U_MODIFIER_LETTER_UNASPIRATED = 0x02ED,					// U+02ED	MODIFIER LETTER UNASPIRATED
-	U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF,			// U+02EF	MODIFIER LETTER LOW DOWN ARROWHEAD
-	U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0,			// U+02F0	MODIFIER LETTER LOW UP ARROWHEAD
-	U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1,			// U+02F1	MODIFIER LETTER LOW LEFT ARROWHEAD
-	U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2,			// U+02F2	MODIFIER LETTER LOW RIGHT ARROWHEAD
-	U_MODIFIER_LETTER_LOW_RING = 0x02F3,					// U+02F3	MODIFIER LETTER LOW RING
-	U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4,			// U+02F4	MODIFIER LETTER MIDDLE GRAVE ACCENT
-	U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5,	// U+02F5	MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
-	U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6,	// U+02F6	MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
-	U_MODIFIER_LETTER_LOW_TILDE = 0x02F7,					// U+02F7	MODIFIER LETTER LOW TILDE
-	U_MODIFIER_LETTER_RAISED_COLON = 0x02F8,				// U+02F8	MODIFIER LETTER RAISED COLON
-	U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9,				// U+02F9	MODIFIER LETTER BEGIN HIGH TONE
-	U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA,				// U+02FA	MODIFIER LETTER END HIGH TONE
-	U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB,				// U+02FB	MODIFIER LETTER BEGIN LOW TONE
-	U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC,				// U+02FC	MODIFIER LETTER END LOW TONE
-	U_MODIFIER_LETTER_SHELF = 0x02FD,						// U+02FD	MODIFIER LETTER SHELF
-	U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE,					// U+02FE	MODIFIER LETTER OPEN SHELF
-	U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF,				// U+02FF	MODIFIER LETTER LOW LEFT ARROW
-	U_GREEK_LOWER_NUMERAL_SIGN = 0x0375,					// U+0375	GREEK LOWER NUMERAL SIGN
-	U_GREEK_TONOS = 0x0384,									// U+0384	GREEK TONOS
-	U_GREEK_DIALYTIKA_TONOS = 0x0385,						// U+0385	GREEK DIALYTIKA TONOS
-	U_GREEK_KORONIS = 0x1FBD,								// U+1FBD	GREEK KORONIS
-	U_GREEK_PSILI = 0x1FBF,									// U+1FBF	GREEK PSILI
-	U_GREEK_PERISPOMENI = 0x1FC0,							// U+1FC0	GREEK PERISPOMENI
-	U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1,				// U+1FC1	GREEK DIALYTIKA AND PERISPOMENI
-	U_GREEK_PSILI_AND_VARIA = 0x1FCD,						// U+1FCD	GREEK PSILI AND VARIA
-	U_GREEK_PSILI_AND_OXIA = 0x1FCE,						// U+1FCE	GREEK PSILI AND OXIA
-	U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF,					// U+1FCF	GREEK PSILI AND PERISPOMENI
-	U_GREEK_DASIA_AND_VARIA = 0x1FDD,						// U+1FDD	GREEK DASIA AND VARIA
-	U_GREEK_DASIA_AND_OXIA = 0x1FDE,						// U+1FDE	GREEK DASIA AND OXIA
-	U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF,					// U+1FDF	GREEK DASIA AND PERISPOMENI
-	U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED,					// U+1FED	GREEK DIALYTIKA AND VARIA
-	U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE,					// U+1FEE	GREEK DIALYTIKA AND OXIA
-	U_GREEK_VARIA = 0x1FEF,									// U+1FEF	GREEK VARIA
-	U_GREEK_OXIA = 0x1FFD,									// U+1FFD	GREEK OXIA
-	U_GREEK_DASIA = 0x1FFE,									// U+1FFE	GREEK DASIA
-
-	U_IDEOGRAPHIC_FULL_STOP = 0x3002,						// U+3002	IDEOGRAPHIC FULL STOP
-	U_LEFT_CORNER_BRACKET = 0x300C,							// U+300C	LEFT CORNER BRACKET
-	U_RIGHT_CORNER_BRACKET = 0x300D,						// U+300D	RIGHT CORNER BRACKET
-	U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010,				// U+3010	LEFT BLACK LENTICULAR BRACKET
-	U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011,				// U+3011	RIGHT BLACK LENTICULAR BRACKET
-
-
-	U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'
-
-	/**
-	 * UTF-8 BOM
-	 * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
-	 * http://www.fileformat.info/info/unicode/char/feff/index.htm
-	 */
-	UTF8_BOM = 65279,
-
-	U_FULLWIDTH_SEMICOLON = 0xFF1B,							// U+FF1B	FULLWIDTH SEMICOLON
-	U_FULLWIDTH_COMMA = 0xFF0C,								// U+FF0C	FULLWIDTH COMMA
+    Null = 0,
+    /**
+     * The `\b` character.
+     */
+    Backspace = 8,
+    /**
+     * The `\t` character.
+     */
+    Tab = 9,
+    /**
+     * The `\n` character.
+     */
+    LineFeed = 10,
+    /**
+     * The `\r` character.
+     */
+    CarriageReturn = 13,
+    Space = 32,
+    /**
+     * The `!` character.
+     */
+    ExclamationMark = 33,
+    /**
+     * The `"` character.
+     */
+    DoubleQuote = 34,
+    /**
+     * The `#` character.
+     */
+    Hash = 35,
+    /**
+     * The `$` character.
+     */
+    DollarSign = 36,
+    /**
+     * The `%` character.
+     */
+    PercentSign = 37,
+    /**
+     * The `&` character.
+     */
+    Ampersand = 38,
+    /**
+     * The `'` character.
+     */
+    SingleQuote = 39,
+    /**
+     * The `(` character.
+     */
+    OpenParen = 40,
+    /**
+     * The `)` character.
+     */
+    CloseParen = 41,
+    /**
+     * The `*` character.
+     */
+    Asterisk = 42,
+    /**
+     * The `+` character.
+     */
+    Plus = 43,
+    /**
+     * The `,` character.
+     */
+    Comma = 44,
+    /**
+     * The `-` character.
+     */
+    Dash = 45,
+    /**
+     * The `.` character.
+     */
+    Period = 46,
+    /**
+     * The `/` character.
+     */
+    Slash = 47,
+    Digit0 = 48,
+    Digit1 = 49,
+    Digit2 = 50,
+    Digit3 = 51,
+    Digit4 = 52,
+    Digit5 = 53,
+    Digit6 = 54,
+    Digit7 = 55,
+    Digit8 = 56,
+    Digit9 = 57,
+    /**
+     * The `:` character.
+     */
+    Colon = 58,
+    /**
+     * The `;` character.
+     */
+    Semicolon = 59,
+    /**
+     * The `<` character.
+     */
+    LessThan = 60,
+    /**
+     * The `=` character.
+     */
+    Equals = 61,
+    /**
+     * The `>` character.
+     */
+    GreaterThan = 62,
+    /**
+     * The `?` character.
+     */
+    QuestionMark = 63,
+    /**
+     * The `@` character.
+     */
+    AtSign = 64,
+    A = 65,
+    B = 66,
+    C = 67,
+    D = 68,
+    E = 69,
+    F = 70,
+    G = 71,
+    H = 72,
+    I = 73,
+    J = 74,
+    K = 75,
+    L = 76,
+    M = 77,
+    N = 78,
+    O = 79,
+    P = 80,
+    Q = 81,
+    R = 82,
+    S = 83,
+    T = 84,
+    U = 85,
+    V = 86,
+    W = 87,
+    X = 88,
+    Y = 89,
+    Z = 90,
+    /**
+     * The `[` character.
+     */
+    OpenSquareBracket = 91,
+    /**
+     * The `\` character.
+     */
+    Backslash = 92,
+    /**
+     * The `]` character.
+     */
+    CloseSquareBracket = 93,
+    /**
+     * The `^` character.
+     */
+    Caret = 94,
+    /**
+     * The `_` character.
+     */
+    Underline = 95,
+    /**
+     * The ``(`)`` character.
+     */
+    BackTick = 96,
+    a = 97,
+    b = 98,
+    c = 99,
+    d = 100,
+    e = 101,
+    f = 102,
+    g = 103,
+    h = 104,
+    i = 105,
+    j = 106,
+    k = 107,
+    l = 108,
+    m = 109,
+    n = 110,
+    o = 111,
+    p = 112,
+    q = 113,
+    r = 114,
+    s = 115,
+    t = 116,
+    u = 117,
+    v = 118,
+    w = 119,
+    x = 120,
+    y = 121,
+    z = 122,
+    /**
+     * The `{` character.
+     */
+    OpenCurlyBrace = 123,
+    /**
+     * The `|` character.
+     */
+    Pipe = 124,
+    /**
+     * The `}` character.
+     */
+    CloseCurlyBrace = 125,
+    /**
+     * The `~` character.
+     */
+    Tilde = 126,
+    U_Combining_Grave_Accent = 0x0300,//	U+0300	Combining Grave Accent
+    U_Combining_Acute_Accent = 0x0301,//	U+0301	Combining Acute Accent
+    U_Combining_Circumflex_Accent = 0x0302,//	U+0302	Combining Circumflex Accent
+    U_Combining_Tilde = 0x0303,//	U+0303	Combining Tilde
+    U_Combining_Macron = 0x0304,//	U+0304	Combining Macron
+    U_Combining_Overline = 0x0305,//	U+0305	Combining Overline
+    U_Combining_Breve = 0x0306,//	U+0306	Combining Breve
+    U_Combining_Dot_Above = 0x0307,//	U+0307	Combining Dot Above
+    U_Combining_Diaeresis = 0x0308,//	U+0308	Combining Diaeresis
+    U_Combining_Hook_Above = 0x0309,//	U+0309	Combining Hook Above
+    U_Combining_Ring_Above = 0x030A,//	U+030A	Combining Ring Above
+    U_Combining_Double_Acute_Accent = 0x030B,//	U+030B	Combining Double Acute Accent
+    U_Combining_Caron = 0x030C,//	U+030C	Combining Caron
+    U_Combining_Vertical_Line_Above = 0x030D,//	U+030D	Combining Vertical Line Above
+    U_Combining_Double_Vertical_Line_Above = 0x030E,//	U+030E	Combining Double Vertical Line Above
+    U_Combining_Double_Grave_Accent = 0x030F,//	U+030F	Combining Double Grave Accent
+    U_Combining_Candrabindu = 0x0310,//	U+0310	Combining Candrabindu
+    U_Combining_Inverted_Breve = 0x0311,//	U+0311	Combining Inverted Breve
+    U_Combining_Turned_Comma_Above = 0x0312,//	U+0312	Combining Turned Comma Above
+    U_Combining_Comma_Above = 0x0313,//	U+0313	Combining Comma Above
+    U_Combining_Reversed_Comma_Above = 0x0314,//	U+0314	Combining Reversed Comma Above
+    U_Combining_Comma_Above_Right = 0x0315,//	U+0315	Combining Comma Above Right
+    U_Combining_Grave_Accent_Below = 0x0316,//	U+0316	Combining Grave Accent Below
+    U_Combining_Acute_Accent_Below = 0x0317,//	U+0317	Combining Acute Accent Below
+    U_Combining_Left_Tack_Below = 0x0318,//	U+0318	Combining Left Tack Below
+    U_Combining_Right_Tack_Below = 0x0319,//	U+0319	Combining Right Tack Below
+    U_Combining_Left_Angle_Above = 0x031A,//	U+031A	Combining Left Angle Above
+    U_Combining_Horn = 0x031B,//	U+031B	Combining Horn
+    U_Combining_Left_Half_Ring_Below = 0x031C,//	U+031C	Combining Left Half Ring Below
+    U_Combining_Up_Tack_Below = 0x031D,//	U+031D	Combining Up Tack Below
+    U_Combining_Down_Tack_Below = 0x031E,//	U+031E	Combining Down Tack Below
+    U_Combining_Plus_Sign_Below = 0x031F,//	U+031F	Combining Plus Sign Below
+    U_Combining_Minus_Sign_Below = 0x0320,//	U+0320	Combining Minus Sign Below
+    U_Combining_Palatalized_Hook_Below = 0x0321,//	U+0321	Combining Palatalized Hook Below
+    U_Combining_Retroflex_Hook_Below = 0x0322,//	U+0322	Combining Retroflex Hook Below
+    U_Combining_Dot_Below = 0x0323,//	U+0323	Combining Dot Below
+    U_Combining_Diaeresis_Below = 0x0324,//	U+0324	Combining Diaeresis Below
+    U_Combining_Ring_Below = 0x0325,//	U+0325	Combining Ring Below
+    U_Combining_Comma_Below = 0x0326,//	U+0326	Combining Comma Below
+    U_Combining_Cedilla = 0x0327,//	U+0327	Combining Cedilla
+    U_Combining_Ogonek = 0x0328,//	U+0328	Combining Ogonek
+    U_Combining_Vertical_Line_Below = 0x0329,//	U+0329	Combining Vertical Line Below
+    U_Combining_Bridge_Below = 0x032A,//	U+032A	Combining Bridge Below
+    U_Combining_Inverted_Double_Arch_Below = 0x032B,//	U+032B	Combining Inverted Double Arch Below
+    U_Combining_Caron_Below = 0x032C,//	U+032C	Combining Caron Below
+    U_Combining_Circumflex_Accent_Below = 0x032D,//	U+032D	Combining Circumflex Accent Below
+    U_Combining_Breve_Below = 0x032E,//	U+032E	Combining Breve Below
+    U_Combining_Inverted_Breve_Below = 0x032F,//	U+032F	Combining Inverted Breve Below
+    U_Combining_Tilde_Below = 0x0330,//	U+0330	Combining Tilde Below
+    U_Combining_Macron_Below = 0x0331,//	U+0331	Combining Macron Below
+    U_Combining_Low_Line = 0x0332,//	U+0332	Combining Low Line
+    U_Combining_Double_Low_Line = 0x0333,//	U+0333	Combining Double Low Line
+    U_Combining_Tilde_Overlay = 0x0334,//	U+0334	Combining Tilde Overlay
+    U_Combining_Short_Stroke_Overlay = 0x0335,//	U+0335	Combining Short Stroke Overlay
+    U_Combining_Long_Stroke_Overlay = 0x0336,//	U+0336	Combining Long Stroke Overlay
+    U_Combining_Short_Solidus_Overlay = 0x0337,//	U+0337	Combining Short Solidus Overlay
+    U_Combining_Long_Solidus_Overlay = 0x0338,//	U+0338	Combining Long Solidus Overlay
+    U_Combining_Right_Half_Ring_Below = 0x0339,//	U+0339	Combining Right Half Ring Below
+    U_Combining_Inverted_Bridge_Below = 0x033A,//	U+033A	Combining Inverted Bridge Below
+    U_Combining_Square_Below = 0x033B,//	U+033B	Combining Square Below
+    U_Combining_Seagull_Below = 0x033C,//	U+033C	Combining Seagull Below
+    U_Combining_X_Above = 0x033D,//	U+033D	Combining X Above
+    U_Combining_Vertical_Tilde = 0x033E,//	U+033E	Combining Vertical Tilde
+    U_Combining_Double_Overline = 0x033F,//	U+033F	Combining Double Overline
+    U_Combining_Grave_Tone_Mark = 0x0340,//	U+0340	Combining Grave Tone Mark
+    U_Combining_Acute_Tone_Mark = 0x0341,//	U+0341	Combining Acute Tone Mark
+    U_Combining_Greek_Perispomeni = 0x0342,//	U+0342	Combining Greek Perispomeni
+    U_Combining_Greek_Koronis = 0x0343,//	U+0343	Combining Greek Koronis
+    U_Combining_Greek_Dialytika_Tonos = 0x0344,//	U+0344	Combining Greek Dialytika Tonos
+    U_Combining_Greek_Ypogegrammeni = 0x0345,//	U+0345	Combining Greek Ypogegrammeni
+    U_Combining_Bridge_Above = 0x0346,//	U+0346	Combining Bridge Above
+    U_Combining_Equals_Sign_Below = 0x0347,//	U+0347	Combining Equals Sign Below
+    U_Combining_Double_Vertical_Line_Below = 0x0348,//	U+0348	Combining Double Vertical Line Below
+    U_Combining_Left_Angle_Below = 0x0349,//	U+0349	Combining Left Angle Below
+    U_Combining_Not_Tilde_Above = 0x034A,//	U+034A	Combining Not Tilde Above
+    U_Combining_Homothetic_Above = 0x034B,//	U+034B	Combining Homothetic Above
+    U_Combining_Almost_Equal_To_Above = 0x034C,//	U+034C	Combining Almost Equal To Above
+    U_Combining_Left_Right_Arrow_Below = 0x034D,//	U+034D	Combining Left Right Arrow Below
+    U_Combining_Upwards_Arrow_Below = 0x034E,//	U+034E	Combining Upwards Arrow Below
+    U_Combining_Grapheme_Joiner = 0x034F,//	U+034F	Combining Grapheme Joiner
+    U_Combining_Right_Arrowhead_Above = 0x0350,//	U+0350	Combining Right Arrowhead Above
+    U_Combining_Left_Half_Ring_Above = 0x0351,//	U+0351	Combining Left Half Ring Above
+    U_Combining_Fermata = 0x0352,//	U+0352	Combining Fermata
+    U_Combining_X_Below = 0x0353,//	U+0353	Combining X Below
+    U_Combining_Left_Arrowhead_Below = 0x0354,//	U+0354	Combining Left Arrowhead Below
+    U_Combining_Right_Arrowhead_Below = 0x0355,//	U+0355	Combining Right Arrowhead Below
+    U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356,//	U+0356	Combining Right Arrowhead And Up Arrowhead Below
+    U_Combining_Right_Half_Ring_Above = 0x0357,//	U+0357	Combining Right Half Ring Above
+    U_Combining_Dot_Above_Right = 0x0358,//	U+0358	Combining Dot Above Right
+    U_Combining_Asterisk_Below = 0x0359,//	U+0359	Combining Asterisk Below
+    U_Combining_Double_Ring_Below = 0x035A,//	U+035A	Combining Double Ring Below
+    U_Combining_Zigzag_Above = 0x035B,//	U+035B	Combining Zigzag Above
+    U_Combining_Double_Breve_Below = 0x035C,//	U+035C	Combining Double Breve Below
+    U_Combining_Double_Breve = 0x035D,//	U+035D	Combining Double Breve
+    U_Combining_Double_Macron = 0x035E,//	U+035E	Combining Double Macron
+    U_Combining_Double_Macron_Below = 0x035F,//	U+035F	Combining Double Macron Below
+    U_Combining_Double_Tilde = 0x0360,//	U+0360	Combining Double Tilde
+    U_Combining_Double_Inverted_Breve = 0x0361,//	U+0361	Combining Double Inverted Breve
+    U_Combining_Double_Rightwards_Arrow_Below = 0x0362,//	U+0362	Combining Double Rightwards Arrow Below
+    U_Combining_Latin_Small_Letter_A = 0x0363,//	U+0363	Combining Latin Small Letter A
+    U_Combining_Latin_Small_Letter_E = 0x0364,//	U+0364	Combining Latin Small Letter E
+    U_Combining_Latin_Small_Letter_I = 0x0365,//	U+0365	Combining Latin Small Letter I
+    U_Combining_Latin_Small_Letter_O = 0x0366,//	U+0366	Combining Latin Small Letter O
+    U_Combining_Latin_Small_Letter_U = 0x0367,//	U+0367	Combining Latin Small Letter U
+    U_Combining_Latin_Small_Letter_C = 0x0368,//	U+0368	Combining Latin Small Letter C
+    U_Combining_Latin_Small_Letter_D = 0x0369,//	U+0369	Combining Latin Small Letter D
+    U_Combining_Latin_Small_Letter_H = 0x036A,//	U+036A	Combining Latin Small Letter H
+    U_Combining_Latin_Small_Letter_M = 0x036B,//	U+036B	Combining Latin Small Letter M
+    U_Combining_Latin_Small_Letter_R = 0x036C,//	U+036C	Combining Latin Small Letter R
+    U_Combining_Latin_Small_Letter_T = 0x036D,//	U+036D	Combining Latin Small Letter T
+    U_Combining_Latin_Small_Letter_V = 0x036E,//	U+036E	Combining Latin Small Letter V
+    U_Combining_Latin_Small_Letter_X = 0x036F,//	U+036F	Combining Latin Small Letter X
+    /**
+     * Unicode Character 'LINE SEPARATOR' (U+2028)
+     * http://www.fileformat.info/info/unicode/char/2028/index.htm
+     */
+    LINE_SEPARATOR = 0x2028,
+    /**
+     * Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
+     * http://www.fileformat.info/info/unicode/char/2029/index.htm
+     */
+    PARAGRAPH_SEPARATOR = 0x2029,
+    /**
+     * Unicode Character 'NEXT LINE' (U+0085)
+     * http://www.fileformat.info/info/unicode/char/0085/index.htm
+     */
+    NEXT_LINE = 0x0085,
+    // http://www.fileformat.info/info/unicode/category/Sk/list.htm
+    U_CIRCUMFLEX = 0x005E,// U+005E	CIRCUMFLEX
+    U_GRAVE_ACCENT = 0x0060,// U+0060	GRAVE ACCENT
+    U_DIAERESIS = 0x00A8,// U+00A8	DIAERESIS
+    U_MACRON = 0x00AF,// U+00AF	MACRON
+    U_ACUTE_ACCENT = 0x00B4,// U+00B4	ACUTE ACCENT
+    U_CEDILLA = 0x00B8,// U+00B8	CEDILLA
+    U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2,// U+02C2	MODIFIER LETTER LEFT ARROWHEAD
+    U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3,// U+02C3	MODIFIER LETTER RIGHT ARROWHEAD
+    U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4,// U+02C4	MODIFIER LETTER UP ARROWHEAD
+    U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5,// U+02C5	MODIFIER LETTER DOWN ARROWHEAD
+    U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2,// U+02D2	MODIFIER LETTER CENTRED RIGHT HALF RING
+    U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3,// U+02D3	MODIFIER LETTER CENTRED LEFT HALF RING
+    U_MODIFIER_LETTER_UP_TACK = 0x02D4,// U+02D4	MODIFIER LETTER UP TACK
+    U_MODIFIER_LETTER_DOWN_TACK = 0x02D5,// U+02D5	MODIFIER LETTER DOWN TACK
+    U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6,// U+02D6	MODIFIER LETTER PLUS SIGN
+    U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7,// U+02D7	MODIFIER LETTER MINUS SIGN
+    U_BREVE = 0x02D8,// U+02D8	BREVE
+    U_DOT_ABOVE = 0x02D9,// U+02D9	DOT ABOVE
+    U_RING_ABOVE = 0x02DA,// U+02DA	RING ABOVE
+    U_OGONEK = 0x02DB,// U+02DB	OGONEK
+    U_SMALL_TILDE = 0x02DC,// U+02DC	SMALL TILDE
+    U_DOUBLE_ACUTE_ACCENT = 0x02DD,// U+02DD	DOUBLE ACUTE ACCENT
+    U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE,// U+02DE	MODIFIER LETTER RHOTIC HOOK
+    U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF,// U+02DF	MODIFIER LETTER CROSS ACCENT
+    U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5,// U+02E5	MODIFIER LETTER EXTRA-HIGH TONE BAR
+    U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6,// U+02E6	MODIFIER LETTER HIGH TONE BAR
+    U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7,// U+02E7	MODIFIER LETTER MID TONE BAR
+    U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8,// U+02E8	MODIFIER LETTER LOW TONE BAR
+    U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9,// U+02E9	MODIFIER LETTER EXTRA-LOW TONE BAR
+    U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA,// U+02EA	MODIFIER LETTER YIN DEPARTING TONE MARK
+    U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB,// U+02EB	MODIFIER LETTER YANG DEPARTING TONE MARK
+    U_MODIFIER_LETTER_UNASPIRATED = 0x02ED,// U+02ED	MODIFIER LETTER UNASPIRATED
+    U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF,// U+02EF	MODIFIER LETTER LOW DOWN ARROWHEAD
+    U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0,// U+02F0	MODIFIER LETTER LOW UP ARROWHEAD
+    U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1,// U+02F1	MODIFIER LETTER LOW LEFT ARROWHEAD
+    U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2,// U+02F2	MODIFIER LETTER LOW RIGHT ARROWHEAD
+    U_MODIFIER_LETTER_LOW_RING = 0x02F3,// U+02F3	MODIFIER LETTER LOW RING
+    U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4,// U+02F4	MODIFIER LETTER MIDDLE GRAVE ACCENT
+    U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5,// U+02F5	MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
+    U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6,// U+02F6	MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
+    U_MODIFIER_LETTER_LOW_TILDE = 0x02F7,// U+02F7	MODIFIER LETTER LOW TILDE
+    U_MODIFIER_LETTER_RAISED_COLON = 0x02F8,// U+02F8	MODIFIER LETTER RAISED COLON
+    U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9,// U+02F9	MODIFIER LETTER BEGIN HIGH TONE
+    U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA,// U+02FA	MODIFIER LETTER END HIGH TONE
+    U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB,// U+02FB	MODIFIER LETTER BEGIN LOW TONE
+    U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC,// U+02FC	MODIFIER LETTER END LOW TONE
+    U_MODIFIER_LETTER_SHELF = 0x02FD,// U+02FD	MODIFIER LETTER SHELF
+    U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE,// U+02FE	MODIFIER LETTER OPEN SHELF
+    U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF,// U+02FF	MODIFIER LETTER LOW LEFT ARROW
+    U_GREEK_LOWER_NUMERAL_SIGN = 0x0375,// U+0375	GREEK LOWER NUMERAL SIGN
+    U_GREEK_TONOS = 0x0384,// U+0384	GREEK TONOS
+    U_GREEK_DIALYTIKA_TONOS = 0x0385,// U+0385	GREEK DIALYTIKA TONOS
+    U_GREEK_KORONIS = 0x1FBD,// U+1FBD	GREEK KORONIS
+    U_GREEK_PSILI = 0x1FBF,// U+1FBF	GREEK PSILI
+    U_GREEK_PERISPOMENI = 0x1FC0,// U+1FC0	GREEK PERISPOMENI
+    U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1,// U+1FC1	GREEK DIALYTIKA AND PERISPOMENI
+    U_GREEK_PSILI_AND_VARIA = 0x1FCD,// U+1FCD	GREEK PSILI AND VARIA
+    U_GREEK_PSILI_AND_OXIA = 0x1FCE,// U+1FCE	GREEK PSILI AND OXIA
+    U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF,// U+1FCF	GREEK PSILI AND PERISPOMENI
+    U_GREEK_DASIA_AND_VARIA = 0x1FDD,// U+1FDD	GREEK DASIA AND VARIA
+    U_GREEK_DASIA_AND_OXIA = 0x1FDE,// U+1FDE	GREEK DASIA AND OXIA
+    U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF,// U+1FDF	GREEK DASIA AND PERISPOMENI
+    U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED,// U+1FED	GREEK DIALYTIKA AND VARIA
+    U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE,// U+1FEE	GREEK DIALYTIKA AND OXIA
+    U_GREEK_VARIA = 0x1FEF,// U+1FEF	GREEK VARIA
+    U_GREEK_OXIA = 0x1FFD,// U+1FFD	GREEK OXIA
+    U_GREEK_DASIA = 0x1FFE,// U+1FFE	GREEK DASIA
+    U_IDEOGRAPHIC_FULL_STOP = 0x3002,// U+3002	IDEOGRAPHIC FULL STOP
+    U_LEFT_CORNER_BRACKET = 0x300C,// U+300C	LEFT CORNER BRACKET
+    U_RIGHT_CORNER_BRACKET = 0x300D,// U+300D	RIGHT CORNER BRACKET
+    U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010,// U+3010	LEFT BLACK LENTICULAR BRACKET
+    U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011,// U+3011	RIGHT BLACK LENTICULAR BRACKET
+    U_OVERLINE = 0x203E,// Unicode Character 'OVERLINE'
+    /**
+     * UTF-8 BOM
+     * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
+     * http://www.fileformat.info/info/unicode/char/feff/index.htm
+     */
+    UTF8_BOM = 65279,
+    U_FULLWIDTH_SEMICOLON = 0xFF1B,// U+FF1B	FULLWIDTH SEMICOLON
+    U_FULLWIDTH_COMMA = 0xFF0C
 }
-
 function roundFloat(number: number, decimalPoints: number): number {
-	const decimal = Math.pow(10, decimalPoints);
-	return Math.round(number * decimal) / decimal;
+    const decimal = Math.pow(10, decimalPoints);
+    return Math.round(number * decimal) / decimal;
 }
-
 export class RGBA {
-	_rgbaBrand: void = undefined;
-
-	/**
-	 * Red: integer in [0-255]
-	 */
-	readonly r: number;
-
-	/**
-	 * Green: integer in [0-255]
-	 */
-	readonly g: number;
-
-	/**
-	 * Blue: integer in [0-255]
-	 */
-	readonly b: number;
-
-	/**
-	 * Alpha: float in [0-1]
-	 */
-	readonly a: number;
-
-	constructor(r: number, g: number, b: number, a: number = 1) {
-		this.r = Math.min(255, Math.max(0, r)) | 0;
-		this.g = Math.min(255, Math.max(0, g)) | 0;
-		this.b = Math.min(255, Math.max(0, b)) | 0;
-		this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
-	}
-
-	static equals(a: RGBA, b: RGBA): boolean {
-		return a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a;
-	}
+    _rgbaBrand: void = undefined;
+    /**
+     * Red: integer in [0-255]
+     */
+    readonly r: number;
+    /**
+     * Green: integer in [0-255]
+     */
+    readonly g: number;
+    /**
+     * Blue: integer in [0-255]
+     */
+    readonly b: number;
+    /**
+     * Alpha: float in [0-1]
+     */
+    readonly a: number;
+    constructor(r: number, g: number, b: number, a: number = 1) {
+        this.r = Math.min(255, Math.max(0, r)) | 0;
+        this.g = Math.min(255, Math.max(0, g)) | 0;
+        this.b = Math.min(255, Math.max(0, b)) | 0;
+        this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
+    }
+    static equals(a: RGBA, b: RGBA): boolean {
+        return a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a;
+    }
 }
-
 export class HSLA {
-
-	_hslaBrand: void = undefined;
-
-	/**
-	 * Hue: integer in [0, 360]
-	 */
-	readonly h: number;
-
-	/**
-	 * Saturation: float in [0, 1]
-	 */
-	readonly s: number;
-
-	/**
-	 * Luminosity: float in [0, 1]
-	 */
-	readonly l: number;
-
-	/**
-	 * Alpha: float in [0, 1]
-	 */
-	readonly a: number;
-
-	constructor(h: number, s: number, l: number, a: number) {
-		this.h = Math.max(Math.min(360, h), 0) | 0;
-		this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
-		this.l = roundFloat(Math.max(Math.min(1, l), 0), 3);
-		this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
-	}
-
-	static equals(a: HSLA, b: HSLA): boolean {
-		return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a;
-	}
-
-	/**
-	 * Converts an RGB color value to HSL. Conversion formula
-	 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
-	 * Assumes r, g, and b are contained in the set [0, 255] and
-	 * returns h in the set [0, 360], s, and l in the set [0, 1].
-	 */
-	static fromRGBA(rgba: RGBA): HSLA {
-		const r = rgba.r / 255;
-		const g = rgba.g / 255;
-		const b = rgba.b / 255;
-		const a = rgba.a;
-
-		const max = Math.max(r, g, b);
-		const min = Math.min(r, g, b);
-		let h = 0;
-		let s = 0;
-		const l = (min + max) / 2;
-		const chroma = max - min;
-
-		if (chroma > 0) {
-			s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1);
-
-			switch (max) {
-				case r: h = (g - b) / chroma + (g < b ? 6 : 0); break;
-				case g: h = (b - r) / chroma + 2; break;
-				case b: h = (r - g) / chroma + 4; break;
-			}
-
-			h *= 60;
-			h = Math.round(h);
-		}
-		return new HSLA(h, s, l, a);
-	}
-
-	private static _hue2rgb(p: number, q: number, t: number): number {
-		if (t < 0) {
-			t += 1;
-		}
-		if (t > 1) {
-			t -= 1;
-		}
-		if (t < 1 / 6) {
-			return p + (q - p) * 6 * t;
-		}
-		if (t < 1 / 2) {
-			return q;
-		}
-		if (t < 2 / 3) {
-			return p + (q - p) * (2 / 3 - t) * 6;
-		}
-		return p;
-	}
-
-	/**
-	 * Converts an HSL color value to RGB. Conversion formula
-	 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
-	 * Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and
-	 * returns r, g, and b in the set [0, 255].
-	 */
-	static toRGBA(hsla: HSLA): RGBA {
-		const h = hsla.h / 360;
-		const { s, l, a } = hsla;
-		let r: number, g: number, b: number;
-
-		if (s === 0) {
-			r = g = b = l; // achromatic
-		} else {
-			const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
-			const p = 2 * l - q;
-			r = HSLA._hue2rgb(p, q, h + 1 / 3);
-			g = HSLA._hue2rgb(p, q, h);
-			b = HSLA._hue2rgb(p, q, h - 1 / 3);
-		}
-
-		return new RGBA(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a);
-	}
+    _hslaBrand: void = undefined;
+    /**
+     * Hue: integer in [0, 360]
+     */
+    readonly h: number;
+    /**
+     * Saturation: float in [0, 1]
+     */
+    readonly s: number;
+    /**
+     * Luminosity: float in [0, 1]
+     */
+    readonly l: number;
+    /**
+     * Alpha: float in [0, 1]
+     */
+    readonly a: number;
+    constructor(h: number, s: number, l: number, a: number) {
+        this.h = Math.max(Math.min(360, h), 0) | 0;
+        this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
+        this.l = roundFloat(Math.max(Math.min(1, l), 0), 3);
+        this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
+    }
+    static equals(a: HSLA, b: HSLA): boolean {
+        return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a;
+    }
+    /**
+     * Converts an RGB color value to HSL. Conversion formula
+     * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
+     * Assumes r, g, and b are contained in the set [0, 255] and
+     * returns h in the set [0, 360], s, and l in the set [0, 1].
+     */
+    static fromRGBA(rgba: RGBA): HSLA {
+        const r = rgba.r / 255;
+        const g = rgba.g / 255;
+        const b = rgba.b / 255;
+        const a = rgba.a;
+        const max = Math.max(r, g, b);
+        const min = Math.min(r, g, b);
+        let h = 0;
+        let s = 0;
+        const l = (min + max) / 2;
+        const chroma = max - min;
+        if (chroma > 0) {
+            s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1);
+            switch (max) {
+                case r:
+                    h = (g - b) / chroma + (g < b ? 6 : 0);
+                    break;
+                case g:
+                    h = (b - r) / chroma + 2;
+                    break;
+                case b:
+                    h = (r - g) / chroma + 4;
+                    break;
+            }
+            h *= 60;
+            h = Math.round(h);
+        }
+        return new HSLA(h, s, l, a);
+    }
+    private static _hue2rgb(p: number, q: number, t: number): number {
+        if (t < 0) {
+            t += 1;
+        }
+        if (t > 1) {
+            t -= 1;
+        }
+        if (t < 1 / 6) {
+            return p + (q - p) * 6 * t;
+        }
+        if (t < 1 / 2) {
+            return q;
+        }
+        if (t < 2 / 3) {
+            return p + (q - p) * (2 / 3 - t) * 6;
+        }
+        return p;
+    }
+    /**
+     * Converts an HSL color value to RGB. Conversion formula
+     * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
+     * Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and
+     * returns r, g, and b in the set [0, 255].
+     */
+    static toRGBA(hsla: HSLA): RGBA {
+        const h = hsla.h / 360;
+        const { s, l, a } = hsla;
+        let r: number, g: number, b: number;
+        if (s === 0) {
+            r = g = b = l; // achromatic
+        }
+        else {
+            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+            const p = 2 * l - q;
+            r = HSLA._hue2rgb(p, q, h + 1 / 3);
+            g = HSLA._hue2rgb(p, q, h);
+            b = HSLA._hue2rgb(p, q, h - 1 / 3);
+        }
+        return new RGBA(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a);
+    }
 }
-
 export class HSVA {
-
-	_hsvaBrand: void = undefined;
-
-	/**
-	 * Hue: integer in [0, 360]
-	 */
-	readonly h: number;
-
-	/**
-	 * Saturation: float in [0, 1]
-	 */
-	readonly s: number;
-
-	/**
-	 * Value: float in [0, 1]
-	 */
-	readonly v: number;
-
-	/**
-	 * Alpha: float in [0, 1]
-	 */
-	readonly a: number;
-
-	constructor(h: number, s: number, v: number, a: number) {
-		this.h = Math.max(Math.min(360, h), 0) | 0;
-		this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
-		this.v = roundFloat(Math.max(Math.min(1, v), 0), 3);
-		this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
-	}
-
-	static equals(a: HSVA, b: HSVA): boolean {
-		return a.h === b.h && a.s === b.s && a.v === b.v && a.a === b.a;
-	}
-
-	// from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm
-	static fromRGBA(rgba: RGBA): HSVA {
-		const r = rgba.r / 255;
-		const g = rgba.g / 255;
-		const b = rgba.b / 255;
-		const cmax = Math.max(r, g, b);
-		const cmin = Math.min(r, g, b);
-		const delta = cmax - cmin;
-		const s = cmax === 0 ? 0 : (delta / cmax);
-		let m: number;
-
-		if (delta === 0) {
-			m = 0;
-		} else if (cmax === r) {
-			m = ((((g - b) / delta) % 6) + 6) % 6;
-		} else if (cmax === g) {
-			m = ((b - r) / delta) + 2;
-		} else {
-			m = ((r - g) / delta) + 4;
-		}
-
-		return new HSVA(Math.round(m * 60), s, cmax, rgba.a);
-	}
-
-	// from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm
-	static toRGBA(hsva: HSVA): RGBA {
-		const { h, s, v, a } = hsva;
-		const c = v * s;
-		const x = c * (1 - Math.abs((h / 60) % 2 - 1));
-		const m = v - c;
-		let [r, g, b] = [0, 0, 0];
-
-		if (h < 60) {
-			r = c;
-			g = x;
-		} else if (h < 120) {
-			r = x;
-			g = c;
-		} else if (h < 180) {
-			g = c;
-			b = x;
-		} else if (h < 240) {
-			g = x;
-			b = c;
-		} else if (h < 300) {
-			r = x;
-			b = c;
-		} else if (h <= 360) {
-			r = c;
-			b = x;
-		}
-
-		r = Math.round((r + m) * 255);
-		g = Math.round((g + m) * 255);
-		b = Math.round((b + m) * 255);
-
-		return new RGBA(r, g, b, a);
-	}
+    _hsvaBrand: void = undefined;
+    /**
+     * Hue: integer in [0, 360]
+     */
+    readonly h: number;
+    /**
+     * Saturation: float in [0, 1]
+     */
+    readonly s: number;
+    /**
+     * Value: float in [0, 1]
+     */
+    readonly v: number;
+    /**
+     * Alpha: float in [0, 1]
+     */
+    readonly a: number;
+    constructor(h: number, s: number, v: number, a: number) {
+        this.h = Math.max(Math.min(360, h), 0) | 0;
+        this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
+        this.v = roundFloat(Math.max(Math.min(1, v), 0), 3);
+        this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
+    }
+    static equals(a: HSVA, b: HSVA): boolean {
+        return a.h === b.h && a.s === b.s && a.v === b.v && a.a === b.a;
+    }
+    // from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm
+    static fromRGBA(rgba: RGBA): HSVA {
+        const r = rgba.r / 255;
+        const g = rgba.g / 255;
+        const b = rgba.b / 255;
+        const cmax = Math.max(r, g, b);
+        const cmin = Math.min(r, g, b);
+        const delta = cmax - cmin;
+        const s = cmax === 0 ? 0 : (delta / cmax);
+        let m: number;
+        if (delta === 0) {
+            m = 0;
+        }
+        else if (cmax === r) {
+            m = ((((g - b) / delta) % 6) + 6) % 6;
+        }
+        else if (cmax === g) {
+            m = ((b - r) / delta) + 2;
+        }
+        else {
+            m = ((r - g) / delta) + 4;
+        }
+        return new HSVA(Math.round(m * 60), s, cmax, rgba.a);
+    }
+    // from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm
+    static toRGBA(hsva: HSVA): RGBA {
+        const { h, s, v, a } = hsva;
+        const c = v * s;
+        const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+        const m = v - c;
+        let [r, g, b] = [0, 0, 0];
+        if (h < 60) {
+            r = c;
+            g = x;
+        }
+        else if (h < 120) {
+            r = x;
+            g = c;
+        }
+        else if (h < 180) {
+            g = c;
+            b = x;
+        }
+        else if (h < 240) {
+            g = x;
+            b = c;
+        }
+        else if (h < 300) {
+            r = x;
+            b = c;
+        }
+        else if (h <= 360) {
+            r = c;
+            b = x;
+        }
+        r = Math.round((r + m) * 255);
+        g = Math.round((g + m) * 255);
+        b = Math.round((b + m) * 255);
+        return new RGBA(r, g, b, a);
+    }
 }
-
 export class Color {
-
-	static fromHex(hex: string): Color {
-		return Color.Format.CSS.parseHex(hex) || Color.red;
-	}
-
-	readonly rgba: RGBA;
-	private _hsla?: HSLA;
-	get hsla(): HSLA {
-		if (this._hsla) {
-			return this._hsla;
-		} else {
-			return HSLA.fromRGBA(this.rgba);
-		}
-	}
-
-	private _hsva?: HSVA;
-	get hsva(): HSVA {
-		if (this._hsva) {
-			return this._hsva;
-		}
-		return HSVA.fromRGBA(this.rgba);
-	}
-
-	constructor(arg: RGBA | HSLA | HSVA) {
-		if (!arg) {
-			throw new Error('Color needs a value');
-		} else if (arg instanceof RGBA) {
-			this.rgba = arg;
-		} else if (arg instanceof HSLA) {
-			this._hsla = arg;
-			this.rgba = HSLA.toRGBA(arg);
-		} else if (arg instanceof HSVA) {
-			this._hsva = arg;
-			this.rgba = HSVA.toRGBA(arg);
-		} else {
-			throw new Error('Invalid color ctor argument');
-		}
-	}
-
-	equals(other: Color | null): boolean {
-		return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva);
-	}
-
-	/**
-	 * http://www.w3.org/TR/WCAG20/#relativeluminancedef
-	 * Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white.
-	 */
-	getRelativeLuminance(): number {
-		const R = Color._relativeLuminanceForComponent(this.rgba.r);
-		const G = Color._relativeLuminanceForComponent(this.rgba.g);
-		const B = Color._relativeLuminanceForComponent(this.rgba.b);
-		const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
-
-		return roundFloat(luminance, 4);
-	}
-
-	private static _relativeLuminanceForComponent(color: number): number {
-		const c = color / 255;
-		return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
-	}
-
-	/**
-	 * http://www.w3.org/TR/WCAG20/#contrast-ratiodef
-	 * Returns the contrast ration number in the set [1, 21].
-	 */
-	getContrastRatio(another: Color): number {
-		const lum1 = this.getRelativeLuminance();
-		const lum2 = another.getRelativeLuminance();
-		return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05);
-	}
-
-	/**
-	 *	http://24ways.org/2010/calculating-color-contrast
-	 *  Return 'true' if darker color otherwise 'false'
-	 */
-	isDarker(): boolean {
-		const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
-		return yiq < 128;
-	}
-
-	/**
-	 *	http://24ways.org/2010/calculating-color-contrast
-	 *  Return 'true' if lighter color otherwise 'false'
-	 */
-	isLighter(): boolean {
-		const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
-		return yiq >= 128;
-	}
-
-	isLighterThan(another: Color): boolean {
-		const lum1 = this.getRelativeLuminance();
-		const lum2 = another.getRelativeLuminance();
-		return lum1 > lum2;
-	}
-
-	isDarkerThan(another: Color): boolean {
-		const lum1 = this.getRelativeLuminance();
-		const lum2 = another.getRelativeLuminance();
-		return lum1 < lum2;
-	}
-
-	lighten(factor: number): Color {
-		return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l + this.hsla.l * factor, this.hsla.a));
-	}
-
-	darken(factor: number): Color {
-		return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l - this.hsla.l * factor, this.hsla.a));
-	}
-
-	transparent(factor: number): Color {
-		const { r, g, b, a } = this.rgba;
-		return new Color(new RGBA(r, g, b, a * factor));
-	}
-
-	isTransparent(): boolean {
-		return this.rgba.a === 0;
-	}
-
-	isOpaque(): boolean {
-		return this.rgba.a === 1;
-	}
-
-	opposite(): Color {
-		return new Color(new RGBA(255 - this.rgba.r, 255 - this.rgba.g, 255 - this.rgba.b, this.rgba.a));
-	}
-
-	blend(c: Color): Color {
-		const rgba = c.rgba;
-
-		// Convert to 0..1 opacity
-		const thisA = this.rgba.a;
-		const colorA = rgba.a;
-
-		const a = thisA + colorA * (1 - thisA);
-		if (a < 1e-6) {
-			return Color.transparent;
-		}
-
-		const r = this.rgba.r * thisA / a + rgba.r * colorA * (1 - thisA) / a;
-		const g = this.rgba.g * thisA / a + rgba.g * colorA * (1 - thisA) / a;
-		const b = this.rgba.b * thisA / a + rgba.b * colorA * (1 - thisA) / a;
-
-		return new Color(new RGBA(r, g, b, a));
-	}
-
-	makeOpaque(opaqueBackground: Color): Color {
-		if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
-			// only allow to blend onto a non-opaque color onto a opaque color
-			return this;
-		}
-
-		const { r, g, b, a } = this.rgba;
-
-		// https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
-		return new Color(new RGBA(
-			opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r),
-			opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g),
-			opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b),
-			1
-		));
-	}
-
-	flatten(...backgrounds: Color[]): Color {
-		const background = backgrounds.reduceRight((accumulator, color) => {
-			return Color._flatten(color, accumulator);
-		});
-		return Color._flatten(this, background);
-	}
-
-	private static _flatten(foreground: Color, background: Color) {
-		const backgroundAlpha = 1 - foreground.rgba.a;
-		return new Color(new RGBA(
-			backgroundAlpha * background.rgba.r + foreground.rgba.a * foreground.rgba.r,
-			backgroundAlpha * background.rgba.g + foreground.rgba.a * foreground.rgba.g,
-			backgroundAlpha * background.rgba.b + foreground.rgba.a * foreground.rgba.b
-		));
-	}
-
-	private _toString?: string;
-	toString(): string {
-		this._toString ??= Color.Format.CSS.format(this);
-		return this._toString;
-	}
-
-	static getLighterColor(of: Color, relative: Color, factor?: number): Color {
-		if (of.isLighterThan(relative)) {
-			return of;
-		}
-		factor = factor ? factor : 0.5;
-		const lum1 = of.getRelativeLuminance();
-		const lum2 = relative.getRelativeLuminance();
-		factor = factor * (lum2 - lum1) / lum2;
-		return of.lighten(factor);
-	}
-
-	static getDarkerColor(of: Color, relative: Color, factor?: number): Color {
-		if (of.isDarkerThan(relative)) {
-			return of;
-		}
-		factor = factor ? factor : 0.5;
-		const lum1 = of.getRelativeLuminance();
-		const lum2 = relative.getRelativeLuminance();
-		factor = factor * (lum1 - lum2) / lum1;
-		return of.darken(factor);
-	}
-
-	static readonly white = new Color(new RGBA(255, 255, 255, 1));
-	static readonly black = new Color(new RGBA(0, 0, 0, 1));
-	static readonly red = new Color(new RGBA(255, 0, 0, 1));
-	static readonly blue = new Color(new RGBA(0, 0, 255, 1));
-	static readonly green = new Color(new RGBA(0, 255, 0, 1));
-	static readonly cyan = new Color(new RGBA(0, 255, 255, 1));
-	static readonly lightgrey = new Color(new RGBA(211, 211, 211, 1));
-	static readonly transparent = new Color(new RGBA(0, 0, 0, 0));
+    static fromHex(hex: string): Color {
+        return Color.Format.CSS.parseHex(hex) || Color.red;
+    }
+    readonly rgba: RGBA;
+    private _hsla?: HSLA;
+    get hsla(): HSLA {
+        if (this._hsla) {
+            return this._hsla;
+        }
+        else {
+            return HSLA.fromRGBA(this.rgba);
+        }
+    }
+    private _hsva?: HSVA;
+    get hsva(): HSVA {
+        if (this._hsva) {
+            return this._hsva;
+        }
+        return HSVA.fromRGBA(this.rgba);
+    }
+    constructor(arg: RGBA | HSLA | HSVA) {
+        if (!arg) {
+            throw new Error('Color needs a value');
+        }
+        else if (arg instanceof RGBA) {
+            this.rgba = arg;
+        }
+        else if (arg instanceof HSLA) {
+            this._hsla = arg;
+            this.rgba = HSLA.toRGBA(arg);
+        }
+        else if (arg instanceof HSVA) {
+            this._hsva = arg;
+            this.rgba = HSVA.toRGBA(arg);
+        }
+        else {
+            throw new Error('Invalid color ctor argument');
+        }
+    }
+    equals(other: Color | null): boolean {
+        return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva);
+    }
+    /**
+     * http://www.w3.org/TR/WCAG20/#relativeluminancedef
+     * Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white.
+     */
+    getRelativeLuminance(): number {
+        const R = Color._relativeLuminanceForComponent(this.rgba.r);
+        const G = Color._relativeLuminanceForComponent(this.rgba.g);
+        const B = Color._relativeLuminanceForComponent(this.rgba.b);
+        const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
+        return roundFloat(luminance, 4);
+    }
+    private static _relativeLuminanceForComponent(color: number): number {
+        const c = color / 255;
+        return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
+    }
+    /**
+     * http://www.w3.org/TR/WCAG20/#contrast-ratiodef
+     * Returns the contrast ration number in the set [1, 21].
+     */
+    getContrastRatio(another: Color): number {
+        const lum1 = this.getRelativeLuminance();
+        const lum2 = another.getRelativeLuminance();
+        return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05);
+    }
+    /**
+     *	http://24ways.org/2010/calculating-color-contrast
+     *  Return 'true' if darker color otherwise 'false'
+     */
+    isDarker(): boolean {
+        const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
+        return yiq < 128;
+    }
+    /**
+     *	http://24ways.org/2010/calculating-color-contrast
+     *  Return 'true' if lighter color otherwise 'false'
+     */
+    isLighter(): boolean {
+        const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
+        return yiq >= 128;
+    }
+    isLighterThan(another: Color): boolean {
+        const lum1 = this.getRelativeLuminance();
+        const lum2 = another.getRelativeLuminance();
+        return lum1 > lum2;
+    }
+    isDarkerThan(another: Color): boolean {
+        const lum1 = this.getRelativeLuminance();
+        const lum2 = another.getRelativeLuminance();
+        return lum1 < lum2;
+    }
+    lighten(factor: number): Color {
+        return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l + this.hsla.l * factor, this.hsla.a));
+    }
+    darken(factor: number): Color {
+        return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l - this.hsla.l * factor, this.hsla.a));
+    }
+    transparent(factor: number): Color {
+        const { r, g, b, a } = this.rgba;
+        return new Color(new RGBA(r, g, b, a * factor));
+    }
+    isTransparent(): boolean {
+        return this.rgba.a === 0;
+    }
+    isOpaque(): boolean {
+        return this.rgba.a === 1;
+    }
+    opposite(): Color {
+        return new Color(new RGBA(255 - this.rgba.r, 255 - this.rgba.g, 255 - this.rgba.b, this.rgba.a));
+    }
+    blend(c: Color): Color {
+        const rgba = c.rgba;
+        // Convert to 0..1 opacity
+        const thisA = this.rgba.a;
+        const colorA = rgba.a;
+        const a = thisA + colorA * (1 - thisA);
+        if (a < 1e-6) {
+            return Color.transparent;
+        }
+        const r = this.rgba.r * thisA / a + rgba.r * colorA * (1 - thisA) / a;
+        const g = this.rgba.g * thisA / a + rgba.g * colorA * (1 - thisA) / a;
+        const b = this.rgba.b * thisA / a + rgba.b * colorA * (1 - thisA) / a;
+        return new Color(new RGBA(r, g, b, a));
+    }
+    makeOpaque(opaqueBackground: Color): Color {
+        if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
+            // only allow to blend onto a non-opaque color onto a opaque color
+            return this;
+        }
+        const { r, g, b, a } = this.rgba;
+        // https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
+        return new Color(new RGBA(opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r), opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g), opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b), 1));
+    }
+    flatten(...backgrounds: Color[]): Color {
+        const background = backgrounds.reduceRight((accumulator, color) => {
+            return Color._flatten(color, accumulator);
+        });
+        return Color._flatten(this, background);
+    }
+    private static _flatten(foreground: Color, background: Color) {
+        const backgroundAlpha = 1 - foreground.rgba.a;
+        return new Color(new RGBA(backgroundAlpha * background.rgba.r + foreground.rgba.a * foreground.rgba.r, backgroundAlpha * background.rgba.g + foreground.rgba.a * foreground.rgba.g, backgroundAlpha * background.rgba.b + foreground.rgba.a * foreground.rgba.b));
+    }
+    private _toString?: string;
+    toString(): string {
+        this._toString ??= Color.Format.CSS.format(this);
+        return this._toString;
+    }
+    static getLighterColor(of: Color, relative: Color, factor?: number): Color {
+        if (of.isLighterThan(relative)) {
+            return of;
+        }
+        factor = factor ? factor : 0.5;
+        const lum1 = of.getRelativeLuminance();
+        const lum2 = relative.getRelativeLuminance();
+        factor = factor * (lum2 - lum1) / lum2;
+        return of.lighten(factor);
+    }
+    static getDarkerColor(of: Color, relative: Color, factor?: number): Color {
+        if (of.isDarkerThan(relative)) {
+            return of;
+        }
+        factor = factor ? factor : 0.5;
+        const lum1 = of.getRelativeLuminance();
+        const lum2 = relative.getRelativeLuminance();
+        factor = factor * (lum1 - lum2) / lum1;
+        return of.darken(factor);
+    }
+    static readonly white = new Color(new RGBA(255, 255, 255, 1));
+    static readonly black = new Color(new RGBA(0, 0, 0, 1));
+    static readonly red = new Color(new RGBA(255, 0, 0, 1));
+    static readonly blue = new Color(new RGBA(0, 0, 255, 1));
+    static readonly green = new Color(new RGBA(0, 255, 0, 1));
+    static readonly cyan = new Color(new RGBA(0, 255, 255, 1));
+    static readonly lightgrey = new Color(new RGBA(211, 211, 211, 1));
+    static readonly transparent = new Color(new RGBA(0, 0, 0, 0));
 }
-
 export namespace Color {
-	export namespace Format {
-		export namespace CSS {
-
-			export function formatRGB(color: Color): string {
-				if (color.rgba.a === 1) {
-					return `rgb(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b})`;
-				}
-
-				return Color.Format.CSS.formatRGBA(color);
-			}
-
-			export function formatRGBA(color: Color): string {
-				return `rgba(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b}, ${+(color.rgba.a).toFixed(2)})`;
-			}
-
-			export function formatHSL(color: Color): string {
-				if (color.hsla.a === 1) {
-					return `hsl(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%)`;
-				}
-
-				return Color.Format.CSS.formatHSLA(color);
-			}
-
-			export function formatHSLA(color: Color): string {
-				return `hsla(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%, ${color.hsla.a.toFixed(2)})`;
-			}
-
-			function _toTwoDigitHex(n: number): string {
-				const r = n.toString(16);
-				return r.length !== 2 ? '0' + r : r;
-			}
-
-			/**
-			 * Formats the color as #RRGGBB
-			 */
-			export function formatHex(color: Color): string {
-				return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}`;
-			}
-
-			/**
-			 * Formats the color as #RRGGBBAA
-			 * If 'compact' is set, colors without transparancy will be printed as #RRGGBB
-			 */
-			export function formatHexA(color: Color, compact = false): string {
-				if (compact && color.rgba.a === 1) {
-					return Color.Format.CSS.formatHex(color);
-				}
-
-				return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}${_toTwoDigitHex(Math.round(color.rgba.a * 255))}`;
-			}
-
-			/**
-			 * The default format will use HEX if opaque and RGBA otherwise.
-			 */
-			export function format(color: Color): string {
-				if (color.isOpaque()) {
-					return Color.Format.CSS.formatHex(color);
-				}
-
-				return Color.Format.CSS.formatRGBA(color);
-			}
-
-			/**
-			 * Converts an Hex color value to a Color.
-			 * returns r, g, and b are contained in the set [0, 255]
-			 * @param hex string (#RGB, #RGBA, #RRGGBB or #RRGGBBAA).
-			 */
-			export function parseHex(hex: string): Color | null {
-				const length = hex.length;
-
-				if (length === 0) {
-					// Invalid color
-					return null;
-				}
-
-				if (hex.charCodeAt(0) !== CharCode.Hash) {
-					// Does not begin with a #
-					return null;
-				}
-
-				if (length === 7) {
-					// #RRGGBB format
-					const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
-					const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
-					const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
-					return new Color(new RGBA(r, g, b, 1));
-				}
-
-				if (length === 9) {
-					// #RRGGBBAA format
-					const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
-					const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
-					const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
-					const a = 16 * _parseHexDigit(hex.charCodeAt(7)) + _parseHexDigit(hex.charCodeAt(8));
-					return new Color(new RGBA(r, g, b, a / 255));
-				}
-
-				if (length === 4) {
-					// #RGB format
-					const r = _parseHexDigit(hex.charCodeAt(1));
-					const g = _parseHexDigit(hex.charCodeAt(2));
-					const b = _parseHexDigit(hex.charCodeAt(3));
-					return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b));
-				}
-
-				if (length === 5) {
-					// #RGBA format
-					const r = _parseHexDigit(hex.charCodeAt(1));
-					const g = _parseHexDigit(hex.charCodeAt(2));
-					const b = _parseHexDigit(hex.charCodeAt(3));
-					const a = _parseHexDigit(hex.charCodeAt(4));
-					return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b, (16 * a + a) / 255));
-				}
-
-				// Invalid color
-				return null;
-			}
-
-			function _parseHexDigit(charCode: CharCode): number {
-				switch (charCode) {
-					case CharCode.Digit0: return 0;
-					case CharCode.Digit1: return 1;
-					case CharCode.Digit2: return 2;
-					case CharCode.Digit3: return 3;
-					case CharCode.Digit4: return 4;
-					case CharCode.Digit5: return 5;
-					case CharCode.Digit6: return 6;
-					case CharCode.Digit7: return 7;
-					case CharCode.Digit8: return 8;
-					case CharCode.Digit9: return 9;
-					case CharCode.a: return 10;
-					case CharCode.A: return 10;
-					case CharCode.b: return 11;
-					case CharCode.B: return 11;
-					case CharCode.c: return 12;
-					case CharCode.C: return 12;
-					case CharCode.d: return 13;
-					case CharCode.D: return 13;
-					case CharCode.e: return 14;
-					case CharCode.E: return 14;
-					case CharCode.f: return 15;
-					case CharCode.F: return 15;
-				}
-				return 0;
-			}
-		}
-	}
+    export namespace Format {
+        export namespace CSS {
+            export function formatRGB(color: Color): string {
+                if (color.rgba.a === 1) {
+                    return `rgb(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b})`;
+                }
+                return Color.Format.CSS.formatRGBA(color);
+            }
+            export function formatRGBA(color: Color): string {
+                return `rgba(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b}, ${+(color.rgba.a).toFixed(2)})`;
+            }
+            export function formatHSL(color: Color): string {
+                if (color.hsla.a === 1) {
+                    return `hsl(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%)`;
+                }
+                return Color.Format.CSS.formatHSLA(color);
+            }
+            export function formatHSLA(color: Color): string {
+                return `hsla(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%, ${color.hsla.a.toFixed(2)})`;
+            }
+            function _toTwoDigitHex(n: number): string {
+                const r = n.toString(16);
+                return r.length !== 2 ? '0' + r : r;
+            }
+            /**
+             * Formats the color as #RRGGBB
+             */
+            export function formatHex(color: Color): string {
+                return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}`;
+            }
+            /**
+             * Formats the color as #RRGGBBAA
+             * If 'compact' is set, colors without transparancy will be printed as #RRGGBB
+             */
+            export function formatHexA(color: Color, compact = false): string {
+                if (compact && color.rgba.a === 1) {
+                    return Color.Format.CSS.formatHex(color);
+                }
+                return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}${_toTwoDigitHex(Math.round(color.rgba.a * 255))}`;
+            }
+            /**
+             * The default format will use HEX if opaque and RGBA otherwise.
+             */
+            export function format(color: Color): string {
+                if (color.isOpaque()) {
+                    return Color.Format.CSS.formatHex(color);
+                }
+                return Color.Format.CSS.formatRGBA(color);
+            }
+            /**
+             * Converts an Hex color value to a Color.
+             * returns r, g, and b are contained in the set [0, 255]
+             * @param hex string (#RGB, #RGBA, #RRGGBB or #RRGGBBAA).
+             */
+            export function parseHex(hex: string): Color | null {
+                const length = hex.length;
+                if (length === 0) {
+                    // Invalid color
+                    return null;
+                }
+                if (hex.charCodeAt(0) !== CharCode.Hash) {
+                    // Does not begin with a #
+                    return null;
+                }
+                if (length === 7) {
+                    // #RRGGBB format
+                    const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
+                    const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
+                    const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
+                    return new Color(new RGBA(r, g, b, 1));
+                }
+                if (length === 9) {
+                    // #RRGGBBAA format
+                    const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
+                    const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
+                    const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
+                    const a = 16 * _parseHexDigit(hex.charCodeAt(7)) + _parseHexDigit(hex.charCodeAt(8));
+                    return new Color(new RGBA(r, g, b, a / 255));
+                }
+                if (length === 4) {
+                    // #RGB format
+                    const r = _parseHexDigit(hex.charCodeAt(1));
+                    const g = _parseHexDigit(hex.charCodeAt(2));
+                    const b = _parseHexDigit(hex.charCodeAt(3));
+                    return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b));
+                }
+                if (length === 5) {
+                    // #RGBA format
+                    const r = _parseHexDigit(hex.charCodeAt(1));
+                    const g = _parseHexDigit(hex.charCodeAt(2));
+                    const b = _parseHexDigit(hex.charCodeAt(3));
+                    const a = _parseHexDigit(hex.charCodeAt(4));
+                    return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b, (16 * a + a) / 255));
+                }
+                // Invalid color
+                return null;
+            }
+            function _parseHexDigit(charCode: CharCode): number {
+                switch (charCode) {
+                    case CharCode.Digit0: return 0;
+                    case CharCode.Digit1: return 1;
+                    case CharCode.Digit2: return 2;
+                    case CharCode.Digit3: return 3;
+                    case CharCode.Digit4: return 4;
+                    case CharCode.Digit5: return 5;
+                    case CharCode.Digit6: return 6;
+                    case CharCode.Digit7: return 7;
+                    case CharCode.Digit8: return 8;
+                    case CharCode.Digit9: return 9;
+                    case CharCode.a: return 10;
+                    case CharCode.A: return 10;
+                    case CharCode.b: return 11;
+                    case CharCode.B: return 11;
+                    case CharCode.c: return 12;
+                    case CharCode.C: return 12;
+                    case CharCode.d: return 13;
+                    case CharCode.D: return 13;
+                    case CharCode.e: return 14;
+                    case CharCode.E: return 14;
+                    case CharCode.f: return 15;
+                    case CharCode.F: return 15;
+                }
+                return 0;
+            }
+        }
+    }
 }
diff --git a/extensions/notebook-renderers/Source/colorMap.ts b/extensions/notebook-renderers/Source/colorMap.ts
index 934ad06acec62..7dbcb9f5a6de2 100644
--- a/extensions/notebook-renderers/Source/colorMap.ts
+++ b/extensions/notebook-renderers/Source/colorMap.ts
@@ -2,61 +2,66 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
-export const ansiColorIdentifiers: { colorName: string; colorValue: string }[] = [];
-export const ansiColorMap: { [key: string]: { index: number } } = {
-	'terminal.ansiBlack': {
-		index: 0,
-	},
-	'terminal.ansiRed': {
-		index: 1,
-	},
-	'terminal.ansiGreen': {
-		index: 2,
-	},
-	'terminal.ansiYellow': {
-		index: 3,
-	},
-	'terminal.ansiBlue': {
-		index: 4,
-	},
-	'terminal.ansiMagenta': {
-		index: 5,
-	},
-	'terminal.ansiCyan': {
-		index: 6,
-	},
-	'terminal.ansiWhite': {
-		index: 7,
-	},
-	'terminal.ansiBrightBlack': {
-		index: 8,
-	},
-	'terminal.ansiBrightRed': {
-		index: 9,
-	},
-	'terminal.ansiBrightGreen': {
-		index: 10,
-	},
-	'terminal.ansiBrightYellow': {
-		index: 11,
-	},
-	'terminal.ansiBrightBlue': {
-		index: 12,
-	},
-	'terminal.ansiBrightMagenta': {
-		index: 13,
-	},
-	'terminal.ansiBrightCyan': {
-		index: 14,
-	},
-	'terminal.ansiBrightWhite': {
-		index: 15,
-	}
+export const ansiColorIdentifiers: {
+    colorName: string;
+    colorValue: string;
+}[] = [];
+export const ansiColorMap: {
+    [key: string]: {
+        index: number;
+    };
+} = {
+    'terminal.ansiBlack': {
+        index: 0,
+    },
+    'terminal.ansiRed': {
+        index: 1,
+    },
+    'terminal.ansiGreen': {
+        index: 2,
+    },
+    'terminal.ansiYellow': {
+        index: 3,
+    },
+    'terminal.ansiBlue': {
+        index: 4,
+    },
+    'terminal.ansiMagenta': {
+        index: 5,
+    },
+    'terminal.ansiCyan': {
+        index: 6,
+    },
+    'terminal.ansiWhite': {
+        index: 7,
+    },
+    'terminal.ansiBrightBlack': {
+        index: 8,
+    },
+    'terminal.ansiBrightRed': {
+        index: 9,
+    },
+    'terminal.ansiBrightGreen': {
+        index: 10,
+    },
+    'terminal.ansiBrightYellow': {
+        index: 11,
+    },
+    'terminal.ansiBrightBlue': {
+        index: 12,
+    },
+    'terminal.ansiBrightMagenta': {
+        index: 13,
+    },
+    'terminal.ansiBrightCyan': {
+        index: 14,
+    },
+    'terminal.ansiBrightWhite': {
+        index: 15,
+    }
 };
-
 for (const id in ansiColorMap) {
-	const entry = ansiColorMap[id];
-	const colorName = id.substring(13);
-	ansiColorIdentifiers[entry.index] = { colorName, colorValue: 'var(--vscode-' + id.replace('.', '-') + ')' };
+    const entry = ansiColorMap[id];
+    const colorName = id.substring(13);
+    ansiColorIdentifiers[entry.index] = { colorName, colorValue: 'var(--vscode-' + id.replace('.', '-') + ')' };
 }
diff --git a/extensions/notebook-renderers/Source/htmlHelper.ts b/extensions/notebook-renderers/Source/htmlHelper.ts
index 819a3a640affd..38d402c508cca 100644
--- a/extensions/notebook-renderers/Source/htmlHelper.ts
+++ b/extensions/notebook-renderers/Source/htmlHelper.ts
@@ -2,9 +2,8 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export const ttPolicy = (typeof window !== 'undefined') ?
-	window.trustedTypes?.createPolicy('notebookRenderer', {
-		createHTML: value => value,
-		createScript: value => value,
-	}) : undefined;
+    window.trustedTypes?.createPolicy('notebookRenderer', {
+        createHTML: value => value,
+        createScript: value => value,
+    }) : undefined;
diff --git a/extensions/notebook-renderers/Source/index.ts b/extensions/notebook-renderers/Source/index.ts
index 8f5fa908cb938..5179fa21058c8 100644
--- a/extensions/notebook-renderers/Source/index.ts
+++ b/extensions/notebook-renderers/Source/index.ts
@@ -2,429 +2,372 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
 import { createOutputContent, appendOutput, scrollableClass } from './textHelper';
 import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, OutputWithAppend, RenderOptions } from './rendererTypes';
 import { ttPolicy } from './htmlHelper';
 import { formatStackTrace } from './stackTraceHelper';
-
 function clearContainer(container: HTMLElement) {
-	while (container.firstChild) {
-		container.firstChild.remove();
-	}
+    while (container.firstChild) {
+        container.firstChild.remove();
+    }
 }
-
 function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable {
-	const blob = new Blob([outputInfo.data()], { type: outputInfo.mime });
-	const src = URL.createObjectURL(blob);
-	const disposable = {
-		dispose: () => {
-			URL.revokeObjectURL(src);
-		}
-	};
-
-	if (element.firstChild) {
-		const display = element.firstChild as HTMLElement;
-		if (display.firstChild && display.firstChild.nodeName === 'IMG' && display.firstChild instanceof HTMLImageElement) {
-			display.firstChild.src = src;
-			return disposable;
-		}
-	}
-
-	const image = document.createElement('img');
-	image.src = src;
-	const alt = getAltText(outputInfo);
-	if (alt) {
-		image.alt = alt;
-	}
-	image.setAttribute('data-vscode-context', JSON.stringify({
-		webviewSection: 'image',
-		outputId: outputInfo.id,
-		'preventDefaultContextMenuItems': true
-	}));
-	const display = document.createElement('div');
-	display.classList.add('display');
-	display.appendChild(image);
-	element.appendChild(display);
-
-	return disposable;
+    const blob = new Blob([outputInfo.data()], { type: outputInfo.mime });
+    const src = URL.createObjectURL(blob);
+    const disposable = {
+        dispose: () => {
+            URL.revokeObjectURL(src);
+        }
+    };
+    if (element.firstChild) {
+        const display = element.firstChild as HTMLElement;
+        if (display.firstChild && display.firstChild.nodeName === 'IMG' && display.firstChild instanceof HTMLImageElement) {
+            display.firstChild.src = src;
+            return disposable;
+        }
+    }
+    const image = document.createElement('img');
+    image.src = src;
+    const alt = getAltText(outputInfo);
+    if (alt) {
+        image.alt = alt;
+    }
+    image.setAttribute('data-vscode-context', JSON.stringify({
+        webviewSection: 'image',
+        outputId: outputInfo.id,
+        'preventDefaultContextMenuItems': true
+    }));
+    const display = document.createElement('div');
+    display.classList.add('display');
+    display.appendChild(image);
+    element.appendChild(display);
+    return disposable;
 }
-
 const preservedScriptAttributes: (keyof HTMLScriptElement)[] = [
-	'type', 'src', 'nonce', 'noModule', 'async',
+    'type', 'src', 'nonce', 'noModule', 'async',
 ];
-
 const domEval = (container: Element) => {
-	const arr = Array.from(container.getElementsByTagName('script'));
-	for (let n = 0; n < arr.length; n++) {
-		const node = arr[n];
-		const scriptTag = document.createElement('script');
-		const trustedScript = ttPolicy?.createScript(node.innerText) ?? node.innerText;
-		scriptTag.text = trustedScript as string;
-		for (const key of preservedScriptAttributes) {
-			const val = node[key] || node.getAttribute && node.getAttribute(key);
-			if (val) {
-				scriptTag.setAttribute(key, val as any);
-			}
-		}
-
-		// TODO@connor4312: should script with src not be removed?
-		container.appendChild(scriptTag).parentNode!.removeChild(scriptTag);
-	}
+    const arr = Array.from(container.getElementsByTagName('script'));
+    for (let n = 0; n < arr.length; n++) {
+        const node = arr[n];
+        const scriptTag = document.createElement('script');
+        const trustedScript = ttPolicy?.createScript(node.innerText) ?? node.innerText;
+        scriptTag.text = trustedScript as string;
+        for (const key of preservedScriptAttributes) {
+            const val = node[key] || node.getAttribute && node.getAttribute(key);
+            if (val) {
+                scriptTag.setAttribute(key, val as any);
+            }
+        }
+        // TODO@connor4312: should script with src not be removed?
+        container.appendChild(scriptTag).parentNode!.removeChild(scriptTag);
+    }
 };
-
 function getAltText(outputInfo: OutputItem) {
-	const metadata = outputInfo.metadata;
-	if (typeof metadata === 'object' && metadata && 'vscode_altText' in metadata && typeof metadata.vscode_altText === 'string') {
-		return metadata.vscode_altText;
-	}
-	return undefined;
+    const metadata = outputInfo.metadata;
+    if (typeof metadata === 'object' && metadata && 'vscode_altText' in metadata && typeof metadata.vscode_altText === 'string') {
+        return metadata.vscode_altText;
+    }
+    return undefined;
 }
-
 function fixUpSvgElement(outputInfo: OutputItem, element: HTMLElement) {
-	if (outputInfo.mime.indexOf('svg') > -1) {
-		const svgElement = element.querySelector('svg');
-		const altText = getAltText(outputInfo);
-		if (svgElement && altText) {
-			const title = document.createElement('title');
-			title.innerText = altText;
-			svgElement.prepend(title);
-		}
-
-		if (svgElement) {
-			svgElement.classList.add('output-image');
-
-			svgElement.setAttribute('data-vscode-context', JSON.stringify({
-				webviewSection: 'image',
-				outputId: outputInfo.id,
-				'preventDefaultContextMenuItems': true
-			}));
-		}
-	}
+    if (outputInfo.mime.indexOf('svg') > -1) {
+        const svgElement = element.querySelector('svg');
+        const altText = getAltText(outputInfo);
+        if (svgElement && altText) {
+            const title = document.createElement('title');
+            title.innerText = altText;
+            svgElement.prepend(title);
+        }
+        if (svgElement) {
+            svgElement.classList.add('output-image');
+            svgElement.setAttribute('data-vscode-context', JSON.stringify({
+                webviewSection: 'image',
+                outputId: outputInfo.id,
+                'preventDefaultContextMenuItems': true
+            }));
+        }
+    }
 }
-
 async function renderHTML(outputInfo: OutputItem, container: HTMLElement, signal: AbortSignal, hooks: Iterable): Promise {
-	clearContainer(container);
-	let element: HTMLElement = document.createElement('div');
-	const htmlContent = outputInfo.text();
-	const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent;
-	element.innerHTML = trustedHtml as string;
-	fixUpSvgElement(outputInfo, element);
-
-	for (const hook of hooks) {
-		element = (await hook.postRender(outputInfo, element, signal)) ?? element;
-		if (signal.aborted) {
-			return;
-		}
-	}
-
-	container.appendChild(element);
-	domEval(element);
+    clearContainer(container);
+    let element: HTMLElement = document.createElement('div');
+    const htmlContent = outputInfo.text();
+    const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent;
+    element.innerHTML = trustedHtml as string;
+    fixUpSvgElement(outputInfo, element);
+    for (const hook of hooks) {
+        element = (await hook.postRender(outputInfo, element, signal)) ?? element;
+        if (signal.aborted) {
+            return;
+        }
+    }
+    container.appendChild(element);
+    domEval(element);
 }
-
 async function renderJavascript(outputInfo: OutputItem, container: HTMLElement, signal: AbortSignal, hooks: Iterable): Promise {
-	let scriptText = outputInfo.text();
-
-	for (const hook of hooks) {
-		scriptText = (await hook.preEvaluate(outputInfo, container, scriptText, signal)) ?? scriptText;
-		if (signal.aborted) {
-			return;
-		}
-	}
-
-	const script = document.createElement('script');
-	script.type = 'module';
-	script.textContent = scriptText;
-
-	const element = document.createElement('div');
-	const trustedHtml = ttPolicy?.createHTML(script.outerHTML) ?? script.outerHTML;
-	element.innerHTML = trustedHtml as string;
-	container.appendChild(element);
-	domEval(element);
+    let scriptText = outputInfo.text();
+    for (const hook of hooks) {
+        scriptText = (await hook.preEvaluate(outputInfo, container, scriptText, signal)) ?? scriptText;
+        if (signal.aborted) {
+            return;
+        }
+    }
+    const script = document.createElement('script');
+    script.type = 'module';
+    script.textContent = scriptText;
+    const element = document.createElement('div');
+    const trustedHtml = ttPolicy?.createHTML(script.outerHTML) ?? script.outerHTML;
+    element.innerHTML = trustedHtml as string;
+    container.appendChild(element);
+    domEval(element);
 }
-
 interface Event {
-	(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
+    (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
 }
-
-function createDisposableStore(): { push(...disposables: IDisposable[]): void; dispose(): void } {
-	const localDisposables: IDisposable[] = [];
-	const disposable = {
-		push: (...disposables: IDisposable[]) => {
-			localDisposables.push(...disposables);
-		},
-		dispose: () => {
-			localDisposables.forEach(d => d.dispose());
-		}
-	};
-
-	return disposable;
+function createDisposableStore(): {
+    push(...disposables: IDisposable[]): void;
+    dispose(): void;
+} {
+    const localDisposables: IDisposable[] = [];
+    const disposable = {
+        push: (...disposables: IDisposable[]) => {
+            localDisposables.push(...disposables);
+        },
+        dispose: () => {
+            localDisposables.forEach(d => d.dispose());
+        }
+    };
+    return disposable;
 }
-
 type DisposableStore = ReturnType;
-
-function renderError(
-	outputInfo: OutputItem,
-	outputElement: HTMLElement,
-	ctx: IRichRenderContext,
-	trustHtml: boolean
-): IDisposable {
-	const disposableStore = createDisposableStore();
-
-	clearContainer(outputElement);
-
-	type ErrorLike = Partial;
-
-	let err: ErrorLike;
-	try {
-		err = JSON.parse(outputInfo.text());
-	} catch (e) {
-		console.log(e);
-		return disposableStore;
-	}
-
-	const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message;
-
-	if (err.stack) {
-		const minimalError = ctx.settings.minimalError && !!headerMessage?.length;
-		outputElement.classList.add('traceback');
-
-		const { formattedStack, errorLocation } = formatStackTrace(err.stack);
-
-		const outputScrolling = !minimalError && scrollingEnabled(outputInfo, ctx.settings);
-		const lineLimit = minimalError ? 1000 : ctx.settings.lineLimit;
-		const outputOptions = { linesLimit: lineLimit, scrollable: outputScrolling, trustHtml, linkifyFilePaths: false };
-
-		const content = createOutputContent(outputInfo.id, formattedStack, outputOptions);
-		const stackTraceElement = document.createElement('div');
-		stackTraceElement.appendChild(content);
-		outputElement.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
-		disposableStore.push(ctx.onDidChangeSettings(e => {
-			outputElement.classList.toggle('word-wrap', e.outputWordWrap);
-		}));
-
-		if (minimalError) {
-			createMinimalError(errorLocation, headerMessage, stackTraceElement, outputElement);
-		} else {
-			stackTraceElement.classList.toggle('scrollable', outputScrolling);
-			outputElement.appendChild(stackTraceElement);
-			initializeScroll(stackTraceElement, disposableStore);
-		}
-	} else {
-		const header = document.createElement('div');
-		if (headerMessage) {
-			header.innerText = headerMessage;
-			outputElement.appendChild(header);
-		}
-	}
-
-	outputElement.classList.add('error');
-	return disposableStore;
+function renderError(outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRichRenderContext, trustHtml: boolean): IDisposable {
+    const disposableStore = createDisposableStore();
+    clearContainer(outputElement);
+    type ErrorLike = Partial;
+    let err: ErrorLike;
+    try {
+        err = JSON.parse(outputInfo.text());
+    }
+    catch (e) {
+        console.log(e);
+        return disposableStore;
+    }
+    const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message;
+    if (err.stack) {
+        const minimalError = ctx.settings.minimalError && !!headerMessage?.length;
+        outputElement.classList.add('traceback');
+        const { formattedStack, errorLocation } = formatStackTrace(err.stack);
+        const outputScrolling = !minimalError && scrollingEnabled(outputInfo, ctx.settings);
+        const lineLimit = minimalError ? 1000 : ctx.settings.lineLimit;
+        const outputOptions = { linesLimit: lineLimit, scrollable: outputScrolling, trustHtml, linkifyFilePaths: false };
+        const content = createOutputContent(outputInfo.id, formattedStack, outputOptions);
+        const stackTraceElement = document.createElement('div');
+        stackTraceElement.appendChild(content);
+        outputElement.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
+        disposableStore.push(ctx.onDidChangeSettings(e => {
+            outputElement.classList.toggle('word-wrap', e.outputWordWrap);
+        }));
+        if (minimalError) {
+            createMinimalError(errorLocation, headerMessage, stackTraceElement, outputElement);
+        }
+        else {
+            stackTraceElement.classList.toggle('scrollable', outputScrolling);
+            outputElement.appendChild(stackTraceElement);
+            initializeScroll(stackTraceElement, disposableStore);
+        }
+    }
+    else {
+        const header = document.createElement('div');
+        if (headerMessage) {
+            header.innerText = headerMessage;
+            outputElement.appendChild(header);
+        }
+    }
+    outputElement.classList.add('error');
+    return disposableStore;
 }
-
 function createMinimalError(errorLocation: string | undefined, headerMessage: string, stackTrace: HTMLDivElement, outputElement: HTMLElement) {
-	const outputDiv = document.createElement('div');
-	const headerSection = document.createElement('div');
-	headerSection.classList.add('error-output-header');
-
-	if (errorLocation && errorLocation.indexOf(' {
-		e.preventDefault();
-		const hidden = stackTrace.style.display === 'none';
-		stackTrace.style.display = hidden ? '' : 'none';
-		toggleStackLink.innerText = hidden ? 'Hide Details' : 'Show Details';
-	};
-
-	outputDiv.appendChild(stackTrace);
-	stackTrace.style.display = 'none';
-	outputElement.appendChild(outputDiv);
+    const outputDiv = document.createElement('div');
+    const headerSection = document.createElement('div');
+    headerSection.classList.add('error-output-header');
+    if (errorLocation && errorLocation.indexOf(' {
+        e.preventDefault();
+        const hidden = stackTrace.style.display === 'none';
+        stackTrace.style.display = hidden ? '' : 'none';
+        toggleStackLink.innerText = hidden ? 'Hide Details' : 'Show Details';
+    };
+    outputDiv.appendChild(stackTrace);
+    stackTrace.style.display = 'none';
+    outputElement.appendChild(outputDiv);
 }
-
 function getPreviousMatchingContentGroup(outputElement: HTMLElement) {
-	const outputContainer = outputElement.parentElement;
-	let match: HTMLElement | undefined = undefined;
-
-	let previous = outputContainer?.previousSibling;
-	while (previous) {
-		const outputElement = (previous.firstChild as HTMLElement | null);
-		if (!outputElement || !outputElement.classList.contains('output-stream')) {
-			break;
-		}
-
-		match = outputElement.firstChild as HTMLElement;
-		previous = previous?.previousSibling;
-	}
-
-	return match;
+    const outputContainer = outputElement.parentElement;
+    let match: HTMLElement | undefined = undefined;
+    let previous = outputContainer?.previousSibling;
+    while (previous) {
+        const outputElement = (previous.firstChild as HTMLElement | null);
+        if (!outputElement || !outputElement.classList.contains('output-stream')) {
+            break;
+        }
+        match = outputElement.firstChild as HTMLElement;
+        previous = previous?.previousSibling;
+    }
+    return match;
 }
-
 function onScrollHandler(e: globalThis.Event) {
-	const target = e.target as HTMLElement;
-	if (target.scrollTop === 0) {
-		target.classList.remove('more-above');
-	} else {
-		target.classList.add('more-above');
-	}
+    const target = e.target as HTMLElement;
+    if (target.scrollTop === 0) {
+        target.classList.remove('more-above');
+    }
+    else {
+        target.classList.add('more-above');
+    }
 }
-
 function onKeypressHandler(e: KeyboardEvent) {
-	if (e.ctrlKey || e.shiftKey) {
-		return;
-	}
-	if (e.code === 'ArrowDown' || e.code === 'ArrowUp' ||
-		e.code === 'End' || e.code === 'Home' ||
-		e.code === 'PageUp' || e.code === 'PageDown') {
-		// These should change the scroll position, not adjust the selected cell in the notebook
-		e.stopPropagation();
-	}
+    if (e.ctrlKey || e.shiftKey) {
+        return;
+    }
+    if (e.code === 'ArrowDown' || e.code === 'ArrowUp' ||
+        e.code === 'End' || e.code === 'Home' ||
+        e.code === 'PageUp' || e.code === 'PageDown') {
+        // These should change the scroll position, not adjust the selected cell in the notebook
+        e.stopPropagation();
+    }
 }
-
 // if there is a scrollable output, it will be scrolled to the given value if provided or the bottom of the element
 function initializeScroll(scrollableElement: HTMLElement, disposables: DisposableStore, scrollTop?: number) {
-	if (scrollableElement.classList.contains(scrollableClass)) {
-		const scrollbarVisible = scrollableElement.scrollHeight > scrollableElement.clientHeight;
-		scrollableElement.classList.toggle('scrollbar-visible', scrollbarVisible);
-		scrollableElement.scrollTop = scrollTop !== undefined ? scrollTop : scrollableElement.scrollHeight;
-		if (scrollbarVisible) {
-			scrollableElement.addEventListener('scroll', onScrollHandler);
-			disposables.push({ dispose: () => scrollableElement.removeEventListener('scroll', onScrollHandler) });
-			scrollableElement.addEventListener('keydown', onKeypressHandler);
-			disposables.push({ dispose: () => scrollableElement.removeEventListener('keydown', onKeypressHandler) });
-		}
-	}
+    if (scrollableElement.classList.contains(scrollableClass)) {
+        const scrollbarVisible = scrollableElement.scrollHeight > scrollableElement.clientHeight;
+        scrollableElement.classList.toggle('scrollbar-visible', scrollbarVisible);
+        scrollableElement.scrollTop = scrollTop !== undefined ? scrollTop : scrollableElement.scrollHeight;
+        if (scrollbarVisible) {
+            scrollableElement.addEventListener('scroll', onScrollHandler);
+            disposables.push({ dispose: () => scrollableElement.removeEventListener('scroll', onScrollHandler) });
+            scrollableElement.addEventListener('keydown', onKeypressHandler);
+            disposables.push({ dispose: () => scrollableElement.removeEventListener('keydown', onKeypressHandler) });
+        }
+    }
 }
-
 // Find the scrollTop of the existing scrollable output, return undefined if at the bottom or element doesn't exist
 function findScrolledHeight(container: HTMLElement): number | undefined {
-	const scrollableElement = container.querySelector('.' + scrollableClass);
-	if (scrollableElement && scrollableElement.scrollHeight - scrollableElement.scrollTop - scrollableElement.clientHeight > 2) {
-		// not scrolled to the bottom
-		return scrollableElement.scrollTop;
-	}
-	return undefined;
+    const scrollableElement = container.querySelector('.' + scrollableClass);
+    if (scrollableElement && scrollableElement.scrollHeight - scrollableElement.scrollTop - scrollableElement.clientHeight > 2) {
+        // not scrolled to the bottom
+        return scrollableElement.scrollTop;
+    }
+    return undefined;
 }
-
 function scrollingEnabled(output: OutputItem, options: RenderOptions) {
-	const metadata = output.metadata;
-	return (typeof metadata === 'object' && metadata
-		&& 'scrollable' in metadata && typeof metadata.scrollable === 'boolean') ?
-		metadata.scrollable : options.outputScrolling;
+    const metadata = output.metadata;
+    return (typeof metadata === 'object' && metadata
+        && 'scrollable' in metadata && typeof metadata.scrollable === 'boolean') ?
+        metadata.scrollable : options.outputScrolling;
 }
-
 //  div.cell_container
 //    div.output_container
 //      div.output.output-stream		<-- outputElement parameter
 //        div.scrollable? tabindex="0" 	<-- contentParent
 //          div output-item-id="{guid}"	<-- content from outputItem parameter
 function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable {
-	const disposableStore = createDisposableStore();
-	const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
-	const outputOptions = { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml: false, error, linkifyFilePaths: ctx.settings.linkifyFilePaths };
-
-	outputElement.classList.add('output-stream');
-
-	const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined;
-
-	const previousOutputParent = getPreviousMatchingContentGroup(outputElement);
-	// If the previous output item for the same cell was also a stream, append this output to the previous
-	if (previousOutputParent) {
-		const existingContent = previousOutputParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
-		if (existingContent) {
-			appendOutput(outputInfo, existingContent, outputOptions);
-		} else {
-			const newContent = createOutputContent(outputInfo.id, outputInfo.text(), outputOptions);
-			previousOutputParent.appendChild(newContent);
-		}
-		previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight);
-		previousOutputParent.scrollTop = scrollTop !== undefined ? scrollTop : previousOutputParent.scrollHeight;
-	} else {
-		const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
-		let contentParent = existingContent?.parentElement;
-		if (existingContent && contentParent) {
-			appendOutput(outputInfo, existingContent, outputOptions);
-		} else {
-			const newContent = createOutputContent(outputInfo.id, outputInfo.text(), outputOptions);
-			contentParent = document.createElement('div');
-			contentParent.appendChild(newContent);
-			while (outputElement.firstChild) {
-				outputElement.firstChild.remove();
-			}
-			outputElement.appendChild(contentParent);
-		}
-
-		contentParent.classList.toggle('scrollable', outputScrolling);
-		outputElement.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
-		disposableStore.push(ctx.onDidChangeSettings(e => {
-			outputElement.classList.toggle('word-wrap', e.outputWordWrap);
-		}));
-
-		initializeScroll(contentParent, disposableStore, scrollTop);
-	}
-
-	return disposableStore;
+    const disposableStore = createDisposableStore();
+    const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
+    const outputOptions = { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml: false, error, linkifyFilePaths: ctx.settings.linkifyFilePaths };
+    outputElement.classList.add('output-stream');
+    const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined;
+    const previousOutputParent = getPreviousMatchingContentGroup(outputElement);
+    // If the previous output item for the same cell was also a stream, append this output to the previous
+    if (previousOutputParent) {
+        const existingContent = previousOutputParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
+        if (existingContent) {
+            appendOutput(outputInfo, existingContent, outputOptions);
+        }
+        else {
+            const newContent = createOutputContent(outputInfo.id, outputInfo.text(), outputOptions);
+            previousOutputParent.appendChild(newContent);
+        }
+        previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight);
+        previousOutputParent.scrollTop = scrollTop !== undefined ? scrollTop : previousOutputParent.scrollHeight;
+    }
+    else {
+        const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
+        let contentParent = existingContent?.parentElement;
+        if (existingContent && contentParent) {
+            appendOutput(outputInfo, existingContent, outputOptions);
+        }
+        else {
+            const newContent = createOutputContent(outputInfo.id, outputInfo.text(), outputOptions);
+            contentParent = document.createElement('div');
+            contentParent.appendChild(newContent);
+            while (outputElement.firstChild) {
+                outputElement.firstChild.remove();
+            }
+            outputElement.appendChild(contentParent);
+        }
+        contentParent.classList.toggle('scrollable', outputScrolling);
+        outputElement.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
+        disposableStore.push(ctx.onDidChangeSettings(e => {
+            outputElement.classList.toggle('word-wrap', e.outputWordWrap);
+        }));
+        initializeScroll(contentParent, disposableStore, scrollTop);
+    }
+    return disposableStore;
 }
-
 function renderText(outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRichRenderContext): IDisposable {
-	const disposableStore = createDisposableStore();
-	clearContainer(outputElement);
-
-	const text = outputInfo.text();
-	const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
-	const outputOptions = { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml: false, linkifyFilePaths: ctx.settings.linkifyFilePaths };
-	const content = createOutputContent(outputInfo.id, text, outputOptions);
-	content.classList.add('output-plaintext');
-	outputElement.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
-	disposableStore.push(ctx.onDidChangeSettings(e => {
-		outputElement.classList.toggle('word-wrap', e.outputWordWrap);
-	}));
-
-	content.classList.toggle('scrollable', outputScrolling);
-	outputElement.appendChild(content);
-	initializeScroll(content, disposableStore);
-
-	return disposableStore;
+    const disposableStore = createDisposableStore();
+    clearContainer(outputElement);
+    const text = outputInfo.text();
+    const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
+    const outputOptions = { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml: false, linkifyFilePaths: ctx.settings.linkifyFilePaths };
+    const content = createOutputContent(outputInfo.id, text, outputOptions);
+    content.classList.add('output-plaintext');
+    outputElement.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
+    disposableStore.push(ctx.onDidChangeSettings(e => {
+        outputElement.classList.toggle('word-wrap', e.outputWordWrap);
+    }));
+    content.classList.toggle('scrollable', outputScrolling);
+    outputElement.appendChild(content);
+    initializeScroll(content, disposableStore);
+    return disposableStore;
 }
-
 export const activate: ActivationFunction = (ctx) => {
-	const disposables = new Map();
-	const htmlHooks = new Set();
-	const jsHooks = new Set();
-
-	const latestContext = ctx as (RendererContext & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event });
-
-	const style = document.createElement('style');
-	style.textContent = `
+    const disposables = new Map();
+    const htmlHooks = new Set();
+    const jsHooks = new Set();
+    const latestContext = ctx as (RendererContext & {
+        readonly settings: RenderOptions;
+        readonly onDidChangeSettings: Event;
+    });
+    const style = document.createElement('style');
+    style.textContent = `
 	#container div.output.remove-padding {
 		padding-left: 0;
 		padding-right: 0;
@@ -535,105 +478,102 @@ export const activate: ActivationFunction = (ctx) => {
 		padding-right: 12px;
 	}
 	`;
-	document.body.appendChild(style);
-
-	return {
-		renderOutputItem: async (outputInfo, element, signal?: AbortSignal) => {
-			element.classList.add('remove-padding');
-			switch (outputInfo.mime) {
-				case 'text/html':
-				case 'image/svg+xml': {
-					if (!ctx.workspace.isTrusted) {
-						return;
-					}
-
-					await renderHTML(outputInfo, element, signal!, htmlHooks);
-					break;
-				}
-				case 'application/javascript': {
-					if (!ctx.workspace.isTrusted) {
-						return;
-					}
-
-					renderJavascript(outputInfo, element, signal!, jsHooks);
-					break;
-				}
-				case 'image/gif':
-				case 'image/png':
-				case 'image/jpeg':
-				case 'image/git':
-					{
-						disposables.get(outputInfo.id)?.dispose();
-						const disposable = renderImage(outputInfo, element);
-						disposables.set(outputInfo.id, disposable);
-					}
-					break;
-				case 'application/vnd.code.notebook.error':
-					{
-						disposables.get(outputInfo.id)?.dispose();
-						const disposable = renderError(outputInfo, element, latestContext, ctx.workspace.isTrusted);
-						disposables.set(outputInfo.id, disposable);
-					}
-					break;
-				case 'application/vnd.code.notebook.stdout':
-				case 'application/x.notebook.stdout':
-				case 'application/x.notebook.stream':
-					{
-						disposables.get(outputInfo.id)?.dispose();
-						const disposable = renderStream(outputInfo, element, false, latestContext);
-						disposables.set(outputInfo.id, disposable);
-					}
-					break;
-				case 'application/vnd.code.notebook.stderr':
-				case 'application/x.notebook.stderr':
-					{
-						disposables.get(outputInfo.id)?.dispose();
-						const disposable = renderStream(outputInfo, element, true, latestContext);
-						disposables.set(outputInfo.id, disposable);
-					}
-					break;
-				case 'text/plain':
-					{
-						disposables.get(outputInfo.id)?.dispose();
-						const disposable = renderText(outputInfo, element, latestContext);
-						disposables.set(outputInfo.id, disposable);
-					}
-					break;
-				default:
-					if (outputInfo.mime.indexOf('text/') > -1) {
-						disposables.get(outputInfo.id)?.dispose();
-						const disposable = renderText(outputInfo, element, latestContext);
-						disposables.set(outputInfo.id, disposable);
-					}
-					break;
-			}
-			if (element.querySelector('div')) {
-				element.querySelector('div')!.tabIndex = 0;
-			}
-
-		},
-		disposeOutputItem: (id: string | undefined) => {
-			if (id) {
-				disposables.get(id)?.dispose();
-			} else {
-				disposables.forEach(d => d.dispose());
-			}
-		},
-		experimental_registerHtmlRenderingHook: (hook: HtmlRenderingHook): IDisposable => {
-			htmlHooks.add(hook);
-			return {
-				dispose: () => {
-					htmlHooks.delete(hook);
-				}
-			};
-		},
-		experimental_registerJavaScriptRenderingHook: (hook: JavaScriptRenderingHook): IDisposable => {
-			jsHooks.add(hook);
-			return {
-				dispose: () => {
-					jsHooks.delete(hook);
-				}
-			};
-		}
-	};
+    document.body.appendChild(style);
+    return {
+        renderOutputItem: async (outputInfo, element, signal?: AbortSignal) => {
+            element.classList.add('remove-padding');
+            switch (outputInfo.mime) {
+                case 'text/html':
+                case 'image/svg+xml': {
+                    if (!ctx.workspace.isTrusted) {
+                        return;
+                    }
+                    await renderHTML(outputInfo, element, signal!, htmlHooks);
+                    break;
+                }
+                case 'application/javascript': {
+                    if (!ctx.workspace.isTrusted) {
+                        return;
+                    }
+                    renderJavascript(outputInfo, element, signal!, jsHooks);
+                    break;
+                }
+                case 'image/gif':
+                case 'image/png':
+                case 'image/jpeg':
+                case 'image/git':
+                    {
+                        disposables.get(outputInfo.id)?.dispose();
+                        const disposable = renderImage(outputInfo, element);
+                        disposables.set(outputInfo.id, disposable);
+                    }
+                    break;
+                case 'application/vnd.code.notebook.error':
+                    {
+                        disposables.get(outputInfo.id)?.dispose();
+                        const disposable = renderError(outputInfo, element, latestContext, ctx.workspace.isTrusted);
+                        disposables.set(outputInfo.id, disposable);
+                    }
+                    break;
+                case 'application/vnd.code.notebook.stdout':
+                case 'application/x.notebook.stdout':
+                case 'application/x.notebook.stream':
+                    {
+                        disposables.get(outputInfo.id)?.dispose();
+                        const disposable = renderStream(outputInfo, element, false, latestContext);
+                        disposables.set(outputInfo.id, disposable);
+                    }
+                    break;
+                case 'application/vnd.code.notebook.stderr':
+                case 'application/x.notebook.stderr':
+                    {
+                        disposables.get(outputInfo.id)?.dispose();
+                        const disposable = renderStream(outputInfo, element, true, latestContext);
+                        disposables.set(outputInfo.id, disposable);
+                    }
+                    break;
+                case 'text/plain':
+                    {
+                        disposables.get(outputInfo.id)?.dispose();
+                        const disposable = renderText(outputInfo, element, latestContext);
+                        disposables.set(outputInfo.id, disposable);
+                    }
+                    break;
+                default:
+                    if (outputInfo.mime.indexOf('text/') > -1) {
+                        disposables.get(outputInfo.id)?.dispose();
+                        const disposable = renderText(outputInfo, element, latestContext);
+                        disposables.set(outputInfo.id, disposable);
+                    }
+                    break;
+            }
+            if (element.querySelector('div')) {
+                element.querySelector('div')!.tabIndex = 0;
+            }
+        },
+        disposeOutputItem: (id: string | undefined) => {
+            if (id) {
+                disposables.get(id)?.dispose();
+            }
+            else {
+                disposables.forEach(d => d.dispose());
+            }
+        },
+        experimental_registerHtmlRenderingHook: (hook: HtmlRenderingHook): IDisposable => {
+            htmlHooks.add(hook);
+            return {
+                dispose: () => {
+                    htmlHooks.delete(hook);
+                }
+            };
+        },
+        experimental_registerJavaScriptRenderingHook: (hook: JavaScriptRenderingHook): IDisposable => {
+            jsHooks.add(hook);
+            return {
+                dispose: () => {
+                    jsHooks.delete(hook);
+                }
+            };
+        }
+    };
 };
diff --git a/extensions/notebook-renderers/Source/linkify.ts b/extensions/notebook-renderers/Source/linkify.ts
index 99760213d8b35..e8ea9461854e3 100644
--- a/extensions/notebook-renderers/Source/linkify.ts
+++ b/extensions/notebook-renderers/Source/linkify.ts
@@ -2,12 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { ttPolicy } from './htmlHelper';
-
 const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f';
 const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug');
-
 const WIN_ABSOLUTE_PATH = /(?<=^|\s)(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/;
 const WIN_RELATIVE_PATH = /(?<=^|\s)(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/;
 const WIN_PATH = new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`);
@@ -16,196 +13,175 @@ const LINE_COLUMN = /(?:\:([\d]+))?(?:\:([\d]+))?/;
 const isWindows = (typeof navigator !== 'undefined') ? navigator.userAgent && navigator.userAgent.indexOf('Windows') >= 0 : false;
 const PATH_LINK_REGEX = new RegExp(`${isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`, 'g');
 const HTML_LINK_REGEX = /]*?\s+)?href=(["'])(.*?)\1[^>]*?>.*?<\/a>/gi;
-
 const MAX_LENGTH = 2000;
-
 type LinkKind = 'web' | 'path' | 'html' | 'text';
 type LinkPart = {
-	kind: LinkKind;
-	value: string;
-	captures: string[];
+    kind: LinkKind;
+    value: string;
+    captures: string[];
 };
-
 export type LinkOptions = {
-	trustHtml?: boolean;
-	linkifyFilePaths: boolean;
+    trustHtml?: boolean;
+    linkifyFilePaths: boolean;
 };
-
 export class LinkDetector {
-
-	// used by unit tests
-	static injectedHtmlCreator: (value: string) => string;
-
-	private shouldGenerateHtml(trustHtml: boolean) {
-		return trustHtml && (!!LinkDetector.injectedHtmlCreator || !!ttPolicy);
-	}
-
-	private createHtml(value: string) {
-		if (LinkDetector.injectedHtmlCreator) {
-			return LinkDetector.injectedHtmlCreator(value);
-		}
-		else {
-			return ttPolicy?.createHTML(value).toString();
-		}
-	}
-
-	/**
-	 * Matches and handles web urls, absolute and relative file links in the string provided.
-	 * Returns  element that wraps the processed string, where matched links are replaced by .
-	 * 'onclick' event is attached to all anchored links that opens them in the editor.
-	 * When splitLines is true, each line of the text, even if it contains no links, is wrapped in a 
-	 * and added as a child of the returned .
-	 */
-	linkify(text: string, options: LinkOptions, splitLines?: boolean): HTMLElement {
-		if (splitLines) {
-			const lines = text.split('\n');
-			for (let i = 0; i < lines.length - 1; i++) {
-				lines[i] = lines[i] + '\n';
-			}
-			if (!lines[lines.length - 1]) {
-				// Remove the last element ('') that split added.
-				lines.pop();
-			}
-			const elements = lines.map(line => this.linkify(line, options, false));
-			if (elements.length === 1) {
-				// Do not wrap single line with extra span.
-				return elements[0];
-			}
-			const container = document.createElement('span');
-			elements.forEach(e => container.appendChild(e));
-			return container;
-		}
-
-		const container = document.createElement('span');
-		for (const part of this.detectLinks(text, !!options.trustHtml, options.linkifyFilePaths)) {
-			try {
-				let span: HTMLSpanElement | null = null;
-				switch (part.kind) {
-					case 'text':
-						container.appendChild(document.createTextNode(part.value));
-						break;
-					case 'web':
-					case 'path':
-						container.appendChild(this.createWebLink(part.value));
-						break;
-					case 'html':
-						span = document.createElement('span');
-						span.innerHTML = this.createHtml(part.value)!;
-						container.appendChild(span);
-						break;
-				}
-			} catch (e) {
-				container.appendChild(document.createTextNode(part.value));
-			}
-		}
-		return container;
-	}
-
-	private createWebLink(url: string): Node {
-		const link = this.createLink(url);
-		link.href = url;
-		return link;
-	}
-
-	// private createPathLink(text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: string | undefined): Node {
-	// 	if (path[0] === '/' && path[1] === '/') {
-	// 		// Most likely a url part which did not match, for example ftp://path.
-	// 		return document.createTextNode(text);
-	// 	}
-
-	// 	const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } };
-	// 	if (path[0] === '.') {
-	// 		if (!workspaceFolder) {
-	// 			return document.createTextNode(text);
-	// 		}
-	// 		const uri = workspaceFolder.toResource(path);
-	// 		const link = this.createLink(text);
-	// 		this.decorateLink(link, uri, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
-	// 		return link;
-	// 	}
-
-	// 	if (path[0] === '~') {
-	// 		const userHome = this.pathService.resolvedUserHome;
-	// 		if (userHome) {
-	// 			path = osPath.join(userHome.fsPath, path.substring(1));
-	// 		}
-	// 	}
-
-	// 	const link = this.createLink(text);
-	// 	link.tabIndex = 0;
-	// 	const uri = URI.file(osPath.normalize(path));
-	// 	this.fileService.resolve(uri).then(stat => {
-	// 		if (stat.isDirectory) {
-	// 			return;
-	// 		}
-	// 		this.decorateLink(link, uri, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
-	// 	}).catch(() => {
-	// 		// If the uri can not be resolved we should not spam the console with error, remain quite #86587
-	// 	});
-	// 	return link;
-	// }
-
-	private createLink(text: string): HTMLAnchorElement {
-		const link = document.createElement('a');
-		link.textContent = text;
-		return link;
-	}
-
-	private detectLinks(text: string, trustHtml: boolean, detectFilepaths: boolean): LinkPart[] {
-		if (text.length > MAX_LENGTH) {
-			return [{ kind: 'text', value: text, captures: [] }];
-		}
-
-		const regexes: RegExp[] = [];
-		const kinds: LinkKind[] = [];
-		const result: LinkPart[] = [];
-
-		if (this.shouldGenerateHtml(trustHtml)) {
-			regexes.push(HTML_LINK_REGEX);
-			kinds.push('html');
-		}
-		regexes.push(WEB_LINK_REGEX);
-		kinds.push('web');
-		if (detectFilepaths) {
-			regexes.push(PATH_LINK_REGEX);
-			kinds.push('path');
-		}
-
-
-		const splitOne = (text: string, regexIndex: number) => {
-			if (regexIndex >= regexes.length) {
-				result.push({ value: text, kind: 'text', captures: [] });
-				return;
-			}
-			const regex = regexes[regexIndex];
-			let currentIndex = 0;
-			let match;
-			regex.lastIndex = 0;
-			while ((match = regex.exec(text)) !== null) {
-				const stringBeforeMatch = text.substring(currentIndex, match.index);
-				if (stringBeforeMatch) {
-					splitOne(stringBeforeMatch, regexIndex + 1);
-				}
-				const value = match[0];
-				result.push({
-					value: value,
-					kind: kinds[regexIndex],
-					captures: match.slice(1)
-				});
-				currentIndex = match.index + value.length;
-			}
-			const stringAfterMatches = text.substring(currentIndex);
-			if (stringAfterMatches) {
-				splitOne(stringAfterMatches, regexIndex + 1);
-			}
-		};
-
-		splitOne(text, 0);
-		return result;
-	}
+    // used by unit tests
+    static injectedHtmlCreator: (value: string) => string;
+    private shouldGenerateHtml(trustHtml: boolean) {
+        return trustHtml && (!!LinkDetector.injectedHtmlCreator || !!ttPolicy);
+    }
+    private createHtml(value: string) {
+        if (LinkDetector.injectedHtmlCreator) {
+            return LinkDetector.injectedHtmlCreator(value);
+        }
+        else {
+            return ttPolicy?.createHTML(value).toString();
+        }
+    }
+    /**
+     * Matches and handles web urls, absolute and relative file links in the string provided.
+     * Returns  element that wraps the processed string, where matched links are replaced by .
+     * 'onclick' event is attached to all anchored links that opens them in the editor.
+     * When splitLines is true, each line of the text, even if it contains no links, is wrapped in a 
+     * and added as a child of the returned .
+     */
+    linkify(text: string, options: LinkOptions, splitLines?: boolean): HTMLElement {
+        if (splitLines) {
+            const lines = text.split('\n');
+            for (let i = 0; i < lines.length - 1; i++) {
+                lines[i] = lines[i] + '\n';
+            }
+            if (!lines[lines.length - 1]) {
+                // Remove the last element ('') that split added.
+                lines.pop();
+            }
+            const elements = lines.map(line => this.linkify(line, options, false));
+            if (elements.length === 1) {
+                // Do not wrap single line with extra span.
+                return elements[0];
+            }
+            const container = document.createElement('span');
+            elements.forEach(e => container.appendChild(e));
+            return container;
+        }
+        const container = document.createElement('span');
+        for (const part of this.detectLinks(text, !!options.trustHtml, options.linkifyFilePaths)) {
+            try {
+                let span: HTMLSpanElement | null = null;
+                switch (part.kind) {
+                    case 'text':
+                        container.appendChild(document.createTextNode(part.value));
+                        break;
+                    case 'web':
+                    case 'path':
+                        container.appendChild(this.createWebLink(part.value));
+                        break;
+                    case 'html':
+                        span = document.createElement('span');
+                        span.innerHTML = this.createHtml(part.value)!;
+                        container.appendChild(span);
+                        break;
+                }
+            }
+            catch (e) {
+                container.appendChild(document.createTextNode(part.value));
+            }
+        }
+        return container;
+    }
+    private createWebLink(url: string): Node {
+        const link = this.createLink(url);
+        link.href = url;
+        return link;
+    }
+    // private createPathLink(text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: string | undefined): Node {
+    // 	if (path[0] === '/' && path[1] === '/') {
+    // 		// Most likely a url part which did not match, for example ftp://path.
+    // 		return document.createTextNode(text);
+    // 	}
+    // 	const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } };
+    // 	if (path[0] === '.') {
+    // 		if (!workspaceFolder) {
+    // 			return document.createTextNode(text);
+    // 		}
+    // 		const uri = workspaceFolder.toResource(path);
+    // 		const link = this.createLink(text);
+    // 		this.decorateLink(link, uri, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
+    // 		return link;
+    // 	}
+    // 	if (path[0] === '~') {
+    // 		const userHome = this.pathService.resolvedUserHome;
+    // 		if (userHome) {
+    // 			path = osPath.join(userHome.fsPath, path.substring(1));
+    // 		}
+    // 	}
+    // 	const link = this.createLink(text);
+    // 	link.tabIndex = 0;
+    // 	const uri = URI.file(osPath.normalize(path));
+    // 	this.fileService.resolve(uri).then(stat => {
+    // 		if (stat.isDirectory) {
+    // 			return;
+    // 		}
+    // 		this.decorateLink(link, uri, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
+    // 	}).catch(() => {
+    // 		// If the uri can not be resolved we should not spam the console with error, remain quite #86587
+    // 	});
+    // 	return link;
+    // }
+    private createLink(text: string): HTMLAnchorElement {
+        const link = document.createElement('a');
+        link.textContent = text;
+        return link;
+    }
+    private detectLinks(text: string, trustHtml: boolean, detectFilepaths: boolean): LinkPart[] {
+        if (text.length > MAX_LENGTH) {
+            return [{ kind: 'text', value: text, captures: [] }];
+        }
+        const regexes: RegExp[] = [];
+        const kinds: LinkKind[] = [];
+        const result: LinkPart[] = [];
+        if (this.shouldGenerateHtml(trustHtml)) {
+            regexes.push(HTML_LINK_REGEX);
+            kinds.push('html');
+        }
+        regexes.push(WEB_LINK_REGEX);
+        kinds.push('web');
+        if (detectFilepaths) {
+            regexes.push(PATH_LINK_REGEX);
+            kinds.push('path');
+        }
+        const splitOne = (text: string, regexIndex: number) => {
+            if (regexIndex >= regexes.length) {
+                result.push({ value: text, kind: 'text', captures: [] });
+                return;
+            }
+            const regex = regexes[regexIndex];
+            let currentIndex = 0;
+            let match;
+            regex.lastIndex = 0;
+            while ((match = regex.exec(text)) !== null) {
+                const stringBeforeMatch = text.substring(currentIndex, match.index);
+                if (stringBeforeMatch) {
+                    splitOne(stringBeforeMatch, regexIndex + 1);
+                }
+                const value = match[0];
+                result.push({
+                    value: value,
+                    kind: kinds[regexIndex],
+                    captures: match.slice(1)
+                });
+                currentIndex = match.index + value.length;
+            }
+            const stringAfterMatches = text.substring(currentIndex);
+            if (stringAfterMatches) {
+                splitOne(stringAfterMatches, regexIndex + 1);
+            }
+        };
+        splitOne(text, 0);
+        return result;
+    }
 }
-
 const linkDetector = new LinkDetector();
 export function linkify(text: string, linkOptions: LinkOptions, splitLines?: boolean) {
-	return linkDetector.linkify(text, linkOptions, splitLines);
+    return linkDetector.linkify(text, linkOptions, splitLines);
 }
diff --git a/extensions/notebook-renderers/Source/rendererTypes.ts b/extensions/notebook-renderers/Source/rendererTypes.ts
index bab7a34af9f7e..21e7631257fe1 100644
--- a/extensions/notebook-renderers/Source/rendererTypes.ts
+++ b/extensions/notebook-renderers/Source/rendererTypes.ts
@@ -2,50 +2,45 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { OutputItem, RendererContext } from 'vscode-notebook-renderer';
 import { Event } from 'vscode';
-
 export interface IDisposable {
-	dispose(): void;
+    dispose(): void;
 }
-
 export interface HtmlRenderingHook {
-	/**
-	 * Invoked after the output item has been rendered but before it has been appended to the document.
-	 *
-	 * @return A new `HTMLElement` or `undefined` to continue using the provided element.
-	 */
-	postRender(outputItem: OutputItem, element: HTMLElement, signal: AbortSignal): HTMLElement | undefined | Promise;
+    /**
+     * Invoked after the output item has been rendered but before it has been appended to the document.
+     *
+     * @return A new `HTMLElement` or `undefined` to continue using the provided element.
+     */
+    postRender(outputItem: OutputItem, element: HTMLElement, signal: AbortSignal): HTMLElement | undefined | Promise;
 }
-
 export interface JavaScriptRenderingHook {
-	/**
-	 * Invoked before the script is evaluated.
-	 *
-	 * @return A new string of JavaScript or `undefined` to continue using the provided string.
-	 */
-	preEvaluate(outputItem: OutputItem, element: HTMLElement, script: string, signal: AbortSignal): string | undefined | Promise;
+    /**
+     * Invoked before the script is evaluated.
+     *
+     * @return A new string of JavaScript or `undefined` to continue using the provided string.
+     */
+    preEvaluate(outputItem: OutputItem, element: HTMLElement, script: string, signal: AbortSignal): string | undefined | Promise;
 }
-
 export interface RenderOptions {
-	readonly lineLimit: number;
-	readonly outputScrolling: boolean;
-	readonly outputWordWrap: boolean;
-	readonly linkifyFilePaths: boolean;
-	readonly minimalError: boolean;
+    readonly lineLimit: number;
+    readonly outputScrolling: boolean;
+    readonly outputWordWrap: boolean;
+    readonly linkifyFilePaths: boolean;
+    readonly minimalError: boolean;
 }
-
-export type IRichRenderContext = RendererContext & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event };
-
+export type IRichRenderContext = RendererContext & {
+    readonly settings: RenderOptions;
+    readonly onDidChangeSettings: Event;
+};
 export type OutputElementOptions = {
-	linesLimit: number;
-	scrollable?: boolean;
-	error?: boolean;
-	trustHtml?: boolean;
-	linkifyFilePaths: boolean;
+    linesLimit: number;
+    scrollable?: boolean;
+    error?: boolean;
+    trustHtml?: boolean;
+    linkifyFilePaths: boolean;
 };
-
 export interface OutputWithAppend extends OutputItem {
-	appendedText?(): string | undefined;
+    appendedText?(): string | undefined;
 }
diff --git a/extensions/notebook-renderers/Source/stackTraceHelper.ts b/extensions/notebook-renderers/Source/stackTraceHelper.ts
index ecf0eddb40ebc..94de39d2383ac 100644
--- a/extensions/notebook-renderers/Source/stackTraceHelper.ts
+++ b/extensions/notebook-renderers/Source/stackTraceHelper.ts
@@ -2,102 +2,97 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
-export function formatStackTrace(stack: string): { formattedStack: string; errorLocation?: string } {
-	let cleaned: string;
-	// Ansi colors are described here:
-	// https://en.wikipedia.org/wiki/ANSI_escape_code under the SGR section
-
-	// Remove background colors. The ones from IPython don't work well with
-	// themes 40-49 sets background color
-	cleaned = stack.replace(/\u001b\[4\dm/g, '');
-
-	// Also remove specific foreground colors (38 is the ascii code for picking one) (they don't translate either)
-	// Turn them into default foreground
-	cleaned = cleaned.replace(/\u001b\[38;.*?\d+m/g, '\u001b[39m');
-
-	// Turn all foreground colors after the --> to default foreground
-	cleaned = cleaned.replace(/(;32m[ ->]*?)(\d+)(.*)\n/g, (_s, prefix, num, suffix) => {
-		suffix = suffix.replace(/\u001b\[3\d+m/g, '\u001b[39m');
-		return `${prefix}${num}${suffix}\n`;
-	});
-
-	if (isIpythonStackTrace(cleaned)) {
-		return linkifyStack(cleaned);
-	}
-
-	return { formattedStack: cleaned };
+export function formatStackTrace(stack: string): {
+    formattedStack: string;
+    errorLocation?: string;
+} {
+    let cleaned: string;
+    // Ansi colors are described here:
+    // https://en.wikipedia.org/wiki/ANSI_escape_code under the SGR section
+    // Remove background colors. The ones from IPython don't work well with
+    // themes 40-49 sets background color
+    cleaned = stack.replace(/\u001b\[4\dm/g, '');
+    // Also remove specific foreground colors (38 is the ascii code for picking one) (they don't translate either)
+    // Turn them into default foreground
+    cleaned = cleaned.replace(/\u001b\[38;.*?\d+m/g, '\u001b[39m');
+    // Turn all foreground colors after the --> to default foreground
+    cleaned = cleaned.replace(/(;32m[ ->]*?)(\d+)(.*)\n/g, (_s, prefix, num, suffix) => {
+        suffix = suffix.replace(/\u001b\[3\d+m/g, '\u001b[39m');
+        return `${prefix}${num}${suffix}\n`;
+    });
+    if (isIpythonStackTrace(cleaned)) {
+        return linkifyStack(cleaned);
+    }
+    return { formattedStack: cleaned };
 }
-
 const formatSequence = /\u001b\[.+?m/g;
 const fileRegex = /File\s+(?:\u001b\[.+?m)?(.+):(\d+)/;
 const lineNumberRegex = /^((?:\u001b\[.+?m)?[ \->]+?)(\d+)(?:\u001b\[0m)?( .*)/;
 const cellRegex = /(?Cell\s+(?:\u001b\[.+?m)?In\s*\[(?\d+)\],\s*)(?line (?\d+)).*/;
 // older versions of IPython ~8.3.0
 const inputRegex = /(?Input\s+?(?:\u001b\[.+?m)(?In\s*\[(?\d+)\]))(?.*)/;
-
 function isIpythonStackTrace(stack: string) {
-	return cellRegex.test(stack) || inputRegex.test(stack) || fileRegex.test(stack);
+    return cellRegex.test(stack) || inputRegex.test(stack) || fileRegex.test(stack);
 }
-
 function stripFormatting(text: string) {
-	return text.replace(formatSequence, '').trim();
+    return text.replace(formatSequence, '').trim();
 }
-
-type cellLocation = { kind: 'cell'; path: string };
-type fileLocation = { kind: 'file'; path: string };
-
+type cellLocation = {
+    kind: 'cell';
+    path: string;
+};
+type fileLocation = {
+    kind: 'file';
+    path: string;
+};
 type location = cellLocation | fileLocation;
-
-function linkifyStack(stack: string): { formattedStack: string; errorLocation?: string } {
-	const lines = stack.split('\n');
-
-	let fileOrCell: location | undefined;
-	let locationLink = '';
-
-	for (const i in lines) {
-
-		const original = lines[i];
-		if (fileRegex.test(original)) {
-			const fileMatch = lines[i].match(fileRegex);
-			fileOrCell = { kind: 'file', path: stripFormatting(fileMatch![1]) };
-
-			continue;
-		} else if (cellRegex.test(original)) {
-			fileOrCell = {
-				kind: 'cell',
-				path: stripFormatting(original.replace(cellRegex, 'vscode-notebook-cell:?execution_count=$'))
-			};
-			const link = original.replace(cellRegex, `\'>line $`);
-			lines[i] = original.replace(cellRegex, `$${link}`);
-			locationLink = locationLink || link;
-
-			continue;
-		} else if (inputRegex.test(original)) {
-			fileOrCell = {
-				kind: 'cell',
-				path: stripFormatting(original.replace(inputRegex, 'vscode-notebook-cell:?execution_count=$'))
-			};
-			const link = original.replace(inputRegex, `$`);
-			lines[i] = original.replace(inputRegex, `Input ${link}$`);
-
-			continue;
-		} else if (!fileOrCell || original.trim() === '') {
-			// we don't have a location, so don't linkify anything
-			fileOrCell = undefined;
-
-			continue;
-		} else if (lineNumberRegex.test(original)) {
-			lines[i] = original.replace(lineNumberRegex, (_s, prefix, num, suffix) => {
-				return fileOrCell?.kind === 'file' ?
-					`${prefix}${num}${suffix}` :
-					`${prefix}${num}${suffix}`;
-			});
-
-			continue;
-		}
-	}
-
-	const errorLocation = locationLink;
-	return { formattedStack: lines.join('\n'), errorLocation };
+function linkifyStack(stack: string): {
+    formattedStack: string;
+    errorLocation?: string;
+} {
+    const lines = stack.split('\n');
+    let fileOrCell: location | undefined;
+    let locationLink = '';
+    for (const i in lines) {
+        const original = lines[i];
+        if (fileRegex.test(original)) {
+            const fileMatch = lines[i].match(fileRegex);
+            fileOrCell = { kind: 'file', path: stripFormatting(fileMatch![1]) };
+            continue;
+        }
+        else if (cellRegex.test(original)) {
+            fileOrCell = {
+                kind: 'cell',
+                path: stripFormatting(original.replace(cellRegex, 'vscode-notebook-cell:?execution_count=$'))
+            };
+            const link = original.replace(cellRegex, `\'>line $`);
+            lines[i] = original.replace(cellRegex, `$${link}`);
+            locationLink = locationLink || link;
+            continue;
+        }
+        else if (inputRegex.test(original)) {
+            fileOrCell = {
+                kind: 'cell',
+                path: stripFormatting(original.replace(inputRegex, 'vscode-notebook-cell:?execution_count=$'))
+            };
+            const link = original.replace(inputRegex, `$`);
+            lines[i] = original.replace(inputRegex, `Input ${link}$`);
+            continue;
+        }
+        else if (!fileOrCell || original.trim() === '') {
+            // we don't have a location, so don't linkify anything
+            fileOrCell = undefined;
+            continue;
+        }
+        else if (lineNumberRegex.test(original)) {
+            lines[i] = original.replace(lineNumberRegex, (_s, prefix, num, suffix) => {
+                return fileOrCell?.kind === 'file' ?
+                    `${prefix}${num}${suffix}` :
+                    `${prefix}${num}${suffix}`;
+            });
+            continue;
+        }
+    }
+    const errorLocation = locationLink;
+    return { formattedStack: lines.join('\n'), errorLocation };
 }
diff --git a/extensions/notebook-renderers/Source/textHelper.ts b/extensions/notebook-renderers/Source/textHelper.ts
index 9c080c7f9e42f..a9a22b2816e0a 100644
--- a/extensions/notebook-renderers/Source/textHelper.ts
+++ b/extensions/notebook-renderers/Source/textHelper.ts
@@ -2,175 +2,141 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { handleANSIOutput } from './ansi';
 import { LinkOptions } from './linkify';
 import { OutputElementOptions, OutputWithAppend } from './rendererTypes';
 export const scrollableClass = 'scrollable';
-
 const softScrollableLineLimit = 5000;
 const hardScrollableLineLimit = 8000;
-
 /**
  * Output is Truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings...]
  */
 function generateViewMoreElement(outputId: string) {
-
-	const container = document.createElement('div');
-	container.classList.add('truncation-message');
-	const first = document.createElement('span');
-	first.textContent = 'Output is truncated. View as a ';
-	container.appendChild(first);
-
-	const viewAsScrollableLink = document.createElement('a');
-	viewAsScrollableLink.textContent = 'scrollable element';
-	viewAsScrollableLink.href = `command:cellOutput.enableScrolling?${outputId}`;
-	viewAsScrollableLink.ariaLabel = 'enable scrollable output';
-	container.appendChild(viewAsScrollableLink);
-
-	const second = document.createElement('span');
-	second.textContent = ' or open in a ';
-	container.appendChild(second);
-
-	const openInTextEditorLink = document.createElement('a');
-	openInTextEditorLink.textContent = 'text editor';
-	openInTextEditorLink.href = `command:workbench.action.openLargeOutput?${outputId}`;
-	openInTextEditorLink.ariaLabel = 'open output in text editor';
-	container.appendChild(openInTextEditorLink);
-
-	const third = document.createElement('span');
-	third.textContent = '. Adjust cell output ';
-	container.appendChild(third);
-
-	const layoutSettingsLink = document.createElement('a');
-	layoutSettingsLink.textContent = 'settings';
-	layoutSettingsLink.href = `command:workbench.action.openSettings?%5B%22%40tag%3AnotebookOutputLayout%22%5D`;
-	layoutSettingsLink.ariaLabel = 'notebook output settings';
-	container.appendChild(layoutSettingsLink);
-
-	const fourth = document.createElement('span');
-	fourth.textContent = '...';
-	container.appendChild(fourth);
-
-	return container;
+    const container = document.createElement('div');
+    container.classList.add('truncation-message');
+    const first = document.createElement('span');
+    first.textContent = 'Output is truncated. View as a ';
+    container.appendChild(first);
+    const viewAsScrollableLink = document.createElement('a');
+    viewAsScrollableLink.textContent = 'scrollable element';
+    viewAsScrollableLink.href = `command:cellOutput.enableScrolling?${outputId}`;
+    viewAsScrollableLink.ariaLabel = 'enable scrollable output';
+    container.appendChild(viewAsScrollableLink);
+    const second = document.createElement('span');
+    second.textContent = ' or open in a ';
+    container.appendChild(second);
+    const openInTextEditorLink = document.createElement('a');
+    openInTextEditorLink.textContent = 'text editor';
+    openInTextEditorLink.href = `command:workbench.action.openLargeOutput?${outputId}`;
+    openInTextEditorLink.ariaLabel = 'open output in text editor';
+    container.appendChild(openInTextEditorLink);
+    const third = document.createElement('span');
+    third.textContent = '. Adjust cell output ';
+    container.appendChild(third);
+    const layoutSettingsLink = document.createElement('a');
+    layoutSettingsLink.textContent = 'settings';
+    layoutSettingsLink.href = `command:workbench.action.openSettings?%5B%22%40tag%3AnotebookOutputLayout%22%5D`;
+    layoutSettingsLink.ariaLabel = 'notebook output settings';
+    container.appendChild(layoutSettingsLink);
+    const fourth = document.createElement('span');
+    fourth.textContent = '...';
+    container.appendChild(fourth);
+    return container;
 }
-
 function generateNestedViewAllElement(outputId: string) {
-	const container = document.createElement('div');
-
-	const link = document.createElement('a');
-	link.textContent = '...';
-	link.href = `command:workbench.action.openLargeOutput?${outputId}`;
-	link.ariaLabel = 'Open full output in text editor';
-	link.title = 'Open full output in text editor';
-	link.style.setProperty('text-decoration', 'none');
-	container.appendChild(link);
-
-	return container;
+    const container = document.createElement('div');
+    const link = document.createElement('a');
+    link.textContent = '...';
+    link.href = `command:workbench.action.openLargeOutput?${outputId}`;
+    link.ariaLabel = 'Open full output in text editor';
+    link.title = 'Open full output in text editor';
+    link.style.setProperty('text-decoration', 'none');
+    container.appendChild(link);
+    return container;
 }
-
 function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number, linkOptions: LinkOptions) {
-	const container = document.createElement('div');
-	container.setAttribute('data-vscode-context', JSON.stringify({
-		webviewSection: 'text',
-		outputId: id,
-		'preventDefaultContextMenuItems': true
-	}));
-	const lineCount = buffer.length;
-
-	if (lineCount <= linesLimit) {
-		const spanElement = handleANSIOutput(buffer.join('\n'), linkOptions);
-		container.appendChild(spanElement);
-		return container;
-	}
-
-	container.appendChild(handleANSIOutput(buffer.slice(0, linesLimit - 5).join('\n'), linkOptions));
-
-	// truncated piece
-	const elipses = document.createElement('div');
-	elipses.innerText = '...';
-	container.appendChild(elipses);
-
-	container.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n'), linkOptions));
-
-	container.appendChild(generateViewMoreElement(id));
-
-	return container;
+    const container = document.createElement('div');
+    container.setAttribute('data-vscode-context', JSON.stringify({
+        webviewSection: 'text',
+        outputId: id,
+        'preventDefaultContextMenuItems': true
+    }));
+    const lineCount = buffer.length;
+    if (lineCount <= linesLimit) {
+        const spanElement = handleANSIOutput(buffer.join('\n'), linkOptions);
+        container.appendChild(spanElement);
+        return container;
+    }
+    container.appendChild(handleANSIOutput(buffer.slice(0, linesLimit - 5).join('\n'), linkOptions));
+    // truncated piece
+    const elipses = document.createElement('div');
+    elipses.innerText = '...';
+    container.appendChild(elipses);
+    container.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n'), linkOptions));
+    container.appendChild(generateViewMoreElement(id));
+    return container;
 }
-
 function scrollableArrayOfString(id: string, buffer: string[], linkOptions: LinkOptions) {
-	const element = document.createElement('div');
-	element.setAttribute('data-vscode-context', JSON.stringify({
-		webviewSection: 'text',
-		outputId: id,
-		'preventDefaultContextMenuItems': true
-	}));
-	if (buffer.length > softScrollableLineLimit) {
-		element.appendChild(generateNestedViewAllElement(id));
-	}
-
-	element.appendChild(handleANSIOutput(buffer.slice(-1 * softScrollableLineLimit).join('\n'), linkOptions));
-
-	return element;
+    const element = document.createElement('div');
+    element.setAttribute('data-vscode-context', JSON.stringify({
+        webviewSection: 'text',
+        outputId: id,
+        'preventDefaultContextMenuItems': true
+    }));
+    if (buffer.length > softScrollableLineLimit) {
+        element.appendChild(generateNestedViewAllElement(id));
+    }
+    element.appendChild(handleANSIOutput(buffer.slice(-1 * softScrollableLineLimit).join('\n'), linkOptions));
+    return element;
 }
-
 const outputLengths: Record = {};
-
 function appendScrollableOutput(element: HTMLElement, id: string, appended: string, linkOptions: LinkOptions) {
-	if (!outputLengths[id]) {
-		outputLengths[id] = 0;
-	}
-
-	const buffer = appended.split(/\r\n|\r|\n/g);
-	const appendedLength = buffer.length + outputLengths[id];
-	// Only append outputs up to the hard limit of lines, then replace it with the last softLimit number of lines
-	if (appendedLength > hardScrollableLineLimit) {
-		return false;
-	}
-	else {
-		element.appendChild(handleANSIOutput(buffer.join('\n'), linkOptions));
-		outputLengths[id] = appendedLength;
-	}
-	return true;
+    if (!outputLengths[id]) {
+        outputLengths[id] = 0;
+    }
+    const buffer = appended.split(/\r\n|\r|\n/g);
+    const appendedLength = buffer.length + outputLengths[id];
+    // Only append outputs up to the hard limit of lines, then replace it with the last softLimit number of lines
+    if (appendedLength > hardScrollableLineLimit) {
+        return false;
+    }
+    else {
+        element.appendChild(handleANSIOutput(buffer.join('\n'), linkOptions));
+        outputLengths[id] = appendedLength;
+    }
+    return true;
 }
-
 export function createOutputContent(id: string, outputText: string, options: OutputElementOptions): HTMLElement {
-	const { linesLimit, error, scrollable, trustHtml, linkifyFilePaths } = options;
-	const linkOptions: LinkOptions = { linkifyFilePaths, trustHtml };
-	const buffer = outputText.split(/\r\n|\r|\n/g);
-	outputLengths[id] = outputLengths[id] = Math.min(buffer.length, softScrollableLineLimit);
-
-	let outputElement: HTMLElement;
-	if (scrollable) {
-		outputElement = scrollableArrayOfString(id, buffer, linkOptions);
-	} else {
-		outputElement = truncatedArrayOfString(id, buffer, linesLimit, linkOptions);
-	}
-
-	outputElement.setAttribute('output-item-id', id);
-	if (error) {
-		outputElement.classList.add('error');
-	}
-
-	return outputElement;
+    const { linesLimit, error, scrollable, trustHtml, linkifyFilePaths } = options;
+    const linkOptions: LinkOptions = { linkifyFilePaths, trustHtml };
+    const buffer = outputText.split(/\r\n|\r|\n/g);
+    outputLengths[id] = outputLengths[id] = Math.min(buffer.length, softScrollableLineLimit);
+    let outputElement: HTMLElement;
+    if (scrollable) {
+        outputElement = scrollableArrayOfString(id, buffer, linkOptions);
+    }
+    else {
+        outputElement = truncatedArrayOfString(id, buffer, linesLimit, linkOptions);
+    }
+    outputElement.setAttribute('output-item-id', id);
+    if (error) {
+        outputElement.classList.add('error');
+    }
+    return outputElement;
 }
-
 export function appendOutput(outputInfo: OutputWithAppend, existingContent: HTMLElement, options: OutputElementOptions) {
-	const appendedText = outputInfo.appendedText?.();
-	const linkOptions = { linkifyFilePaths: options.linkifyFilePaths, trustHtml: options.trustHtml };
-	// appending output only supported for scrollable ouputs currently
-	if (appendedText && options.scrollable) {
-		if (appendScrollableOutput(existingContent, outputInfo.id, appendedText, linkOptions)) {
-			return;
-		}
-	}
-
-	const newContent = createOutputContent(outputInfo.id, outputInfo.text(), options);
-	existingContent.replaceWith(newContent);
-	while (newContent.nextSibling) {
-		// clear out any stale content if we had previously combined streaming outputs into this one
-		newContent.nextSibling.remove();
-	}
-
+    const appendedText = outputInfo.appendedText?.();
+    const linkOptions = { linkifyFilePaths: options.linkifyFilePaths, trustHtml: options.trustHtml };
+    // appending output only supported for scrollable ouputs currently
+    if (appendedText && options.scrollable) {
+        if (appendScrollableOutput(existingContent, outputInfo.id, appendedText, linkOptions)) {
+            return;
+        }
+    }
+    const newContent = createOutputContent(outputInfo.id, outputInfo.text(), options);
+    existingContent.replaceWith(newContent);
+    while (newContent.nextSibling) {
+        // clear out any stale content if we had previously combined streaming outputs into this one
+        newContent.nextSibling.remove();
+    }
 }
-
diff --git a/extensions/npm/Source/commands.ts b/extensions/npm/Source/commands.ts
index a5b1deaef396e..9a5dd9ab460e1 100644
--- a/extensions/npm/Source/commands.ts
+++ b/extensions/npm/Source/commands.ts
@@ -2,66 +2,53 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
-import {
-	detectNpmScriptsForFolder,
-	findScriptAtPosition,
-	runScript,
-	IFolderTaskItem
-} from './tasks';
-
-
+import { detectNpmScriptsForFolder, findScriptAtPosition, runScript, IFolderTaskItem } from './tasks';
 export function runSelectedScript(context: vscode.ExtensionContext) {
-	const editor = vscode.window.activeTextEditor;
-	if (!editor) {
-		return;
-	}
-	const document = editor.document;
-	const contents = document.getText();
-	const script = findScriptAtPosition(editor.document, contents, editor.selection.anchor);
-	if (script) {
-		runScript(context, script, document);
-	} else {
-		const message = vscode.l10n.t("Could not find a valid npm script at the selection.");
-		vscode.window.showErrorMessage(message);
-	}
+    const editor = vscode.window.activeTextEditor;
+    if (!editor) {
+        return;
+    }
+    const document = editor.document;
+    const contents = document.getText();
+    const script = findScriptAtPosition(editor.document, contents, editor.selection.anchor);
+    if (script) {
+        runScript(context, script, document);
+    }
+    else {
+        const message = vscode.l10n.t("Could not find a valid npm script at the selection.");
+        vscode.window.showErrorMessage(message);
+    }
 }
-
 export async function selectAndRunScriptFromFolder(context: vscode.ExtensionContext, selectedFolders: vscode.Uri[]) {
-	if (selectedFolders.length === 0) {
-		return;
-	}
-	const selectedFolder = selectedFolders[0];
-
-	const taskList: IFolderTaskItem[] = await detectNpmScriptsForFolder(context, selectedFolder);
-
-	if (taskList && taskList.length > 0) {
-		const quickPick = vscode.window.createQuickPick();
-		quickPick.placeholder = 'Select an npm script to run in folder';
-		quickPick.items = taskList;
-
-		const toDispose: vscode.Disposable[] = [];
-
-		const pickPromise = new Promise((c) => {
-			toDispose.push(quickPick.onDidAccept(() => {
-				toDispose.forEach(d => d.dispose());
-				c(quickPick.selectedItems[0]);
-			}));
-			toDispose.push(quickPick.onDidHide(() => {
-				toDispose.forEach(d => d.dispose());
-				c(undefined);
-			}));
-		});
-		quickPick.show();
-		const result = await pickPromise;
-		quickPick.dispose();
-		if (result) {
-			vscode.tasks.executeTask(result.task);
-		}
-	}
-	else {
-		vscode.window.showInformationMessage(`No npm scripts found in ${selectedFolder.fsPath}`, { modal: true });
-	}
+    if (selectedFolders.length === 0) {
+        return;
+    }
+    const selectedFolder = selectedFolders[0];
+    const taskList: IFolderTaskItem[] = await detectNpmScriptsForFolder(context, selectedFolder);
+    if (taskList && taskList.length > 0) {
+        const quickPick = vscode.window.createQuickPick();
+        quickPick.placeholder = 'Select an npm script to run in folder';
+        quickPick.items = taskList;
+        const toDispose: vscode.Disposable[] = [];
+        const pickPromise = new Promise((c) => {
+            toDispose.push(quickPick.onDidAccept(() => {
+                toDispose.forEach(d => d.dispose());
+                c(quickPick.selectedItems[0]);
+            }));
+            toDispose.push(quickPick.onDidHide(() => {
+                toDispose.forEach(d => d.dispose());
+                c(undefined);
+            }));
+        });
+        quickPick.show();
+        const result = await pickPromise;
+        quickPick.dispose();
+        if (result) {
+            vscode.tasks.executeTask(result.task);
+        }
+    }
+    else {
+        vscode.window.showInformationMessage(`No npm scripts found in ${selectedFolder.fsPath}`, { modal: true });
+    }
 }
diff --git a/extensions/npm/Source/features/bowerJSONContribution.ts b/extensions/npm/Source/features/bowerJSONContribution.ts
index c7a1f38095441..ba74f1271c6e9 100644
--- a/extensions/npm/Source/features/bowerJSONContribution.ts
+++ b/extensions/npm/Source/features/bowerJSONContribution.ts
@@ -2,207 +2,194 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { MarkdownString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, Uri, l10n } from 'vscode';
 import { IJSONContribution, ISuggestionsCollector } from './jsonContributions';
 import { XHRRequest } from 'request-light';
 import { Location } from 'jsonc-parser';
-
-
 const USER_AGENT = 'Visual Studio Code';
-
 export class BowerJSONContribution implements IJSONContribution {
-
-	private topRanked = ['twitter', 'bootstrap', 'angular-1.1.6', 'angular-latest', 'angulerjs', 'd3', 'myjquery', 'jq', 'abcdef1234567890', 'jQuery', 'jquery-1.11.1', 'jquery',
-		'sushi-vanilla-x-data', 'font-awsome', 'Font-Awesome', 'font-awesome', 'fontawesome', 'html5-boilerplate', 'impress.js', 'homebrew',
-		'backbone', 'moment1', 'momentjs', 'moment', 'linux', 'animate.css', 'animate-css', 'reveal.js', 'jquery-file-upload', 'blueimp-file-upload', 'threejs', 'express', 'chosen',
-		'normalize-css', 'normalize.css', 'semantic', 'semantic-ui', 'Semantic-UI', 'modernizr', 'underscore', 'underscore1',
-		'material-design-icons', 'ionic', 'chartjs', 'Chart.js', 'nnnick-chartjs', 'select2-ng', 'select2-dist', 'phantom', 'skrollr', 'scrollr', 'less.js', 'leancss', 'parser-lib',
-		'hui', 'bootstrap-languages', 'async', 'gulp', 'jquery-pjax', 'coffeescript', 'hammer.js', 'ace', 'leaflet', 'jquery-mobile', 'sweetalert', 'typeahead.js', 'soup', 'typehead.js',
-		'sails', 'codeigniter2'];
-
-	private xhr: XHRRequest;
-
-	public constructor(xhr: XHRRequest) {
-		this.xhr = xhr;
-	}
-
-	public getDocumentSelector(): DocumentSelector {
-		return [{ language: 'json', scheme: '*', pattern: '**/bower.json' }, { language: 'json', scheme: '*', pattern: '**/.bower.json' }];
-	}
-
-	private isEnabled() {
-		return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo');
-	}
-
-	public collectDefaultSuggestions(_resource: Uri, collector: ISuggestionsCollector): Thenable {
-		const defaultValue = {
-			'name': '${1:name}',
-			'description': '${2:description}',
-			'authors': ['${3:author}'],
-			'version': '${4:1.0.0}',
-			'main': '${5:pathToMain}',
-			'dependencies': {}
-		};
-		const proposal = new CompletionItem(l10n.t("Default bower.json"));
-		proposal.kind = CompletionItemKind.Class;
-		proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t'));
-		collector.add(proposal);
-		return Promise.resolve(null);
-	}
-
-	public collectPropertySuggestions(_resource: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable | null {
-		if (!this.isEnabled()) {
-			return null;
-		}
-		if ((location.matches(['dependencies']) || location.matches(['devDependencies']))) {
-			if (currentWord.length > 0) {
-				const queryUrl = 'https://registry.bower.io/packages/search/' + encodeURIComponent(currentWord);
-
-				return this.xhr({
-					url: queryUrl,
-					headers: { agent: USER_AGENT }
-				}).then((success) => {
-					if (success.status === 200) {
-						try {
-							const obj = JSON.parse(success.responseText);
-							if (Array.isArray(obj)) {
-								const results = <{ name: string; description: string }[]>obj;
-								for (const result of results) {
-									const name = result.name;
-									const description = result.description || '';
-									const insertText = new SnippetString().appendText(JSON.stringify(name));
-									if (addValue) {
-										insertText.appendText(': ').appendPlaceholder('latest');
-										if (!isLast) {
-											insertText.appendText(',');
-										}
-									}
-									const proposal = new CompletionItem(name);
-									proposal.kind = CompletionItemKind.Property;
-									proposal.insertText = insertText;
-									proposal.filterText = JSON.stringify(name);
-									proposal.documentation = description;
-									collector.add(proposal);
-								}
-								collector.setAsIncomplete();
-							}
-						} catch (e) {
-							// ignore
-						}
-					} else {
-						collector.error(l10n.t("Request to the bower repository failed: {0}", success.responseText));
-						return 0;
-					}
-					return undefined;
-				}, (error) => {
-					collector.error(l10n.t("Request to the bower repository failed: {0}", error.responseText));
-					return 0;
-				});
-			} else {
-				this.topRanked.forEach((name) => {
-					const insertText = new SnippetString().appendText(JSON.stringify(name));
-					if (addValue) {
-						insertText.appendText(': ').appendPlaceholder('latest');
-						if (!isLast) {
-							insertText.appendText(',');
-						}
-					}
-
-					const proposal = new CompletionItem(name);
-					proposal.kind = CompletionItemKind.Property;
-					proposal.insertText = insertText;
-					proposal.filterText = JSON.stringify(name);
-					proposal.documentation = '';
-					collector.add(proposal);
-				});
-				collector.setAsIncomplete();
-				return Promise.resolve(null);
-			}
-		}
-		return null;
-	}
-
-	public collectValueSuggestions(_resource: Uri, location: Location, collector: ISuggestionsCollector): Promise | null {
-		if (!this.isEnabled()) {
-			return null;
-		}
-		if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
-			// not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26
-			const proposal = new CompletionItem(l10n.t("latest"));
-			proposal.insertText = new SnippetString('"${1:latest}"');
-			proposal.filterText = '""';
-			proposal.kind = CompletionItemKind.Value;
-			proposal.documentation = 'The latest version of the package';
-			collector.add(proposal);
-		}
-		return null;
-	}
-
-	public resolveSuggestion(_resource: Uri | undefined, item: CompletionItem): Thenable | null {
-		if (item.kind === CompletionItemKind.Property && item.documentation === '') {
-
-			let label = item.label;
-			if (typeof label !== 'string') {
-				label = label.label;
-			}
-
-			return this.getInfo(label).then(documentation => {
-				if (documentation) {
-					item.documentation = documentation;
-					return item;
-				}
-				return null;
-			});
-		}
-		return null;
-	}
-
-	private getInfo(pack: string): Thenable {
-		const queryUrl = 'https://registry.bower.io/packages/' + encodeURIComponent(pack);
-
-		return this.xhr({
-			url: queryUrl,
-			headers: { agent: USER_AGENT }
-		}).then((success) => {
-			try {
-				const obj = JSON.parse(success.responseText);
-				if (obj && obj.url) {
-					let url: string = obj.url;
-					if (url.indexOf('git://') === 0) {
-						url = url.substring(6);
-					}
-					if (url.length >= 4 && url.substr(url.length - 4) === '.git') {
-						url = url.substring(0, url.length - 4);
-					}
-					return url;
-				}
-			} catch (e) {
-				// ignore
-			}
-			return undefined;
-		}, () => {
-			return undefined;
-		});
-	}
-
-	public getInfoContribution(_resource: Uri, location: Location): Thenable | null {
-		if (!this.isEnabled()) {
-			return null;
-		}
-		if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
-			const pack = location.path[location.path.length - 1];
-			if (typeof pack === 'string') {
-				return this.getInfo(pack).then(documentation => {
-					if (documentation) {
-						const str = new MarkdownString();
-						str.appendText(documentation);
-						return [str];
-					}
-					return null;
-				});
-			}
-		}
-		return null;
-	}
+    private topRanked = ['twitter', 'bootstrap', 'angular-1.1.6', 'angular-latest', 'angulerjs', 'd3', 'myjquery', 'jq', 'abcdef1234567890', 'jQuery', 'jquery-1.11.1', 'jquery',
+        'sushi-vanilla-x-data', 'font-awsome', 'Font-Awesome', 'font-awesome', 'fontawesome', 'html5-boilerplate', 'impress.js', 'homebrew',
+        'backbone', 'moment1', 'momentjs', 'moment', 'linux', 'animate.css', 'animate-css', 'reveal.js', 'jquery-file-upload', 'blueimp-file-upload', 'threejs', 'express', 'chosen',
+        'normalize-css', 'normalize.css', 'semantic', 'semantic-ui', 'Semantic-UI', 'modernizr', 'underscore', 'underscore1',
+        'material-design-icons', 'ionic', 'chartjs', 'Chart.js', 'nnnick-chartjs', 'select2-ng', 'select2-dist', 'phantom', 'skrollr', 'scrollr', 'less.js', 'leancss', 'parser-lib',
+        'hui', 'bootstrap-languages', 'async', 'gulp', 'jquery-pjax', 'coffeescript', 'hammer.js', 'ace', 'leaflet', 'jquery-mobile', 'sweetalert', 'typeahead.js', 'soup', 'typehead.js',
+        'sails', 'codeigniter2'];
+    private xhr: XHRRequest;
+    public constructor(xhr: XHRRequest) {
+        this.xhr = xhr;
+    }
+    public getDocumentSelector(): DocumentSelector {
+        return [{ language: 'json', scheme: '*', pattern: '**/bower.json' }, { language: 'json', scheme: '*', pattern: '**/.bower.json' }];
+    }
+    private isEnabled() {
+        return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo');
+    }
+    public collectDefaultSuggestions(_resource: Uri, collector: ISuggestionsCollector): Thenable {
+        const defaultValue = {
+            'name': '${1:name}',
+            'description': '${2:description}',
+            'authors': ['${3:author}'],
+            'version': '${4:1.0.0}',
+            'main': '${5:pathToMain}',
+            'dependencies': {}
+        };
+        const proposal = new CompletionItem(l10n.t("Default bower.json"));
+        proposal.kind = CompletionItemKind.Class;
+        proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t'));
+        collector.add(proposal);
+        return Promise.resolve(null);
+    }
+    public collectPropertySuggestions(_resource: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable | null {
+        if (!this.isEnabled()) {
+            return null;
+        }
+        if ((location.matches(['dependencies']) || location.matches(['devDependencies']))) {
+            if (currentWord.length > 0) {
+                const queryUrl = 'https://registry.bower.io/packages/search/' + encodeURIComponent(currentWord);
+                return this.xhr({
+                    url: queryUrl,
+                    headers: { agent: USER_AGENT }
+                }).then((success) => {
+                    if (success.status === 200) {
+                        try {
+                            const obj = JSON.parse(success.responseText);
+                            if (Array.isArray(obj)) {
+                                const results = <{
+                                    name: string;
+                                    description: string;
+                                }[]>obj;
+                                for (const result of results) {
+                                    const name = result.name;
+                                    const description = result.description || '';
+                                    const insertText = new SnippetString().appendText(JSON.stringify(name));
+                                    if (addValue) {
+                                        insertText.appendText(': ').appendPlaceholder('latest');
+                                        if (!isLast) {
+                                            insertText.appendText(',');
+                                        }
+                                    }
+                                    const proposal = new CompletionItem(name);
+                                    proposal.kind = CompletionItemKind.Property;
+                                    proposal.insertText = insertText;
+                                    proposal.filterText = JSON.stringify(name);
+                                    proposal.documentation = description;
+                                    collector.add(proposal);
+                                }
+                                collector.setAsIncomplete();
+                            }
+                        }
+                        catch (e) {
+                            // ignore
+                        }
+                    }
+                    else {
+                        collector.error(l10n.t("Request to the bower repository failed: {0}", success.responseText));
+                        return 0;
+                    }
+                    return undefined;
+                }, (error) => {
+                    collector.error(l10n.t("Request to the bower repository failed: {0}", error.responseText));
+                    return 0;
+                });
+            }
+            else {
+                this.topRanked.forEach((name) => {
+                    const insertText = new SnippetString().appendText(JSON.stringify(name));
+                    if (addValue) {
+                        insertText.appendText(': ').appendPlaceholder('latest');
+                        if (!isLast) {
+                            insertText.appendText(',');
+                        }
+                    }
+                    const proposal = new CompletionItem(name);
+                    proposal.kind = CompletionItemKind.Property;
+                    proposal.insertText = insertText;
+                    proposal.filterText = JSON.stringify(name);
+                    proposal.documentation = '';
+                    collector.add(proposal);
+                });
+                collector.setAsIncomplete();
+                return Promise.resolve(null);
+            }
+        }
+        return null;
+    }
+    public collectValueSuggestions(_resource: Uri, location: Location, collector: ISuggestionsCollector): Promise | null {
+        if (!this.isEnabled()) {
+            return null;
+        }
+        if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
+            // not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26
+            const proposal = new CompletionItem(l10n.t("latest"));
+            proposal.insertText = new SnippetString('"${1:latest}"');
+            proposal.filterText = '""';
+            proposal.kind = CompletionItemKind.Value;
+            proposal.documentation = 'The latest version of the package';
+            collector.add(proposal);
+        }
+        return null;
+    }
+    public resolveSuggestion(_resource: Uri | undefined, item: CompletionItem): Thenable | null {
+        if (item.kind === CompletionItemKind.Property && item.documentation === '') {
+            let label = item.label;
+            if (typeof label !== 'string') {
+                label = label.label;
+            }
+            return this.getInfo(label).then(documentation => {
+                if (documentation) {
+                    item.documentation = documentation;
+                    return item;
+                }
+                return null;
+            });
+        }
+        return null;
+    }
+    private getInfo(pack: string): Thenable {
+        const queryUrl = 'https://registry.bower.io/packages/' + encodeURIComponent(pack);
+        return this.xhr({
+            url: queryUrl,
+            headers: { agent: USER_AGENT }
+        }).then((success) => {
+            try {
+                const obj = JSON.parse(success.responseText);
+                if (obj && obj.url) {
+                    let url: string = obj.url;
+                    if (url.indexOf('git://') === 0) {
+                        url = url.substring(6);
+                    }
+                    if (url.length >= 4 && url.substr(url.length - 4) === '.git') {
+                        url = url.substring(0, url.length - 4);
+                    }
+                    return url;
+                }
+            }
+            catch (e) {
+                // ignore
+            }
+            return undefined;
+        }, () => {
+            return undefined;
+        });
+    }
+    public getInfoContribution(_resource: Uri, location: Location): Thenable | null {
+        if (!this.isEnabled()) {
+            return null;
+        }
+        if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
+            const pack = location.path[location.path.length - 1];
+            if (typeof pack === 'string') {
+                return this.getInfo(pack).then(documentation => {
+                    if (documentation) {
+                        const str = new MarkdownString();
+                        str.appendText(documentation);
+                        return [str];
+                    }
+                    return null;
+                });
+            }
+        }
+        return null;
+    }
 }
diff --git a/extensions/npm/Source/features/date.ts b/extensions/npm/Source/features/date.ts
index e2f3b44f81888..d75bab9af1fa7 100644
--- a/extensions/npm/Source/features/date.ts
+++ b/extensions/npm/Source/features/date.ts
@@ -2,17 +2,13 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { l10n } from 'vscode';
-
-
 const minute = 60;
 const hour = minute * 60;
 const day = hour * 24;
 const week = day * 7;
 const month = day * 30;
 const year = day * 365;
-
 /**
  * Create a localized of the time between now and the specified date.
  * @param date The date to generate the difference from.
@@ -23,179 +19,188 @@ const year = day * 365;
  * is less than 30 seconds.
  */
 export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean, disallowNow?: boolean): string {
-	if (typeof date !== 'number') {
-		date = date.getTime();
-	}
-
-	const seconds = Math.round((new Date().getTime() - date) / 1000);
-	if (seconds < -30) {
-		return l10n.t('in {0}', fromNow(new Date().getTime() + seconds * 1000, false));
-	}
-
-	if (!disallowNow && seconds < 30) {
-		return l10n.t('now');
-	}
-
-	let value: number;
-	if (seconds < minute) {
-		value = seconds;
-
-		if (appendAgoLabel) {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} second ago', value)
-					: l10n.t('{0} sec ago', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} seconds ago', value)
-					: l10n.t('{0} secs ago', value);
-			}
-		} else {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} second', value)
-					: l10n.t('{0} sec', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} seconds', value)
-					: l10n.t('{0} secs', value);
-			}
-		}
-	}
-
-	if (seconds < hour) {
-		value = Math.floor(seconds / minute);
-		if (appendAgoLabel) {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} minute ago', value)
-					: l10n.t('{0} min ago', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} minutes ago', value)
-					: l10n.t('{0} mins ago', value);
-			}
-		} else {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} minute', value)
-					: l10n.t('{0} min', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} minutes', value)
-					: l10n.t('{0} mins', value);
-			}
-		}
-	}
-
-	if (seconds < day) {
-		value = Math.floor(seconds / hour);
-		if (appendAgoLabel) {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} hour ago', value)
-					: l10n.t('{0} hr ago', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} hours ago', value)
-					: l10n.t('{0} hrs ago', value);
-			}
-		} else {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} hour', value)
-					: l10n.t('{0} hr', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} hours', value)
-					: l10n.t('{0} hrs', value);
-			}
-		}
-	}
-
-	if (seconds < week) {
-		value = Math.floor(seconds / day);
-		if (appendAgoLabel) {
-			return value === 1
-				? l10n.t('{0} day ago', value)
-				: l10n.t('{0} days ago', value);
-		} else {
-			return value === 1
-				? l10n.t('{0} day', value)
-				: l10n.t('{0} days', value);
-		}
-	}
-
-	if (seconds < month) {
-		value = Math.floor(seconds / week);
-		if (appendAgoLabel) {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} week ago', value)
-					: l10n.t('{0} wk ago', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} weeks ago', value)
-					: l10n.t('{0} wks ago', value);
-			}
-		} else {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} week', value)
-					: l10n.t('{0} wk', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} weeks', value)
-					: l10n.t('{0} wks', value);
-			}
-		}
-	}
-
-	if (seconds < year) {
-		value = Math.floor(seconds / month);
-		if (appendAgoLabel) {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} month ago', value)
-					: l10n.t('{0} mo ago', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} months ago', value)
-					: l10n.t('{0} mos ago', value);
-			}
-		} else {
-			if (value === 1) {
-				return useFullTimeWords
-					? l10n.t('{0} month', value)
-					: l10n.t('{0} mo', value);
-			} else {
-				return useFullTimeWords
-					? l10n.t('{0} months', value)
-					: l10n.t('{0} mos', value);
-			}
-		}
-	}
-
-	value = Math.floor(seconds / year);
-	if (appendAgoLabel) {
-		if (value === 1) {
-			return useFullTimeWords
-				? l10n.t('{0} year ago', value)
-				: l10n.t('{0} yr ago', value);
-		} else {
-			return useFullTimeWords
-				? l10n.t('{0} years ago', value)
-				: l10n.t('{0} yrs ago', value);
-		}
-	} else {
-		if (value === 1) {
-			return useFullTimeWords
-				? l10n.t('{0} year', value)
-				: l10n.t('{0} yr', value);
-		} else {
-			return useFullTimeWords
-				? l10n.t('{0} years', value)
-				: l10n.t('{0} yrs', value);
-		}
-	}
+    if (typeof date !== 'number') {
+        date = date.getTime();
+    }
+    const seconds = Math.round((new Date().getTime() - date) / 1000);
+    if (seconds < -30) {
+        return l10n.t('in {0}', fromNow(new Date().getTime() + seconds * 1000, false));
+    }
+    if (!disallowNow && seconds < 30) {
+        return l10n.t('now');
+    }
+    let value: number;
+    if (seconds < minute) {
+        value = seconds;
+        if (appendAgoLabel) {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} second ago', value)
+                    : l10n.t('{0} sec ago', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} seconds ago', value)
+                    : l10n.t('{0} secs ago', value);
+            }
+        }
+        else {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} second', value)
+                    : l10n.t('{0} sec', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} seconds', value)
+                    : l10n.t('{0} secs', value);
+            }
+        }
+    }
+    if (seconds < hour) {
+        value = Math.floor(seconds / minute);
+        if (appendAgoLabel) {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} minute ago', value)
+                    : l10n.t('{0} min ago', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} minutes ago', value)
+                    : l10n.t('{0} mins ago', value);
+            }
+        }
+        else {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} minute', value)
+                    : l10n.t('{0} min', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} minutes', value)
+                    : l10n.t('{0} mins', value);
+            }
+        }
+    }
+    if (seconds < day) {
+        value = Math.floor(seconds / hour);
+        if (appendAgoLabel) {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} hour ago', value)
+                    : l10n.t('{0} hr ago', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} hours ago', value)
+                    : l10n.t('{0} hrs ago', value);
+            }
+        }
+        else {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} hour', value)
+                    : l10n.t('{0} hr', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} hours', value)
+                    : l10n.t('{0} hrs', value);
+            }
+        }
+    }
+    if (seconds < week) {
+        value = Math.floor(seconds / day);
+        if (appendAgoLabel) {
+            return value === 1
+                ? l10n.t('{0} day ago', value)
+                : l10n.t('{0} days ago', value);
+        }
+        else {
+            return value === 1
+                ? l10n.t('{0} day', value)
+                : l10n.t('{0} days', value);
+        }
+    }
+    if (seconds < month) {
+        value = Math.floor(seconds / week);
+        if (appendAgoLabel) {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} week ago', value)
+                    : l10n.t('{0} wk ago', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} weeks ago', value)
+                    : l10n.t('{0} wks ago', value);
+            }
+        }
+        else {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} week', value)
+                    : l10n.t('{0} wk', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} weeks', value)
+                    : l10n.t('{0} wks', value);
+            }
+        }
+    }
+    if (seconds < year) {
+        value = Math.floor(seconds / month);
+        if (appendAgoLabel) {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} month ago', value)
+                    : l10n.t('{0} mo ago', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} months ago', value)
+                    : l10n.t('{0} mos ago', value);
+            }
+        }
+        else {
+            if (value === 1) {
+                return useFullTimeWords
+                    ? l10n.t('{0} month', value)
+                    : l10n.t('{0} mo', value);
+            }
+            else {
+                return useFullTimeWords
+                    ? l10n.t('{0} months', value)
+                    : l10n.t('{0} mos', value);
+            }
+        }
+    }
+    value = Math.floor(seconds / year);
+    if (appendAgoLabel) {
+        if (value === 1) {
+            return useFullTimeWords
+                ? l10n.t('{0} year ago', value)
+                : l10n.t('{0} yr ago', value);
+        }
+        else {
+            return useFullTimeWords
+                ? l10n.t('{0} years ago', value)
+                : l10n.t('{0} yrs ago', value);
+        }
+    }
+    else {
+        if (value === 1) {
+            return useFullTimeWords
+                ? l10n.t('{0} year', value)
+                : l10n.t('{0} yr', value);
+        }
+        else {
+            return useFullTimeWords
+                ? l10n.t('{0} years', value)
+                : l10n.t('{0} yrs', value);
+        }
+    }
 }
diff --git a/extensions/npm/Source/features/jsonContributions.ts b/extensions/npm/Source/features/jsonContributions.ts
index cf65a5b0851c4..680907196c3cc 100644
--- a/extensions/npm/Source/features/jsonContributions.ts
+++ b/extensions/npm/Source/features/jsonContributions.ts
@@ -2,174 +2,151 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { Location, getLocation, createScanner, SyntaxKind, ScanError, JSONScanner } from 'jsonc-parser';
 import { BowerJSONContribution } from './bowerJSONContribution';
 import { PackageJSONContribution } from './packageJSONContribution';
 import { XHRRequest } from 'request-light';
-
-import {
-	CompletionItem, CompletionItemProvider, CompletionList, TextDocument, Position, Hover, HoverProvider,
-	CancellationToken, Range, DocumentSelector, languages, Disposable, Uri, MarkdownString
-} from 'vscode';
-
+import { CompletionItem, CompletionItemProvider, CompletionList, TextDocument, Position, Hover, HoverProvider, CancellationToken, Range, DocumentSelector, languages, Disposable, Uri, MarkdownString } from 'vscode';
 export interface ISuggestionsCollector {
-	add(suggestion: CompletionItem): void;
-	error(message: string): void;
-	log(message: string): void;
-	setAsIncomplete(): void;
+    add(suggestion: CompletionItem): void;
+    error(message: string): void;
+    log(message: string): void;
+    setAsIncomplete(): void;
 }
-
 export interface IJSONContribution {
-	getDocumentSelector(): DocumentSelector;
-	getInfoContribution(resourceUri: Uri, location: Location): Thenable | null;
-	collectPropertySuggestions(resourceUri: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable | null;
-	collectValueSuggestions(resourceUri: Uri, location: Location, result: ISuggestionsCollector): Thenable | null;
-	collectDefaultSuggestions(resourceUri: Uri, result: ISuggestionsCollector): Thenable;
-	resolveSuggestion?(resourceUri: Uri | undefined, item: CompletionItem): Thenable | null;
+    getDocumentSelector(): DocumentSelector;
+    getInfoContribution(resourceUri: Uri, location: Location): Thenable | null;
+    collectPropertySuggestions(resourceUri: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable | null;
+    collectValueSuggestions(resourceUri: Uri, location: Location, result: ISuggestionsCollector): Thenable | null;
+    collectDefaultSuggestions(resourceUri: Uri, result: ISuggestionsCollector): Thenable;
+    resolveSuggestion?(resourceUri: Uri | undefined, item: CompletionItem): Thenable | null;
 }
-
 export function addJSONProviders(xhr: XHRRequest, npmCommandPath: string | undefined): Disposable {
-	const contributions = [new PackageJSONContribution(xhr, npmCommandPath), new BowerJSONContribution(xhr)];
-	const subscriptions: Disposable[] = [];
-	contributions.forEach(contribution => {
-		const selector = contribution.getDocumentSelector();
-		subscriptions.push(languages.registerCompletionItemProvider(selector, new JSONCompletionItemProvider(contribution), '"', ':'));
-		subscriptions.push(languages.registerHoverProvider(selector, new JSONHoverProvider(contribution)));
-	});
-	return Disposable.from(...subscriptions);
+    const contributions = [new PackageJSONContribution(xhr, npmCommandPath), new BowerJSONContribution(xhr)];
+    const subscriptions: Disposable[] = [];
+    contributions.forEach(contribution => {
+        const selector = contribution.getDocumentSelector();
+        subscriptions.push(languages.registerCompletionItemProvider(selector, new JSONCompletionItemProvider(contribution), '"', ':'));
+        subscriptions.push(languages.registerHoverProvider(selector, new JSONHoverProvider(contribution)));
+    });
+    return Disposable.from(...subscriptions);
 }
-
 export class JSONHoverProvider implements HoverProvider {
-
-	constructor(private jsonContribution: IJSONContribution) {
-	}
-
-	public provideHover(document: TextDocument, position: Position, _token: CancellationToken): Thenable | null {
-		const offset = document.offsetAt(position);
-		const location = getLocation(document.getText(), offset);
-		if (!location.previousNode) {
-			return null;
-		}
-		const node = location.previousNode;
-		if (node && node.offset <= offset && offset <= node.offset + node.length) {
-			const promise = this.jsonContribution.getInfoContribution(document.uri, location);
-			if (promise) {
-				return promise.then(htmlContent => {
-					const range = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
-					const result: Hover = {
-						contents: htmlContent || [],
-						range: range
-					};
-					return result;
-				});
-			}
-		}
-		return null;
-	}
+    constructor(private jsonContribution: IJSONContribution) {
+    }
+    public provideHover(document: TextDocument, position: Position, _token: CancellationToken): Thenable | null {
+        const offset = document.offsetAt(position);
+        const location = getLocation(document.getText(), offset);
+        if (!location.previousNode) {
+            return null;
+        }
+        const node = location.previousNode;
+        if (node && node.offset <= offset && offset <= node.offset + node.length) {
+            const promise = this.jsonContribution.getInfoContribution(document.uri, location);
+            if (promise) {
+                return promise.then(htmlContent => {
+                    const range = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
+                    const result: Hover = {
+                        contents: htmlContent || [],
+                        range: range
+                    };
+                    return result;
+                });
+            }
+        }
+        return null;
+    }
 }
-
 export class JSONCompletionItemProvider implements CompletionItemProvider {
-
-	private lastResource: Uri | undefined;
-
-	constructor(private jsonContribution: IJSONContribution) {
-	}
-
-	public resolveCompletionItem(item: CompletionItem, _token: CancellationToken): Thenable {
-		if (this.jsonContribution.resolveSuggestion) {
-			const resolver = this.jsonContribution.resolveSuggestion(this.lastResource, item);
-			if (resolver) {
-				return resolver;
-			}
-		}
-		return Promise.resolve(item);
-	}
-
-	public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): Thenable | null {
-		this.lastResource = document.uri;
-
-
-		const currentWord = this.getCurrentWord(document, position);
-		let overwriteRange: Range;
-
-		const items: CompletionItem[] = [];
-		let isIncomplete = false;
-
-		const offset = document.offsetAt(position);
-		const location = getLocation(document.getText(), offset);
-
-		const node = location.previousNode;
-		if (node && node.offset <= offset && offset <= node.offset + node.length && (node.type === 'property' || node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
-			overwriteRange = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
-		} else {
-			overwriteRange = new Range(document.positionAt(offset - currentWord.length), position);
-		}
-
-		const proposed: { [key: string]: boolean } = {};
-		const collector: ISuggestionsCollector = {
-			add: (suggestion: CompletionItem) => {
-				const key = typeof suggestion.label === 'string'
-					? suggestion.label
-					: suggestion.label.label;
-				if (!proposed[key]) {
-					proposed[key] = true;
-					suggestion.range = { replacing: overwriteRange, inserting: new Range(overwriteRange.start, overwriteRange.start) };
-					items.push(suggestion);
-				}
-			},
-			setAsIncomplete: () => isIncomplete = true,
-			error: (message: string) => console.error(message),
-			log: (message: string) => console.log(message)
-		};
-
-		let collectPromise: Thenable | null = null;
-
-		if (location.isAtPropertyKey) {
-			const scanner = createScanner(document.getText(), true);
-			const addValue = !location.previousNode || !this.hasColonAfter(scanner, location.previousNode.offset + location.previousNode.length);
-			const isLast = this.isLast(scanner, document.offsetAt(position));
-			collectPromise = this.jsonContribution.collectPropertySuggestions(document.uri, location, currentWord, addValue, isLast, collector);
-		} else {
-			if (location.path.length === 0) {
-				collectPromise = this.jsonContribution.collectDefaultSuggestions(document.uri, collector);
-			} else {
-				collectPromise = this.jsonContribution.collectValueSuggestions(document.uri, location, collector);
-			}
-		}
-		if (collectPromise) {
-			return collectPromise.then(() => {
-				if (items.length > 0 || isIncomplete) {
-					return new CompletionList(items, isIncomplete);
-				}
-				return null;
-			});
-		}
-		return null;
-	}
-
-	private getCurrentWord(document: TextDocument, position: Position) {
-		let i = position.character - 1;
-		const text = document.lineAt(position.line).text;
-		while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) {
-			i--;
-		}
-		return text.substring(i + 1, position.character);
-	}
-
-	private isLast(scanner: JSONScanner, offset: number): boolean {
-		scanner.setPosition(offset);
-		let nextToken = scanner.scan();
-		if (nextToken === SyntaxKind.StringLiteral && scanner.getTokenError() === ScanError.UnexpectedEndOfString) {
-			nextToken = scanner.scan();
-		}
-		return nextToken === SyntaxKind.CloseBraceToken || nextToken === SyntaxKind.EOF;
-	}
-	private hasColonAfter(scanner: JSONScanner, offset: number): boolean {
-		scanner.setPosition(offset);
-		return scanner.scan() === SyntaxKind.ColonToken;
-	}
-
+    private lastResource: Uri | undefined;
+    constructor(private jsonContribution: IJSONContribution) {
+    }
+    public resolveCompletionItem(item: CompletionItem, _token: CancellationToken): Thenable {
+        if (this.jsonContribution.resolveSuggestion) {
+            const resolver = this.jsonContribution.resolveSuggestion(this.lastResource, item);
+            if (resolver) {
+                return resolver;
+            }
+        }
+        return Promise.resolve(item);
+    }
+    public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): Thenable | null {
+        this.lastResource = document.uri;
+        const currentWord = this.getCurrentWord(document, position);
+        let overwriteRange: Range;
+        const items: CompletionItem[] = [];
+        let isIncomplete = false;
+        const offset = document.offsetAt(position);
+        const location = getLocation(document.getText(), offset);
+        const node = location.previousNode;
+        if (node && node.offset <= offset && offset <= node.offset + node.length && (node.type === 'property' || node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
+            overwriteRange = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
+        }
+        else {
+            overwriteRange = new Range(document.positionAt(offset - currentWord.length), position);
+        }
+        const proposed: {
+            [key: string]: boolean;
+        } = {};
+        const collector: ISuggestionsCollector = {
+            add: (suggestion: CompletionItem) => {
+                const key = typeof suggestion.label === 'string'
+                    ? suggestion.label
+                    : suggestion.label.label;
+                if (!proposed[key]) {
+                    proposed[key] = true;
+                    suggestion.range = { replacing: overwriteRange, inserting: new Range(overwriteRange.start, overwriteRange.start) };
+                    items.push(suggestion);
+                }
+            },
+            setAsIncomplete: () => isIncomplete = true,
+            error: (message: string) => console.error(message),
+            log: (message: string) => console.log(message)
+        };
+        let collectPromise: Thenable | null = null;
+        if (location.isAtPropertyKey) {
+            const scanner = createScanner(document.getText(), true);
+            const addValue = !location.previousNode || !this.hasColonAfter(scanner, location.previousNode.offset + location.previousNode.length);
+            const isLast = this.isLast(scanner, document.offsetAt(position));
+            collectPromise = this.jsonContribution.collectPropertySuggestions(document.uri, location, currentWord, addValue, isLast, collector);
+        }
+        else {
+            if (location.path.length === 0) {
+                collectPromise = this.jsonContribution.collectDefaultSuggestions(document.uri, collector);
+            }
+            else {
+                collectPromise = this.jsonContribution.collectValueSuggestions(document.uri, location, collector);
+            }
+        }
+        if (collectPromise) {
+            return collectPromise.then(() => {
+                if (items.length > 0 || isIncomplete) {
+                    return new CompletionList(items, isIncomplete);
+                }
+                return null;
+            });
+        }
+        return null;
+    }
+    private getCurrentWord(document: TextDocument, position: Position) {
+        let i = position.character - 1;
+        const text = document.lineAt(position.line).text;
+        while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) {
+            i--;
+        }
+        return text.substring(i + 1, position.character);
+    }
+    private isLast(scanner: JSONScanner, offset: number): boolean {
+        scanner.setPosition(offset);
+        let nextToken = scanner.scan();
+        if (nextToken === SyntaxKind.StringLiteral && scanner.getTokenError() === ScanError.UnexpectedEndOfString) {
+            nextToken = scanner.scan();
+        }
+        return nextToken === SyntaxKind.CloseBraceToken || nextToken === SyntaxKind.EOF;
+    }
+    private hasColonAfter(scanner: JSONScanner, offset: number): boolean {
+        scanner.setPosition(offset);
+        return scanner.scan() === SyntaxKind.ColonToken;
+    }
 }
-
 export const xhrDisabled = () => Promise.reject({ responseText: 'Use of online resources is disabled.' });
diff --git a/extensions/npm/Source/features/packageJSONContribution.ts b/extensions/npm/Source/features/packageJSONContribution.ts
index 999f39664f1c2..39065845a46d1 100644
--- a/extensions/npm/Source/features/packageJSONContribution.ts
+++ b/extensions/npm/Source/features/packageJSONContribution.ts
@@ -2,400 +2,371 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, MarkdownString, Uri, l10n } from 'vscode';
 import { IJSONContribution, ISuggestionsCollector } from './jsonContributions';
 import { XHRRequest } from 'request-light';
 import { Location } from 'jsonc-parser';
-
 import * as cp from 'child_process';
 import { dirname } from 'path';
 import { fromNow } from './date';
-
 const LIMIT = 40;
-
 const USER_AGENT = 'Visual Studio Code';
-
 export class PackageJSONContribution implements IJSONContribution {
-
-	private mostDependedOn = ['lodash', 'async', 'underscore', 'request', 'commander', 'express', 'debug', 'chalk', 'colors', 'q', 'coffee-script',
-		'mkdirp', 'optimist', 'through2', 'yeoman-generator', 'moment', 'bluebird', 'glob', 'gulp-util', 'minimist', 'cheerio', 'pug', 'redis', 'node-uuid',
-		'socket', 'io', 'uglify-js', 'winston', 'through', 'fs-extra', 'handlebars', 'body-parser', 'rimraf', 'mime', 'semver', 'mongodb', 'jquery',
-		'grunt', 'connect', 'yosay', 'underscore', 'string', 'xml2js', 'ejs', 'mongoose', 'marked', 'extend', 'mocha', 'superagent', 'js-yaml', 'xtend',
-		'shelljs', 'gulp', 'yargs', 'browserify', 'minimatch', 'react', 'less', 'prompt', 'inquirer', 'ws', 'event-stream', 'inherits', 'mysql', 'esprima',
-		'jsdom', 'stylus', 'when', 'readable-stream', 'aws-sdk', 'concat-stream', 'chai', 'Thenable', 'wrench'];
-
-	private knownScopes = ['@types', '@angular', '@babel', '@nuxtjs', '@vue', '@bazel'];
-
-	public getDocumentSelector(): DocumentSelector {
-		return [{ language: 'json', scheme: '*', pattern: '**/package.json' }];
-	}
-
-	public constructor(private xhr: XHRRequest, private npmCommandPath: string | undefined) {
-	}
-
-	public collectDefaultSuggestions(_resource: Uri, result: ISuggestionsCollector): Thenable {
-		const defaultValue = {
-			'name': '${1:name}',
-			'description': '${2:description}',
-			'authors': '${3:author}',
-			'version': '${4:1.0.0}',
-			'main': '${5:pathToMain}',
-			'dependencies': {}
-		};
-		const proposal = new CompletionItem(l10n.t("Default package.json"));
-		proposal.kind = CompletionItemKind.Module;
-		proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t'));
-		result.add(proposal);
-		return Promise.resolve(null);
-	}
-
-	private isEnabled() {
-		return this.npmCommandPath || this.onlineEnabled();
-	}
-
-	private onlineEnabled() {
-		return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo');
-	}
-
-	public collectPropertySuggestions(
-		_resource: Uri,
-		location: Location,
-		currentWord: string,
-		addValue: boolean,
-		isLast: boolean,
-		collector: ISuggestionsCollector
-	): Thenable | null {
-		if (!this.isEnabled()) {
-			return null;
-		}
-
-		if ((location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) {
-			let queryUrl: string;
-			if (currentWord.length > 0) {
-				if (currentWord[0] === '@') {
-					if (currentWord.indexOf('/') !== -1) {
-						return this.collectScopedPackages(currentWord, addValue, isLast, collector);
-					}
-					for (const scope of this.knownScopes) {
-						const proposal = new CompletionItem(scope);
-						proposal.kind = CompletionItemKind.Property;
-						proposal.insertText = new SnippetString().appendText(`"${scope}/`).appendTabstop().appendText('"');
-						proposal.filterText = JSON.stringify(scope);
-						proposal.documentation = '';
-						proposal.command = {
-							title: '',
-							command: 'editor.action.triggerSuggest'
-						};
-						collector.add(proposal);
-					}
-					collector.setAsIncomplete();
-				}
-
-				queryUrl = `https://registry.npmjs.org/-/v1/search?size=${LIMIT}&text=${encodeURIComponent(currentWord)}`;
-				return this.xhr({
-					url: queryUrl,
-					headers: { agent: USER_AGENT }
-				}).then((success) => {
-					if (success.status === 200) {
-						try {
-							const obj = JSON.parse(success.responseText);
-							if (obj && obj.objects && Array.isArray(obj.objects)) {
-								const results = <{ package: SearchPackageInfo }[]>obj.objects;
-								for (const result of results) {
-									this.processPackage(result.package, addValue, isLast, collector);
-								}
-
-							}
-						} catch (e) {
-							// ignore
-						}
-						collector.setAsIncomplete();
-					} else {
-						collector.error(l10n.t("Request to the NPM repository failed: {0}", success.responseText));
-						return 0;
-					}
-					return undefined;
-				}, (error) => {
-					collector.error(l10n.t("Request to the NPM repository failed: {0}", error.responseText));
-					return 0;
-				});
-			} else {
-				this.mostDependedOn.forEach((name) => {
-					const insertText = new SnippetString().appendText(JSON.stringify(name));
-					if (addValue) {
-						insertText.appendText(': "').appendTabstop().appendText('"');
-						if (!isLast) {
-							insertText.appendText(',');
-						}
-					}
-					const proposal = new CompletionItem(name);
-					proposal.kind = CompletionItemKind.Property;
-					proposal.insertText = insertText;
-					proposal.filterText = JSON.stringify(name);
-					proposal.documentation = '';
-					collector.add(proposal);
-				});
-				this.collectScopedPackages(currentWord, addValue, isLast, collector);
-				collector.setAsIncomplete();
-				return Promise.resolve(null);
-			}
-		}
-		return null;
-	}
-
-	private collectScopedPackages(currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable {
-		const segments = currentWord.split('/');
-		if (segments.length === 2 && segments[0].length > 1) {
-			const scope = segments[0].substr(1);
-			let name = segments[1];
-			if (name.length < 4) {
-				name = '';
-			}
-			const queryUrl = `https://registry.npmjs.com/-/v1/search?text=scope:${scope}%20${name}&size=250`;
-			return this.xhr({
-				url: queryUrl,
-				headers: { agent: USER_AGENT }
-			}).then((success) => {
-				if (success.status === 200) {
-					try {
-						const obj = JSON.parse(success.responseText);
-						if (obj && Array.isArray(obj.objects)) {
-							const objects = <{ package: SearchPackageInfo }[]>obj.objects;
-							for (const object of objects) {
-								this.processPackage(object.package, addValue, isLast, collector);
-							}
-						}
-					} catch (e) {
-						// ignore
-					}
-					collector.setAsIncomplete();
-				} else {
-					collector.error(l10n.t("Request to the NPM repository failed: {0}", success.responseText));
-				}
-				return null;
-			});
-		}
-		return Promise.resolve(null);
-	}
-
-	public async collectValueSuggestions(resource: Uri, location: Location, result: ISuggestionsCollector): Promise {
-		if (!this.isEnabled()) {
-			return null;
-		}
-
-		if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
-			const currentKey = location.path[location.path.length - 1];
-			if (typeof currentKey === 'string') {
-				const info = await this.fetchPackageInfo(currentKey, resource);
-				if (info && info.version) {
-
-					let name = JSON.stringify(info.version);
-					let proposal = new CompletionItem(name);
-					proposal.kind = CompletionItemKind.Property;
-					proposal.insertText = name;
-					proposal.documentation = l10n.t("The currently latest version of the package");
-					result.add(proposal);
-
-					name = JSON.stringify('^' + info.version);
-					proposal = new CompletionItem(name);
-					proposal.kind = CompletionItemKind.Property;
-					proposal.insertText = name;
-					proposal.documentation = l10n.t("Matches the most recent major version (1.x.x)");
-					result.add(proposal);
-
-					name = JSON.stringify('~' + info.version);
-					proposal = new CompletionItem(name);
-					proposal.kind = CompletionItemKind.Property;
-					proposal.insertText = name;
-					proposal.documentation = l10n.t("Matches the most recent minor version (1.2.x)");
-					result.add(proposal);
-				}
-			}
-		}
-		return null;
-	}
-
-	private getDocumentation(description: string | undefined, version: string | undefined, time: string | undefined, homepage: string | undefined): MarkdownString {
-		const str = new MarkdownString();
-		if (description) {
-			str.appendText(description);
-		}
-		if (version) {
-			str.appendText('\n\n');
-			str.appendText(time ? l10n.t("Latest version: {0} published {1}", version, fromNow(Date.parse(time), true, true)) : l10n.t("Latest version: {0}", version));
-		}
-		if (homepage) {
-			str.appendText('\n\n');
-			str.appendText(homepage);
-		}
-		return str;
-	}
-
-	public resolveSuggestion(resource: Uri | undefined, item: CompletionItem): Thenable | null {
-		if (item.kind === CompletionItemKind.Property && !item.documentation) {
-
-			let name = item.label;
-			if (typeof name !== 'string') {
-				name = name.label;
-			}
-
-			return this.fetchPackageInfo(name, resource).then(info => {
-				if (info) {
-					item.documentation = this.getDocumentation(info.description, info.version, info.time, info.homepage);
-					return item;
-				}
-				return null;
-			});
-		}
-		return null;
-	}
-
-	private isValidNPMName(name: string): boolean {
-		// following rules from https://github.com/npm/validate-npm-package-name,
-		// leading slash added as additional security measure
-		if (!name || name.length > 214 || name.match(/^[-_.\s]/)) {
-			return false;
-		}
-		const match = name.match(/^(?:@([^/~\s)('!*]+?)[/])?([^/~)('!*\s]+?)$/);
-		if (match) {
-			const scope = match[1];
-			if (scope && encodeURIComponent(scope) !== scope) {
-				return false;
-			}
-			const name = match[2];
-			return encodeURIComponent(name) === name;
-		}
-		return false;
-	}
-
-	private async fetchPackageInfo(pack: string, resource: Uri | undefined): Promise {
-		if (!this.isValidNPMName(pack)) {
-			return undefined; // avoid unnecessary lookups
-		}
-		let info: ViewPackageInfo | undefined;
-		if (this.npmCommandPath) {
-			info = await this.npmView(this.npmCommandPath, pack, resource);
-		}
-		if (!info && this.onlineEnabled()) {
-			info = await this.npmjsView(pack);
-		}
-		return info;
-	}
-
-	private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise {
-		return new Promise((resolve, _reject) => {
-			const args = ['view', '--json', '--', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time'];
-			const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined;
-
-			// corepack npm wrapper would automatically update package.json. disable that behavior.
-			// COREPACK_ENABLE_AUTO_PIN disables the package.json overwrite, and
-			// COREPACK_ENABLE_PROJECT_SPEC makes the npm view command succeed
-			//   even if packageManager specified a package manager other than npm.
-			const env = { ...process.env, COREPACK_ENABLE_AUTO_PIN: '0', COREPACK_ENABLE_PROJECT_SPEC: '0' };
-			let options: cp.ExecFileOptions = { cwd, env };
-			let commandPath: string = npmCommandPath;
-			if (process.platform === 'win32') {
-				options = { cwd, env, shell: true };
-				commandPath = `"${npmCommandPath}"`;
-			}
-			cp.execFile(commandPath, args, options, (error, stdout) => {
-				if (!error) {
-					try {
-						const content = JSON.parse(stdout);
-						const version = content['dist-tags.latest'] || content['version'];
-						resolve({
-							description: content['description'],
-							version,
-							time: content.time?.[version],
-							homepage: content['homepage']
-						});
-						return;
-					} catch (e) {
-						// ignore
-					}
-				}
-				resolve(undefined);
-			});
-		});
-	}
-
-	private async npmjsView(pack: string): Promise {
-		const queryUrl = 'https://registry.npmjs.org/' + encodeURIComponent(pack);
-		try {
-			const success = await this.xhr({
-				url: queryUrl,
-				headers: { agent: USER_AGENT }
-			});
-			const obj = JSON.parse(success.responseText);
-			const version = obj['dist-tags']?.latest || Object.keys(obj.versions).pop() || '';
-			return {
-				description: obj.description || '',
-				version,
-				time: obj.time?.[version],
-				homepage: obj.homepage || ''
-			};
-		}
-		catch (e) {
-			//ignore
-		}
-		return undefined;
-	}
-
-	public getInfoContribution(resource: Uri, location: Location): Thenable | null {
-		if (!this.isEnabled()) {
-			return null;
-		}
-		if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
-			const pack = location.path[location.path.length - 1];
-			if (typeof pack === 'string') {
-				return this.fetchPackageInfo(pack, resource).then(info => {
-					if (info) {
-						return [this.getDocumentation(info.description, info.version, info.time, info.homepage)];
-					}
-					return null;
-				});
-			}
-		}
-		return null;
-	}
-
-	private processPackage(pack: SearchPackageInfo, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector) {
-		if (pack && pack.name) {
-			const name = pack.name;
-			const insertText = new SnippetString().appendText(JSON.stringify(name));
-			if (addValue) {
-				insertText.appendText(': "');
-				if (pack.version) {
-					insertText.appendVariable('version', pack.version);
-				} else {
-					insertText.appendTabstop();
-				}
-				insertText.appendText('"');
-				if (!isLast) {
-					insertText.appendText(',');
-				}
-			}
-			const proposal = new CompletionItem(name);
-			proposal.kind = CompletionItemKind.Property;
-			proposal.insertText = insertText;
-			proposal.filterText = JSON.stringify(name);
-			proposal.documentation = this.getDocumentation(pack.description, pack.version, undefined, pack?.links?.homepage);
-			collector.add(proposal);
-		}
-	}
+    private mostDependedOn = ['lodash', 'async', 'underscore', 'request', 'commander', 'express', 'debug', 'chalk', 'colors', 'q', 'coffee-script',
+        'mkdirp', 'optimist', 'through2', 'yeoman-generator', 'moment', 'bluebird', 'glob', 'gulp-util', 'minimist', 'cheerio', 'pug', 'redis', 'node-uuid',
+        'socket', 'io', 'uglify-js', 'winston', 'through', 'fs-extra', 'handlebars', 'body-parser', 'rimraf', 'mime', 'semver', 'mongodb', 'jquery',
+        'grunt', 'connect', 'yosay', 'underscore', 'string', 'xml2js', 'ejs', 'mongoose', 'marked', 'extend', 'mocha', 'superagent', 'js-yaml', 'xtend',
+        'shelljs', 'gulp', 'yargs', 'browserify', 'minimatch', 'react', 'less', 'prompt', 'inquirer', 'ws', 'event-stream', 'inherits', 'mysql', 'esprima',
+        'jsdom', 'stylus', 'when', 'readable-stream', 'aws-sdk', 'concat-stream', 'chai', 'Thenable', 'wrench'];
+    private knownScopes = ['@types', '@angular', '@babel', '@nuxtjs', '@vue', '@bazel'];
+    public getDocumentSelector(): DocumentSelector {
+        return [{ language: 'json', scheme: '*', pattern: '**/package.json' }];
+    }
+    public constructor(private xhr: XHRRequest, private npmCommandPath: string | undefined) {
+    }
+    public collectDefaultSuggestions(_resource: Uri, result: ISuggestionsCollector): Thenable {
+        const defaultValue = {
+            'name': '${1:name}',
+            'description': '${2:description}',
+            'authors': '${3:author}',
+            'version': '${4:1.0.0}',
+            'main': '${5:pathToMain}',
+            'dependencies': {}
+        };
+        const proposal = new CompletionItem(l10n.t("Default package.json"));
+        proposal.kind = CompletionItemKind.Module;
+        proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t'));
+        result.add(proposal);
+        return Promise.resolve(null);
+    }
+    private isEnabled() {
+        return this.npmCommandPath || this.onlineEnabled();
+    }
+    private onlineEnabled() {
+        return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo');
+    }
+    public collectPropertySuggestions(_resource: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable | null {
+        if (!this.isEnabled()) {
+            return null;
+        }
+        if ((location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) {
+            let queryUrl: string;
+            if (currentWord.length > 0) {
+                if (currentWord[0] === '@') {
+                    if (currentWord.indexOf('/') !== -1) {
+                        return this.collectScopedPackages(currentWord, addValue, isLast, collector);
+                    }
+                    for (const scope of this.knownScopes) {
+                        const proposal = new CompletionItem(scope);
+                        proposal.kind = CompletionItemKind.Property;
+                        proposal.insertText = new SnippetString().appendText(`"${scope}/`).appendTabstop().appendText('"');
+                        proposal.filterText = JSON.stringify(scope);
+                        proposal.documentation = '';
+                        proposal.command = {
+                            title: '',
+                            command: 'editor.action.triggerSuggest'
+                        };
+                        collector.add(proposal);
+                    }
+                    collector.setAsIncomplete();
+                }
+                queryUrl = `https://registry.npmjs.org/-/v1/search?size=${LIMIT}&text=${encodeURIComponent(currentWord)}`;
+                return this.xhr({
+                    url: queryUrl,
+                    headers: { agent: USER_AGENT }
+                }).then((success) => {
+                    if (success.status === 200) {
+                        try {
+                            const obj = JSON.parse(success.responseText);
+                            if (obj && obj.objects && Array.isArray(obj.objects)) {
+                                const results = <{
+                                    package: SearchPackageInfo;
+                                }[]>obj.objects;
+                                for (const result of results) {
+                                    this.processPackage(result.package, addValue, isLast, collector);
+                                }
+                            }
+                        }
+                        catch (e) {
+                            // ignore
+                        }
+                        collector.setAsIncomplete();
+                    }
+                    else {
+                        collector.error(l10n.t("Request to the NPM repository failed: {0}", success.responseText));
+                        return 0;
+                    }
+                    return undefined;
+                }, (error) => {
+                    collector.error(l10n.t("Request to the NPM repository failed: {0}", error.responseText));
+                    return 0;
+                });
+            }
+            else {
+                this.mostDependedOn.forEach((name) => {
+                    const insertText = new SnippetString().appendText(JSON.stringify(name));
+                    if (addValue) {
+                        insertText.appendText(': "').appendTabstop().appendText('"');
+                        if (!isLast) {
+                            insertText.appendText(',');
+                        }
+                    }
+                    const proposal = new CompletionItem(name);
+                    proposal.kind = CompletionItemKind.Property;
+                    proposal.insertText = insertText;
+                    proposal.filterText = JSON.stringify(name);
+                    proposal.documentation = '';
+                    collector.add(proposal);
+                });
+                this.collectScopedPackages(currentWord, addValue, isLast, collector);
+                collector.setAsIncomplete();
+                return Promise.resolve(null);
+            }
+        }
+        return null;
+    }
+    private collectScopedPackages(currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable {
+        const segments = currentWord.split('/');
+        if (segments.length === 2 && segments[0].length > 1) {
+            const scope = segments[0].substr(1);
+            let name = segments[1];
+            if (name.length < 4) {
+                name = '';
+            }
+            const queryUrl = `https://registry.npmjs.com/-/v1/search?text=scope:${scope}%20${name}&size=250`;
+            return this.xhr({
+                url: queryUrl,
+                headers: { agent: USER_AGENT }
+            }).then((success) => {
+                if (success.status === 200) {
+                    try {
+                        const obj = JSON.parse(success.responseText);
+                        if (obj && Array.isArray(obj.objects)) {
+                            const objects = <{
+                                package: SearchPackageInfo;
+                            }[]>obj.objects;
+                            for (const object of objects) {
+                                this.processPackage(object.package, addValue, isLast, collector);
+                            }
+                        }
+                    }
+                    catch (e) {
+                        // ignore
+                    }
+                    collector.setAsIncomplete();
+                }
+                else {
+                    collector.error(l10n.t("Request to the NPM repository failed: {0}", success.responseText));
+                }
+                return null;
+            });
+        }
+        return Promise.resolve(null);
+    }
+    public async collectValueSuggestions(resource: Uri, location: Location, result: ISuggestionsCollector): Promise {
+        if (!this.isEnabled()) {
+            return null;
+        }
+        if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
+            const currentKey = location.path[location.path.length - 1];
+            if (typeof currentKey === 'string') {
+                const info = await this.fetchPackageInfo(currentKey, resource);
+                if (info && info.version) {
+                    let name = JSON.stringify(info.version);
+                    let proposal = new CompletionItem(name);
+                    proposal.kind = CompletionItemKind.Property;
+                    proposal.insertText = name;
+                    proposal.documentation = l10n.t("The currently latest version of the package");
+                    result.add(proposal);
+                    name = JSON.stringify('^' + info.version);
+                    proposal = new CompletionItem(name);
+                    proposal.kind = CompletionItemKind.Property;
+                    proposal.insertText = name;
+                    proposal.documentation = l10n.t("Matches the most recent major version (1.x.x)");
+                    result.add(proposal);
+                    name = JSON.stringify('~' + info.version);
+                    proposal = new CompletionItem(name);
+                    proposal.kind = CompletionItemKind.Property;
+                    proposal.insertText = name;
+                    proposal.documentation = l10n.t("Matches the most recent minor version (1.2.x)");
+                    result.add(proposal);
+                }
+            }
+        }
+        return null;
+    }
+    private getDocumentation(description: string | undefined, version: string | undefined, time: string | undefined, homepage: string | undefined): MarkdownString {
+        const str = new MarkdownString();
+        if (description) {
+            str.appendText(description);
+        }
+        if (version) {
+            str.appendText('\n\n');
+            str.appendText(time ? l10n.t("Latest version: {0} published {1}", version, fromNow(Date.parse(time), true, true)) : l10n.t("Latest version: {0}", version));
+        }
+        if (homepage) {
+            str.appendText('\n\n');
+            str.appendText(homepage);
+        }
+        return str;
+    }
+    public resolveSuggestion(resource: Uri | undefined, item: CompletionItem): Thenable | null {
+        if (item.kind === CompletionItemKind.Property && !item.documentation) {
+            let name = item.label;
+            if (typeof name !== 'string') {
+                name = name.label;
+            }
+            return this.fetchPackageInfo(name, resource).then(info => {
+                if (info) {
+                    item.documentation = this.getDocumentation(info.description, info.version, info.time, info.homepage);
+                    return item;
+                }
+                return null;
+            });
+        }
+        return null;
+    }
+    private isValidNPMName(name: string): boolean {
+        // following rules from https://github.com/npm/validate-npm-package-name,
+        // leading slash added as additional security measure
+        if (!name || name.length > 214 || name.match(/^[-_.\s]/)) {
+            return false;
+        }
+        const match = name.match(/^(?:@([^/~\s)('!*]+?)[/])?([^/~)('!*\s]+?)$/);
+        if (match) {
+            const scope = match[1];
+            if (scope && encodeURIComponent(scope) !== scope) {
+                return false;
+            }
+            const name = match[2];
+            return encodeURIComponent(name) === name;
+        }
+        return false;
+    }
+    private async fetchPackageInfo(pack: string, resource: Uri | undefined): Promise {
+        if (!this.isValidNPMName(pack)) {
+            return undefined; // avoid unnecessary lookups
+        }
+        let info: ViewPackageInfo | undefined;
+        if (this.npmCommandPath) {
+            info = await this.npmView(this.npmCommandPath, pack, resource);
+        }
+        if (!info && this.onlineEnabled()) {
+            info = await this.npmjsView(pack);
+        }
+        return info;
+    }
+    private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise {
+        return new Promise((resolve, _reject) => {
+            const args = ['view', '--json', '--', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time'];
+            const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined;
+            // corepack npm wrapper would automatically update package.json. disable that behavior.
+            // COREPACK_ENABLE_AUTO_PIN disables the package.json overwrite, and
+            // COREPACK_ENABLE_PROJECT_SPEC makes the npm view command succeed
+            //   even if packageManager specified a package manager other than npm.
+            const env = { ...process.env, COREPACK_ENABLE_AUTO_PIN: '0', COREPACK_ENABLE_PROJECT_SPEC: '0' };
+            let options: cp.ExecFileOptions = { cwd, env };
+            let commandPath: string = npmCommandPath;
+            if (process.platform === 'win32') {
+                options = { cwd, env, shell: true };
+                commandPath = `"${npmCommandPath}"`;
+            }
+            cp.execFile(commandPath, args, options, (error, stdout) => {
+                if (!error) {
+                    try {
+                        const content = JSON.parse(stdout);
+                        const version = content['dist-tags.latest'] || content['version'];
+                        resolve({
+                            description: content['description'],
+                            version,
+                            time: content.time?.[version],
+                            homepage: content['homepage']
+                        });
+                        return;
+                    }
+                    catch (e) {
+                        // ignore
+                    }
+                }
+                resolve(undefined);
+            });
+        });
+    }
+    private async npmjsView(pack: string): Promise {
+        const queryUrl = 'https://registry.npmjs.org/' + encodeURIComponent(pack);
+        try {
+            const success = await this.xhr({
+                url: queryUrl,
+                headers: { agent: USER_AGENT }
+            });
+            const obj = JSON.parse(success.responseText);
+            const version = obj['dist-tags']?.latest || Object.keys(obj.versions).pop() || '';
+            return {
+                description: obj.description || '',
+                version,
+                time: obj.time?.[version],
+                homepage: obj.homepage || ''
+            };
+        }
+        catch (e) {
+            //ignore
+        }
+        return undefined;
+    }
+    public getInfoContribution(resource: Uri, location: Location): Thenable | null {
+        if (!this.isEnabled()) {
+            return null;
+        }
+        if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
+            const pack = location.path[location.path.length - 1];
+            if (typeof pack === 'string') {
+                return this.fetchPackageInfo(pack, resource).then(info => {
+                    if (info) {
+                        return [this.getDocumentation(info.description, info.version, info.time, info.homepage)];
+                    }
+                    return null;
+                });
+            }
+        }
+        return null;
+    }
+    private processPackage(pack: SearchPackageInfo, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector) {
+        if (pack && pack.name) {
+            const name = pack.name;
+            const insertText = new SnippetString().appendText(JSON.stringify(name));
+            if (addValue) {
+                insertText.appendText(': "');
+                if (pack.version) {
+                    insertText.appendVariable('version', pack.version);
+                }
+                else {
+                    insertText.appendTabstop();
+                }
+                insertText.appendText('"');
+                if (!isLast) {
+                    insertText.appendText(',');
+                }
+            }
+            const proposal = new CompletionItem(name);
+            proposal.kind = CompletionItemKind.Property;
+            proposal.insertText = insertText;
+            proposal.filterText = JSON.stringify(name);
+            proposal.documentation = this.getDocumentation(pack.description, pack.version, undefined, pack?.links?.homepage);
+            collector.add(proposal);
+        }
+    }
 }
-
 interface SearchPackageInfo {
-	name: string;
-	description?: string;
-	version?: string;
-	links?: { homepage?: string };
+    name: string;
+    description?: string;
+    version?: string;
+    links?: {
+        homepage?: string;
+    };
 }
-
 interface ViewPackageInfo {
-	description: string;
-	version?: string;
-	time?: string;
-	homepage?: string;
+    description: string;
+    version?: string;
+    time?: string;
+    homepage?: string;
 }
diff --git a/extensions/npm/Source/npmBrowserMain.ts b/extensions/npm/Source/npmBrowserMain.ts
index c562b5a4a3234..600b61fa1b59c 100644
--- a/extensions/npm/Source/npmBrowserMain.ts
+++ b/extensions/npm/Source/npmBrowserMain.ts
@@ -2,14 +2,11 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as httpRequest from 'request-light';
 import * as vscode from 'vscode';
 import { addJSONProviders } from './features/jsonContributions';
-
 export async function activate(context: vscode.ExtensionContext): Promise {
-	context.subscriptions.push(addJSONProviders(httpRequest.xhr, undefined));
+    context.subscriptions.push(addJSONProviders(httpRequest.xhr, undefined));
 }
-
 export function deactivate(): void {
 }
diff --git a/extensions/npm/Source/npmMain.ts b/extensions/npm/Source/npmMain.ts
index 60758c8cb6425..dadc5c9d1f81b 100644
--- a/extensions/npm/Source/npmMain.ts
+++ b/extensions/npm/Source/npmMain.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as httpRequest from 'request-light';
 import * as vscode from 'vscode';
 import { addJSONProviders } from './features/jsonContributions';
@@ -12,156 +11,134 @@ import { getPackageManager, invalidateTasksCache, NpmTaskProvider, hasPackageJso
 import { invalidateHoverScriptsCache, NpmScriptHoverProvider } from './scriptHover';
 import { NpmScriptLensProvider } from './npmScriptLens';
 import which from 'which';
-
 let treeDataProvider: NpmScriptsTreeDataProvider | undefined;
-
 function invalidateScriptCaches() {
-	invalidateHoverScriptsCache();
-	invalidateTasksCache();
-	if (treeDataProvider) {
-		treeDataProvider.refresh();
-	}
+    invalidateHoverScriptsCache();
+    invalidateTasksCache();
+    if (treeDataProvider) {
+        treeDataProvider.refresh();
+    }
 }
-
 export async function activate(context: vscode.ExtensionContext): Promise {
-	configureHttpRequest();
-	context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
-		if (e.affectsConfiguration('http.proxy') || e.affectsConfiguration('http.proxyStrictSSL')) {
-			configureHttpRequest();
-		}
-	}));
-
-	const npmCommandPath = await getNPMCommandPath();
-	context.subscriptions.push(addJSONProviders(httpRequest.xhr, npmCommandPath));
-	registerTaskProvider(context);
-
-	treeDataProvider = registerExplorer(context);
-
-	context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => {
-		if (e.affectsConfiguration('npm.exclude') || e.affectsConfiguration('npm.autoDetect') || e.affectsConfiguration('npm.scriptExplorerExclude')) {
-			invalidateTasksCache();
-			if (treeDataProvider) {
-				treeDataProvider.refresh();
-			}
-		}
-		if (e.affectsConfiguration('npm.scriptExplorerAction')) {
-			if (treeDataProvider) {
-				treeDataProvider.refresh();
-			}
-		}
-	}));
-
-	registerHoverProvider(context);
-
-	context.subscriptions.push(vscode.commands.registerCommand('npm.runSelectedScript', runSelectedScript));
-
-	if (await hasPackageJson()) {
-		vscode.commands.executeCommand('setContext', 'npm:showScriptExplorer', true);
-	}
-
-	context.subscriptions.push(vscode.commands.registerCommand('npm.runScriptFromFolder', selectAndRunScriptFromFolder));
-	context.subscriptions.push(vscode.commands.registerCommand('npm.refresh', () => {
-		invalidateScriptCaches();
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('npm.packageManager', (args) => {
-		if (args instanceof vscode.Uri) {
-			return getPackageManager(context, args);
-		}
-		return '';
-	}));
-	context.subscriptions.push(new NpmScriptLensProvider());
-
-	context.subscriptions.push(vscode.window.registerTerminalQuickFixProvider('ms-vscode.npm-command', {
-		provideTerminalQuickFixes({ outputMatch }) {
-			if (!outputMatch) {
-				return;
-			}
-
-			const lines = outputMatch.regexMatch[1];
-			const fixes: vscode.TerminalQuickFixTerminalCommand[] = [];
-			for (const line of lines.split('\n')) {
-				// search from the second char, since the lines might be prefixed with
-				// "npm ERR!" which comes before the actual command suggestion.
-				const begin = line.indexOf('npm', 1);
-				if (begin === -1) {
-					continue;
-				}
-
-				const end = line.lastIndexOf('#');
-				fixes.push({ terminalCommand: line.slice(begin, end === -1 ? undefined : end - 1) });
-			}
-
-			return fixes;
-		},
-	}));
+    configureHttpRequest();
+    context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
+        if (e.affectsConfiguration('http.proxy') || e.affectsConfiguration('http.proxyStrictSSL')) {
+            configureHttpRequest();
+        }
+    }));
+    const npmCommandPath = await getNPMCommandPath();
+    context.subscriptions.push(addJSONProviders(httpRequest.xhr, npmCommandPath));
+    registerTaskProvider(context);
+    treeDataProvider = registerExplorer(context);
+    context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => {
+        if (e.affectsConfiguration('npm.exclude') || e.affectsConfiguration('npm.autoDetect') || e.affectsConfiguration('npm.scriptExplorerExclude')) {
+            invalidateTasksCache();
+            if (treeDataProvider) {
+                treeDataProvider.refresh();
+            }
+        }
+        if (e.affectsConfiguration('npm.scriptExplorerAction')) {
+            if (treeDataProvider) {
+                treeDataProvider.refresh();
+            }
+        }
+    }));
+    registerHoverProvider(context);
+    context.subscriptions.push(vscode.commands.registerCommand('npm.runSelectedScript', runSelectedScript));
+    if (await hasPackageJson()) {
+        vscode.commands.executeCommand('setContext', 'npm:showScriptExplorer', true);
+    }
+    context.subscriptions.push(vscode.commands.registerCommand('npm.runScriptFromFolder', selectAndRunScriptFromFolder));
+    context.subscriptions.push(vscode.commands.registerCommand('npm.refresh', () => {
+        invalidateScriptCaches();
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('npm.packageManager', (args) => {
+        if (args instanceof vscode.Uri) {
+            return getPackageManager(context, args);
+        }
+        return '';
+    }));
+    context.subscriptions.push(new NpmScriptLensProvider());
+    context.subscriptions.push(vscode.window.registerTerminalQuickFixProvider('ms-vscode.npm-command', {
+        provideTerminalQuickFixes({ outputMatch }) {
+            if (!outputMatch) {
+                return;
+            }
+            const lines = outputMatch.regexMatch[1];
+            const fixes: vscode.TerminalQuickFixTerminalCommand[] = [];
+            for (const line of lines.split('\n')) {
+                // search from the second char, since the lines might be prefixed with
+                // "npm ERR!" which comes before the actual command suggestion.
+                const begin = line.indexOf('npm', 1);
+                if (begin === -1) {
+                    continue;
+                }
+                const end = line.lastIndexOf('#');
+                fixes.push({ terminalCommand: line.slice(begin, end === -1 ? undefined : end - 1) });
+            }
+            return fixes;
+        },
+    }));
 }
-
 async function getNPMCommandPath(): Promise {
-	if (vscode.workspace.isTrusted && canRunNpmInCurrentWorkspace()) {
-		try {
-			return await which(process.platform === 'win32' ? 'npm.cmd' : 'npm');
-		} catch (e) {
-			return undefined;
-		}
-	}
-	return undefined;
+    if (vscode.workspace.isTrusted && canRunNpmInCurrentWorkspace()) {
+        try {
+            return await which(process.platform === 'win32' ? 'npm.cmd' : 'npm');
+        }
+        catch (e) {
+            return undefined;
+        }
+    }
+    return undefined;
 }
-
 function canRunNpmInCurrentWorkspace() {
-	if (vscode.workspace.workspaceFolders) {
-		return vscode.workspace.workspaceFolders.some(f => f.uri.scheme === 'file');
-	}
-	return false;
+    if (vscode.workspace.workspaceFolders) {
+        return vscode.workspace.workspaceFolders.some(f => f.uri.scheme === 'file');
+    }
+    return false;
 }
-
 let taskProvider: NpmTaskProvider;
 function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined {
-	if (vscode.workspace.workspaceFolders) {
-		const watcher = vscode.workspace.createFileSystemWatcher('**/package.json');
-		watcher.onDidChange((_e) => invalidateScriptCaches());
-		watcher.onDidDelete((_e) => invalidateScriptCaches());
-		watcher.onDidCreate((_e) => invalidateScriptCaches());
-		context.subscriptions.push(watcher);
-
-		const workspaceWatcher = vscode.workspace.onDidChangeWorkspaceFolders((_e) => invalidateScriptCaches());
-		context.subscriptions.push(workspaceWatcher);
-
-		taskProvider = new NpmTaskProvider(context);
-		const disposable = vscode.tasks.registerTaskProvider('npm', taskProvider);
-		context.subscriptions.push(disposable);
-		return disposable;
-	}
-	return undefined;
+    if (vscode.workspace.workspaceFolders) {
+        const watcher = vscode.workspace.createFileSystemWatcher('**/package.json');
+        watcher.onDidChange((_e) => invalidateScriptCaches());
+        watcher.onDidDelete((_e) => invalidateScriptCaches());
+        watcher.onDidCreate((_e) => invalidateScriptCaches());
+        context.subscriptions.push(watcher);
+        const workspaceWatcher = vscode.workspace.onDidChangeWorkspaceFolders((_e) => invalidateScriptCaches());
+        context.subscriptions.push(workspaceWatcher);
+        taskProvider = new NpmTaskProvider(context);
+        const disposable = vscode.tasks.registerTaskProvider('npm', taskProvider);
+        context.subscriptions.push(disposable);
+        return disposable;
+    }
+    return undefined;
 }
-
 function registerExplorer(context: vscode.ExtensionContext): NpmScriptsTreeDataProvider | undefined {
-	if (vscode.workspace.workspaceFolders) {
-		const treeDataProvider = new NpmScriptsTreeDataProvider(context, taskProvider!);
-		const view = vscode.window.createTreeView('npm', { treeDataProvider: treeDataProvider, showCollapseAll: true });
-		context.subscriptions.push(view);
-		return treeDataProvider;
-	}
-	return undefined;
+    if (vscode.workspace.workspaceFolders) {
+        const treeDataProvider = new NpmScriptsTreeDataProvider(context, taskProvider!);
+        const view = vscode.window.createTreeView('npm', { treeDataProvider: treeDataProvider, showCollapseAll: true });
+        context.subscriptions.push(view);
+        return treeDataProvider;
+    }
+    return undefined;
 }
-
 function registerHoverProvider(context: vscode.ExtensionContext): NpmScriptHoverProvider | undefined {
-	if (vscode.workspace.workspaceFolders) {
-		const npmSelector: vscode.DocumentSelector = {
-			language: 'json',
-			scheme: 'file',
-			pattern: '**/package.json'
-		};
-		const provider = new NpmScriptHoverProvider(context);
-		context.subscriptions.push(vscode.languages.registerHoverProvider(npmSelector, provider));
-		return provider;
-	}
-	return undefined;
+    if (vscode.workspace.workspaceFolders) {
+        const npmSelector: vscode.DocumentSelector = {
+            language: 'json',
+            scheme: 'file',
+            pattern: '**/package.json'
+        };
+        const provider = new NpmScriptHoverProvider(context);
+        context.subscriptions.push(vscode.languages.registerHoverProvider(npmSelector, provider));
+        return provider;
+    }
+    return undefined;
 }
-
 function configureHttpRequest() {
-	const httpSettings = vscode.workspace.getConfiguration('http');
-	httpRequest.configure(httpSettings.get('proxy', ''), httpSettings.get('proxyStrictSSL', true));
+    const httpSettings = vscode.workspace.getConfiguration('http');
+    httpRequest.configure(httpSettings.get('proxy', ''), httpSettings.get('proxyStrictSSL', true));
 }
-
 export function deactivate(): void {
 }
diff --git a/extensions/npm/Source/npmScriptLens.ts b/extensions/npm/Source/npmScriptLens.ts
index c8e506904f882..a76b703176975 100644
--- a/extensions/npm/Source/npmScriptLens.ts
+++ b/extensions/npm/Source/npmScriptLens.ts
@@ -2,112 +2,73 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
-import {
-	CodeLens,
-	CodeLensProvider,
-	Disposable,
-	EventEmitter,
-	languages,
-	TextDocument,
-	Uri,
-	workspace,
-	l10n
-} from 'vscode';
+import { CodeLens, CodeLensProvider, Disposable, EventEmitter, languages, TextDocument, Uri, workspace, l10n } from 'vscode';
 import { findPreferredPM } from './preferred-pm';
 import { readScripts } from './readScripts';
-
-
 const enum Constants {
-	ConfigKey = 'debug.javascript.codelens.npmScripts',
+    ConfigKey = 'debug.javascript.codelens.npmScripts'
 }
-
 const getFreshLensLocation = () => workspace.getConfiguration().get(Constants.ConfigKey);
-
 /**
  * Npm script lens provider implementation. Can show a "Debug" text above any
  * npm script, or the npm scripts section.
  */
 export class NpmScriptLensProvider implements CodeLensProvider, Disposable {
-	private lensLocation = getFreshLensLocation();
-	private readonly changeEmitter = new EventEmitter();
-	private subscriptions: Disposable[] = [];
-
-	/**
-	 * @inheritdoc
-	 */
-	public readonly onDidChangeCodeLenses = this.changeEmitter.event;
-
-	constructor() {
-		this.subscriptions.push(
-			this.changeEmitter,
-			workspace.onDidChangeConfiguration(evt => {
-				if (evt.affectsConfiguration(Constants.ConfigKey)) {
-					this.lensLocation = getFreshLensLocation();
-					this.changeEmitter.fire();
-				}
-			}),
-			languages.registerCodeLensProvider(
-				{
-					language: 'json',
-					pattern: '**/package.json',
-				},
-				this,
-			)
-		);
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public async provideCodeLenses(document: TextDocument): Promise {
-		if (this.lensLocation === 'never') {
-			return [];
-		}
-
-		const tokens = readScripts(document);
-		if (!tokens) {
-			return [];
-		}
-
-		const title = '$(debug-start) ' + l10n.t("Debug");
-		const cwd = path.dirname(document.uri.fsPath);
-		if (this.lensLocation === 'top') {
-			return [
-				new CodeLens(
-					tokens.location.range,
-					{
-						title,
-						command: 'extension.js-debug.npmScript',
-						arguments: [cwd],
-					},
-				),
-			];
-		}
-
-		if (this.lensLocation === 'all') {
-			const packageManager = await findPreferredPM(Uri.joinPath(document.uri, '..').fsPath);
-			return tokens.scripts.map(
-				({ name, nameRange }) =>
-					new CodeLens(
-						nameRange,
-						{
-							title,
-							command: 'extension.js-debug.createDebuggerTerminal',
-							arguments: [`${packageManager.name} run ${name}`, workspace.getWorkspaceFolder(document.uri), { cwd }],
-						},
-					),
-			);
-		}
-
-		return [];
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public dispose() {
-		this.subscriptions.forEach(s => s.dispose());
-	}
+    private lensLocation = getFreshLensLocation();
+    private readonly changeEmitter = new EventEmitter();
+    private subscriptions: Disposable[] = [];
+    /**
+     * @inheritdoc
+     */
+    public readonly onDidChangeCodeLenses = this.changeEmitter.event;
+    constructor() {
+        this.subscriptions.push(this.changeEmitter, workspace.onDidChangeConfiguration(evt => {
+            if (evt.affectsConfiguration(Constants.ConfigKey)) {
+                this.lensLocation = getFreshLensLocation();
+                this.changeEmitter.fire();
+            }
+        }), languages.registerCodeLensProvider({
+            language: 'json',
+            pattern: '**/package.json',
+        }, this));
+    }
+    /**
+     * @inheritdoc
+     */
+    public async provideCodeLenses(document: TextDocument): Promise {
+        if (this.lensLocation === 'never') {
+            return [];
+        }
+        const tokens = readScripts(document);
+        if (!tokens) {
+            return [];
+        }
+        const title = '$(debug-start) ' + l10n.t("Debug");
+        const cwd = path.dirname(document.uri.fsPath);
+        if (this.lensLocation === 'top') {
+            return [
+                new CodeLens(tokens.location.range, {
+                    title,
+                    command: 'extension.js-debug.npmScript',
+                    arguments: [cwd],
+                }),
+            ];
+        }
+        if (this.lensLocation === 'all') {
+            const packageManager = await findPreferredPM(Uri.joinPath(document.uri, '..').fsPath);
+            return tokens.scripts.map(({ name, nameRange }) => new CodeLens(nameRange, {
+                title,
+                command: 'extension.js-debug.createDebuggerTerminal',
+                arguments: [`${packageManager.name} run ${name}`, workspace.getWorkspaceFolder(document.uri), { cwd }],
+            }));
+        }
+        return [];
+    }
+    /**
+     * @inheritdoc
+     */
+    public dispose() {
+        this.subscriptions.forEach(s => s.dispose());
+    }
 }
diff --git a/extensions/npm/Source/npmView.ts b/extensions/npm/Source/npmView.ts
index e041b43f0919e..b94299a9be23b 100644
--- a/extensions/npm/Source/npmView.ts
+++ b/extensions/npm/Source/npmView.ts
@@ -2,332 +2,281 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
-import {
-	commands, Event, EventEmitter, ExtensionContext,
-	Range,
-	Selection, Task,
-	TaskGroup, tasks, TextDocument, TextDocumentShowOptions, ThemeIcon, TreeDataProvider, TreeItem, TreeItemLabel, TreeItemCollapsibleState, Uri,
-	window, workspace, WorkspaceFolder, Position, Location, l10n
-} from 'vscode';
+import { commands, Event, EventEmitter, ExtensionContext, Range, Selection, Task, TaskGroup, tasks, TextDocument, TextDocumentShowOptions, ThemeIcon, TreeDataProvider, TreeItem, TreeItemLabel, TreeItemCollapsibleState, Uri, window, workspace, WorkspaceFolder, Position, Location, l10n } from 'vscode';
 import { readScripts } from './readScripts';
-import {
-	createTask, getPackageManager, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, INpmTaskDefinition,
-	NpmTaskProvider,
-	startDebugging,
-	ITaskWithLocation,
-	INSTALL_SCRIPT
-} from './tasks';
-
-
+import { createTask, getPackageManager, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, INpmTaskDefinition, NpmTaskProvider, startDebugging, ITaskWithLocation, INSTALL_SCRIPT } from './tasks';
 class Folder extends TreeItem {
-	packages: PackageJSON[] = [];
-	workspaceFolder: WorkspaceFolder;
-
-	constructor(folder: WorkspaceFolder) {
-		super(folder.name, TreeItemCollapsibleState.Expanded);
-		this.contextValue = 'folder';
-		this.resourceUri = folder.uri;
-		this.workspaceFolder = folder;
-		this.iconPath = ThemeIcon.Folder;
-	}
-
-	addPackage(packageJson: PackageJSON) {
-		this.packages.push(packageJson);
-	}
+    packages: PackageJSON[] = [];
+    workspaceFolder: WorkspaceFolder;
+    constructor(folder: WorkspaceFolder) {
+        super(folder.name, TreeItemCollapsibleState.Expanded);
+        this.contextValue = 'folder';
+        this.resourceUri = folder.uri;
+        this.workspaceFolder = folder;
+        this.iconPath = ThemeIcon.Folder;
+    }
+    addPackage(packageJson: PackageJSON) {
+        this.packages.push(packageJson);
+    }
 }
-
 const packageName = 'package.json';
-
 class PackageJSON extends TreeItem {
-	path: string;
-	folder: Folder;
-	scripts: NpmScript[] = [];
-
-	static getLabel(relativePath: string): string {
-		if (relativePath.length > 0) {
-			return path.join(relativePath, packageName);
-		}
-		return packageName;
-	}
-
-	constructor(folder: Folder, relativePath: string) {
-		super(PackageJSON.getLabel(relativePath), TreeItemCollapsibleState.Expanded);
-		this.folder = folder;
-		this.path = relativePath;
-		this.contextValue = 'packageJSON';
-		if (relativePath) {
-			this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, relativePath, packageName));
-		} else {
-			this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, packageName));
-		}
-		this.iconPath = ThemeIcon.File;
-	}
-
-	addScript(script: NpmScript) {
-		this.scripts.push(script);
-	}
+    path: string;
+    folder: Folder;
+    scripts: NpmScript[] = [];
+    static getLabel(relativePath: string): string {
+        if (relativePath.length > 0) {
+            return path.join(relativePath, packageName);
+        }
+        return packageName;
+    }
+    constructor(folder: Folder, relativePath: string) {
+        super(PackageJSON.getLabel(relativePath), TreeItemCollapsibleState.Expanded);
+        this.folder = folder;
+        this.path = relativePath;
+        this.contextValue = 'packageJSON';
+        if (relativePath) {
+            this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, relativePath, packageName));
+        }
+        else {
+            this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, packageName));
+        }
+        this.iconPath = ThemeIcon.File;
+    }
+    addScript(script: NpmScript) {
+        this.scripts.push(script);
+    }
 }
-
 type ExplorerCommands = 'open' | 'run';
-
 class NpmScript extends TreeItem {
-	task: Task;
-	package: PackageJSON;
-	taskLocation?: Location;
-
-	constructor(_context: ExtensionContext, packageJson: PackageJSON, task: ITaskWithLocation) {
-		const name = packageJson.path.length > 0
-			? task.task.name.substring(0, task.task.name.length - packageJson.path.length - 2)
-			: task.task.name;
-		super(name, TreeItemCollapsibleState.None);
-		this.taskLocation = task.location;
-		const command: ExplorerCommands = name === `${INSTALL_SCRIPT} ` ? 'run' : workspace.getConfiguration('npm').get('scriptExplorerAction') || 'open';
-
-		const commandList = {
-			'open': {
-				title: 'Edit Script',
-				command: 'vscode.open',
-				arguments: [
-					this.taskLocation?.uri,
-					this.taskLocation ?
-						{
-							selection: new Range(this.taskLocation.range.start, this.taskLocation.range.start)
-						} satisfies TextDocumentShowOptions
-						: undefined
-				]
-			},
-			'run': {
-				title: 'Run Script',
-				command: 'npm.runScript',
-				arguments: [this]
-			}
-		};
-		this.contextValue = 'script';
-		this.package = packageJson;
-		this.task = task.task;
-		this.command = commandList[command];
-
-		if (this.task.group && this.task.group === TaskGroup.Clean) {
-			this.iconPath = new ThemeIcon('wrench-subaction');
-		} else {
-			this.iconPath = new ThemeIcon('wrench');
-		}
-		if (this.task.detail) {
-			this.tooltip = this.task.detail;
-			this.description = this.task.detail;
-		}
-	}
-
-	getFolder(): WorkspaceFolder {
-		return this.package.folder.workspaceFolder;
-	}
+    task: Task;
+    package: PackageJSON;
+    taskLocation?: Location;
+    constructor(_context: ExtensionContext, packageJson: PackageJSON, task: ITaskWithLocation) {
+        const name = packageJson.path.length > 0
+            ? task.task.name.substring(0, task.task.name.length - packageJson.path.length - 2)
+            : task.task.name;
+        super(name, TreeItemCollapsibleState.None);
+        this.taskLocation = task.location;
+        const command: ExplorerCommands = name === `${INSTALL_SCRIPT} ` ? 'run' : workspace.getConfiguration('npm').get('scriptExplorerAction') || 'open';
+        const commandList = {
+            'open': {
+                title: 'Edit Script',
+                command: 'vscode.open',
+                arguments: [
+                    this.taskLocation?.uri,
+                    this.taskLocation ?
+                        {
+                            selection: new Range(this.taskLocation.range.start, this.taskLocation.range.start)
+                        } satisfies TextDocumentShowOptions
+                        : undefined
+                ]
+            },
+            'run': {
+                title: 'Run Script',
+                command: 'npm.runScript',
+                arguments: [this]
+            }
+        };
+        this.contextValue = 'script';
+        this.package = packageJson;
+        this.task = task.task;
+        this.command = commandList[command];
+        if (this.task.group && this.task.group === TaskGroup.Clean) {
+            this.iconPath = new ThemeIcon('wrench-subaction');
+        }
+        else {
+            this.iconPath = new ThemeIcon('wrench');
+        }
+        if (this.task.detail) {
+            this.tooltip = this.task.detail;
+            this.description = this.task.detail;
+        }
+    }
+    getFolder(): WorkspaceFolder {
+        return this.package.folder.workspaceFolder;
+    }
 }
-
 class NoScripts extends TreeItem {
-	constructor(message: string) {
-		super(message, TreeItemCollapsibleState.None);
-		this.contextValue = 'noscripts';
-	}
+    constructor(message: string) {
+        super(message, TreeItemCollapsibleState.None);
+        this.contextValue = 'noscripts';
+    }
 }
-
 type TaskTree = Folder[] | PackageJSON[] | NoScripts[];
-
 export class NpmScriptsTreeDataProvider implements TreeDataProvider {
-	private taskTree: TaskTree | null = null;
-	private extensionContext: ExtensionContext;
-	private _onDidChangeTreeData: EventEmitter = new EventEmitter();
-	readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event;
-
-	constructor(private context: ExtensionContext, public taskProvider: NpmTaskProvider) {
-		const subscriptions = context.subscriptions;
-		this.extensionContext = context;
-		subscriptions.push(commands.registerCommand('npm.runScript', this.runScript, this));
-		subscriptions.push(commands.registerCommand('npm.debugScript', this.debugScript, this));
-		subscriptions.push(commands.registerCommand('npm.openScript', this.openScript, this));
-		subscriptions.push(commands.registerCommand('npm.runInstall', this.runInstall, this));
-	}
-
-	private async runScript(script: NpmScript) {
-		// Call getPackageManager to trigger the multiple lock files warning.
-		await getPackageManager(this.context, script.getFolder().uri);
-		tasks.executeTask(script.task);
-	}
-
-	private async debugScript(script: NpmScript) {
-		startDebugging(this.extensionContext, script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder());
-	}
-
-	private findScriptPosition(document: TextDocument, script?: NpmScript) {
-		const scripts = readScripts(document);
-		if (!scripts) {
-			return undefined;
-		}
-
-		if (!script) {
-			return scripts.location.range.start;
-		}
-
-		const found = scripts.scripts.find(s => getTaskName(s.name, script.task.definition.path) === script.task.name);
-		return found?.nameRange.start;
-	}
-
-	private async runInstall(selection: PackageJSON) {
-		let uri: Uri | undefined = undefined;
-		if (selection instanceof PackageJSON) {
-			uri = selection.resourceUri;
-		}
-		if (!uri) {
-			return;
-		}
-		const task = await createTask(await getPackageManager(this.context, selection.folder.workspaceFolder.uri, true), 'install', ['install'], selection.folder.workspaceFolder, uri, undefined, []);
-		tasks.executeTask(task);
-	}
-
-	private async openScript(selection: PackageJSON | NpmScript) {
-		let uri: Uri | undefined = undefined;
-		if (selection instanceof PackageJSON) {
-			uri = selection.resourceUri!;
-		} else if (selection instanceof NpmScript) {
-			uri = selection.package.resourceUri;
-		}
-		if (!uri) {
-			return;
-		}
-		const document: TextDocument = await workspace.openTextDocument(uri);
-		const position = this.findScriptPosition(document, selection instanceof NpmScript ? selection : undefined) || new Position(0, 0);
-		await window.showTextDocument(document, { preserveFocus: true, selection: new Selection(position, position) });
-	}
-
-	public refresh() {
-		this.taskTree = null;
-		this._onDidChangeTreeData.fire(null);
-	}
-
-	getTreeItem(element: TreeItem): TreeItem {
-		return element;
-	}
-
-	getParent(element: TreeItem): TreeItem | null {
-		if (element instanceof Folder) {
-			return null;
-		}
-		if (element instanceof PackageJSON) {
-			return element.folder;
-		}
-		if (element instanceof NpmScript) {
-			return element.package;
-		}
-		if (element instanceof NoScripts) {
-			return null;
-		}
-		return null;
-	}
-
-	async getChildren(element?: TreeItem): Promise {
-		if (!this.taskTree) {
-			const taskItems = await this.taskProvider.tasksWithLocation;
-			if (taskItems) {
-				const taskTree = this.buildTaskTree(taskItems);
-				this.taskTree = this.sortTaskTree(taskTree);
-				if (this.taskTree.length === 0) {
-					let message = l10n.t("No scripts found.");
-					if (!isAutoDetectionEnabled()) {
-						message = l10n.t('The setting "npm.autoDetect" is "off".');
-					}
-					this.taskTree = [new NoScripts(message)];
-				}
-			}
-		}
-		if (element instanceof Folder) {
-			return element.packages;
-		}
-		if (element instanceof PackageJSON) {
-			return element.scripts;
-		}
-		if (element instanceof NpmScript) {
-			return [];
-		}
-		if (element instanceof NoScripts) {
-			return [];
-		}
-		if (!element) {
-			if (this.taskTree) {
-				return this.taskTree;
-			}
-		}
-		return [];
-	}
-
-	private isInstallTask(task: Task): boolean {
-		const fullName = getTaskName('install', task.definition.path);
-		return fullName === task.name;
-	}
-
-	private getTaskTreeItemLabel(taskTreeLabel: string | TreeItemLabel | undefined): string {
-		if (taskTreeLabel === undefined) {
-			return '';
-		}
-
-		if (typeof taskTreeLabel === 'string') {
-			return taskTreeLabel;
-		}
-
-		return taskTreeLabel.label;
-	}
-
-	private sortTaskTree(taskTree: TaskTree) {
-		return taskTree.sort((first: TreeItem, second: TreeItem) => {
-			const firstLabel = this.getTaskTreeItemLabel(first.label);
-			const secondLabel = this.getTaskTreeItemLabel(second.label);
-			return firstLabel.localeCompare(secondLabel);
-		});
-	}
-
-	private buildTaskTree(tasks: ITaskWithLocation[]): TaskTree {
-		const folders: Map = new Map();
-		const packages: Map = new Map();
-
-		let folder = null;
-		let packageJson = null;
-
-		const excludeConfig: Map = new Map();
-
-		tasks.forEach(each => {
-			const location = each.location;
-			if (location && !excludeConfig.has(location.uri.toString())) {
-				const regularExpressionsSetting = workspace.getConfiguration('npm', location.uri).get('scriptExplorerExclude', []);
-				excludeConfig.set(location.uri.toString(), regularExpressionsSetting?.map(value => RegExp(value)));
-			}
-			const regularExpressions = (location && excludeConfig.has(location.uri.toString())) ? excludeConfig.get(location.uri.toString()) : undefined;
-
-			if (regularExpressions && regularExpressions.some((regularExpression) => (each.task.definition).script.match(regularExpression))) {
-				return;
-			}
-
-			if (isWorkspaceFolder(each.task.scope) && !this.isInstallTask(each.task)) {
-				folder = folders.get(each.task.scope.name);
-				if (!folder) {
-					folder = new Folder(each.task.scope);
-					folders.set(each.task.scope.name, folder);
-				}
-				const definition: INpmTaskDefinition = each.task.definition;
-				const relativePath = definition.path ? definition.path : '';
-				const fullPath = path.join(each.task.scope.name, relativePath);
-				packageJson = packages.get(fullPath);
-				if (!packageJson) {
-					packageJson = new PackageJSON(folder, relativePath);
-					folder.addPackage(packageJson);
-					packages.set(fullPath, packageJson);
-				}
-				const script = new NpmScript(this.extensionContext, packageJson, each);
-				packageJson.addScript(script);
-			}
-		});
-		if (folders.size === 1) {
-			return [...packages.values()];
-		}
-		return [...folders.values()];
-	}
+    private taskTree: TaskTree | null = null;
+    private extensionContext: ExtensionContext;
+    private _onDidChangeTreeData: EventEmitter = new EventEmitter();
+    readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event;
+    constructor(private context: ExtensionContext, public taskProvider: NpmTaskProvider) {
+        const subscriptions = context.subscriptions;
+        this.extensionContext = context;
+        subscriptions.push(commands.registerCommand('npm.runScript', this.runScript, this));
+        subscriptions.push(commands.registerCommand('npm.debugScript', this.debugScript, this));
+        subscriptions.push(commands.registerCommand('npm.openScript', this.openScript, this));
+        subscriptions.push(commands.registerCommand('npm.runInstall', this.runInstall, this));
+    }
+    private async runScript(script: NpmScript) {
+        // Call getPackageManager to trigger the multiple lock files warning.
+        await getPackageManager(this.context, script.getFolder().uri);
+        tasks.executeTask(script.task);
+    }
+    private async debugScript(script: NpmScript) {
+        startDebugging(this.extensionContext, script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder());
+    }
+    private findScriptPosition(document: TextDocument, script?: NpmScript) {
+        const scripts = readScripts(document);
+        if (!scripts) {
+            return undefined;
+        }
+        if (!script) {
+            return scripts.location.range.start;
+        }
+        const found = scripts.scripts.find(s => getTaskName(s.name, script.task.definition.path) === script.task.name);
+        return found?.nameRange.start;
+    }
+    private async runInstall(selection: PackageJSON) {
+        let uri: Uri | undefined = undefined;
+        if (selection instanceof PackageJSON) {
+            uri = selection.resourceUri;
+        }
+        if (!uri) {
+            return;
+        }
+        const task = await createTask(await getPackageManager(this.context, selection.folder.workspaceFolder.uri, true), 'install', ['install'], selection.folder.workspaceFolder, uri, undefined, []);
+        tasks.executeTask(task);
+    }
+    private async openScript(selection: PackageJSON | NpmScript) {
+        let uri: Uri | undefined = undefined;
+        if (selection instanceof PackageJSON) {
+            uri = selection.resourceUri!;
+        }
+        else if (selection instanceof NpmScript) {
+            uri = selection.package.resourceUri;
+        }
+        if (!uri) {
+            return;
+        }
+        const document: TextDocument = await workspace.openTextDocument(uri);
+        const position = this.findScriptPosition(document, selection instanceof NpmScript ? selection : undefined) || new Position(0, 0);
+        await window.showTextDocument(document, { preserveFocus: true, selection: new Selection(position, position) });
+    }
+    public refresh() {
+        this.taskTree = null;
+        this._onDidChangeTreeData.fire(null);
+    }
+    getTreeItem(element: TreeItem): TreeItem {
+        return element;
+    }
+    getParent(element: TreeItem): TreeItem | null {
+        if (element instanceof Folder) {
+            return null;
+        }
+        if (element instanceof PackageJSON) {
+            return element.folder;
+        }
+        if (element instanceof NpmScript) {
+            return element.package;
+        }
+        if (element instanceof NoScripts) {
+            return null;
+        }
+        return null;
+    }
+    async getChildren(element?: TreeItem): Promise {
+        if (!this.taskTree) {
+            const taskItems = await this.taskProvider.tasksWithLocation;
+            if (taskItems) {
+                const taskTree = this.buildTaskTree(taskItems);
+                this.taskTree = this.sortTaskTree(taskTree);
+                if (this.taskTree.length === 0) {
+                    let message = l10n.t("No scripts found.");
+                    if (!isAutoDetectionEnabled()) {
+                        message = l10n.t('The setting "npm.autoDetect" is "off".');
+                    }
+                    this.taskTree = [new NoScripts(message)];
+                }
+            }
+        }
+        if (element instanceof Folder) {
+            return element.packages;
+        }
+        if (element instanceof PackageJSON) {
+            return element.scripts;
+        }
+        if (element instanceof NpmScript) {
+            return [];
+        }
+        if (element instanceof NoScripts) {
+            return [];
+        }
+        if (!element) {
+            if (this.taskTree) {
+                return this.taskTree;
+            }
+        }
+        return [];
+    }
+    private isInstallTask(task: Task): boolean {
+        const fullName = getTaskName('install', task.definition.path);
+        return fullName === task.name;
+    }
+    private getTaskTreeItemLabel(taskTreeLabel: string | TreeItemLabel | undefined): string {
+        if (taskTreeLabel === undefined) {
+            return '';
+        }
+        if (typeof taskTreeLabel === 'string') {
+            return taskTreeLabel;
+        }
+        return taskTreeLabel.label;
+    }
+    private sortTaskTree(taskTree: TaskTree) {
+        return taskTree.sort((first: TreeItem, second: TreeItem) => {
+            const firstLabel = this.getTaskTreeItemLabel(first.label);
+            const secondLabel = this.getTaskTreeItemLabel(second.label);
+            return firstLabel.localeCompare(secondLabel);
+        });
+    }
+    private buildTaskTree(tasks: ITaskWithLocation[]): TaskTree {
+        const folders: Map = new Map();
+        const packages: Map = new Map();
+        let folder = null;
+        let packageJson = null;
+        const excludeConfig: Map = new Map();
+        tasks.forEach(each => {
+            const location = each.location;
+            if (location && !excludeConfig.has(location.uri.toString())) {
+                const regularExpressionsSetting = workspace.getConfiguration('npm', location.uri).get('scriptExplorerExclude', []);
+                excludeConfig.set(location.uri.toString(), regularExpressionsSetting?.map(value => RegExp(value)));
+            }
+            const regularExpressions = (location && excludeConfig.has(location.uri.toString())) ? excludeConfig.get(location.uri.toString()) : undefined;
+            if (regularExpressions && regularExpressions.some((regularExpression) => (each.task.definition).script.match(regularExpression))) {
+                return;
+            }
+            if (isWorkspaceFolder(each.task.scope) && !this.isInstallTask(each.task)) {
+                folder = folders.get(each.task.scope.name);
+                if (!folder) {
+                    folder = new Folder(each.task.scope);
+                    folders.set(each.task.scope.name, folder);
+                }
+                const definition: INpmTaskDefinition = each.task.definition;
+                const relativePath = definition.path ? definition.path : '';
+                const fullPath = path.join(each.task.scope.name, relativePath);
+                packageJson = packages.get(fullPath);
+                if (!packageJson) {
+                    packageJson = new PackageJSON(folder, relativePath);
+                    folder.addPackage(packageJson);
+                    packages.set(fullPath, packageJson);
+                }
+                const script = new NpmScript(this.extensionContext, packageJson, each);
+                packageJson.addScript(script);
+            }
+        });
+        if (folders.size === 1) {
+            return [...packages.values()];
+        }
+        return [...folders.values()];
+    }
 }
diff --git a/extensions/npm/Source/preferred-pm.ts b/extensions/npm/Source/preferred-pm.ts
index c85b65b6ea35e..447d81a3e176a 100644
--- a/extensions/npm/Source/preferred-pm.ts
+++ b/extensions/npm/Source/preferred-pm.ts
@@ -2,108 +2,95 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import findWorkspaceRoot = require('../node_modules/find-yarn-workspace-root');
 import findUp from 'find-up';
 import * as path from 'path';
 import whichPM from 'which-pm';
 import { Uri, workspace } from 'vscode';
-
 interface PreferredProperties {
-	isPreferred: boolean;
-	hasLockfile: boolean;
+    isPreferred: boolean;
+    hasLockfile: boolean;
 }
-
 async function pathExists(filePath: string) {
-	try {
-		await workspace.fs.stat(Uri.file(filePath));
-	} catch {
-		return false;
-	}
-	return true;
+    try {
+        await workspace.fs.stat(Uri.file(filePath));
+    }
+    catch {
+        return false;
+    }
+    return true;
 }
-
 async function isBunPreferred(pkgPath: string): Promise {
-	if (await pathExists(path.join(pkgPath, 'bun.lockb'))) {
-		return { isPreferred: true, hasLockfile: true };
-	}
-
-	return { isPreferred: false, hasLockfile: false };
+    if (await pathExists(path.join(pkgPath, 'bun.lockb'))) {
+        return { isPreferred: true, hasLockfile: true };
+    }
+    return { isPreferred: false, hasLockfile: false };
 }
-
 async function isPNPMPreferred(pkgPath: string): Promise {
-	if (await pathExists(path.join(pkgPath, 'pnpm-lock.yaml'))) {
-		return { isPreferred: true, hasLockfile: true };
-	}
-	if (await pathExists(path.join(pkgPath, 'shrinkwrap.yaml'))) {
-		return { isPreferred: true, hasLockfile: true };
-	}
-	if (await findUp('pnpm-lock.yaml', { cwd: pkgPath })) {
-		return { isPreferred: true, hasLockfile: true };
-	}
-
-	return { isPreferred: false, hasLockfile: false };
+    if (await pathExists(path.join(pkgPath, 'pnpm-lock.yaml'))) {
+        return { isPreferred: true, hasLockfile: true };
+    }
+    if (await pathExists(path.join(pkgPath, 'shrinkwrap.yaml'))) {
+        return { isPreferred: true, hasLockfile: true };
+    }
+    if (await findUp('pnpm-lock.yaml', { cwd: pkgPath })) {
+        return { isPreferred: true, hasLockfile: true };
+    }
+    return { isPreferred: false, hasLockfile: false };
 }
-
 async function isYarnPreferred(pkgPath: string): Promise {
-	if (await pathExists(path.join(pkgPath, 'yarn.lock'))) {
-		return { isPreferred: true, hasLockfile: true };
-	}
-
-	try {
-		if (typeof findWorkspaceRoot(pkgPath) === 'string') {
-			return { isPreferred: true, hasLockfile: false };
-		}
-	} catch (err) { }
-
-	return { isPreferred: false, hasLockfile: false };
+    if (await pathExists(path.join(pkgPath, 'yarn.lock'))) {
+        return { isPreferred: true, hasLockfile: true };
+    }
+    try {
+        if (typeof findWorkspaceRoot(pkgPath) === 'string') {
+            return { isPreferred: true, hasLockfile: false };
+        }
+    }
+    catch (err) { }
+    return { isPreferred: false, hasLockfile: false };
 }
-
 async function isNPMPreferred(pkgPath: string): Promise {
-	const lockfileExists = await pathExists(path.join(pkgPath, 'package-lock.json'));
-	return { isPreferred: lockfileExists, hasLockfile: lockfileExists };
+    const lockfileExists = await pathExists(path.join(pkgPath, 'package-lock.json'));
+    return { isPreferred: lockfileExists, hasLockfile: lockfileExists };
 }
-
-export async function findPreferredPM(pkgPath: string): Promise<{ name: string; multipleLockFilesDetected: boolean }> {
-	const detectedPackageManagerNames: string[] = [];
-	const detectedPackageManagerProperties: PreferredProperties[] = [];
-
-	const npmPreferred = await isNPMPreferred(pkgPath);
-	if (npmPreferred.isPreferred) {
-		detectedPackageManagerNames.push('npm');
-		detectedPackageManagerProperties.push(npmPreferred);
-	}
-
-	const pnpmPreferred = await isPNPMPreferred(pkgPath);
-	if (pnpmPreferred.isPreferred) {
-		detectedPackageManagerNames.push('pnpm');
-		detectedPackageManagerProperties.push(pnpmPreferred);
-	}
-
-	const yarnPreferred = await isYarnPreferred(pkgPath);
-	if (yarnPreferred.isPreferred) {
-		detectedPackageManagerNames.push('yarn');
-		detectedPackageManagerProperties.push(yarnPreferred);
-	}
-
-	const bunPreferred = await isBunPreferred(pkgPath);
-	if (bunPreferred.isPreferred) {
-		detectedPackageManagerNames.push('bun');
-		detectedPackageManagerProperties.push(bunPreferred);
-	}
-
-	const pmUsedForInstallation: { name: string } | null = await whichPM(pkgPath);
-
-	if (pmUsedForInstallation && !detectedPackageManagerNames.includes(pmUsedForInstallation.name)) {
-		detectedPackageManagerNames.push(pmUsedForInstallation.name);
-		detectedPackageManagerProperties.push({ isPreferred: true, hasLockfile: false });
-	}
-
-	let lockfilesCount = 0;
-	detectedPackageManagerProperties.forEach(detected => lockfilesCount += detected.hasLockfile ? 1 : 0);
-
-	return {
-		name: detectedPackageManagerNames[0] || 'npm',
-		multipleLockFilesDetected: lockfilesCount > 1
-	};
+export async function findPreferredPM(pkgPath: string): Promise<{
+    name: string;
+    multipleLockFilesDetected: boolean;
+}> {
+    const detectedPackageManagerNames: string[] = [];
+    const detectedPackageManagerProperties: PreferredProperties[] = [];
+    const npmPreferred = await isNPMPreferred(pkgPath);
+    if (npmPreferred.isPreferred) {
+        detectedPackageManagerNames.push('npm');
+        detectedPackageManagerProperties.push(npmPreferred);
+    }
+    const pnpmPreferred = await isPNPMPreferred(pkgPath);
+    if (pnpmPreferred.isPreferred) {
+        detectedPackageManagerNames.push('pnpm');
+        detectedPackageManagerProperties.push(pnpmPreferred);
+    }
+    const yarnPreferred = await isYarnPreferred(pkgPath);
+    if (yarnPreferred.isPreferred) {
+        detectedPackageManagerNames.push('yarn');
+        detectedPackageManagerProperties.push(yarnPreferred);
+    }
+    const bunPreferred = await isBunPreferred(pkgPath);
+    if (bunPreferred.isPreferred) {
+        detectedPackageManagerNames.push('bun');
+        detectedPackageManagerProperties.push(bunPreferred);
+    }
+    const pmUsedForInstallation: {
+        name: string;
+    } | null = await whichPM(pkgPath);
+    if (pmUsedForInstallation && !detectedPackageManagerNames.includes(pmUsedForInstallation.name)) {
+        detectedPackageManagerNames.push(pmUsedForInstallation.name);
+        detectedPackageManagerProperties.push({ isPreferred: true, hasLockfile: false });
+    }
+    let lockfilesCount = 0;
+    detectedPackageManagerProperties.forEach(detected => lockfilesCount += detected.hasLockfile ? 1 : 0);
+    return {
+        name: detectedPackageManagerNames[0] || 'npm',
+        multipleLockFilesDetected: lockfilesCount > 1
+    };
 }
diff --git a/extensions/npm/Source/readScripts.ts b/extensions/npm/Source/readScripts.ts
index bcceabf4f0108..353aa4e9d2f58 100644
--- a/extensions/npm/Source/readScripts.ts
+++ b/extensions/npm/Source/readScripts.ts
@@ -2,72 +2,68 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { JSONVisitor, visit } from 'jsonc-parser';
 import { Location, Position, Range, TextDocument } from 'vscode';
-
 export interface INpmScriptReference {
-	name: string;
-	value: string;
-	nameRange: Range;
-	valueRange: Range;
+    name: string;
+    value: string;
+    nameRange: Range;
+    valueRange: Range;
 }
-
 export interface INpmScriptInfo {
-	location: Location;
-	scripts: INpmScriptReference[];
+    location: Location;
+    scripts: INpmScriptReference[];
 }
-
 export const readScripts = (document: TextDocument, buffer = document.getText()): INpmScriptInfo | undefined => {
-	let start: Position | undefined;
-	let end: Position | undefined;
-	let inScripts = false;
-	let buildingScript: { name: string; nameRange: Range } | void;
-	let level = 0;
-
-	const scripts: INpmScriptReference[] = [];
-	const visitor: JSONVisitor = {
-		onError() {
-			// no-op
-		},
-		onObjectBegin() {
-			level++;
-		},
-		onObjectEnd(offset) {
-			if (inScripts) {
-				end = document.positionAt(offset);
-				inScripts = false;
-			}
-			level--;
-		},
-		onLiteralValue(value: unknown, offset: number, length: number) {
-			if (buildingScript && typeof value === 'string') {
-				scripts.push({
-					...buildingScript,
-					value,
-					valueRange: new Range(document.positionAt(offset), document.positionAt(offset + length)),
-				});
-				buildingScript = undefined;
-			}
-		},
-		onObjectProperty(property: string, offset: number, length: number) {
-			if (level === 1 && property === 'scripts') {
-				inScripts = true;
-				start = document.positionAt(offset);
-			} else if (inScripts) {
-				buildingScript = {
-					name: property,
-					nameRange: new Range(document.positionAt(offset), document.positionAt(offset + length))
-				};
-			}
-		},
-	};
-
-	visit(buffer, visitor);
-
-	if (start === undefined) {
-		return undefined;
-	}
-
-	return { location: new Location(document.uri, new Range(start, end ?? start)), scripts };
+    let start: Position | undefined;
+    let end: Position | undefined;
+    let inScripts = false;
+    let buildingScript: {
+        name: string;
+        nameRange: Range;
+    } | void;
+    let level = 0;
+    const scripts: INpmScriptReference[] = [];
+    const visitor: JSONVisitor = {
+        onError() {
+            // no-op
+        },
+        onObjectBegin() {
+            level++;
+        },
+        onObjectEnd(offset) {
+            if (inScripts) {
+                end = document.positionAt(offset);
+                inScripts = false;
+            }
+            level--;
+        },
+        onLiteralValue(value: unknown, offset: number, length: number) {
+            if (buildingScript && typeof value === 'string') {
+                scripts.push({
+                    ...buildingScript,
+                    value,
+                    valueRange: new Range(document.positionAt(offset), document.positionAt(offset + length)),
+                });
+                buildingScript = undefined;
+            }
+        },
+        onObjectProperty(property: string, offset: number, length: number) {
+            if (level === 1 && property === 'scripts') {
+                inScripts = true;
+                start = document.positionAt(offset);
+            }
+            else if (inScripts) {
+                buildingScript = {
+                    name: property,
+                    nameRange: new Range(document.positionAt(offset), document.positionAt(offset + length))
+                };
+            }
+        },
+    };
+    visit(buffer, visitor);
+    if (start === undefined) {
+        return undefined;
+    }
+    return { location: new Location(document.uri, new Range(start, end ?? start)), scripts };
 };
diff --git a/extensions/npm/Source/scriptHover.ts b/extensions/npm/Source/scriptHover.ts
index b3a87e0519821..788662b954fe7 100644
--- a/extensions/npm/Source/scriptHover.ts
+++ b/extensions/npm/Source/scriptHover.ts
@@ -2,129 +2,97 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { dirname } from 'path';
-import {
-	CancellationToken, commands, ExtensionContext,
-	Hover, HoverProvider, MarkdownString, l10n, Position, ProviderResult,
-	tasks, TextDocument,
-	Uri, workspace
-} from 'vscode';
+import { CancellationToken, commands, ExtensionContext, Hover, HoverProvider, MarkdownString, l10n, Position, ProviderResult, tasks, TextDocument, Uri, workspace } from 'vscode';
 import { INpmScriptInfo, readScripts } from './readScripts';
-import {
-	createTask,
-	getPackageManager, startDebugging
-} from './tasks';
-
-
+import { createTask, getPackageManager, startDebugging } from './tasks';
 let cachedDocument: Uri | undefined = undefined;
 let cachedScripts: INpmScriptInfo | undefined = undefined;
-
 export function invalidateHoverScriptsCache(document?: TextDocument) {
-	if (!document) {
-		cachedDocument = undefined;
-		return;
-	}
-	if (document.uri === cachedDocument) {
-		cachedDocument = undefined;
-	}
+    if (!document) {
+        cachedDocument = undefined;
+        return;
+    }
+    if (document.uri === cachedDocument) {
+        cachedDocument = undefined;
+    }
 }
-
 export class NpmScriptHoverProvider implements HoverProvider {
-	private enabled: boolean;
-
-	constructor(private context: ExtensionContext) {
-		context.subscriptions.push(commands.registerCommand('npm.runScriptFromHover', this.runScriptFromHover, this));
-		context.subscriptions.push(commands.registerCommand('npm.debugScriptFromHover', this.debugScriptFromHover, this));
-		context.subscriptions.push(workspace.onDidChangeTextDocument((e) => {
-			invalidateHoverScriptsCache(e.document);
-		}));
-
-		const isEnabled = () => workspace.getConfiguration('npm').get('scriptHover', true);
-		this.enabled = isEnabled();
-		context.subscriptions.push(workspace.onDidChangeConfiguration((e) => {
-			if (e.affectsConfiguration('npm.scriptHover')) {
-				this.enabled = isEnabled();
-			}
-		}));
-	}
-
-	public provideHover(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult {
-		if (!this.enabled) {
-			return;
-		}
-
-		let hover: Hover | undefined = undefined;
-
-		if (!cachedDocument || cachedDocument.fsPath !== document.uri.fsPath) {
-			cachedScripts = readScripts(document);
-			cachedDocument = document.uri;
-		}
-
-		cachedScripts?.scripts.forEach(({ name, nameRange }) => {
-			if (nameRange.contains(position)) {
-				const contents: MarkdownString = new MarkdownString();
-				contents.isTrusted = true;
-				contents.appendMarkdown(this.createRunScriptMarkdown(name, document.uri));
-				contents.appendMarkdown(this.createDebugScriptMarkdown(name, document.uri));
-				hover = new Hover(contents);
-			}
-		});
-		return hover;
-	}
-
-	private createRunScriptMarkdown(script: string, documentUri: Uri): string {
-		const args = {
-			documentUri: documentUri,
-			script: script,
-		};
-		return this.createMarkdownLink(
-			l10n.t("Run Script"),
-			'npm.runScriptFromHover',
-			args,
-			l10n.t("Run the script as a task")
-		);
-	}
-
-	private createDebugScriptMarkdown(script: string, documentUri: Uri): string {
-		const args = {
-			documentUri: documentUri,
-			script: script,
-		};
-		return this.createMarkdownLink(
-			l10n.t("Debug Script"),
-			'npm.debugScriptFromHover',
-			args,
-			l10n.t("Runs the script under the debugger"),
-			'|'
-		);
-	}
-
-	private createMarkdownLink(label: string, cmd: string, args: any, tooltip: string, separator?: string): string {
-		const encodedArgs = encodeURIComponent(JSON.stringify(args));
-		let prefix = '';
-		if (separator) {
-			prefix = ` ${separator} `;
-		}
-		return `${prefix}[${label}](command:${cmd}?${encodedArgs} "${tooltip}")`;
-	}
-
-	public async runScriptFromHover(args: any) {
-		const script = args.script;
-		const documentUri = args.documentUri;
-		const folder = workspace.getWorkspaceFolder(documentUri);
-		if (folder) {
-			const task = await createTask(await getPackageManager(this.context, folder.uri), script, ['run', script], folder, documentUri);
-			await tasks.executeTask(task);
-		}
-	}
-
-	public debugScriptFromHover(args: { script: string; documentUri: Uri }) {
-		const script = args.script;
-		const documentUri = args.documentUri;
-		const folder = workspace.getWorkspaceFolder(documentUri);
-		if (folder) {
-			startDebugging(this.context, script, dirname(documentUri.fsPath), folder);
-		}
-	}
+    private enabled: boolean;
+    constructor(private context: ExtensionContext) {
+        context.subscriptions.push(commands.registerCommand('npm.runScriptFromHover', this.runScriptFromHover, this));
+        context.subscriptions.push(commands.registerCommand('npm.debugScriptFromHover', this.debugScriptFromHover, this));
+        context.subscriptions.push(workspace.onDidChangeTextDocument((e) => {
+            invalidateHoverScriptsCache(e.document);
+        }));
+        const isEnabled = () => workspace.getConfiguration('npm').get('scriptHover', true);
+        this.enabled = isEnabled();
+        context.subscriptions.push(workspace.onDidChangeConfiguration((e) => {
+            if (e.affectsConfiguration('npm.scriptHover')) {
+                this.enabled = isEnabled();
+            }
+        }));
+    }
+    public provideHover(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult {
+        if (!this.enabled) {
+            return;
+        }
+        let hover: Hover | undefined = undefined;
+        if (!cachedDocument || cachedDocument.fsPath !== document.uri.fsPath) {
+            cachedScripts = readScripts(document);
+            cachedDocument = document.uri;
+        }
+        cachedScripts?.scripts.forEach(({ name, nameRange }) => {
+            if (nameRange.contains(position)) {
+                const contents: MarkdownString = new MarkdownString();
+                contents.isTrusted = true;
+                contents.appendMarkdown(this.createRunScriptMarkdown(name, document.uri));
+                contents.appendMarkdown(this.createDebugScriptMarkdown(name, document.uri));
+                hover = new Hover(contents);
+            }
+        });
+        return hover;
+    }
+    private createRunScriptMarkdown(script: string, documentUri: Uri): string {
+        const args = {
+            documentUri: documentUri,
+            script: script,
+        };
+        return this.createMarkdownLink(l10n.t("Run Script"), 'npm.runScriptFromHover', args, l10n.t("Run the script as a task"));
+    }
+    private createDebugScriptMarkdown(script: string, documentUri: Uri): string {
+        const args = {
+            documentUri: documentUri,
+            script: script,
+        };
+        return this.createMarkdownLink(l10n.t("Debug Script"), 'npm.debugScriptFromHover', args, l10n.t("Runs the script under the debugger"), '|');
+    }
+    private createMarkdownLink(label: string, cmd: string, args: any, tooltip: string, separator?: string): string {
+        const encodedArgs = encodeURIComponent(JSON.stringify(args));
+        let prefix = '';
+        if (separator) {
+            prefix = ` ${separator} `;
+        }
+        return `${prefix}[${label}](command:${cmd}?${encodedArgs} "${tooltip}")`;
+    }
+    public async runScriptFromHover(args: any) {
+        const script = args.script;
+        const documentUri = args.documentUri;
+        const folder = workspace.getWorkspaceFolder(documentUri);
+        if (folder) {
+            const task = await createTask(await getPackageManager(this.context, folder.uri), script, ['run', script], folder, documentUri);
+            await tasks.executeTask(task);
+        }
+    }
+    public debugScriptFromHover(args: {
+        script: string;
+        documentUri: Uri;
+    }) {
+        const script = args.script;
+        const documentUri = args.documentUri;
+        const folder = workspace.getWorkspaceFolder(documentUri);
+        if (folder) {
+            startDebugging(this.context, script, dirname(documentUri.fsPath), folder);
+        }
+    }
 }
diff --git a/extensions/npm/Source/tasks.ts b/extensions/npm/Source/tasks.ts
index 6f1a1fbe649cf..4f210469b1a4a 100644
--- a/extensions/npm/Source/tasks.ts
+++ b/extensions/npm/Source/tasks.ts
@@ -2,454 +2,396 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
-import {
-	TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace,
-	TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window, Position, ExtensionContext, env,
-	ShellQuotedString, ShellQuoting, commands, Location, CancellationTokenSource, l10n
-} from 'vscode';
+import { TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window, Position, ExtensionContext, env, ShellQuotedString, ShellQuoting, commands, Location, CancellationTokenSource, l10n } from 'vscode';
 import * as path from 'path';
 import * as fs from 'fs';
 import minimatch from 'minimatch';
 import { Utils } from 'vscode-uri';
 import { findPreferredPM } from './preferred-pm';
 import { readScripts } from './readScripts';
-
 const excludeRegex = new RegExp('^(node_modules|.vscode-test)$', 'i');
-
 export interface INpmTaskDefinition extends TaskDefinition {
-	script: string;
-	path?: string;
+    script: string;
+    path?: string;
 }
-
 export interface IFolderTaskItem extends QuickPickItem {
-	label: string;
-	task: Task;
+    label: string;
+    task: Task;
 }
-
 type AutoDetect = 'on' | 'off';
-
 let cachedTasks: ITaskWithLocation[] | undefined = undefined;
-
 export const INSTALL_SCRIPT = 'install';
-
 export interface ITaskLocation {
-	document: Uri;
-	line: Position;
+    document: Uri;
+    line: Position;
 }
-
 export interface ITaskWithLocation {
-	task: Task;
-	location?: Location;
+    task: Task;
+    location?: Location;
 }
-
 export class NpmTaskProvider implements TaskProvider {
-
-	constructor(private context: ExtensionContext) {
-	}
-
-	get tasksWithLocation(): Promise {
-		return provideNpmScripts(this.context, false);
-	}
-
-	public async provideTasks() {
-		const tasks = await provideNpmScripts(this.context, true);
-		return tasks.map(task => task.task);
-	}
-
-	public async resolveTask(_task: Task): Promise {
-		const npmTask = (_task.definition).script;
-		if (npmTask) {
-			const kind: INpmTaskDefinition = (_task.definition);
-			let packageJsonUri: Uri;
-			if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) {
-				// scope is required to be a WorkspaceFolder for resolveTask
-				return undefined;
-			}
-			if (kind.path) {
-				packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.path + `${kind.path.endsWith('/') ? '' : '/'}` + 'package.json' });
-			} else {
-				packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' });
-			}
-			const cmd = [kind.script];
-			if (kind.script !== INSTALL_SCRIPT) {
-				cmd.unshift('run');
-			}
-			return createTask(await getPackageManager(this.context, _task.scope.uri), kind, cmd, _task.scope, packageJsonUri);
-		}
-		return undefined;
-	}
+    constructor(private context: ExtensionContext) {
+    }
+    get tasksWithLocation(): Promise {
+        return provideNpmScripts(this.context, false);
+    }
+    public async provideTasks() {
+        const tasks = await provideNpmScripts(this.context, true);
+        return tasks.map(task => task.task);
+    }
+    public async resolveTask(_task: Task): Promise {
+        const npmTask = (_task.definition).script;
+        if (npmTask) {
+            const kind: INpmTaskDefinition = (_task.definition);
+            let packageJsonUri: Uri;
+            if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) {
+                // scope is required to be a WorkspaceFolder for resolveTask
+                return undefined;
+            }
+            if (kind.path) {
+                packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.path + `${kind.path.endsWith('/') ? '' : '/'}` + 'package.json' });
+            }
+            else {
+                packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' });
+            }
+            const cmd = [kind.script];
+            if (kind.script !== INSTALL_SCRIPT) {
+                cmd.unshift('run');
+            }
+            return createTask(await getPackageManager(this.context, _task.scope.uri), kind, cmd, _task.scope, packageJsonUri);
+        }
+        return undefined;
+    }
 }
-
 export function invalidateTasksCache() {
-	cachedTasks = undefined;
+    cachedTasks = undefined;
 }
-
 const buildNames: string[] = ['build', 'compile', 'watch'];
 function isBuildTask(name: string): boolean {
-	for (const buildName of buildNames) {
-		if (name.indexOf(buildName) !== -1) {
-			return true;
-		}
-	}
-	return false;
+    for (const buildName of buildNames) {
+        if (name.indexOf(buildName) !== -1) {
+            return true;
+        }
+    }
+    return false;
 }
-
 const testNames: string[] = ['test'];
 function isTestTask(name: string): boolean {
-	for (const testName of testNames) {
-		if (name === testName) {
-			return true;
-		}
-	}
-	return false;
+    for (const testName of testNames) {
+        if (name === testName) {
+            return true;
+        }
+    }
+    return false;
 }
-
 function isPrePostScript(name: string): boolean {
-	const prePostScripts: Set = new Set([
-		'preuninstall', 'postuninstall', 'prepack', 'postpack', 'preinstall', 'postinstall',
-		'prepack', 'postpack', 'prepublish', 'postpublish', 'preversion', 'postversion',
-		'prestop', 'poststop', 'prerestart', 'postrestart', 'preshrinkwrap', 'postshrinkwrap',
-		'pretest', 'postest', 'prepublishOnly'
-	]);
-
-	const prepost = ['pre' + name, 'post' + name];
-	for (const knownScript of prePostScripts) {
-		if (knownScript === prepost[0] || knownScript === prepost[1]) {
-			return true;
-		}
-	}
-	return false;
+    const prePostScripts: Set = new Set([
+        'preuninstall', 'postuninstall', 'prepack', 'postpack', 'preinstall', 'postinstall',
+        'prepack', 'postpack', 'prepublish', 'postpublish', 'preversion', 'postversion',
+        'prestop', 'poststop', 'prerestart', 'postrestart', 'preshrinkwrap', 'postshrinkwrap',
+        'pretest', 'postest', 'prepublishOnly'
+    ]);
+    const prepost = ['pre' + name, 'post' + name];
+    for (const knownScript of prePostScripts) {
+        if (knownScript === prepost[0] || knownScript === prepost[1]) {
+            return true;
+        }
+    }
+    return false;
 }
-
 export function isWorkspaceFolder(value: any): value is WorkspaceFolder {
-	return value && typeof value !== 'number';
+    return value && typeof value !== 'number';
 }
-
 export async function getPackageManager(extensionContext: ExtensionContext, folder: Uri, showWarning: boolean = true): Promise {
-	let packageManagerName = workspace.getConfiguration('npm', folder).get('packageManager', 'npm');
-
-	if (packageManagerName === 'auto') {
-		const { name, multipleLockFilesDetected: multiplePMDetected } = await findPreferredPM(folder.fsPath);
-		packageManagerName = name;
-		const neverShowWarning = 'npm.multiplePMWarning.neverShow';
-		if (showWarning && multiplePMDetected && !extensionContext.globalState.get(neverShowWarning)) {
-			const multiplePMWarning = l10n.t('Using {0} as the preferred package manager. Found multiple lockfiles for {1}.  To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', packageManagerName, folder.fsPath);
-			const neverShowAgain = l10n.t("Do not show again");
-			const learnMore = l10n.t("Learn more");
-			window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => {
-				switch (result) {
-					case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break;
-					case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json'));
-				}
-			});
-		}
-	}
-
-	return packageManagerName;
+    let packageManagerName = workspace.getConfiguration('npm', folder).get('packageManager', 'npm');
+    if (packageManagerName === 'auto') {
+        const { name, multipleLockFilesDetected: multiplePMDetected } = await findPreferredPM(folder.fsPath);
+        packageManagerName = name;
+        const neverShowWarning = 'npm.multiplePMWarning.neverShow';
+        if (showWarning && multiplePMDetected && !extensionContext.globalState.get(neverShowWarning)) {
+            const multiplePMWarning = l10n.t('Using {0} as the preferred package manager. Found multiple lockfiles for {1}.  To resolve this issue, delete the lockfiles that don\'t match your preferred package manager or change the setting "npm.packageManager" to a value other than "auto".', packageManagerName, folder.fsPath);
+            const neverShowAgain = l10n.t("Do not show again");
+            const learnMore = l10n.t("Learn more");
+            window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => {
+                switch (result) {
+                    case neverShowAgain:
+                        extensionContext.globalState.update(neverShowWarning, true);
+                        break;
+                    case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json'));
+                }
+            });
+        }
+    }
+    return packageManagerName;
 }
-
 export async function hasNpmScripts(): Promise {
-	const folders = workspace.workspaceFolders;
-	if (!folders) {
-		return false;
-	}
-	try {
-		for (const folder of folders) {
-			if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
-				const relativePattern = new RelativePattern(folder, '**/package.json');
-				const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
-				if (paths.length > 0) {
-					return true;
-				}
-			}
-		}
-		return false;
-	} catch (error) {
-		return Promise.reject(error);
-	}
+    const folders = workspace.workspaceFolders;
+    if (!folders) {
+        return false;
+    }
+    try {
+        for (const folder of folders) {
+            if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
+                const relativePattern = new RelativePattern(folder, '**/package.json');
+                const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
+                if (paths.length > 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    catch (error) {
+        return Promise.reject(error);
+    }
 }
-
 async function detectNpmScripts(context: ExtensionContext, showWarning: boolean): Promise {
-
-	const emptyTasks: ITaskWithLocation[] = [];
-	const allTasks: ITaskWithLocation[] = [];
-	const visitedPackageJsonFiles: Set = new Set();
-
-	const folders = workspace.workspaceFolders;
-	if (!folders) {
-		return emptyTasks;
-	}
-	try {
-		for (const folder of folders) {
-			if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
-				const relativePattern = new RelativePattern(folder, '**/package.json');
-				const paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**');
-				for (const path of paths) {
-					if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) {
-						const tasks = await provideNpmScriptsForFolder(context, path, showWarning);
-						visitedPackageJsonFiles.add(path.fsPath);
-						allTasks.push(...tasks);
-					}
-				}
-			}
-		}
-		return allTasks;
-	} catch (error) {
-		return Promise.reject(error);
-	}
+    const emptyTasks: ITaskWithLocation[] = [];
+    const allTasks: ITaskWithLocation[] = [];
+    const visitedPackageJsonFiles: Set = new Set();
+    const folders = workspace.workspaceFolders;
+    if (!folders) {
+        return emptyTasks;
+    }
+    try {
+        for (const folder of folders) {
+            if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) {
+                const relativePattern = new RelativePattern(folder, '**/package.json');
+                const paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**');
+                for (const path of paths) {
+                    if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) {
+                        const tasks = await provideNpmScriptsForFolder(context, path, showWarning);
+                        visitedPackageJsonFiles.add(path.fsPath);
+                        allTasks.push(...tasks);
+                    }
+                }
+            }
+        }
+        return allTasks;
+    }
+    catch (error) {
+        return Promise.reject(error);
+    }
 }
-
-
 export async function detectNpmScriptsForFolder(context: ExtensionContext, folder: Uri): Promise {
-
-	const folderTasks: IFolderTaskItem[] = [];
-
-	try {
-		if (excludeRegex.test(Utils.basename(folder))) {
-			return folderTasks;
-		}
-		const relativePattern = new RelativePattern(folder.fsPath, '**/package.json');
-		const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
-
-		const visitedPackageJsonFiles: Set = new Set();
-		for (const path of paths) {
-			if (!visitedPackageJsonFiles.has(path.fsPath)) {
-				const tasks = await provideNpmScriptsForFolder(context, path, true);
-				visitedPackageJsonFiles.add(path.fsPath);
-				folderTasks.push(...tasks.map(t => ({ label: t.task.name, task: t.task })));
-			}
-		}
-		return folderTasks;
-	} catch (error) {
-		return Promise.reject(error);
-	}
+    const folderTasks: IFolderTaskItem[] = [];
+    try {
+        if (excludeRegex.test(Utils.basename(folder))) {
+            return folderTasks;
+        }
+        const relativePattern = new RelativePattern(folder.fsPath, '**/package.json');
+        const paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
+        const visitedPackageJsonFiles: Set = new Set();
+        for (const path of paths) {
+            if (!visitedPackageJsonFiles.has(path.fsPath)) {
+                const tasks = await provideNpmScriptsForFolder(context, path, true);
+                visitedPackageJsonFiles.add(path.fsPath);
+                folderTasks.push(...tasks.map(t => ({ label: t.task.name, task: t.task })));
+            }
+        }
+        return folderTasks;
+    }
+    catch (error) {
+        return Promise.reject(error);
+    }
 }
-
 export async function provideNpmScripts(context: ExtensionContext, showWarning: boolean): Promise {
-	if (!cachedTasks) {
-		cachedTasks = await detectNpmScripts(context, showWarning);
-	}
-	return cachedTasks;
+    if (!cachedTasks) {
+        cachedTasks = await detectNpmScripts(context, showWarning);
+    }
+    return cachedTasks;
 }
-
 export function isAutoDetectionEnabled(folder?: WorkspaceFolder): boolean {
-	return workspace.getConfiguration('npm', folder?.uri).get('autoDetect') === 'on';
+    return workspace.getConfiguration('npm', folder?.uri).get('autoDetect') === 'on';
 }
-
 function isExcluded(folder: WorkspaceFolder, packageJsonUri: Uri) {
-	function testForExclusionPattern(path: string, pattern: string): boolean {
-		return minimatch(path, pattern, { dot: true });
-	}
-
-	const exclude = workspace.getConfiguration('npm', folder.uri).get('exclude');
-	const packageJsonFolder = path.dirname(packageJsonUri.fsPath);
-
-	if (exclude) {
-		if (Array.isArray(exclude)) {
-			for (const pattern of exclude) {
-				if (testForExclusionPattern(packageJsonFolder, pattern)) {
-					return true;
-				}
-			}
-		} else if (testForExclusionPattern(packageJsonFolder, exclude)) {
-			return true;
-		}
-	}
-	return false;
+    function testForExclusionPattern(path: string, pattern: string): boolean {
+        return minimatch(path, pattern, { dot: true });
+    }
+    const exclude = workspace.getConfiguration('npm', folder.uri).get('exclude');
+    const packageJsonFolder = path.dirname(packageJsonUri.fsPath);
+    if (exclude) {
+        if (Array.isArray(exclude)) {
+            for (const pattern of exclude) {
+                if (testForExclusionPattern(packageJsonFolder, pattern)) {
+                    return true;
+                }
+            }
+        }
+        else if (testForExclusionPattern(packageJsonFolder, exclude)) {
+            return true;
+        }
+    }
+    return false;
 }
-
 function isDebugScript(script: string): boolean {
-	const match = script.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/);
-	return match !== null;
+    const match = script.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/);
+    return match !== null;
 }
-
 async function provideNpmScriptsForFolder(context: ExtensionContext, packageJsonUri: Uri, showWarning: boolean): Promise {
-	const emptyTasks: ITaskWithLocation[] = [];
-
-	const folder = workspace.getWorkspaceFolder(packageJsonUri);
-	if (!folder) {
-		return emptyTasks;
-	}
-	const scripts = await getScripts(packageJsonUri);
-	if (!scripts) {
-		return emptyTasks;
-	}
-
-	const result: ITaskWithLocation[] = [];
-
-	const packageManager = await getPackageManager(context, folder.uri, showWarning);
-
-	for (const { name, value, nameRange } of scripts.scripts) {
-		const task = await createTask(packageManager, name, ['run', name], folder!, packageJsonUri, value, undefined);
-		result.push({ task, location: new Location(packageJsonUri, nameRange) });
-	}
-
-	if (!workspace.getConfiguration('npm', folder).get('scriptExplorerExclude', []).find(e => e.includes(INSTALL_SCRIPT))) {
-		result.push({ task: await createTask(packageManager, INSTALL_SCRIPT, [INSTALL_SCRIPT], folder, packageJsonUri, 'install dependencies from package', []) });
-	}
-	return result;
+    const emptyTasks: ITaskWithLocation[] = [];
+    const folder = workspace.getWorkspaceFolder(packageJsonUri);
+    if (!folder) {
+        return emptyTasks;
+    }
+    const scripts = await getScripts(packageJsonUri);
+    if (!scripts) {
+        return emptyTasks;
+    }
+    const result: ITaskWithLocation[] = [];
+    const packageManager = await getPackageManager(context, folder.uri, showWarning);
+    for (const { name, value, nameRange } of scripts.scripts) {
+        const task = await createTask(packageManager, name, ['run', name], folder!, packageJsonUri, value, undefined);
+        result.push({ task, location: new Location(packageJsonUri, nameRange) });
+    }
+    if (!workspace.getConfiguration('npm', folder).get('scriptExplorerExclude', []).find(e => e.includes(INSTALL_SCRIPT))) {
+        result.push({ task: await createTask(packageManager, INSTALL_SCRIPT, [INSTALL_SCRIPT], folder, packageJsonUri, 'install dependencies from package', []) });
+    }
+    return result;
 }
-
 export function getTaskName(script: string, relativePath: string | undefined) {
-	if (relativePath && relativePath.length) {
-		return `${script} - ${relativePath.substring(0, relativePath.length - 1)}`;
-	}
-	return script;
+    if (relativePath && relativePath.length) {
+        return `${script} - ${relativePath.substring(0, relativePath.length - 1)}`;
+    }
+    return script;
 }
-
 export async function createTask(packageManager: string, script: INpmTaskDefinition | string, cmd: string[], folder: WorkspaceFolder, packageJsonUri: Uri, scriptValue?: string, matcher?: any): Promise {
-	let kind: INpmTaskDefinition;
-	if (typeof script === 'string') {
-		kind = { type: 'npm', script: script };
-	} else {
-		kind = script;
-	}
-
-	function getCommandLine(cmd: string[]): (string | ShellQuotedString)[] {
-		const result: (string | ShellQuotedString)[] = new Array(cmd.length);
-		for (let i = 0; i < cmd.length; i++) {
-			if (/\s/.test(cmd[i])) {
-				result[i] = { value: cmd[i], quoting: cmd[i].includes('--') ? ShellQuoting.Weak : ShellQuoting.Strong };
-			} else {
-				result[i] = cmd[i];
-			}
-		}
-		if (workspace.getConfiguration('npm', folder.uri).get('runSilent')) {
-			result.unshift('--silent');
-		}
-		return result;
-	}
-
-	function getRelativePath(packageJsonUri: Uri): string {
-		const rootUri = folder.uri;
-		const absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
-		return absolutePath.substring(rootUri.path.length + 1);
-	}
-
-	const relativePackageJson = getRelativePath(packageJsonUri);
-	if (relativePackageJson.length && !kind.path) {
-		kind.path = relativePackageJson.substring(0, relativePackageJson.length - 1);
-	}
-	const taskName = getTaskName(kind.script, relativePackageJson);
-	const cwd = path.dirname(packageJsonUri.fsPath);
-	const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(packageManager, getCommandLine(cmd), { cwd: cwd }), matcher);
-	task.detail = scriptValue;
-
-	const lowerCaseTaskName = kind.script.toLowerCase();
-	if (isBuildTask(lowerCaseTaskName)) {
-		task.group = TaskGroup.Build;
-	} else if (isTestTask(lowerCaseTaskName)) {
-		task.group = TaskGroup.Test;
-	} else if (isPrePostScript(lowerCaseTaskName)) {
-		task.group = TaskGroup.Clean; // hack: use Clean group to tag pre/post scripts
-	} else if (scriptValue && isDebugScript(scriptValue)) {
-		// todo@connor4312: all scripts are now debuggable, what is a 'debug script'?
-		task.group = TaskGroup.Rebuild; // hack: use Rebuild group to tag debug scripts
-	}
-	return task;
+    let kind: INpmTaskDefinition;
+    if (typeof script === 'string') {
+        kind = { type: 'npm', script: script };
+    }
+    else {
+        kind = script;
+    }
+    function getCommandLine(cmd: string[]): (string | ShellQuotedString)[] {
+        const result: (string | ShellQuotedString)[] = new Array(cmd.length);
+        for (let i = 0; i < cmd.length; i++) {
+            if (/\s/.test(cmd[i])) {
+                result[i] = { value: cmd[i], quoting: cmd[i].includes('--') ? ShellQuoting.Weak : ShellQuoting.Strong };
+            }
+            else {
+                result[i] = cmd[i];
+            }
+        }
+        if (workspace.getConfiguration('npm', folder.uri).get('runSilent')) {
+            result.unshift('--silent');
+        }
+        return result;
+    }
+    function getRelativePath(packageJsonUri: Uri): string {
+        const rootUri = folder.uri;
+        const absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
+        return absolutePath.substring(rootUri.path.length + 1);
+    }
+    const relativePackageJson = getRelativePath(packageJsonUri);
+    if (relativePackageJson.length && !kind.path) {
+        kind.path = relativePackageJson.substring(0, relativePackageJson.length - 1);
+    }
+    const taskName = getTaskName(kind.script, relativePackageJson);
+    const cwd = path.dirname(packageJsonUri.fsPath);
+    const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(packageManager, getCommandLine(cmd), { cwd: cwd }), matcher);
+    task.detail = scriptValue;
+    const lowerCaseTaskName = kind.script.toLowerCase();
+    if (isBuildTask(lowerCaseTaskName)) {
+        task.group = TaskGroup.Build;
+    }
+    else if (isTestTask(lowerCaseTaskName)) {
+        task.group = TaskGroup.Test;
+    }
+    else if (isPrePostScript(lowerCaseTaskName)) {
+        task.group = TaskGroup.Clean; // hack: use Clean group to tag pre/post scripts
+    }
+    else if (scriptValue && isDebugScript(scriptValue)) {
+        // todo@connor4312: all scripts are now debuggable, what is a 'debug script'?
+        task.group = TaskGroup.Rebuild; // hack: use Rebuild group to tag debug scripts
+    }
+    return task;
 }
-
-
 export function getPackageJsonUriFromTask(task: Task): Uri | null {
-	if (isWorkspaceFolder(task.scope)) {
-		if (task.definition.path) {
-			return Uri.file(path.join(task.scope.uri.fsPath, task.definition.path, 'package.json'));
-		} else {
-			return Uri.file(path.join(task.scope.uri.fsPath, 'package.json'));
-		}
-	}
-	return null;
+    if (isWorkspaceFolder(task.scope)) {
+        if (task.definition.path) {
+            return Uri.file(path.join(task.scope.uri.fsPath, task.definition.path, 'package.json'));
+        }
+        else {
+            return Uri.file(path.join(task.scope.uri.fsPath, 'package.json'));
+        }
+    }
+    return null;
 }
-
 export async function hasPackageJson(): Promise {
-	// Faster than `findFiles` for workspaces with a root package.json.
-	if (await hasRootPackageJson()) {
-		return true;
-	}
-	const token = new CancellationTokenSource();
-	// Search for files for max 1 second.
-	const timeout = setTimeout(() => token.cancel(), 1000);
-	const files = await workspace.findFiles('**/package.json', undefined, 1, token.token);
-	clearTimeout(timeout);
-	return files.length > 0;
+    // Faster than `findFiles` for workspaces with a root package.json.
+    if (await hasRootPackageJson()) {
+        return true;
+    }
+    const token = new CancellationTokenSource();
+    // Search for files for max 1 second.
+    const timeout = setTimeout(() => token.cancel(), 1000);
+    const files = await workspace.findFiles('**/package.json', undefined, 1, token.token);
+    clearTimeout(timeout);
+    return files.length > 0;
 }
-
 async function hasRootPackageJson(): Promise {
-	const folders = workspace.workspaceFolders;
-	if (!folders) {
-		return false;
-	}
-	for (const folder of folders) {
-		if (folder.uri.scheme === 'file') {
-			const packageJson = path.join(folder.uri.fsPath, 'package.json');
-			if (await exists(packageJson)) {
-				return true;
-			}
-		}
-	}
-	return false;
+    const folders = workspace.workspaceFolders;
+    if (!folders) {
+        return false;
+    }
+    for (const folder of folders) {
+        if (folder.uri.scheme === 'file') {
+            const packageJson = path.join(folder.uri.fsPath, 'package.json');
+            if (await exists(packageJson)) {
+                return true;
+            }
+        }
+    }
+    return false;
 }
-
 async function exists(file: string): Promise {
-	return new Promise((resolve, _reject) => {
-		fs.exists(file, (value) => {
-			resolve(value);
-		});
-	});
+    return new Promise((resolve, _reject) => {
+        fs.exists(file, (value) => {
+            resolve(value);
+        });
+    });
 }
-
 export async function runScript(context: ExtensionContext, script: string, document: TextDocument) {
-	const uri = document.uri;
-	const folder = workspace.getWorkspaceFolder(uri);
-	if (folder) {
-		const task = await createTask(await getPackageManager(context, folder.uri), script, ['run', script], folder, uri);
-		tasks.executeTask(task);
-	}
+    const uri = document.uri;
+    const folder = workspace.getWorkspaceFolder(uri);
+    if (folder) {
+        const task = await createTask(await getPackageManager(context, folder.uri), script, ['run', script], folder, uri);
+        tasks.executeTask(task);
+    }
 }
-
 export async function startDebugging(context: ExtensionContext, scriptName: string, cwd: string, folder: WorkspaceFolder) {
-	commands.executeCommand(
-		'extension.js-debug.createDebuggerTerminal',
-		`${await getPackageManager(context, folder.uri)} run ${scriptName}`,
-		folder,
-		{ cwd },
-	);
+    commands.executeCommand('extension.js-debug.createDebuggerTerminal', `${await getPackageManager(context, folder.uri)} run ${scriptName}`, folder, { cwd });
 }
-
-
-export type StringMap = { [s: string]: string };
-
+export type StringMap = {
+    [s: string]: string;
+};
 export function findScriptAtPosition(document: TextDocument, buffer: string, position: Position): string | undefined {
-	const read = readScripts(document, buffer);
-	if (!read) {
-		return undefined;
-	}
-
-	for (const script of read.scripts) {
-		if (script.nameRange.start.isBeforeOrEqual(position) && script.valueRange.end.isAfterOrEqual(position)) {
-			return script.name;
-		}
-	}
-
-	return undefined;
+    const read = readScripts(document, buffer);
+    if (!read) {
+        return undefined;
+    }
+    for (const script of read.scripts) {
+        if (script.nameRange.start.isBeforeOrEqual(position) && script.valueRange.end.isAfterOrEqual(position)) {
+            return script.name;
+        }
+    }
+    return undefined;
 }
-
 export async function getScripts(packageJsonUri: Uri) {
-	if (packageJsonUri.scheme !== 'file') {
-		return undefined;
-	}
-
-	const packageJson = packageJsonUri.fsPath;
-	if (!await exists(packageJson)) {
-		return undefined;
-	}
-
-	try {
-		const document: TextDocument = await workspace.openTextDocument(packageJsonUri);
-		return readScripts(document);
-	} catch (e) {
-		const localizedParseError = l10n.t("Npm task detection: failed to parse the file {0}", packageJsonUri.fsPath);
-		throw new Error(localizedParseError);
-	}
+    if (packageJsonUri.scheme !== 'file') {
+        return undefined;
+    }
+    const packageJson = packageJsonUri.fsPath;
+    if (!await exists(packageJson)) {
+        return undefined;
+    }
+    try {
+        const document: TextDocument = await workspace.openTextDocument(packageJsonUri);
+        return readScripts(document);
+    }
+    catch (e) {
+        const localizedParseError = l10n.t("Npm task detection: failed to parse the file {0}", packageJsonUri.fsPath);
+        throw new Error(localizedParseError);
+    }
 }
diff --git a/extensions/php-language-features/Source/features/completionItemProvider.ts b/extensions/php-language-features/Source/features/completionItemProvider.ts
index ba82b71f60663..12a3587ef5aaa 100644
--- a/extensions/php-language-features/Source/features/completionItemProvider.ts
+++ b/extensions/php-language-features/Source/features/completionItemProvider.ts
@@ -2,112 +2,100 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { CompletionItemProvider, CompletionItem, CompletionItemKind, CancellationToken, TextDocument, Position, Range, TextEdit, workspace, CompletionContext } from 'vscode';
 import * as phpGlobals from './phpGlobals';
 import * as phpGlobalFunctions from './phpGlobalFunctions';
-
 export default class PHPCompletionItemProvider implements CompletionItemProvider {
-
-	public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken, context: CompletionContext): Promise {
-		const result: CompletionItem[] = [];
-
-		const shouldProvideCompletionItems = workspace.getConfiguration('php').get('suggest.basic', true);
-		if (!shouldProvideCompletionItems) {
-			return Promise.resolve(result);
-		}
-
-		let range = document.getWordRangeAtPosition(position);
-		const prefix = range ? document.getText(range) : '';
-		if (!range) {
-			range = new Range(position, position);
-		}
-
-		if (context.triggerCharacter === '>') {
-			const twoBeforeCursor = new Position(position.line, Math.max(0, position.character - 2));
-			const previousTwoChars = document.getText(new Range(twoBeforeCursor, position));
-			if (previousTwoChars !== '->') {
-				return Promise.resolve(result);
-			}
-		}
-
-		const added: any = {};
-		const createNewProposal = function (kind: CompletionItemKind, name: string, entry: phpGlobals.IEntry | null): CompletionItem {
-			const proposal: CompletionItem = new CompletionItem(name);
-			proposal.kind = kind;
-			if (entry) {
-				if (entry.description) {
-					proposal.documentation = entry.description;
-				}
-				if (entry.signature) {
-					proposal.detail = entry.signature;
-				}
-			}
-			return proposal;
-		};
-
-		const matches = (name: string) => {
-			return prefix.length === 0 || name.length >= prefix.length && name.substr(0, prefix.length) === prefix;
-		};
-
-		if (matches('php') && range.start.character >= 2) {
-			const twoBeforePosition = new Position(range.start.line, range.start.character - 2);
-			const beforeWord = document.getText(new Range(twoBeforePosition, range.start));
-
-			if (beforeWord === ' {
+        const result: CompletionItem[] = [];
+        const shouldProvideCompletionItems = workspace.getConfiguration('php').get('suggest.basic', true);
+        if (!shouldProvideCompletionItems) {
+            return Promise.resolve(result);
+        }
+        let range = document.getWordRangeAtPosition(position);
+        const prefix = range ? document.getText(range) : '';
+        if (!range) {
+            range = new Range(position, position);
+        }
+        if (context.triggerCharacter === '>') {
+            const twoBeforeCursor = new Position(position.line, Math.max(0, position.character - 2));
+            const previousTwoChars = document.getText(new Range(twoBeforeCursor, position));
+            if (previousTwoChars !== '->') {
+                return Promise.resolve(result);
+            }
+        }
+        const added: any = {};
+        const createNewProposal = function (kind: CompletionItemKind, name: string, entry: phpGlobals.IEntry | null): CompletionItem {
+            const proposal: CompletionItem = new CompletionItem(name);
+            proposal.kind = kind;
+            if (entry) {
+                if (entry.description) {
+                    proposal.documentation = entry.description;
+                }
+                if (entry.signature) {
+                    proposal.detail = entry.signature;
+                }
+            }
+            return proposal;
+        };
+        const matches = (name: string) => {
+            return prefix.length === 0 || name.length >= prefix.length && name.substr(0, prefix.length) === prefix;
+        };
+        if (matches('php') && range.start.character >= 2) {
+            const twoBeforePosition = new Position(range.start.line, range.start.character - 2);
+            const beforeWord = document.getText(new Range(twoBeforePosition, range.start));
+            if (beforeWord === '('suggest.basic', true);
-		if (!enable) {
-			return undefined;
-		}
-
-		const wordRange = document.getWordRangeAtPosition(position);
-		if (!wordRange) {
-			return undefined;
-		}
-
-		const name = document.getText(wordRange);
-
-		const entry = phpGlobalFunctions.globalfunctions[name] || phpGlobals.compiletimeconstants[name] || phpGlobals.globalvariables[name] || phpGlobals.keywords[name];
-		if (entry && entry.description) {
-			const signature = name + (entry.signature || '');
-			const contents: MarkedString[] = [textToMarkedString(entry.description), { language: 'php', value: signature }];
-			return new Hover(contents, wordRange);
-		}
-
-		return undefined;
-	}
+    public provideHover(document: TextDocument, position: Position, _token: CancellationToken): Hover | undefined {
+        const enable = workspace.getConfiguration('php').get('suggest.basic', true);
+        if (!enable) {
+            return undefined;
+        }
+        const wordRange = document.getWordRangeAtPosition(position);
+        if (!wordRange) {
+            return undefined;
+        }
+        const name = document.getText(wordRange);
+        const entry = phpGlobalFunctions.globalfunctions[name] || phpGlobals.compiletimeconstants[name] || phpGlobals.globalvariables[name] || phpGlobals.keywords[name];
+        if (entry && entry.description) {
+            const signature = name + (entry.signature || '');
+            const contents: MarkedString[] = [textToMarkedString(entry.description), { language: 'php', value: signature }];
+            return new Hover(contents, wordRange);
+        }
+        return undefined;
+    }
 }
diff --git a/extensions/php-language-features/Source/features/phpGlobalFunctions.ts b/extensions/php-language-features/Source/features/phpGlobalFunctions.ts
index caf9eb11b7112..3d4f974682444 100644
--- a/extensions/php-language-features/Source/features/phpGlobalFunctions.ts
+++ b/extensions/php-language-features/Source/features/phpGlobalFunctions.ts
@@ -2,6030 +2,6027 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 // THIS IS GENERATED FILE. DO NOT MODIFY.
-
 import { IEntries } from './phpGlobals';
-
 export const globalfunctions: IEntries = {
-	debug_backtrace: {
-		description: 'Generates a backtrace',
-		signature: '([ int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT [, int $limit = 0 ]]): array'
-	},
-	debug_print_backtrace: {
-		description: 'Prints a backtrace',
-		signature: '([ int $options = 0 [, int $limit = 0 ]]): void'
-	},
-	error_clear_last: {
-		description: 'Clear the most recent error',
-		signature: '(void): void'
-	},
-	error_get_last: {
-		description: 'Get the last occurred error',
-		signature: '(void): array'
-	},
-	error_log: {
-		description: 'Send an error message to the defined error handling routines',
-		signature: '( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]]): bool'
-	},
-	error_reporting: {
-		description: 'Sets which PHP errors are reported',
-		signature: '([ int $level ]): int'
-	},
-	restore_error_handler: {
-		description: 'Restores the previous error handler function',
-		signature: '(void): bool'
-	},
-	restore_exception_handler: {
-		description: 'Restores the previously defined exception handler function',
-		signature: '(void): bool'
-	},
-	set_error_handler: {
-		description: 'Sets a user-defined error handler function',
-		signature: '( callable $error_handler [, int $error_types = E_ALL | E_STRICT ]): mixed'
-	},
-	set_exception_handler: {
-		description: 'Sets a user-defined exception handler function',
-		signature: '( callable $exception_handler ): callable'
-	},
-	trigger_error: {
-		description: 'Generates a user-level error/warning/notice message',
-		signature: '( string $error_msg [, int $error_type = E_USER_NOTICE ]): bool'
-	},
-	user_error: {
-		description: 'Alias of trigger_error',
-	},
-	opcache_compile_file: {
-		description: 'Compiles and caches a PHP script without executing it',
-		signature: '( string $file ): bool'
-	},
-	opcache_get_configuration: {
-		description: 'Get configuration information about the cache',
-		signature: '(void): array'
-	},
-	opcache_get_status: {
-		description: 'Get status information about the cache',
-		signature: '([ bool $get_scripts ]): array'
-	},
-	opcache_invalidate: {
-		description: 'Invalidates a cached script',
-		signature: '( string $script [, bool $force ]): bool'
-	},
-	opcache_is_script_cached: {
-		description: 'Tells whether a script is cached in OPCache',
-		signature: '( string $file ): bool'
-	},
-	opcache_reset: {
-		description: 'Resets the contents of the opcode cache',
-		signature: '(void): bool'
-	},
-	flush: {
-		description: 'Flush system output buffer',
-		signature: '(void): void'
-	},
-	ob_clean: {
-		description: 'Clean (erase) the output buffer',
-		signature: '(void): void'
-	},
-	ob_end_clean: {
-		description: 'Clean (erase) the output buffer and turn off output buffering',
-		signature: '(void): bool'
-	},
-	ob_end_flush: {
-		description: 'Flush (send) the output buffer and turn off output buffering',
-		signature: '(void): bool'
-	},
-	ob_flush: {
-		description: 'Flush (send) the output buffer',
-		signature: '(void): void'
-	},
-	ob_get_clean: {
-		description: 'Get current buffer contents and delete current output buffer',
-		signature: '(void): string'
-	},
-	ob_get_contents: {
-		description: 'Return the contents of the output buffer',
-		signature: '(void): string'
-	},
-	ob_get_flush: {
-		description: 'Flush the output buffer, return it as a string and turn off output buffering',
-		signature: '(void): string'
-	},
-	ob_get_length: {
-		description: 'Return the length of the output buffer',
-		signature: '(void): int'
-	},
-	ob_get_level: {
-		description: 'Return the nesting level of the output buffering mechanism',
-		signature: '(void): int'
-	},
-	ob_get_status: {
-		description: 'Get status of output buffers',
-		signature: '([ bool $full_status = FALSE ]): array'
-	},
-	ob_gzhandler: {
-		description: 'ob_start callback function to gzip output buffer',
-		signature: '( string $buffer , int $mode ): string'
-	},
-	ob_implicit_flush: {
-		description: 'Turn implicit flush on/off',
-		signature: '([ int $flag = 1 ]): void'
-	},
-	ob_list_handlers: {
-		description: 'List all output handlers in use',
-		signature: '(void): array'
-	},
-	ob_start: {
-		description: 'Turn on output buffering',
-		signature: '([ callable $output_callback [, int $chunk_size = 0 [, int $flags ]]]): bool'
-	},
-	output_add_rewrite_var: {
-		description: 'Add URL rewriter values',
-		signature: '( string $name , string $value ): bool'
-	},
-	output_reset_rewrite_vars: {
-		description: 'Reset URL rewriter values',
-		signature: '(void): bool'
-	},
-	assert_options: {
-		description: 'Set/get the various assert flags',
-		signature: '( int $what [, mixed $value ]): mixed'
-	},
-	assert: {
-		description: 'Checks if assertion is FALSE',
-		signature: '( mixed $assertion [, string $description [, Throwable $exception ]]): bool'
-	},
-	cli_get_process_title: {
-		description: 'Returns the current process title',
-		signature: '(void): string'
-	},
-	cli_set_process_title: {
-		description: 'Sets the process title',
-		signature: '( string $title ): bool'
-	},
-	dl: {
-		description: 'Loads a PHP extension at runtime',
-		signature: '( string $library ): bool'
-	},
-	extension_loaded: {
-		description: 'Find out whether an extension is loaded',
-		signature: '( string $name ): bool'
-	},
-	gc_collect_cycles: {
-		description: 'Forces collection of any existing garbage cycles',
-		signature: '(void): int'
-	},
-	gc_disable: {
-		description: 'Deactivates the circular reference collector',
-		signature: '(void): void'
-	},
-	gc_enable: {
-		description: 'Activates the circular reference collector',
-		signature: '(void): void'
-	},
-	gc_enabled: {
-		description: 'Returns status of the circular reference collector',
-		signature: '(void): bool'
-	},
-	gc_mem_caches: {
-		description: 'Reclaims memory used by the Zend Engine memory manager',
-		signature: '(void): int'
-	},
-	gc_status: {
-		description: 'Gets information about the garbage collector',
-		signature: '(void): array'
-	},
-	get_cfg_var: {
-		description: 'Gets the value of a PHP configuration option',
-		signature: '( string $option ): mixed'
-	},
-	get_current_user: {
-		description: 'Gets the name of the owner of the current PHP script',
-		signature: '(void): string'
-	},
-	get_defined_constants: {
-		description: 'Returns an associative array with the names of all the constants and their values',
-		signature: '([ bool $categorize ]): array'
-	},
-	get_extension_funcs: {
-		description: 'Returns an array with the names of the functions of a module',
-		signature: '( string $module_name ): array'
-	},
-	get_include_path: {
-		description: 'Gets the current include_path configuration option',
-		signature: '(void): string'
-	},
-	get_included_files: {
-		description: 'Returns an array with the names of included or required files',
-		signature: '(void): array'
-	},
-	get_loaded_extensions: {
-		description: 'Returns an array with the names of all modules compiled and loaded',
-		signature: '([ bool $zend_extensions ]): array'
-	},
-	get_magic_quotes_gpc: {
-		description: 'Gets the current configuration setting of magic_quotes_gpc',
-		signature: '(void): bool'
-	},
-	get_magic_quotes_runtime: {
-		description: 'Gets the current active configuration setting of magic_quotes_runtime',
-		signature: '(void): bool'
-	},
-	get_required_files: {
-		description: 'Alias of get_included_files',
-	},
-	get_resources: {
-		description: 'Returns active resources',
-		signature: '([ string $type ]): resource'
-	},
-	getenv: {
-		description: 'Gets the value of an environment variable',
-		signature: '( string $varname [, bool $local_only ]): array'
-	},
-	getlastmod: {
-		description: 'Gets time of last page modification',
-		signature: '(void): int'
-	},
-	getmygid: {
-		description: 'Get PHP script owner\'s GID',
-		signature: '(void): int'
-	},
-	getmyinode: {
-		description: 'Gets the inode of the current script',
-		signature: '(void): int'
-	},
-	getmypid: {
-		description: 'Gets PHP\'s process ID',
-		signature: '(void): int'
-	},
-	getmyuid: {
-		description: 'Gets PHP script owner\'s UID',
-		signature: '(void): int'
-	},
-	getopt: {
-		description: 'Gets options from the command line argument list',
-		signature: '( string $options [, array $longopts [, int $optind ]]): array'
-	},
-	getrusage: {
-		description: 'Gets the current resource usages',
-		signature: '([ int $who = 0 ]): array'
-	},
-	ini_alter: {
-		description: 'Alias of ini_set',
-	},
-	ini_get_all: {
-		description: 'Gets all configuration options',
-		signature: '([ string $extension [, bool $details ]]): array'
-	},
-	ini_get: {
-		description: 'Gets the value of a configuration option',
-		signature: '( string $varname ): string'
-	},
-	ini_restore: {
-		description: 'Restores the value of a configuration option',
-		signature: '( string $varname ): void'
-	},
-	ini_set: {
-		description: 'Sets the value of a configuration option',
-		signature: '( string $varname , string $newvalue ): string'
-	},
-	magic_quotes_runtime: {
-		description: 'Alias of set_magic_quotes_runtime',
-	},
-	main: {
-		description: 'Dummy for main',
-	},
-	memory_get_peak_usage: {
-		description: 'Returns the peak of memory allocated by PHP',
-		signature: '([ bool $real_usage ]): int'
-	},
-	memory_get_usage: {
-		description: 'Returns the amount of memory allocated to PHP',
-		signature: '([ bool $real_usage ]): int'
-	},
-	php_ini_loaded_file: {
-		description: 'Retrieve a path to the loaded php.ini file',
-		signature: '(void): string'
-	},
-	php_ini_scanned_files: {
-		description: 'Return a list of .ini files parsed from the additional ini dir',
-		signature: '(void): string'
-	},
-	php_logo_guid: {
-		description: 'Gets the logo guid',
-		signature: '(void): string'
-	},
-	php_sapi_name: {
-		description: 'Returns the type of interface between web server and PHP',
-		signature: '(void): string'
-	},
-	php_uname: {
-		description: 'Returns information about the operating system PHP is running on',
-		signature: '([ string $mode = "a" ]): string'
-	},
-	phpcredits: {
-		description: 'Prints out the credits for PHP',
-		signature: '([ int $flag = CREDITS_ALL ]): bool'
-	},
-	phpinfo: {
-		description: 'Outputs information about PHP\'s configuration',
-		signature: '([ int $what = INFO_ALL ]): bool'
-	},
-	phpversion: {
-		description: 'Gets the current PHP version',
-		signature: '([ string $extension ]): string'
-	},
-	putenv: {
-		description: 'Sets the value of an environment variable',
-		signature: '( string $setting ): bool'
-	},
-	restore_include_path: {
-		description: 'Restores the value of the include_path configuration option',
-		signature: '(void): void'
-	},
-	set_include_path: {
-		description: 'Sets the include_path configuration option',
-		signature: '( string $new_include_path ): string'
-	},
-	set_magic_quotes_runtime: {
-		description: 'Sets the current active configuration setting of magic_quotes_runtime',
-		signature: '( bool $new_setting ): bool'
-	},
-	set_time_limit: {
-		description: 'Limits the maximum execution time',
-		signature: '( int $seconds ): bool'
-	},
-	sys_get_temp_dir: {
-		description: 'Returns directory path used for temporary files',
-		signature: '(void): string'
-	},
-	version_compare: {
-		description: 'Compares two "PHP-standardized" version number strings',
-		signature: '( string $version1 , string $version2 , string $operator ): bool'
-	},
-	zend_logo_guid: {
-		description: 'Gets the Zend guid',
-		signature: '(void): string'
-	},
-	zend_thread_id: {
-		description: 'Returns a unique identifier for the current thread',
-		signature: '(void): int'
-	},
-	zend_version: {
-		description: 'Gets the version of the current Zend engine',
-		signature: '(void): string'
-	},
-	bzclose: {
-		description: 'Close a bzip2 file',
-		signature: '( resource $bz ): int'
-	},
-	bzcompress: {
-		description: 'Compress a string into bzip2 encoded data',
-		signature: '( string $source [, int $blocksize = 4 [, int $workfactor = 0 ]]): mixed'
-	},
-	bzdecompress: {
-		description: 'Decompresses bzip2 encoded data',
-		signature: '( string $source [, int $small = 0 ]): mixed'
-	},
-	bzerrno: {
-		description: 'Returns a bzip2 error number',
-		signature: '( resource $bz ): int'
-	},
-	bzerror: {
-		description: 'Returns the bzip2 error number and error string in an array',
-		signature: '( resource $bz ): array'
-	},
-	bzerrstr: {
-		description: 'Returns a bzip2 error string',
-		signature: '( resource $bz ): string'
-	},
-	bzflush: {
-		description: 'Force a write of all buffered data',
-		signature: '( resource $bz ): bool'
-	},
-	bzopen: {
-		description: 'Opens a bzip2 compressed file',
-		signature: '( mixed $file , string $mode ): resource'
-	},
-	bzread: {
-		description: 'Binary safe bzip2 file read',
-		signature: '( resource $bz [, int $length = 1024 ]): string'
-	},
-	bzwrite: {
-		description: 'Binary safe bzip2 file write',
-		signature: '( resource $bz , string $data [, int $length ]): int'
-	},
-	PharException: {
-		description: 'The PharException class provides a phar-specific exception class    for try/catch blocks',
-	},
-	zip_close: {
-		description: 'Close a ZIP file archive',
-		signature: '( resource $zip ): void'
-	},
-	zip_entry_close: {
-		description: 'Close a directory entry',
-		signature: '( resource $zip_entry ): bool'
-	},
-	zip_entry_compressedsize: {
-		description: 'Retrieve the compressed size of a directory entry',
-		signature: '( resource $zip_entry ): int'
-	},
-	zip_entry_compressionmethod: {
-		description: 'Retrieve the compression method of a directory entry',
-		signature: '( resource $zip_entry ): string'
-	},
-	zip_entry_filesize: {
-		description: 'Retrieve the actual file size of a directory entry',
-		signature: '( resource $zip_entry ): int'
-	},
-	zip_entry_name: {
-		description: 'Retrieve the name of a directory entry',
-		signature: '( resource $zip_entry ): string'
-	},
-	zip_entry_open: {
-		description: 'Open a directory entry for reading',
-		signature: '( resource $zip , resource $zip_entry [, string $mode ]): bool'
-	},
-	zip_entry_read: {
-		description: 'Read from an open directory entry',
-		signature: '( resource $zip_entry [, int $length = 1024 ]): string'
-	},
-	zip_open: {
-		description: 'Open a ZIP file archive',
-		signature: '( string $filename ): resource'
-	},
-	zip_read: {
-		description: 'Read next entry in a ZIP file archive',
-		signature: '( resource $zip ): resource'
-	},
-	deflate_add: {
-		description: 'Incrementally deflate data',
-		signature: '( resource $context , string $data [, int $flush_mode = ZLIB_SYNC_FLUSH ]): string'
-	},
-	deflate_init: {
-		description: 'Initialize an incremental deflate context',
-		signature: '( int $encoding [, array $options = array() ]): resource'
-	},
-	gzclose: {
-		description: 'Close an open gz-file pointer',
-		signature: '( resource $zp ): bool'
-	},
-	gzcompress: {
-		description: 'Compress a string',
-		signature: '( string $data [, int $level = -1 [, int $encoding = ZLIB_ENCODING_DEFLATE ]]): string'
-	},
-	gzdecode: {
-		description: 'Decodes a gzip compressed string',
-		signature: '( string $data [, int $length ]): string'
-	},
-	gzdeflate: {
-		description: 'Deflate a string',
-		signature: '( string $data [, int $level = -1 [, int $encoding = ZLIB_ENCODING_RAW ]]): string'
-	},
-	gzencode: {
-		description: 'Create a gzip compressed string',
-		signature: '( string $data [, int $level = -1 [, int $encoding_mode = FORCE_GZIP ]]): string'
-	},
-	gzeof: {
-		description: 'Test for EOF on a gz-file pointer',
-		signature: '( resource $zp ): int'
-	},
-	gzfile: {
-		description: 'Read entire gz-file into an array',
-		signature: '( string $filename [, int $use_include_path = 0 ]): array'
-	},
-	gzgetc: {
-		description: 'Get character from gz-file pointer',
-		signature: '( resource $zp ): string'
-	},
-	gzgets: {
-		description: 'Get line from file pointer',
-		signature: '( resource $zp [, int $length ]): string'
-	},
-	gzgetss: {
-		description: 'Get line from gz-file pointer and strip HTML tags',
-		signature: '( resource $zp , int $length [, string $allowable_tags ]): string'
-	},
-	gzinflate: {
-		description: 'Inflate a deflated string',
-		signature: '( string $data [, int $length = 0 ]): string'
-	},
-	gzopen: {
-		description: 'Open gz-file',
-		signature: '( string $filename , string $mode [, int $use_include_path = 0 ]): resource'
-	},
-	gzpassthru: {
-		description: 'Output all remaining data on a gz-file pointer',
-		signature: '( resource $zp ): int'
-	},
-	gzputs: {
-		description: 'Alias of gzwrite',
-	},
-	gzread: {
-		description: 'Binary-safe gz-file read',
-		signature: '( resource $zp , int $length ): string'
-	},
-	gzrewind: {
-		description: 'Rewind the position of a gz-file pointer',
-		signature: '( resource $zp ): bool'
-	},
-	gzseek: {
-		description: 'Seek on a gz-file pointer',
-		signature: '( resource $zp , int $offset [, int $whence = SEEK_SET ]): int'
-	},
-	gztell: {
-		description: 'Tell gz-file pointer read/write position',
-		signature: '( resource $zp ): int'
-	},
-	gzuncompress: {
-		description: 'Uncompress a compressed string',
-		signature: '( string $data [, int $length = 0 ]): string'
-	},
-	gzwrite: {
-		description: 'Binary-safe gz-file write',
-		signature: '( resource $zp , string $string [, int $length ]): int'
-	},
-	inflate_add: {
-		description: 'Incrementally inflate encoded data',
-		signature: '( resource $context , string $encoded_data [, int $flush_mode = ZLIB_SYNC_FLUSH ]): string'
-	},
-	inflate_get_read_len: {
-		description: 'Get number of bytes read so far',
-		signature: '( resource $resource ): int'
-	},
-	inflate_get_status: {
-		description: 'Get decompression status',
-		signature: '( resource $resource ): int'
-	},
-	inflate_init: {
-		description: 'Initialize an incremental inflate context',
-		signature: '( int $encoding [, array $options = array() ]): resource'
-	},
-	readgzfile: {
-		description: 'Output a gz-file',
-		signature: '( string $filename [, int $use_include_path = 0 ]): int'
-	},
-	zlib_decode: {
-		description: 'Uncompress any raw/gzip/zlib encoded data',
-		signature: '( string $data [, string $max_decoded_len ]): string'
-	},
-	zlib_encode: {
-		description: 'Compress data with the specified encoding',
-		signature: '( string $data , int $encoding [, int $level = -1 ]): string'
-	},
-	zlib_get_coding_type: {
-		description: 'Returns the coding type used for output compression',
-		signature: '(void): string'
-	},
-	random_bytes: {
-		description: 'Generates cryptographically secure pseudo-random bytes',
-		signature: '( int $length ): string'
-	},
-	random_int: {
-		description: 'Generates cryptographically secure pseudo-random integers',
-		signature: '( int $min , int $max ): int'
-	},
-	hash_algos: {
-		description: 'Return a list of registered hashing algorithms',
-		signature: '(void): array'
-	},
-	hash_copy: {
-		description: 'Copy hashing context',
-		signature: '( HashContext $context ): HashContext'
-	},
-	hash_equals: {
-		description: 'Timing attack safe string comparison',
-		signature: '( string $known_string , string $user_string ): bool'
-	},
-	hash_file: {
-		description: 'Generate a hash value using the contents of a given file',
-		signature: '( string $algo , string $filename [, bool $raw_output ]): string'
-	},
-	hash_final: {
-		description: 'Finalize an incremental hash and return resulting digest',
-		signature: '( HashContext $context [, bool $raw_output ]): string'
-	},
-	hash_hkdf: {
-		description: 'Generate a HKDF key derivation of a supplied key input',
-		signature: '( string $algo , string $ikm [, int $length = 0 [, string $info = \'\' [, string $salt = \'\' ]]]): string'
-	},
-	hash_hmac_algos: {
-		description: 'Return a list of registered hashing algorithms suitable for hash_hmac',
-		signature: '(void): array'
-	},
-	hash_hmac_file: {
-		description: 'Generate a keyed hash value using the HMAC method and the contents of a given file',
-		signature: '( string $algo , string $filename , string $key [, bool $raw_output ]): string'
-	},
-	hash_hmac: {
-		description: 'Generate a keyed hash value using the HMAC method',
-		signature: '( string $algo , string $data , string $key [, bool $raw_output ]): string'
-	},
-	hash_init: {
-		description: 'Initialize an incremental hashing context',
-		signature: '( string $algo [, int $options = 0 [, string $key ]]): HashContext'
-	},
-	hash_pbkdf2: {
-		description: 'Generate a PBKDF2 key derivation of a supplied password',
-		signature: '( string $algo , string $password , string $salt , int $iterations [, int $length = 0 [, bool $raw_output ]]): string'
-	},
-	hash_update_file: {
-		description: 'Pump data into an active hashing context from a file',
-		signature: '( HashContext $hcontext , string $filename [, resource $scontext ]): bool'
-	},
-	hash_update_stream: {
-		description: 'Pump data into an active hashing context from an open stream',
-		signature: '( HashContext $context , resource $handle [, int $length = -1 ]): int'
-	},
-	hash_update: {
-		description: 'Pump data into an active hashing context',
-		signature: '( HashContext $context , string $data ): bool'
-	},
-	hash: {
-		description: 'Generate a hash value (message digest)',
-		signature: '( string $algo , string $data [, bool $raw_output ]): string'
-	},
-	openssl_cipher_iv_length: {
-		description: 'Gets the cipher iv length',
-		signature: '( string $method ): int'
-	},
-	openssl_csr_export_to_file: {
-		description: 'Exports a CSR to a file',
-		signature: '( mixed $csr , string $outfilename [, bool $notext ]): bool'
-	},
-	openssl_csr_export: {
-		description: 'Exports a CSR as a string',
-		signature: '( mixed $csr , string $out [, bool $notext ]): bool'
-	},
-	openssl_csr_get_public_key: {
-		description: 'Returns the public key of a CSR',
-		signature: '( mixed $csr [, bool $use_shortnames ]): resource'
-	},
-	openssl_csr_get_subject: {
-		description: 'Returns the subject of a CSR',
-		signature: '( mixed $csr [, bool $use_shortnames ]): array'
-	},
-	openssl_csr_new: {
-		description: 'Generates a CSR',
-		signature: '( array $dn , resource $privkey [, array $configargs [, array $extraattribs ]]): mixed'
-	},
-	openssl_csr_sign: {
-		description: 'Sign a CSR with another certificate (or itself) and generate a certificate',
-		signature: '( mixed $csr , mixed $cacert , mixed $priv_key , int $days [, array $configargs [, int $serial = 0 ]]): resource'
-	},
-	openssl_decrypt: {
-		description: 'Decrypts data',
-		signature: '( string $data , string $method , string $key [, int $options = 0 [, string $iv = "" [, string $tag = "" [, string $aad = "" ]]]]): string'
-	},
-	openssl_dh_compute_key: {
-		description: 'Computes shared secret for public value of remote DH public key and local DH key',
-		signature: '( string $pub_key , resource $dh_key ): string'
-	},
-	openssl_digest: {
-		description: 'Computes a digest',
-		signature: '( string $data , string $method [, bool $raw_output ]): string'
-	},
-	openssl_encrypt: {
-		description: 'Encrypts data',
-		signature: '( string $data , string $method , string $key [, int $options = 0 [, string $iv = "" [, string $tag = NULL [, string $aad = "" [, int $tag_length = 16 ]]]]]): string'
-	},
-	openssl_error_string: {
-		description: 'Return openSSL error message',
-		signature: '(void): string'
-	},
-	openssl_free_key: {
-		description: 'Free key resource',
-		signature: '( resource $key_identifier ): void'
-	},
-	openssl_get_cert_locations: {
-		description: 'Retrieve the available certificate locations',
-		signature: '(void): array'
-	},
-	openssl_get_cipher_methods: {
-		description: 'Gets available cipher methods',
-		signature: '([ bool $aliases ]): array'
-	},
-	openssl_get_curve_names: {
-		description: 'Gets list of available curve names for ECC',
-		signature: '(void): array'
-	},
-	openssl_get_md_methods: {
-		description: 'Gets available digest methods',
-		signature: '([ bool $aliases ]): array'
-	},
-	openssl_get_privatekey: {
-		description: 'Alias of openssl_pkey_get_private',
-	},
-	openssl_get_publickey: {
-		description: 'Alias of openssl_pkey_get_public',
-	},
-	openssl_open: {
-		description: 'Open sealed data',
-		signature: '( string $sealed_data , string $open_data , string $env_key , mixed $priv_key_id [, string $method = "RC4" [, string $iv ]]): bool'
-	},
-	openssl_pbkdf2: {
-		description: 'Generates a PKCS5 v2 PBKDF2 string',
-		signature: '( string $password , string $salt , int $key_length , int $iterations [, string $digest_algorithm = "sha1" ]): string'
-	},
-	openssl_pkcs12_export_to_file: {
-		description: 'Exports a PKCS#12 Compatible Certificate Store File',
-		signature: '( mixed $x509 , string $filename , mixed $priv_key , string $pass [, array $args ]): bool'
-	},
-	openssl_pkcs12_export: {
-		description: 'Exports a PKCS#12 Compatible Certificate Store File to variable',
-		signature: '( mixed $x509 , string $out , mixed $priv_key , string $pass [, array $args ]): bool'
-	},
-	openssl_pkcs12_read: {
-		description: 'Parse a PKCS#12 Certificate Store into an array',
-		signature: '( string $pkcs12 , array $certs , string $pass ): bool'
-	},
-	openssl_pkcs7_decrypt: {
-		description: 'Decrypts an S/MIME encrypted message',
-		signature: '( string $infilename , string $outfilename , mixed $recipcert [, mixed $recipkey ]): bool'
-	},
-	openssl_pkcs7_encrypt: {
-		description: 'Encrypt an S/MIME message',
-		signature: '( string $infile , string $outfile , mixed $recipcerts , array $headers [, int $flags = 0 [, int $cipherid = OPENSSL_CIPHER_RC2_40 ]]): bool'
-	},
-	openssl_pkcs7_read: {
-		description: 'Export the PKCS7 file to an array of PEM certificates',
-		signature: '( string $infilename , array $certs ): bool'
-	},
-	openssl_pkcs7_sign: {
-		description: 'Sign an S/MIME message',
-		signature: '( string $infilename , string $outfilename , mixed $signcert , mixed $privkey , array $headers [, int $flags = PKCS7_DETACHED [, string $extracerts ]]): bool'
-	},
-	openssl_pkcs7_verify: {
-		description: 'Verifies the signature of an S/MIME signed message',
-		signature: '( string $filename , int $flags [, string $outfilename [, array $cainfo [, string $extracerts [, string $content [, string $p7bfilename ]]]]]): mixed'
-	},
-	openssl_pkey_export_to_file: {
-		description: 'Gets an exportable representation of a key into a file',
-		signature: '( mixed $key , string $outfilename [, string $passphrase [, array $configargs ]]): bool'
-	},
-	openssl_pkey_export: {
-		description: 'Gets an exportable representation of a key into a string',
-		signature: '( mixed $key , string $out [, string $passphrase [, array $configargs ]]): bool'
-	},
-	openssl_pkey_free: {
-		description: 'Frees a private key',
-		signature: '( resource $key ): void'
-	},
-	openssl_pkey_get_details: {
-		description: 'Returns an array with the key details',
-		signature: '( resource $key ): array'
-	},
-	openssl_pkey_get_private: {
-		description: 'Get a private key',
-		signature: '( mixed $key [, string $passphrase = "" ]): resource'
-	},
-	openssl_pkey_get_public: {
-		description: 'Extract public key from certificate and prepare it for use',
-		signature: '( mixed $certificate ): resource'
-	},
-	openssl_pkey_new: {
-		description: 'Generates a new private key',
-		signature: '([ array $configargs ]): resource'
-	},
-	openssl_private_decrypt: {
-		description: 'Decrypts data with private key',
-		signature: '( string $data , string $decrypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
-	},
-	openssl_private_encrypt: {
-		description: 'Encrypts data with private key',
-		signature: '( string $data , string $crypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
-	},
-	openssl_public_decrypt: {
-		description: 'Decrypts data with public key',
-		signature: '( string $data , string $decrypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
-	},
-	openssl_public_encrypt: {
-		description: 'Encrypts data with public key',
-		signature: '( string $data , string $crypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
-	},
-	openssl_random_pseudo_bytes: {
-		description: 'Generate a pseudo-random string of bytes',
-		signature: '( int $length [, bool $crypto_strong ]): string'
-	},
-	openssl_seal: {
-		description: 'Seal (encrypt) data',
-		signature: '( string $data , string $sealed_data , array $env_keys , array $pub_key_ids [, string $method = "RC4" [, string $iv ]]): int'
-	},
-	openssl_sign: {
-		description: 'Generate signature',
-		signature: '( string $data , string $signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ]): bool'
-	},
-	openssl_spki_export_challenge: {
-		description: 'Exports the challenge assoicated with a signed public key and challenge',
-		signature: '( string $spkac ): string'
-	},
-	openssl_spki_export: {
-		description: 'Exports a valid PEM formatted public key signed public key and challenge',
-		signature: '( string $spkac ): string'
-	},
-	openssl_spki_new: {
-		description: 'Generate a new signed public key and challenge',
-		signature: '( resource $privkey , string $challenge [, int $algorithm = 0 ]): string'
-	},
-	openssl_spki_verify: {
-		description: 'Verifies a signed public key and challenge',
-		signature: '( string $spkac ): string'
-	},
-	openssl_verify: {
-		description: 'Verify signature',
-		signature: '( string $data , string $signature , mixed $pub_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ]): int'
-	},
-	openssl_x509_check_private_key: {
-		description: 'Checks if a private key corresponds to a certificate',
-		signature: '( mixed $cert , mixed $key ): bool'
-	},
-	openssl_x509_checkpurpose: {
-		description: 'Verifies if a certificate can be used for a particular purpose',
-		signature: '( mixed $x509cert , int $purpose [, array $cainfo = array() [, string $untrustedfile ]]): int'
-	},
-	openssl_x509_export_to_file: {
-		description: 'Exports a certificate to file',
-		signature: '( mixed $x509 , string $outfilename [, bool $notext ]): bool'
-	},
-	openssl_x509_export: {
-		description: 'Exports a certificate as a string',
-		signature: '( mixed $x509 , string $output [, bool $notext ]): bool'
-	},
-	openssl_x509_fingerprint: {
-		description: 'Calculates the fingerprint, or digest, of a given X.509 certificate',
-		signature: '( mixed $x509 [, string $hash_algorithm = "sha1" [, bool $raw_output ]]): string'
-	},
-	openssl_x509_free: {
-		description: 'Free certificate resource',
-		signature: '( resource $x509cert ): void'
-	},
-	openssl_x509_parse: {
-		description: 'Parse an X509 certificate and return the information as an array',
-		signature: '( mixed $x509cert [, bool $shortnames ]): array'
-	},
-	openssl_x509_read: {
-		description: 'Parse an X.509 certificate and return a resource identifier for  it',
-		signature: '( mixed $x509certdata ): resource'
-	},
-	password_get_info: {
-		description: 'Returns information about the given hash',
-		signature: '( string $hash ): array'
-	},
-	password_hash: {
-		description: 'Creates a password hash',
-		signature: '( string $password , int $algo [, array $options ]): integer'
-	},
-	password_needs_rehash: {
-		description: 'Checks if the given hash matches the given options',
-		signature: '( string $hash , int $algo [, array $options ]): bool'
-	},
-	password_verify: {
-		description: 'Verifies that a password matches a hash',
-		signature: '( string $password , string $hash ): bool'
-	},
-	sodium_add: {
-		description: 'Add large numbers',
-		signature: '( string $val , string $addv ): void'
-	},
-	sodium_base642bin: {
-		description: 'Description',
-		signature: '( string $b64 , int $id [, string $ignore ]): string'
-	},
-	sodium_bin2base64: {
-		description: 'Description',
-		signature: '( string $bin , int $id ): string'
-	},
-	sodium_bin2hex: {
-		description: 'Encode to hexadecimal',
-		signature: '( string $bin ): string'
-	},
-	sodium_compare: {
-		description: 'Compare large numbers',
-		signature: '( string $buf1 , string $buf2 ): int'
-	},
-	sodium_crypto_aead_aes256gcm_decrypt: {
-		description: 'Decrypt in combined mode with precalculation',
-		signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_aes256gcm_encrypt: {
-		description: 'Encrypt in combined mode with precalculation',
-		signature: '( string $msg , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_aes256gcm_is_available: {
-		description: 'Check if hardware supports AES256-GCM',
-		signature: '(void): bool'
-	},
-	sodium_crypto_aead_aes256gcm_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_aead_chacha20poly1305_decrypt: {
-		description: 'Verify that the ciphertext includes a valid tag',
-		signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_chacha20poly1305_encrypt: {
-		description: 'Encrypt a message',
-		signature: '( string $msg , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_chacha20poly1305_ietf_decrypt: {
-		description: 'Verify that the ciphertext includes a valid tag',
-		signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_chacha20poly1305_ietf_encrypt: {
-		description: 'Encrypt a message',
-		signature: '( string $msg , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_chacha20poly1305_ietf_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_aead_chacha20poly1305_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_aead_xchacha20poly1305_ietf_decrypt: {
-		description: 'Description',
-		signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_xchacha20poly1305_ietf_encrypt: {
-		description: 'Description',
-		signature: '( string $msg , string $ad , string $nonce , string $key ): string'
-	},
-	sodium_crypto_aead_xchacha20poly1305_ietf_keygen: {
-		description: 'Description',
-		signature: '(void): string'
-	},
-	sodium_crypto_auth_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_auth_verify: {
-		description: 'Verifies that the tag is valid for the message',
-		signature: '( string $signature , string $msg , string $key ): bool'
-	},
-	sodium_crypto_auth: {
-		description: 'Compute a tag for the message',
-		signature: '( string $msg , string $key ): string'
-	},
-	sodium_crypto_box_keypair_from_secretkey_and_publickey: {
-		description: 'Description',
-		signature: '( string $secret_key , string $public_key ): string'
-	},
-	sodium_crypto_box_keypair: {
-		description: 'Randomly generate a secret key and a corresponding public key',
-		signature: '(void): string'
-	},
-	sodium_crypto_box_open: {
-		description: 'Verify and decrypt a ciphertext',
-		signature: '( string $ciphertext , string $nonce , string $key ): string'
-	},
-	sodium_crypto_box_publickey_from_secretkey: {
-		description: 'Description',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_box_publickey: {
-		description: 'Description',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_box_seal_open: {
-		description: 'Decrypt the ciphertext',
-		signature: '( string $ciphertext , string $key ): string'
-	},
-	sodium_crypto_box_seal: {
-		description: 'Encrypt a message',
-		signature: '( string $msg , string $key ): string'
-	},
-	sodium_crypto_box_secretkey: {
-		description: 'Description',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_box_seed_keypair: {
-		description: 'Deterministically derive the key pair from a single key',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_box: {
-		description: 'Encrypt a message',
-		signature: '( string $msg , string $nonce , string $key ): string'
-	},
-	sodium_crypto_generichash_final: {
-		description: 'Complete the hash',
-		signature: '( string $state [, int $length = SODIUM_CRYPTO_GENERICHASH_BYTES ]): string'
-	},
-	sodium_crypto_generichash_init: {
-		description: 'Initialize a hash',
-		signature: '([ string $key [, int $length = SODIUM_CRYPTO_GENERICHASH_BYTES ]]): string'
-	},
-	sodium_crypto_generichash_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_generichash_update: {
-		description: 'Add message to a hash',
-		signature: '( string $state , string $msg ): bool'
-	},
-	sodium_crypto_generichash: {
-		description: 'Get a hash of the message',
-		signature: '( string $msg [, string $key [, int $length = SODIUM_CRYPTO_GENERICHASH_BYTES ]]): string'
-	},
-	sodium_crypto_kdf_derive_from_key: {
-		description: 'Derive a subkey',
-		signature: '( int $subkey_len , int $subkey_id , string $context , string $key ): string'
-	},
-	sodium_crypto_kdf_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_kx_client_session_keys: {
-		description: 'Description',
-		signature: '( string $client_keypair , string $server_key ): array'
-	},
-	sodium_crypto_kx_keypair: {
-		description: 'Creates a new sodium keypair',
-		signature: '(void): string'
-	},
-	sodium_crypto_kx_publickey: {
-		description: 'Description',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_kx_secretkey: {
-		description: 'Description',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_kx_seed_keypair: {
-		description: 'Description',
-		signature: '( string $string ): string'
-	},
-	sodium_crypto_kx_server_session_keys: {
-		description: 'Description',
-		signature: '( string $server_keypair , string $client_key ): array'
-	},
-	sodium_crypto_pwhash_scryptsalsa208sha256_str_verify: {
-		description: 'Verify that the password is a valid password verification string',
-		signature: '( string $hash , string $password ): bool'
-	},
-	sodium_crypto_pwhash_scryptsalsa208sha256_str: {
-		description: 'Get an ASCII encoded hash',
-		signature: '( string $password , int $opslimit , int $memlimit ): string'
-	},
-	sodium_crypto_pwhash_scryptsalsa208sha256: {
-		description: 'Derives a key from a password',
-		signature: '( int $length , string $password , string $salt , int $opslimit , int $memlimit ): string'
-	},
-	sodium_crypto_pwhash_str_needs_rehash: {
-		description: 'Description',
-		signature: '( string $password , int $opslimit , int $memlimit ): bool'
-	},
-	sodium_crypto_pwhash_str_verify: {
-		description: 'Verifies that a password matches a hash',
-		signature: '( string $hash , string $password ): bool'
-	},
-	sodium_crypto_pwhash_str: {
-		description: 'Get an ASCII-encoded hash',
-		signature: '( string $password , int $opslimit , int $memlimit ): string'
-	},
-	sodium_crypto_pwhash: {
-		description: 'Derive a key from a password',
-		signature: '( int $length , string $password , string $salt , int $opslimit , int $memlimit [, int $alg ]): string'
-	},
-	sodium_crypto_scalarmult_base: {
-		description: 'Alias of sodium_crypto_box_publickey_from_secretkey',
-	},
-	sodium_crypto_scalarmult: {
-		description: 'Compute a shared secret given a user\'s secret key and another user\'s public key',
-		signature: '( string $n , string $p ): string'
-	},
-	sodium_crypto_secretbox_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_secretbox_open: {
-		description: 'Verify and decrypt a ciphertext',
-		signature: '( string $ciphertext , string $nonce , string $key ): string'
-	},
-	sodium_crypto_secretbox: {
-		description: 'Encrypt a message',
-		signature: '( string $string , string $nonce , string $key ): string'
-	},
-	sodium_crypto_secretstream_xchacha20poly1305_init_pull: {
-		description: 'Description',
-		signature: '( string $header , string $key ): string'
-	},
-	sodium_crypto_secretstream_xchacha20poly1305_init_push: {
-		description: 'Description',
-		signature: '( string $key ): array'
-	},
-	sodium_crypto_secretstream_xchacha20poly1305_keygen: {
-		description: 'Description',
-		signature: '(void): string'
-	},
-	sodium_crypto_secretstream_xchacha20poly1305_pull: {
-		description: 'Description',
-		signature: '( string $state , string $c [, string $ad ]): array'
-	},
-	sodium_crypto_secretstream_xchacha20poly1305_push: {
-		description: 'Description',
-		signature: '( string $state , string $msg [, string $ad [, int $tag ]]): string'
-	},
-	sodium_crypto_secretstream_xchacha20poly1305_rekey: {
-		description: 'Description',
-		signature: '( string $state ): void'
-	},
-	sodium_crypto_shorthash_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_shorthash: {
-		description: 'Compute a fixed-size fingerprint for the message',
-		signature: '( string $msg , string $key ): string'
-	},
-	sodium_crypto_sign_detached: {
-		description: 'Sign the message',
-		signature: '( string $msg , string $secretkey ): string'
-	},
-	sodium_crypto_sign_ed25519_pk_to_curve25519: {
-		description: 'Convert an Ed25519 public key to a Curve25519 public key',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_sign_ed25519_sk_to_curve25519: {
-		description: 'Convert an Ed25519 secret key to a Curve25519 secret key',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_sign_keypair_from_secretkey_and_publickey: {
-		description: 'Description',
-		signature: '( string $secret_key , string $public_key ): string'
-	},
-	sodium_crypto_sign_keypair: {
-		description: 'Randomly generate a secret key and a corresponding public key',
-		signature: '(void): string'
-	},
-	sodium_crypto_sign_open: {
-		description: 'Check that the signed message has a valid signature',
-		signature: '( string $string , string $public_key ): string'
-	},
-	sodium_crypto_sign_publickey_from_secretkey: {
-		description: 'Extract the public key from the secret key',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_sign_publickey: {
-		description: 'Description',
-		signature: '( string $keypair ): string'
-	},
-	sodium_crypto_sign_secretkey: {
-		description: 'Description',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_sign_seed_keypair: {
-		description: 'Deterministically derive the key pair from a single key',
-		signature: '( string $key ): string'
-	},
-	sodium_crypto_sign_verify_detached: {
-		description: 'Verify signature for the message',
-		signature: '( string $signature , string $msg , string $public_key ): bool'
-	},
-	sodium_crypto_sign: {
-		description: 'Sign a message',
-		signature: '( string $msg , string $secret_key ): string'
-	},
-	sodium_crypto_stream_keygen: {
-		description: 'Get random bytes for key',
-		signature: '(void): string'
-	},
-	sodium_crypto_stream_xor: {
-		description: 'Encrypt a message',
-		signature: '( string $msg , string $nonce , string $key ): string'
-	},
-	sodium_crypto_stream: {
-		description: 'Generate a deterministic sequence of bytes from a seed',
-		signature: '( int $length , string $nonce , string $key ): string'
-	},
-	sodium_hex2bin: {
-		description: 'Decodes a hexadecimally encoded binary string',
-		signature: '( string $hex [, string $ignore ]): string'
-	},
-	sodium_increment: {
-		description: 'Increment large number',
-		signature: '( string $val ): void'
-	},
-	sodium_memcmp: {
-		description: 'Test for equality in constant-time',
-		signature: '( string $buf1 , string $buf2 ): int'
-	},
-	sodium_memzero: {
-		description: 'Overwrite buf with zeros',
-		signature: '( string $buf ): void'
-	},
-	sodium_pad: {
-		description: 'Add padding data',
-		signature: '( string $unpadded , int $length ): string'
-	},
-	sodium_unpad: {
-		description: 'Remove padding data',
-		signature: '( string $padded , int $length ): string'
-	},
-	dba_close: {
-		description: 'Close a DBA database',
-		signature: '( resource $handle ): void'
-	},
-	dba_delete: {
-		description: 'Delete DBA entry specified by key',
-		signature: '( string $key , resource $handle ): bool'
-	},
-	dba_exists: {
-		description: 'Check whether key exists',
-		signature: '( string $key , resource $handle ): bool'
-	},
-	dba_fetch: {
-		description: 'Fetch data specified by key',
-		signature: '( string $key , resource $handle , int $skip ): string'
-	},
-	dba_firstkey: {
-		description: 'Fetch first key',
-		signature: '( resource $handle ): string'
-	},
-	dba_handlers: {
-		description: 'List all the handlers available',
-		signature: '([ bool $full_info ]): array'
-	},
-	dba_insert: {
-		description: 'Insert entry',
-		signature: '( string $key , string $value , resource $handle ): bool'
-	},
-	dba_key_split: {
-		description: 'Splits a key in string representation into array representation',
-		signature: '( mixed $key ): mixed'
-	},
-	dba_list: {
-		description: 'List all open database files',
-		signature: '(void): array'
-	},
-	dba_nextkey: {
-		description: 'Fetch next key',
-		signature: '( resource $handle ): string'
-	},
-	dba_open: {
-		description: 'Open database',
-		signature: '( string $path , string $mode [, string $handler [, mixed $... ]]): resource'
-	},
-	dba_optimize: {
-		description: 'Optimize database',
-		signature: '( resource $handle ): bool'
-	},
-	dba_popen: {
-		description: 'Open database persistently',
-		signature: '( string $path , string $mode [, string $handler [, mixed $... ]]): resource'
-	},
-	dba_replace: {
-		description: 'Replace or insert entry',
-		signature: '( string $key , string $value , resource $handle ): bool'
-	},
-	dba_sync: {
-		description: 'Synchronize database',
-		signature: '( resource $handle ): bool'
-	},
-	pdo_drivers: {
-		description: 'Return an array of available PDO drivers',
-		signature: '(void): array'
-	},
-	cal_days_in_month: {
-		description: 'Return the number of days in a month for a given year and calendar',
-		signature: '( int $calendar , int $month , int $year ): int'
-	},
-	cal_from_jd: {
-		description: 'Converts from Julian Day Count to a supported calendar',
-		signature: '( int $jd , int $calendar ): array'
-	},
-	cal_info: {
-		description: 'Returns information about a particular calendar',
-		signature: '([ int $calendar = -1 ]): array'
-	},
-	cal_to_jd: {
-		description: 'Converts from a supported calendar to Julian Day Count',
-		signature: '( int $calendar , int $month , int $day , int $year ): int'
-	},
-	easter_date: {
-		description: 'Get Unix timestamp for midnight on Easter of a given year',
-		signature: '([ int $year = date("Y") ]): int'
-	},
-	easter_days: {
-		description: 'Get number of days after March 21 on which Easter falls for a given year',
-		signature: '([ int $year = date("Y") [, int $method = CAL_EASTER_DEFAULT ]]): int'
-	},
-	frenchtojd: {
-		description: 'Converts a date from the French Republican Calendar to a Julian Day Count',
-		signature: '( int $month , int $day , int $year ): int'
-	},
-	gregoriantojd: {
-		description: 'Converts a Gregorian date to Julian Day Count',
-		signature: '( int $month , int $day , int $year ): int'
-	},
-	jddayofweek: {
-		description: 'Returns the day of the week',
-		signature: '( int $julianday [, int $mode = CAL_DOW_DAYNO ]): mixed'
-	},
-	jdmonthname: {
-		description: 'Returns a month name',
-		signature: '( int $julianday , int $mode ): string'
-	},
-	jdtofrench: {
-		description: 'Converts a Julian Day Count to the French Republican Calendar',
-		signature: '( int $juliandaycount ): string'
-	},
-	jdtogregorian: {
-		description: 'Converts Julian Day Count to Gregorian date',
-		signature: '( int $julianday ): string'
-	},
-	jdtojewish: {
-		description: 'Converts a Julian day count to a Jewish calendar date',
-		signature: '( int $juliandaycount [, bool $hebrew [, int $fl = 0 ]]): string'
-	},
-	jdtojulian: {
-		description: 'Converts a Julian Day Count to a Julian Calendar Date',
-		signature: '( int $julianday ): string'
-	},
-	jdtounix: {
-		description: 'Convert Julian Day to Unix timestamp',
-		signature: '( int $jday ): int'
-	},
-	jewishtojd: {
-		description: 'Converts a date in the Jewish Calendar to Julian Day Count',
-		signature: '( int $month , int $day , int $year ): int'
-	},
-	juliantojd: {
-		description: 'Converts a Julian Calendar date to Julian Day Count',
-		signature: '( int $month , int $day , int $year ): int'
-	},
-	unixtojd: {
-		description: 'Convert Unix timestamp to Julian Day',
-		signature: '([ int $timestamp = time() ]): int'
-	},
-	date_add: {
-		description: 'Adds an amount of days, months, years, hours, minutes and seconds to a   DateTime object',
-		signature: '( DateInterval $interval , DateTime $object ): DateTime'
-	},
-	date_create: {
-		description: 'Returns new DateTime object',
-		signature: '([ string $time = "now" [, DateTimeZone $timezone ]]): DateTime'
-	},
-	date_create_from_format: {
-		description: 'Parses a time string according to a specified format',
-		signature: '( string $format , string $time [, DateTimeZone $timezone ]): DateTime'
-	},
-	date_get_last_errors: {
-		description: 'Returns the warnings and errors',
-		signature: '(void): array'
-	},
-	date_modify: {
-		description: 'Alters the timestamp',
-		signature: '( string $modify , DateTime $object ): DateTime'
-	},
-	date_date_set: {
-		description: 'Sets the date',
-		signature: '( int $year , int $month , int $day , DateTime $object ): DateTime'
-	},
-	date_isodate_set: {
-		description: 'Sets the ISO date',
-		signature: '( int $year , int $week [, int $day = 1 , DateTime $object ]): DateTime'
-	},
-	date_time_set: {
-		description: 'Sets the time',
-		signature: '( int $hour , int $minute [, int $second = 0 [, int $microseconds = 0 , DateTime $object ]]): DateTime'
-	},
-	date_timestamp_set: {
-		description: 'Sets the date and time based on an Unix timestamp',
-		signature: '( int $unixtimestamp , DateTime $object ): DateTime'
-	},
-	date_timezone_set: {
-		description: 'Sets the time zone for the DateTime object',
-		signature: '( DateTimeZone $timezone , DateTime $object ): object'
-	},
-	date_sub: {
-		description: 'Subtracts an amount of days, months, years, hours, minutes and seconds from   a DateTime object',
-		signature: '( DateInterval $interval , DateTime $object ): DateTime'
-	},
-	date_create_immutable: {
-		description: 'Returns new DateTimeImmutable object',
-		signature: '([ string $time = "now" [, DateTimeZone $timezone ]]): DateTimeImmutable'
-	},
-	date_create_immutable_from_format: {
-		description: 'Parses a time string according to a specified format',
-		signature: '( string $format , string $time [, DateTimeZone $timezone ]): DateTimeImmutable'
-	},
-	date_diff: {
-		description: 'Returns the difference between two DateTime objects',
-		signature: '( DateTimeInterface $datetime2 [, bool $absolute , DateTimeInterface $datetime1 ]): DateInterval'
-	},
-	date_format: {
-		description: 'Returns date formatted according to given format',
-		signature: '( DateTimeInterface $object , string $format ): string'
-	},
-	date_offset_get: {
-		description: 'Returns the timezone offset',
-		signature: '( DateTimeInterface $object ): int'
-	},
-	date_timestamp_get: {
-		description: 'Gets the Unix timestamp',
-		signature: '( DateTimeInterface $object ): int'
-	},
-	date_timezone_get: {
-		description: 'Return time zone relative to given DateTime',
-		signature: '( DateTimeInterface $object ): DateTimeZone'
-	},
-	timezone_open: {
-		description: 'Creates new DateTimeZone object',
-		signature: '( string $timezone ): DateTimeZone'
-	},
-	timezone_location_get: {
-		description: 'Returns location information for a timezone',
-		signature: '( DateTimeZone $object ): array'
-	},
-	timezone_name_get: {
-		description: 'Returns the name of the timezone',
-		signature: '( DateTimeZone $object ): string'
-	},
-	timezone_offset_get: {
-		description: 'Returns the timezone offset from GMT',
-		signature: '( DateTimeInterface $datetime , DateTimeZone $object ): int'
-	},
-	timezone_transitions_get: {
-		description: 'Returns all transitions for the timezone',
-		signature: '([ int $timestamp_begin [, int $timestamp_end , DateTimeZone $object ]]): array'
-	},
-	timezone_abbreviations_list: {
-		description: 'Returns associative array containing dst, offset and the timezone name',
-		signature: '(void): array'
-	},
-	timezone_identifiers_list: {
-		description: 'Returns a numerically indexed array containing all defined timezone identifiers',
-		signature: '([ int $what = DateTimeZone::ALL [, string $country ]]): array'
-	},
-	checkdate: {
-		description: 'Validate a Gregorian date',
-		signature: '( int $month , int $day , int $year ): bool'
-	},
-	date_default_timezone_get: {
-		description: 'Gets the default timezone used by all date/time functions in a script',
-		signature: '(void): string'
-	},
-	date_default_timezone_set: {
-		description: 'Sets the default timezone used by all date/time functions in a script',
-		signature: '( string $timezone_identifier ): bool'
-	},
-	date_interval_create_from_date_string: {
-		description: 'Alias of DateInterval::createFromDateString',
-	},
-	date_interval_format: {
-		description: 'Alias of DateInterval::format',
-	},
-	date_parse_from_format: {
-		description: 'Get info about given date formatted according to the specified format',
-		signature: '( string $format , string $date ): array'
-	},
-	date_parse: {
-		description: 'Returns associative array with detailed info about given date',
-		signature: '( string $date ): array'
-	},
-	date_sun_info: {
-		description: 'Returns an array with information about sunset/sunrise and twilight begin/end',
-		signature: '( int $time , float $latitude , float $longitude ): array'
-	},
-	date_sunrise: {
-		description: 'Returns time of sunrise for a given day and location',
-		signature: '( int $timestamp [, int $format = SUNFUNCS_RET_STRING [, float $latitude = ini_get("date.default_latitude") [, float $longitude = ini_get("date.default_longitude") [, float $zenith = ini_get("date.sunrise_zenith") [, float $gmt_offset = 0 ]]]]]): mixed'
-	},
-	date_sunset: {
-		description: 'Returns time of sunset for a given day and location',
-		signature: '( int $timestamp [, int $format = SUNFUNCS_RET_STRING [, float $latitude = ini_get("date.default_latitude") [, float $longitude = ini_get("date.default_longitude") [, float $zenith = ini_get("date.sunset_zenith") [, float $gmt_offset = 0 ]]]]]): mixed'
-	},
-	date: {
-		description: 'Format a local time/date',
-		signature: '( string $format [, int $timestamp = time() ]): string'
-	},
-	getdate: {
-		description: 'Get date/time information',
-		signature: '([ int $timestamp = time() ]): array'
-	},
-	gettimeofday: {
-		description: 'Get current time',
-		signature: '([ bool $return_float ]): mixed'
-	},
-	gmdate: {
-		description: 'Format a GMT/UTC date/time',
-		signature: '( string $format [, int $timestamp = time() ]): string'
-	},
-	gmmktime: {
-		description: 'Get Unix timestamp for a GMT date',
-		signature: '([ int $hour = gmdate("H") [, int $minute = gmdate("i") [, int $second = gmdate("s") [, int $month = gmdate("n") [, int $day = gmdate("j") [, int $year = gmdate("Y") [, int $is_dst = -1 ]]]]]]]): int'
-	},
-	gmstrftime: {
-		description: 'Format a GMT/UTC time/date according to locale settings',
-		signature: '( string $format [, int $timestamp = time() ]): string'
-	},
-	idate: {
-		description: 'Format a local time/date as integer',
-		signature: '( string $format [, int $timestamp = time() ]): int'
-	},
-	localtime: {
-		description: 'Get the local time',
-		signature: '([ int $timestamp = time() [, bool $is_associative ]]): array'
-	},
-	microtime: {
-		description: 'Return current Unix timestamp with microseconds',
-		signature: '([ bool $get_as_float ]): mixed'
-	},
-	mktime: {
-		description: 'Get Unix timestamp for a date',
-		signature: '([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]]): int'
-	},
-	strftime: {
-		description: 'Format a local time/date according to locale settings',
-		signature: '( string $format [, int $timestamp = time() ]): string'
-	},
-	strptime: {
-		description: 'Parse a time/date generated with strftime',
-		signature: '( string $date , string $format ): array'
-	},
-	strtotime: {
-		description: 'Parse about any English textual datetime description into a Unix timestamp',
-		signature: '( string $time [, int $now = time() ]): int'
-	},
-	time: {
-		description: 'Return current Unix timestamp',
-		signature: '(void): int'
-	},
-	timezone_name_from_abbr: {
-		description: 'Returns the timezone name from abbreviation',
-		signature: '( string $abbr [, int $gmtOffset = -1 [, int $isdst = -1 ]]): string'
-	},
-	timezone_version_get: {
-		description: 'Gets the version of the timezonedb',
-		signature: '(void): string'
-	},
-	chdir: {
-		description: 'Change directory',
-		signature: '( string $directory ): bool'
-	},
-	chroot: {
-		description: 'Change the root directory',
-		signature: '( string $directory ): bool'
-	},
-	closedir: {
-		description: 'Close directory handle',
-		signature: '([ resource $dir_handle ]): void'
-	},
-	dir: {
-		description: 'Return an instance of the Directory class',
-		signature: '( string $directory [, resource $context ]): Directory'
-	},
-	getcwd: {
-		description: 'Gets the current working directory',
-		signature: '(void): string'
-	},
-	opendir: {
-		description: 'Open directory handle',
-		signature: '( string $path [, resource $context ]): resource'
-	},
-	readdir: {
-		description: 'Read entry from directory handle',
-		signature: '([ resource $dir_handle ]): string'
-	},
-	rewinddir: {
-		description: 'Rewind directory handle',
-		signature: '([ resource $dir_handle ]): void'
-	},
-	scandir: {
-		description: 'List files and directories inside the specified path',
-		signature: '( string $directory [, int $sorting_order = SCANDIR_SORT_ASCENDING [, resource $context ]]): array'
-	},
-	finfo_buffer: {
-		description: 'Return information about a string buffer',
-		signature: '( resource $finfo , string $string [, int $options = FILEINFO_NONE [, resource $context ]]): string'
-	},
-	finfo_close: {
-		description: 'Close fileinfo resource',
-		signature: '( resource $finfo ): bool'
-	},
-	finfo_file: {
-		description: 'Return information about a file',
-		signature: '( resource $finfo , string $file_name [, int $options = FILEINFO_NONE [, resource $context ]]): string'
-	},
-	finfo_open: {
-		description: 'Create a new fileinfo resource',
-		signature: '([ int $options = FILEINFO_NONE [, string $magic_file ]]): resource'
-	},
-	finfo_set_flags: {
-		description: 'Set libmagic configuration options',
-		signature: '( resource $finfo , int $options ): bool'
-	},
-	mime_content_type: {
-		description: 'Detect MIME Content-type for a file',
-		signature: '( string $filename ): string'
-	},
-	basename: {
-		description: 'Returns trailing name component of path',
-		signature: '( string $path [, string $suffix ]): string'
-	},
-	chgrp: {
-		description: 'Changes file group',
-		signature: '( string $filename , mixed $group ): bool'
-	},
-	chmod: {
-		description: 'Changes file mode',
-		signature: '( string $filename , int $mode ): bool'
-	},
-	chown: {
-		description: 'Changes file owner',
-		signature: '( string $filename , mixed $user ): bool'
-	},
-	clearstatcache: {
-		description: 'Clears file status cache',
-		signature: '([ bool $clear_realpath_cache [, string $filename ]]): void'
-	},
-	copy: {
-		description: 'Copies file',
-		signature: '( string $source , string $dest [, resource $context ]): bool'
-	},
-	delete: {
-		description: 'See unlink or unset',
-	},
-	dirname: {
-		description: 'Returns a parent directory\'s path',
-		signature: '( string $path [, int $levels = 1 ]): string'
-	},
-	disk_free_space: {
-		description: 'Returns available space on filesystem or disk partition',
-		signature: '( string $directory ): float'
-	},
-	disk_total_space: {
-		description: 'Returns the total size of a filesystem or disk partition',
-		signature: '( string $directory ): float'
-	},
-	diskfreespace: {
-		description: 'Alias of disk_free_space',
-	},
-	fclose: {
-		description: 'Closes an open file pointer',
-		signature: '( resource $handle ): bool'
-	},
-	feof: {
-		description: 'Tests for end-of-file on a file pointer',
-		signature: '( resource $handle ): bool'
-	},
-	fflush: {
-		description: 'Flushes the output to a file',
-		signature: '( resource $handle ): bool'
-	},
-	fgetc: {
-		description: 'Gets character from file pointer',
-		signature: '( resource $handle ): string'
-	},
-	fgetcsv: {
-		description: 'Gets line from file pointer and parse for CSV fields',
-		signature: '( resource $handle [, int $length = 0 [, string $delimiter = "," [, string $enclosure = \'"\' [, string $escape = "\\" ]]]]): array'
-	},
-	fgets: {
-		description: 'Gets line from file pointer',
-		signature: '( resource $handle [, int $length ]): string'
-	},
-	fgetss: {
-		description: 'Gets line from file pointer and strip HTML tags',
-		signature: '( resource $handle [, int $length [, string $allowable_tags ]]): string'
-	},
-	file_exists: {
-		description: 'Checks whether a file or directory exists',
-		signature: '( string $filename ): bool'
-	},
-	file_get_contents: {
-		description: 'Reads entire file into a string',
-		signature: '( string $filename [, bool $use_include_path [, resource $context [, int $offset = 0 [, int $maxlen ]]]]): string'
-	},
-	file_put_contents: {
-		description: 'Write data to a file',
-		signature: '( string $filename , mixed $data [, int $flags = 0 [, resource $context ]]): int'
-	},
-	file: {
-		description: 'Reads entire file into an array',
-		signature: '( string $filename [, int $flags = 0 [, resource $context ]]): array'
-	},
-	fileatime: {
-		description: 'Gets last access time of file',
-		signature: '( string $filename ): int'
-	},
-	filectime: {
-		description: 'Gets inode change time of file',
-		signature: '( string $filename ): int'
-	},
-	filegroup: {
-		description: 'Gets file group',
-		signature: '( string $filename ): int'
-	},
-	fileinode: {
-		description: 'Gets file inode',
-		signature: '( string $filename ): int'
-	},
-	filemtime: {
-		description: 'Gets file modification time',
-		signature: '( string $filename ): int'
-	},
-	fileowner: {
-		description: 'Gets file owner',
-		signature: '( string $filename ): int'
-	},
-	fileperms: {
-		description: 'Gets file permissions',
-		signature: '( string $filename ): int'
-	},
-	filesize: {
-		description: 'Gets file size',
-		signature: '( string $filename ): int'
-	},
-	filetype: {
-		description: 'Gets file type',
-		signature: '( string $filename ): string'
-	},
-	flock: {
-		description: 'Portable advisory file locking',
-		signature: '( resource $handle , int $operation [, int $wouldblock ]): bool'
-	},
-	fnmatch: {
-		description: 'Match filename against a pattern',
-		signature: '( string $pattern , string $string [, int $flags = 0 ]): bool'
-	},
-	fopen: {
-		description: 'Opens file or URL',
-		signature: '( string $filename , string $mode [, bool $use_include_path [, resource $context ]]): resource'
-	},
-	fpassthru: {
-		description: 'Output all remaining data on a file pointer',
-		signature: '( resource $handle ): int'
-	},
-	fputcsv: {
-		description: 'Format line as CSV and write to file pointer',
-		signature: '( resource $handle , array $fields [, string $delimiter = "," [, string $enclosure = \'"\' [, string $escape_char = "\\" ]]]): int'
-	},
-	fputs: {
-		description: 'Alias of fwrite',
-	},
-	fread: {
-		description: 'Binary-safe file read',
-		signature: '( resource $handle , int $length ): string'
-	},
-	fscanf: {
-		description: 'Parses input from a file according to a format',
-		signature: '( resource $handle , string $format [, mixed $... ]): mixed'
-	},
-	fseek: {
-		description: 'Seeks on a file pointer',
-		signature: '( resource $handle , int $offset [, int $whence = SEEK_SET ]): int'
-	},
-	fstat: {
-		description: 'Gets information about a file using an open file pointer',
-		signature: '( resource $handle ): array'
-	},
-	ftell: {
-		description: 'Returns the current position of the file read/write pointer',
-		signature: '( resource $handle ): int'
-	},
-	ftruncate: {
-		description: 'Truncates a file to a given length',
-		signature: '( resource $handle , int $size ): bool'
-	},
-	fwrite: {
-		description: 'Binary-safe file write',
-		signature: '( resource $handle , string $string [, int $length ]): int'
-	},
-	glob: {
-		description: 'Find pathnames matching a pattern',
-		signature: '( string $pattern [, int $flags = 0 ]): array'
-	},
-	is_dir: {
-		description: 'Tells whether the filename is a directory',
-		signature: '( string $filename ): bool'
-	},
-	is_executable: {
-		description: 'Tells whether the filename is executable',
-		signature: '( string $filename ): bool'
-	},
-	is_file: {
-		description: 'Tells whether the filename is a regular file',
-		signature: '( string $filename ): bool'
-	},
-	is_link: {
-		description: 'Tells whether the filename is a symbolic link',
-		signature: '( string $filename ): bool'
-	},
-	is_readable: {
-		description: 'Tells whether a file exists and is readable',
-		signature: '( string $filename ): bool'
-	},
-	is_uploaded_file: {
-		description: 'Tells whether the file was uploaded via HTTP POST',
-		signature: '( string $filename ): bool'
-	},
-	is_writable: {
-		description: 'Tells whether the filename is writable',
-		signature: '( string $filename ): bool'
-	},
-	is_writeable: {
-		description: 'Alias of is_writable',
-	},
-	lchgrp: {
-		description: 'Changes group ownership of symlink',
-		signature: '( string $filename , mixed $group ): bool'
-	},
-	lchown: {
-		description: 'Changes user ownership of symlink',
-		signature: '( string $filename , mixed $user ): bool'
-	},
-	link: {
-		description: 'Create a hard link',
-		signature: '( string $target , string $link ): bool'
-	},
-	linkinfo: {
-		description: 'Gets information about a link',
-		signature: '( string $path ): int'
-	},
-	lstat: {
-		description: 'Gives information about a file or symbolic link',
-		signature: '( string $filename ): array'
-	},
-	mkdir: {
-		description: 'Makes directory',
-		signature: '( string $pathname [, int $mode = 0777 [, bool $recursive [, resource $context ]]]): bool'
-	},
-	move_uploaded_file: {
-		description: 'Moves an uploaded file to a new location',
-		signature: '( string $filename , string $destination ): bool'
-	},
-	parse_ini_file: {
-		description: 'Parse a configuration file',
-		signature: '( string $filename [, bool $process_sections [, int $scanner_mode = INI_SCANNER_NORMAL ]]): array'
-	},
-	parse_ini_string: {
-		description: 'Parse a configuration string',
-		signature: '( string $ini [, bool $process_sections [, int $scanner_mode = INI_SCANNER_NORMAL ]]): array'
-	},
-	pathinfo: {
-		description: 'Returns information about a file path',
-		signature: '( string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]): mixed'
-	},
-	pclose: {
-		description: 'Closes process file pointer',
-		signature: '( resource $handle ): int'
-	},
-	popen: {
-		description: 'Opens process file pointer',
-		signature: '( string $command , string $mode ): resource'
-	},
-	readfile: {
-		description: 'Outputs a file',
-		signature: '( string $filename [, bool $use_include_path [, resource $context ]]): int'
-	},
-	readlink: {
-		description: 'Returns the target of a symbolic link',
-		signature: '( string $path ): string'
-	},
-	realpath_cache_get: {
-		description: 'Get realpath cache entries',
-		signature: '(void): array'
-	},
-	realpath_cache_size: {
-		description: 'Get realpath cache size',
-		signature: '(void): int'
-	},
-	realpath: {
-		description: 'Returns canonicalized absolute pathname',
-		signature: '( string $path ): string'
-	},
-	rename: {
-		description: 'Renames a file or directory',
-		signature: '( string $oldname , string $newname [, resource $context ]): bool'
-	},
-	rewind: {
-		description: 'Rewind the position of a file pointer',
-		signature: '( resource $handle ): bool'
-	},
-	rmdir: {
-		description: 'Removes directory',
-		signature: '( string $dirname [, resource $context ]): bool'
-	},
-	set_file_buffer: {
-		description: 'Alias of stream_set_write_buffer',
-	},
-	stat: {
-		description: 'Gives information about a file',
-		signature: '( string $filename ): array'
-	},
-	symlink: {
-		description: 'Creates a symbolic link',
-		signature: '( string $target , string $link ): bool'
-	},
-	tempnam: {
-		description: 'Create file with unique file name',
-		signature: '( string $dir , string $prefix ): string'
-	},
-	tmpfile: {
-		description: 'Creates a temporary file',
-		signature: '(void): resource'
-	},
-	touch: {
-		description: 'Sets access and modification time of file',
-		signature: '( string $filename [, int $time = time() [, int $atime ]]): bool'
-	},
-	umask: {
-		description: 'Changes the current umask',
-		signature: '([ int $mask ]): int'
-	},
-	unlink: {
-		description: 'Deletes a file',
-		signature: '( string $filename [, resource $context ]): bool'
-	},
-	iconv_get_encoding: {
-		description: 'Retrieve internal configuration variables of iconv extension',
-		signature: '([ string $type = "all" ]): mixed'
-	},
-	iconv_mime_decode_headers: {
-		description: 'Decodes multiple MIME header fields at once',
-		signature: '( string $encoded_headers [, int $mode = 0 [, string $charset = ini_get("iconv.internal_encoding") ]]): array'
-	},
-	iconv_mime_decode: {
-		description: 'Decodes a MIME header field',
-		signature: '( string $encoded_header [, int $mode = 0 [, string $charset = ini_get("iconv.internal_encoding") ]]): string'
-	},
-	iconv_mime_encode: {
-		description: 'Composes a MIME header field',
-		signature: '( string $field_name , string $field_value [, array $preferences ]): string'
-	},
-	iconv_set_encoding: {
-		description: 'Set current setting for character encoding conversion',
-		signature: '( string $type , string $charset ): bool'
-	},
-	iconv_strlen: {
-		description: 'Returns the character count of string',
-		signature: '( string $str [, string $charset = ini_get("iconv.internal_encoding") ]): int'
-	},
-	iconv_strpos: {
-		description: 'Finds position of first occurrence of a needle within a haystack',
-		signature: '( string $haystack , string $needle [, int $offset = 0 [, string $charset = ini_get("iconv.internal_encoding") ]]): int'
-	},
-	iconv_strrpos: {
-		description: 'Finds the last occurrence of a needle within a haystack',
-		signature: '( string $haystack , string $needle [, string $charset = ini_get("iconv.internal_encoding") ]): int'
-	},
-	iconv_substr: {
-		description: 'Cut out part of a string',
-		signature: '( string $str , int $offset [, int $length = iconv_strlen($str, $charset) [, string $charset = ini_get("iconv.internal_encoding") ]]): string'
-	},
-	iconv: {
-		description: 'Convert string to requested character encoding',
-		signature: '( string $in_charset , string $out_charset , string $str ): string'
-	},
-	ob_iconv_handler: {
-		description: 'Convert character encoding as output buffer handler',
-		signature: '( string $contents , int $status ): string'
-	},
-	collator_asort: {
-		description: 'Sort array maintaining index association',
-		signature: '( array $arr [, int $sort_flag , Collator $coll ]): bool'
-	},
-	collator_compare: {
-		description: 'Compare two Unicode strings',
-		signature: '( string $str1 , string $str2 , Collator $coll ): int'
-	},
-	collator_create: {
-		description: 'Create a collator',
-		signature: '( string $locale ): Collator'
-	},
-	collator_get_attribute: {
-		description: 'Get collation attribute value',
-		signature: '( int $attr , Collator $coll ): int'
-	},
-	collator_get_error_code: {
-		description: 'Get collator\'s last error code',
-		signature: '( Collator $coll ): int'
-	},
-	collator_get_error_message: {
-		description: 'Get text for collator\'s last error code',
-		signature: '( Collator $coll ): string'
-	},
-	collator_get_locale: {
-		description: 'Get the locale name of the collator',
-		signature: '( int $type , Collator $coll ): string'
-	},
-	collator_get_sort_key: {
-		description: 'Get sorting key for a string',
-		signature: '( string $str , Collator $coll ): string'
-	},
-	collator_get_strength: {
-		description: 'Get current collation strength',
-		signature: '( Collator $coll ): int'
-	},
-	collator_set_attribute: {
-		description: 'Set collation attribute',
-		signature: '( int $attr , int $val , Collator $coll ): bool'
-	},
-	collator_set_strength: {
-		description: 'Set collation strength',
-		signature: '( int $strength , Collator $coll ): bool'
-	},
-	collator_sort_with_sort_keys: {
-		description: 'Sort array using specified collator and sort keys',
-		signature: '( array $arr , Collator $coll ): bool'
-	},
-	collator_sort: {
-		description: 'Sort array using specified collator',
-		signature: '( array $arr [, int $sort_flag , Collator $coll ]): bool'
-	},
-	numfmt_create: {
-		description: 'Create a number formatter',
-		signature: '( string $locale , int $style [, string $pattern ]): NumberFormatter'
-	},
-	numfmt_format_currency: {
-		description: 'Format a currency value',
-		signature: '( float $value , string $currency , NumberFormatter $fmt ): string'
-	},
-	numfmt_format: {
-		description: 'Format a number',
-		signature: '( number $value [, int $type , NumberFormatter $fmt ]): string'
-	},
-	numfmt_get_attribute: {
-		description: 'Get an attribute',
-		signature: '( int $attr , NumberFormatter $fmt ): int'
-	},
-	numfmt_get_error_code: {
-		description: 'Get formatter\'s last error code',
-		signature: '( NumberFormatter $fmt ): int'
-	},
-	numfmt_get_error_message: {
-		description: 'Get formatter\'s last error message',
-		signature: '( NumberFormatter $fmt ): string'
-	},
-	numfmt_get_locale: {
-		description: 'Get formatter locale',
-		signature: '([ int $type , NumberFormatter $fmt ]): string'
-	},
-	numfmt_get_pattern: {
-		description: 'Get formatter pattern',
-		signature: '( NumberFormatter $fmt ): string'
-	},
-	numfmt_get_symbol: {
-		description: 'Get a symbol value',
-		signature: '( int $attr , NumberFormatter $fmt ): string'
-	},
-	numfmt_get_text_attribute: {
-		description: 'Get a text attribute',
-		signature: '( int $attr , NumberFormatter $fmt ): string'
-	},
-	numfmt_parse_currency: {
-		description: 'Parse a currency number',
-		signature: '( string $value , string $currency [, int $position , NumberFormatter $fmt ]): float'
-	},
-	numfmt_parse: {
-		description: 'Parse a number',
-		signature: '( string $value [, int $type [, int $position , NumberFormatter $fmt ]]): mixed'
-	},
-	numfmt_set_attribute: {
-		description: 'Set an attribute',
-		signature: '( int $attr , int $value , NumberFormatter $fmt ): bool'
-	},
-	numfmt_set_pattern: {
-		description: 'Set formatter pattern',
-		signature: '( string $pattern , NumberFormatter $fmt ): bool'
-	},
-	numfmt_set_symbol: {
-		description: 'Set a symbol value',
-		signature: '( int $attr , string $value , NumberFormatter $fmt ): bool'
-	},
-	numfmt_set_text_attribute: {
-		description: 'Set a text attribute',
-		signature: '( int $attr , string $value , NumberFormatter $fmt ): bool'
-	},
-	locale_accept_from_http: {
-		description: 'Tries to find out best available locale based on HTTP "Accept-Language" header',
-		signature: '( string $header ): string'
-	},
-	locale_canonicalize: {
-		description: 'Canonicalize the locale string',
-		signature: '( string $locale ): string'
-	},
-	locale_compose: {
-		description: 'Returns a correctly ordered and delimited locale ID',
-		signature: '( array $subtags ): string'
-	},
-	locale_filter_matches: {
-		description: 'Checks if a language tag filter matches with locale',
-		signature: '( string $langtag , string $locale [, bool $canonicalize ]): bool'
-	},
-	locale_get_all_variants: {
-		description: 'Gets the variants for the input locale',
-		signature: '( string $locale ): array'
-	},
-	locale_get_default: {
-		description: 'Gets the default locale value from the INTL global \'default_locale\'',
-		signature: '(void): string'
-	},
-	locale_get_display_language: {
-		description: 'Returns an appropriately localized display name for language of the inputlocale',
-		signature: '( string $locale [, string $in_locale ]): string'
-	},
-	locale_get_display_name: {
-		description: 'Returns an appropriately localized display name for the input locale',
-		signature: '( string $locale [, string $in_locale ]): string'
-	},
-	locale_get_display_region: {
-		description: 'Returns an appropriately localized display name for region of the input locale',
-		signature: '( string $locale [, string $in_locale ]): string'
-	},
-	locale_get_display_script: {
-		description: 'Returns an appropriately localized display name for script of the input locale',
-		signature: '( string $locale [, string $in_locale ]): string'
-	},
-	locale_get_display_variant: {
-		description: 'Returns an appropriately localized display name for variants of the input locale',
-		signature: '( string $locale [, string $in_locale ]): string'
-	},
-	locale_get_keywords: {
-		description: 'Gets the keywords for the input locale',
-		signature: '( string $locale ): array'
-	},
-	locale_get_primary_language: {
-		description: 'Gets the primary language for the input locale',
-		signature: '( string $locale ): string'
-	},
-	locale_get_region: {
-		description: 'Gets the region for the input locale',
-		signature: '( string $locale ): string'
-	},
-	locale_get_script: {
-		description: 'Gets the script for the input locale',
-		signature: '( string $locale ): string'
-	},
-	locale_lookup: {
-		description: 'Searches the language tag list for the best match to the language',
-		signature: '( array $langtag , string $locale [, bool $canonicalize [, string $default ]]): string'
-	},
-	locale_parse: {
-		description: 'Returns a key-value array of locale ID subtag elements',
-		signature: '( string $locale ): array'
-	},
-	locale_set_default: {
-		description: 'Sets the default runtime locale',
-		signature: '( string $locale ): bool'
-	},
-	normalizer_get_raw_decomposition: {
-		description: 'Gets the Decomposition_Mapping property for the given UTF-8 encoded code point',
-		signature: '( string $input ): string'
-	},
-	normalizer_is_normalized: {
-		description: 'Checks if the provided string is already in the specified normalization   form',
-		signature: '( string $input [, int $form = Normalizer::FORM_C ]): bool'
-	},
-	normalizer_normalize: {
-		description: 'Normalizes the input provided and returns the normalized string',
-		signature: '( string $input [, int $form = Normalizer::FORM_C ]): string'
-	},
-	msgfmt_create: {
-		description: 'Constructs a new Message Formatter',
-		signature: '( string $locale , string $pattern ): MessageFormatter'
-	},
-	msgfmt_format_message: {
-		description: 'Quick format message',
-		signature: '( string $locale , string $pattern , array $args ): string'
-	},
-	msgfmt_format: {
-		description: 'Format the message',
-		signature: '( array $args , MessageFormatter $fmt ): string'
-	},
-	msgfmt_get_error_code: {
-		description: 'Get the error code from last operation',
-		signature: '( MessageFormatter $fmt ): int'
-	},
-	msgfmt_get_error_message: {
-		description: 'Get the error text from the last operation',
-		signature: '( MessageFormatter $fmt ): string'
-	},
-	msgfmt_get_locale: {
-		description: 'Get the locale for which the formatter was created',
-		signature: '( NumberFormatter $formatter ): string'
-	},
-	msgfmt_get_pattern: {
-		description: 'Get the pattern used by the formatter',
-		signature: '( MessageFormatter $fmt ): string'
-	},
-	msgfmt_parse_message: {
-		description: 'Quick parse input string',
-		signature: '( string $locale , string $pattern , string $source , string $value ): array'
-	},
-	msgfmt_parse: {
-		description: 'Parse input string according to pattern',
-		signature: '( string $value , MessageFormatter $fmt ): array'
-	},
-	msgfmt_set_pattern: {
-		description: 'Set the pattern used by the formatter',
-		signature: '( string $pattern , MessageFormatter $fmt ): bool'
-	},
-	intlcal_get_error_code: {
-		description: 'Get last error code on the object',
-		signature: '( IntlCalendar $calendar ): int'
-	},
-	intlcal_get_error_message: {
-		description: 'Get last error message on the object',
-		signature: '( IntlCalendar $calendar ): string'
-	},
-	intltz_get_error_code: {
-		description: 'Get last error code on the object',
-		signature: '(void): int'
-	},
-	intltz_get_error_message: {
-		description: 'Get last error message on the object',
-		signature: '(void): string'
-	},
-	datefmt_create: {
-		description: 'Create a date formatter',
-		signature: '( string $locale , int $datetype , int $timetype [, mixed $timezone = NULL [, mixed $calendar = NULL [, string $pattern = "" ]]]): IntlDateFormatter'
-	},
-	datefmt_format: {
-		description: 'Format the date/time value as a string',
-		signature: '( mixed $value , IntlDateFormatter $fmt ): string'
-	},
-	datefmt_format_object: {
-		description: 'Formats an object',
-		signature: '( object $object [, mixed $format = NULL [, string $locale = NULL ]]): string'
-	},
-	datefmt_get_calendar: {
-		description: 'Get the calendar type used for the IntlDateFormatter',
-		signature: '( IntlDateFormatter $fmt ): int'
-	},
-	datefmt_get_datetype: {
-		description: 'Get the datetype used for the IntlDateFormatter',
-		signature: '( IntlDateFormatter $fmt ): int'
-	},
-	datefmt_get_error_code: {
-		description: 'Get the error code from last operation',
-		signature: '( IntlDateFormatter $fmt ): int'
-	},
-	datefmt_get_error_message: {
-		description: 'Get the error text from the last operation',
-		signature: '( IntlDateFormatter $fmt ): string'
-	},
-	datefmt_get_locale: {
-		description: 'Get the locale used by formatter',
-		signature: '([ int $which , IntlDateFormatter $fmt ]): string'
-	},
-	datefmt_get_pattern: {
-		description: 'Get the pattern used for the IntlDateFormatter',
-		signature: '( IntlDateFormatter $fmt ): string'
-	},
-	datefmt_get_timetype: {
-		description: 'Get the timetype used for the IntlDateFormatter',
-		signature: '( IntlDateFormatter $fmt ): int'
-	},
-	datefmt_get_timezone_id: {
-		description: 'Get the timezone-id used for the IntlDateFormatter',
-		signature: '( IntlDateFormatter $fmt ): string'
-	},
-	datefmt_get_calendar_object: {
-		description: 'Get copy of formatterʼs calendar object',
-		signature: '(void): IntlCalendar'
-	},
-	datefmt_get_timezone: {
-		description: 'Get formatterʼs timezone',
-		signature: '(void): IntlTimeZone'
-	},
-	datefmt_is_lenient: {
-		description: 'Get the lenient used for the IntlDateFormatter',
-		signature: '( IntlDateFormatter $fmt ): bool'
-	},
-	datefmt_localtime: {
-		description: 'Parse string to a field-based time value',
-		signature: '( string $value [, int $position , IntlDateFormatter $fmt ]): array'
-	},
-	datefmt_parse: {
-		description: 'Parse string to a timestamp value',
-		signature: '( string $value [, int $position , IntlDateFormatter $fmt ]): int'
-	},
-	datefmt_set_calendar: {
-		description: 'Sets the calendar type used by the formatter',
-		signature: '( mixed $which , IntlDateFormatter $fmt ): bool'
-	},
-	datefmt_set_lenient: {
-		description: 'Set the leniency of the parser',
-		signature: '( bool $lenient , IntlDateFormatter $fmt ): bool'
-	},
-	datefmt_set_pattern: {
-		description: 'Set the pattern used for the IntlDateFormatter',
-		signature: '( string $pattern , IntlDateFormatter $fmt ): bool'
-	},
-	datefmt_set_timezone_id: {
-		description: 'Sets the time zone to use',
-		signature: '( string $zone , IntlDateFormatter $fmt ): bool'
-	},
-	datefmt_set_timezone: {
-		description: 'Sets formatterʼs timezone',
-		signature: '( mixed $zone , IntlDateFormatter $fmt ): bool'
-	},
-	resourcebundle_count: {
-		description: 'Get number of elements in the bundle',
-		signature: '( ResourceBundle $r ): int'
-	},
-	resourcebundle_create: {
-		description: 'Create a resource bundle',
-		signature: '( string $locale , string $bundlename [, bool $fallback ]): ResourceBundle'
-	},
-	resourcebundle_get_error_code: {
-		description: 'Get bundle\'s last error code',
-		signature: '( ResourceBundle $r ): int'
-	},
-	resourcebundle_get_error_message: {
-		description: 'Get bundle\'s last error message',
-		signature: '( ResourceBundle $r ): string'
-	},
-	resourcebundle_get: {
-		description: 'Get data from the bundle',
-		signature: '( string|int $index [, bool $fallback , ResourceBundle $r ]): mixed'
-	},
-	resourcebundle_locales: {
-		description: 'Get supported locales',
-		signature: '( string $bundlename ): array'
-	},
-	transliterator_create: {
-		description: 'Create a transliterator',
-		signature: '( string $id [, int $direction ]): Transliterator'
-	},
-	transliterator_create_from_rules: {
-		description: 'Create transliterator from rules',
-		signature: '( string $rules [, int $direction , string $id ]): Transliterator'
-	},
-	transliterator_create_inverse: {
-		description: 'Create an inverse transliterator',
-		signature: '(void): Transliterator'
-	},
-	transliterator_get_error_code: {
-		description: 'Get last error code',
-		signature: '(void): int'
-	},
-	transliterator_get_error_message: {
-		description: 'Get last error message',
-		signature: '(void): string'
-	},
-	transliterator_list_ids: {
-		description: 'Get transliterator IDs',
-		signature: '(void): array'
-	},
-	transliterator_transliterate: {
-		description: 'Transliterate a string',
-		signature: '( string $subject [, int $start [, int $end , mixed $transliterator ]]): string'
-	},
-	intl_get_error_code: {
-		description: 'Get the last error code',
-		signature: '(void): int'
-	},
-	intl_get_error_message: {
-		description: 'Get description of the last error',
-		signature: '(void): string'
-	},
-	grapheme_extract: {
-		description: 'Function to extract a sequence of default grapheme clusters from a text buffer, which must be encoded in UTF-8',
-		signature: '( string $haystack , int $size [, int $extract_type [, int $start = 0 [, int $next ]]]): string'
-	},
-	grapheme_stripos: {
-		description: 'Find position (in grapheme units) of first occurrence of a case-insensitive string',
-		signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
-	},
-	grapheme_stristr: {
-		description: 'Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack',
-		signature: '( string $haystack , string $needle [, bool $before_needle ]): string'
-	},
-	grapheme_strlen: {
-		description: 'Get string length in grapheme units',
-		signature: '( string $input ): int'
-	},
-	grapheme_strpos: {
-		description: 'Find position (in grapheme units) of first occurrence of a string',
-		signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
-	},
-	grapheme_strripos: {
-		description: 'Find position (in grapheme units) of last occurrence of a case-insensitive string',
-		signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
-	},
-	grapheme_strrpos: {
-		description: 'Find position (in grapheme units) of last occurrence of a string',
-		signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
-	},
-	grapheme_strstr: {
-		description: 'Returns part of haystack string from the first occurrence of needle to the end of haystack',
-		signature: '( string $haystack , string $needle [, bool $before_needle ]): string'
-	},
-	grapheme_substr: {
-		description: 'Return part of a string',
-		signature: '( string $string , int $start [, int $length ]): string'
-	},
-	idn_to_ascii: {
-		description: 'Convert domain name to IDNA ASCII form',
-		signature: '( string $domain [, int $options = IDNA_DEFAULT [, int $variant = INTL_IDNA_VARIANT_UTS46 [, array $idna_info ]]]): string'
-	},
-	idn_to_utf8: {
-		description: 'Convert domain name from IDNA ASCII to Unicode',
-		signature: '( string $domain [, int $options = IDNA_DEFAULT [, int $variant = INTL_IDNA_VARIANT_UTS46 [, array $idna_info ]]]): string'
-	},
-	intl_error_name: {
-		description: 'Get symbolic name for a given error code',
-		signature: '( int $error_code ): string'
-	},
-	intl_is_failure: {
-		description: 'Check whether the given error code indicates failure',
-		signature: '( int $error_code ): bool'
-	},
-	mb_check_encoding: {
-		description: 'Check if the string is valid for the specified encoding',
-		signature: '([ string $var [, string $encoding = mb_internal_encoding() ]]): bool'
-	},
-	mb_chr: {
-		description: 'Get a specific character',
-		signature: '( int $cp [, string $encoding ]): string'
-	},
-	mb_convert_case: {
-		description: 'Perform case folding on a string',
-		signature: '( string $str , int $mode [, string $encoding = mb_internal_encoding() ]): string'
-	},
-	mb_convert_encoding: {
-		description: 'Convert character encoding',
-		signature: '( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ]): string'
-	},
-	mb_convert_kana: {
-		description: 'Convert "kana" one from another ("zen-kaku", "han-kaku" and more)',
-		signature: '( string $str [, string $option = "KV" [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_convert_variables: {
-		description: 'Convert character code in variable(s)',
-		signature: '( string $to_encoding , mixed $from_encoding , mixed $vars [, mixed $... ]): string'
-	},
-	mb_decode_mimeheader: {
-		description: 'Decode string in MIME header field',
-		signature: '( string $str ): string'
-	},
-	mb_decode_numericentity: {
-		description: 'Decode HTML numeric string reference to character',
-		signature: '( string $str , array $convmap [, string $encoding = mb_internal_encoding() [, bool $is_hex ]]): string'
-	},
-	mb_detect_encoding: {
-		description: 'Detect character encoding',
-		signature: '( string $str [, mixed $encoding_list = mb_detect_order() [, bool $strict ]]): string'
-	},
-	mb_detect_order: {
-		description: 'Set/Get character encoding detection order',
-		signature: '([ mixed $encoding_list = mb_detect_order() ]): mixed'
-	},
-	mb_encode_mimeheader: {
-		description: 'Encode string for MIME header',
-		signature: '( string $str [, string $charset = determined by mb_language() [, string $transfer_encoding = "B" [, string $linefeed = "\r\n" [, int $indent = 0 ]]]]): string'
-	},
-	mb_encode_numericentity: {
-		description: 'Encode character to HTML numeric string reference',
-		signature: '( string $str , array $convmap [, string $encoding = mb_internal_encoding() [, bool $is_hex ]]): string'
-	},
-	mb_encoding_aliases: {
-		description: 'Get aliases of a known encoding type',
-		signature: '( string $encoding ): array'
-	},
-	mb_ereg_match: {
-		description: 'Regular expression match for multibyte string',
-		signature: '( string $pattern , string $string [, string $option = "msr" ]): bool'
-	},
-	mb_ereg_replace_callback: {
-		description: 'Perform a regular expression search and replace with multibyte support using a callback',
-		signature: '( string $pattern , callable $callback , string $string [, string $option = "msr" ]): string'
-	},
-	mb_ereg_replace: {
-		description: 'Replace regular expression with multibyte support',
-		signature: '( string $pattern , string $replacement , string $string [, string $option = "msr" ]): string'
-	},
-	mb_ereg_search_getpos: {
-		description: 'Returns start point for next regular expression match',
-		signature: '(void): int'
-	},
-	mb_ereg_search_getregs: {
-		description: 'Retrieve the result from the last multibyte regular expression match',
-		signature: '(void): array'
-	},
-	mb_ereg_search_init: {
-		description: 'Setup string and regular expression for a multibyte regular expression match',
-		signature: '( string $string [, string $pattern [, string $option = "msr" ]]): bool'
-	},
-	mb_ereg_search_pos: {
-		description: 'Returns position and length of a matched part of the multibyte regular expression for a predefined multibyte string',
-		signature: '([ string $pattern [, string $option = "ms" ]]): array'
-	},
-	mb_ereg_search_regs: {
-		description: 'Returns the matched part of a multibyte regular expression',
-		signature: '([ string $pattern [, string $option = "ms" ]]): array'
-	},
-	mb_ereg_search_setpos: {
-		description: 'Set start point of next regular expression match',
-		signature: '( int $position ): bool'
-	},
-	mb_ereg_search: {
-		description: 'Multibyte regular expression match for predefined multibyte string',
-		signature: '([ string $pattern [, string $option = "ms" ]]): bool'
-	},
-	mb_ereg: {
-		description: 'Regular expression match with multibyte support',
-		signature: '( string $pattern , string $string [, array $regs ]): int'
-	},
-	mb_eregi_replace: {
-		description: 'Replace regular expression with multibyte support ignoring case',
-		signature: '( string $pattern , string $replace , string $string [, string $option = "msri" ]): string'
-	},
-	mb_eregi: {
-		description: 'Regular expression match ignoring case with multibyte support',
-		signature: '( string $pattern , string $string [, array $regs ]): int'
-	},
-	mb_get_info: {
-		description: 'Get internal settings of mbstring',
-		signature: '([ string $type = "all" ]): mixed'
-	},
-	mb_http_input: {
-		description: 'Detect HTTP input character encoding',
-		signature: '([ string $type = "" ]): mixed'
-	},
-	mb_http_output: {
-		description: 'Set/Get HTTP output character encoding',
-		signature: '([ string $encoding = mb_http_output() ]): mixed'
-	},
-	mb_internal_encoding: {
-		description: 'Set/Get internal character encoding',
-		signature: '([ string $encoding = mb_internal_encoding() ]): mixed'
-	},
-	mb_language: {
-		description: 'Set/Get current language',
-		signature: '([ string $language = mb_language() ]): mixed'
-	},
-	mb_list_encodings: {
-		description: 'Returns an array of all supported encodings',
-		signature: '(void): array'
-	},
-	mb_ord: {
-		description: 'Get code point of character',
-		signature: '( string $str [, string $encoding ]): int'
-	},
-	mb_output_handler: {
-		description: 'Callback function converts character encoding in output buffer',
-		signature: '( string $contents , int $status ): string'
-	},
-	mb_parse_str: {
-		description: 'Parse GET/POST/COOKIE data and set global variable',
-		signature: '( string $encoded_string [, array $result ]): array'
-	},
-	mb_preferred_mime_name: {
-		description: 'Get MIME charset string',
-		signature: '( string $encoding ): string'
-	},
-	mb_regex_encoding: {
-		description: 'Set/Get character encoding for multibyte regex',
-		signature: '([ string $encoding = mb_regex_encoding() ]): mixed'
-	},
-	mb_regex_set_options: {
-		description: 'Set/Get the default options for mbregex functions',
-		signature: '([ string $options = mb_regex_set_options() ]): string'
-	},
-	mb_scrub: {
-		description: 'Description',
-		signature: '( string $str [, string $encoding ]): string'
-	},
-	mb_send_mail: {
-		description: 'Send encoded mail',
-		signature: '( string $to , string $subject , string $message [, mixed $additional_headers [, string $additional_parameter ]]): bool'
-	},
-	mb_split: {
-		description: 'Split multibyte string using regular expression',
-		signature: '( string $pattern , string $string [, int $limit = -1 ]): array'
-	},
-	mb_strcut: {
-		description: 'Get part of string',
-		signature: '( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_strimwidth: {
-		description: 'Get truncated string with specified width',
-		signature: '( string $str , int $start , int $width [, string $trimmarker = "" [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_stripos: {
-		description: 'Finds position of first occurrence of a string within another, case insensitive',
-		signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): int'
-	},
-	mb_stristr: {
-		description: 'Finds first occurrence of a string within another, case insensitive',
-		signature: '( string $haystack , string $needle [, bool $before_needle [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_strlen: {
-		description: 'Get string length',
-		signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
-	},
-	mb_strpos: {
-		description: 'Find position of first occurrence of string in a string',
-		signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_strrchr: {
-		description: 'Finds the last occurrence of a character in a string within another',
-		signature: '( string $haystack , string $needle [, bool $part [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_strrichr: {
-		description: 'Finds the last occurrence of a character in a string within another, case insensitive',
-		signature: '( string $haystack , string $needle [, bool $part [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_strripos: {
-		description: 'Finds position of last occurrence of a string within another, case insensitive',
-		signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): int'
-	},
-	mb_strrpos: {
-		description: 'Find position of last occurrence of a string in a string',
-		signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): int'
-	},
-	mb_strstr: {
-		description: 'Finds first occurrence of a string within another',
-		signature: '( string $haystack , string $needle [, bool $before_needle [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	mb_strtolower: {
-		description: 'Make a string lowercase',
-		signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
-	},
-	mb_strtoupper: {
-		description: 'Make a string uppercase',
-		signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
-	},
-	mb_strwidth: {
-		description: 'Return width of string',
-		signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
-	},
-	mb_substitute_character: {
-		description: 'Set/Get substitution character',
-		signature: '([ mixed $substchar = mb_substitute_character() ]): integer'
-	},
-	mb_substr_count: {
-		description: 'Count the number of substring occurrences',
-		signature: '( string $haystack , string $needle [, string $encoding = mb_internal_encoding() ]): string'
-	},
-	mb_substr: {
-		description: 'Get part of string',
-		signature: '( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]]): string'
-	},
-	exif_imagetype: {
-		description: 'Determine the type of an image',
-		signature: '( string $filename ): int'
-	},
-	exif_read_data: {
-		description: 'Reads the EXIF headers from an image file',
-		signature: '( mixed $stream [, string $sections [, bool $arrays [, bool $thumbnail ]]]): array'
-	},
-	exif_tagname: {
-		description: 'Get the header name for an index',
-		signature: '( int $index ): string'
-	},
-	exif_thumbnail: {
-		description: 'Retrieve the embedded thumbnail of an image',
-		signature: '( mixed $stream [, int $width [, int $height [, int $imagetype ]]]): string'
-	},
-	read_exif_data: {
-		description: 'Alias of exif_read_data',
-	},
-	ezmlm_hash: {
-		description: 'Calculate the hash value needed by EZMLM',
-		signature: '( string $addr ): int'
-	},
-	mail: {
-		description: 'Send mail',
-		signature: '( string $to , string $subject , string $message [, mixed $additional_headers [, string $additional_parameters ]]): bool'
-	},
-	bcadd: {
-		description: 'Add two arbitrary precision numbers',
-		signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): string'
-	},
-	bccomp: {
-		description: 'Compare two arbitrary precision numbers',
-		signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): int'
-	},
-	bcdiv: {
-		description: 'Divide two arbitrary precision numbers',
-		signature: '( string $dividend , string $divisor [, int $scale = 0 ]): string'
-	},
-	bcmod: {
-		description: 'Get modulus of an arbitrary precision number',
-		signature: '( string $dividend , string $divisor [, int $scale = 0 ]): string'
-	},
-	bcmul: {
-		description: 'Multiply two arbitrary precision numbers',
-		signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): string'
-	},
-	bcpow: {
-		description: 'Raise an arbitrary precision number to another',
-		signature: '( string $base , string $exponent [, int $scale = 0 ]): string'
-	},
-	bcpowmod: {
-		description: 'Raise an arbitrary precision number to another, reduced by a specified modulus',
-		signature: '( string $base , string $exponent , string $modulus [, int $scale = 0 ]): string'
-	},
-	bcscale: {
-		description: 'Set or get default scale parameter for all bc math functions',
-		signature: '( int $scale ): int'
-	},
-	bcsqrt: {
-		description: 'Get the square root of an arbitrary precision number',
-		signature: '( string $operand [, int $scale = 0 ]): string'
-	},
-	bcsub: {
-		description: 'Subtract one arbitrary precision number from another',
-		signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): string'
-	},
-	abs: {
-		description: 'Absolute value',
-		signature: '( mixed $number ): number'
-	},
-	acos: {
-		description: 'Arc cosine',
-		signature: '( float $arg ): float'
-	},
-	acosh: {
-		description: 'Inverse hyperbolic cosine',
-		signature: '( float $arg ): float'
-	},
-	asin: {
-		description: 'Arc sine',
-		signature: '( float $arg ): float'
-	},
-	asinh: {
-		description: 'Inverse hyperbolic sine',
-		signature: '( float $arg ): float'
-	},
-	atan2: {
-		description: 'Arc tangent of two variables',
-		signature: '( float $y , float $x ): float'
-	},
-	atan: {
-		description: 'Arc tangent',
-		signature: '( float $arg ): float'
-	},
-	atanh: {
-		description: 'Inverse hyperbolic tangent',
-		signature: '( float $arg ): float'
-	},
-	base_convert: {
-		description: 'Convert a number between arbitrary bases',
-		signature: '( string $number , int $frombase , int $tobase ): string'
-	},
-	bindec: {
-		description: 'Binary to decimal',
-		signature: '( string $binary_string ): float'
-	},
-	ceil: {
-		description: 'Round fractions up',
-		signature: '( float $value ): float'
-	},
-	cos: {
-		description: 'Cosine',
-		signature: '( float $arg ): float'
-	},
-	cosh: {
-		description: 'Hyperbolic cosine',
-		signature: '( float $arg ): float'
-	},
-	decbin: {
-		description: 'Decimal to binary',
-		signature: '( int $number ): string'
-	},
-	dechex: {
-		description: 'Decimal to hexadecimal',
-		signature: '( int $number ): string'
-	},
-	decoct: {
-		description: 'Decimal to octal',
-		signature: '( int $number ): string'
-	},
-	deg2rad: {
-		description: 'Converts the number in degrees to the radian equivalent',
-		signature: '( float $number ): float'
-	},
-	exp: {
-		description: 'Calculates the exponent of e',
-		signature: '( float $arg ): float'
-	},
-	expm1: {
-		description: 'Returns exp(number) - 1, computed in a way that is accurate even   when the value of number is close to zero',
-		signature: '( float $arg ): float'
-	},
-	floor: {
-		description: 'Round fractions down',
-		signature: '( float $value ): float'
-	},
-	fmod: {
-		description: 'Returns the floating point remainder (modulo) of the division  of the arguments',
-		signature: '( float $x , float $y ): float'
-	},
-	getrandmax: {
-		description: 'Show largest possible random value',
-		signature: '(void): int'
-	},
-	hexdec: {
-		description: 'Hexadecimal to decimal',
-		signature: '( string $hex_string ): number'
-	},
-	hypot: {
-		description: 'Calculate the length of the hypotenuse of a right-angle triangle',
-		signature: '( float $x , float $y ): float'
-	},
-	intdiv: {
-		description: 'Integer division',
-		signature: '( int $dividend , int $divisor ): int'
-	},
-	is_finite: {
-		description: 'Finds whether a value is a legal finite number',
-		signature: '( float $val ): bool'
-	},
-	is_infinite: {
-		description: 'Finds whether a value is infinite',
-		signature: '( float $val ): bool'
-	},
-	is_nan: {
-		description: 'Finds whether a value is not a number',
-		signature: '( float $val ): bool'
-	},
-	lcg_value: {
-		description: 'Combined linear congruential generator',
-		signature: '(void): float'
-	},
-	log10: {
-		description: 'Base-10 logarithm',
-		signature: '( float $arg ): float'
-	},
-	log1p: {
-		description: 'Returns log(1 + number), computed in a way that is accurate even when   the value of number is close to zero',
-		signature: '( float $number ): float'
-	},
-	log: {
-		description: 'Natural logarithm',
-		signature: '( float $arg [, float $base = M_E ]): float'
-	},
-	max: {
-		description: 'Find highest value',
-		signature: '( array $values , mixed $value1 [, mixed $... ]): string'
-	},
-	min: {
-		description: 'Find lowest value',
-		signature: '( array $values , mixed $value1 [, mixed $... ]): string'
-	},
-	mt_getrandmax: {
-		description: 'Show largest possible random value',
-		signature: '(void): int'
-	},
-	mt_rand: {
-		description: 'Generate a random value via the Mersenne Twister Random Number Generator',
-		signature: '( int $min , int $max ): int'
-	},
-	mt_srand: {
-		description: 'Seeds the Mersenne Twister Random Number Generator',
-		signature: '([ int $seed [, int $mode = MT_RAND_MT19937 ]]): void'
-	},
-	octdec: {
-		description: 'Octal to decimal',
-		signature: '( string $octal_string ): number'
-	},
-	pi: {
-		description: 'Get value of pi',
-		signature: '(void): float'
-	},
-	pow: {
-		description: 'Exponential expression',
-		signature: '( number $base , number $exp ): number'
-	},
-	rad2deg: {
-		description: 'Converts the radian number to the equivalent number in degrees',
-		signature: '( float $number ): float'
-	},
-	rand: {
-		description: 'Generate a random integer',
-		signature: '( int $min , int $max ): int'
-	},
-	round: {
-		description: 'Rounds a float',
-		signature: '( float $val [, int $precision = 0 [, int $mode = PHP_ROUND_HALF_UP ]]): float'
-	},
-	sin: {
-		description: 'Sine',
-		signature: '( float $arg ): float'
-	},
-	sinh: {
-		description: 'Hyperbolic sine',
-		signature: '( float $arg ): float'
-	},
-	sqrt: {
-		description: 'Square root',
-		signature: '( float $arg ): float'
-	},
-	srand: {
-		description: 'Seed the random number generator',
-		signature: '([ int $seed ]): void'
-	},
-	tan: {
-		description: 'Tangent',
-		signature: '( float $arg ): float'
-	},
-	tanh: {
-		description: 'Hyperbolic tangent',
-		signature: '( float $arg ): float'
-	},
-	pcntl_alarm: {
-		description: 'Set an alarm clock for delivery of a signal',
-		signature: '( int $seconds ): int'
-	},
-	pcntl_async_signals: {
-		description: 'Enable/disable asynchronous signal handling or return the old setting',
-		signature: '([ bool $on ]): bool'
-	},
-	pcntl_errno: {
-		description: 'Alias of pcntl_get_last_error',
-	},
-	pcntl_exec: {
-		description: 'Executes specified program in current process space',
-		signature: '( string $path [, array $args [, array $envs ]]): void'
-	},
-	pcntl_fork: {
-		description: 'Forks the currently running process',
-		signature: '(void): int'
-	},
-	pcntl_get_last_error: {
-		description: 'Retrieve the error number set by the last pcntl function which failed',
-		signature: '(void): int'
-	},
-	pcntl_getpriority: {
-		description: 'Get the priority of any process',
-		signature: '([ int $pid = getmypid() [, int $process_identifier = PRIO_PROCESS ]]): int'
-	},
-	pcntl_setpriority: {
-		description: 'Change the priority of any process',
-		signature: '( int $priority [, int $pid = getmypid() [, int $process_identifier = PRIO_PROCESS ]]): bool'
-	},
-	pcntl_signal_dispatch: {
-		description: 'Calls signal handlers for pending signals',
-		signature: '(void): bool'
-	},
-	pcntl_signal_get_handler: {
-		description: 'Get the current handler for specified signal',
-		signature: '( int $signo ): mixed'
-	},
-	pcntl_signal: {
-		description: 'Installs a signal handler',
-		signature: '( int $signo , callable|int $handler [, bool $restart_syscalls ]): bool'
-	},
-	pcntl_sigprocmask: {
-		description: 'Sets and retrieves blocked signals',
-		signature: '( int $how , array $set [, array $oldset ]): bool'
-	},
-	pcntl_sigtimedwait: {
-		description: 'Waits for signals, with a timeout',
-		signature: '( array $set [, array $siginfo [, int $seconds = 0 [, int $nanoseconds = 0 ]]]): int'
-	},
-	pcntl_sigwaitinfo: {
-		description: 'Waits for signals',
-		signature: '( array $set [, array $siginfo ]): int'
-	},
-	pcntl_strerror: {
-		description: 'Retrieve the system error message associated with the given errno',
-		signature: '( int $errno ): string'
-	},
-	pcntl_wait: {
-		description: 'Waits on or returns the status of a forked child',
-		signature: '( int $status [, int $options = 0 [, array $rusage ]]): int'
-	},
-	pcntl_waitpid: {
-		description: 'Waits on or returns the status of a forked child',
-		signature: '( int $pid , int $status [, int $options = 0 [, array $rusage ]]): int'
-	},
-	pcntl_wexitstatus: {
-		description: 'Returns the return code of a terminated child',
-		signature: '( int $status ): int'
-	},
-	pcntl_wifexited: {
-		description: 'Checks if status code represents a normal exit',
-		signature: '( int $status ): bool'
-	},
-	pcntl_wifsignaled: {
-		description: 'Checks whether the status code represents a termination due to a signal',
-		signature: '( int $status ): bool'
-	},
-	pcntl_wifstopped: {
-		description: 'Checks whether the child process is currently stopped',
-		signature: '( int $status ): bool'
-	},
-	pcntl_wstopsig: {
-		description: 'Returns the signal which caused the child to stop',
-		signature: '( int $status ): int'
-	},
-	pcntl_wtermsig: {
-		description: 'Returns the signal which caused the child to terminate',
-		signature: '( int $status ): int'
-	},
-	posix_access: {
-		description: 'Determine accessibility of a file',
-		signature: '( string $file [, int $mode = POSIX_F_OK ]): bool'
-	},
-	posix_ctermid: {
-		description: 'Get path name of controlling terminal',
-		signature: '(void): string'
-	},
-	posix_errno: {
-		description: 'Alias of posix_get_last_error',
-	},
-	posix_get_last_error: {
-		description: 'Retrieve the error number set by the last posix function that failed',
-		signature: '(void): int'
-	},
-	posix_getcwd: {
-		description: 'Pathname of current directory',
-		signature: '(void): string'
-	},
-	posix_getegid: {
-		description: 'Return the effective group ID of the current process',
-		signature: '(void): int'
-	},
-	posix_geteuid: {
-		description: 'Return the effective user ID of the current process',
-		signature: '(void): int'
-	},
-	posix_getgid: {
-		description: 'Return the real group ID of the current process',
-		signature: '(void): int'
-	},
-	posix_getgrgid: {
-		description: 'Return info about a group by group id',
-		signature: '( int $gid ): array'
-	},
-	posix_getgrnam: {
-		description: 'Return info about a group by name',
-		signature: '( string $name ): array'
-	},
-	posix_getgroups: {
-		description: 'Return the group set of the current process',
-		signature: '(void): array'
-	},
-	posix_getlogin: {
-		description: 'Return login name',
-		signature: '(void): string'
-	},
-	posix_getpgid: {
-		description: 'Get process group id for job control',
-		signature: '( int $pid ): int'
-	},
-	posix_getpgrp: {
-		description: 'Return the current process group identifier',
-		signature: '(void): int'
-	},
-	posix_getpid: {
-		description: 'Return the current process identifier',
-		signature: '(void): int'
-	},
-	posix_getppid: {
-		description: 'Return the parent process identifier',
-		signature: '(void): int'
-	},
-	posix_getpwnam: {
-		description: 'Return info about a user by username',
-		signature: '( string $username ): array'
-	},
-	posix_getpwuid: {
-		description: 'Return info about a user by user id',
-		signature: '( int $uid ): array'
-	},
-	posix_getrlimit: {
-		description: 'Return info about system resource limits',
-		signature: '(void): array'
-	},
-	posix_getsid: {
-		description: 'Get the current sid of the process',
-		signature: '( int $pid ): int'
-	},
-	posix_getuid: {
-		description: 'Return the real user ID of the current process',
-		signature: '(void): int'
-	},
-	posix_initgroups: {
-		description: 'Calculate the group access list',
-		signature: '( string $name , int $base_group_id ): bool'
-	},
-	posix_isatty: {
-		description: 'Determine if a file descriptor is an interactive terminal',
-		signature: '( mixed $fd ): bool'
-	},
-	posix_kill: {
-		description: 'Send a signal to a process',
-		signature: '( int $pid , int $sig ): bool'
-	},
-	posix_mkfifo: {
-		description: 'Create a fifo special file (a named pipe)',
-		signature: '( string $pathname , int $mode ): bool'
-	},
-	posix_mknod: {
-		description: 'Create a special or ordinary file (POSIX.1)',
-		signature: '( string $pathname , int $mode [, int $major = 0 [, int $minor = 0 ]]): bool'
-	},
-	posix_setegid: {
-		description: 'Set the effective GID of the current process',
-		signature: '( int $gid ): bool'
-	},
-	posix_seteuid: {
-		description: 'Set the effective UID of the current process',
-		signature: '( int $uid ): bool'
-	},
-	posix_setgid: {
-		description: 'Set the GID of the current process',
-		signature: '( int $gid ): bool'
-	},
-	posix_setpgid: {
-		description: 'Set process group id for job control',
-		signature: '( int $pid , int $pgid ): bool'
-	},
-	posix_setrlimit: {
-		description: 'Set system resource limits',
-		signature: '( int $resource , int $softlimit , int $hardlimit ): bool'
-	},
-	posix_setsid: {
-		description: 'Make the current process a session leader',
-		signature: '(void): int'
-	},
-	posix_setuid: {
-		description: 'Set the UID of the current process',
-		signature: '( int $uid ): bool'
-	},
-	posix_strerror: {
-		description: 'Retrieve the system error message associated with the given errno',
-		signature: '( int $errno ): string'
-	},
-	posix_times: {
-		description: 'Get process times',
-		signature: '(void): array'
-	},
-	posix_ttyname: {
-		description: 'Determine terminal device name',
-		signature: '( mixed $fd ): string'
-	},
-	posix_uname: {
-		description: 'Get system name',
-		signature: '(void): array'
-	},
-	escapeshellarg: {
-		description: 'Escape a string to be used as a shell argument',
-		signature: '( string $arg ): string'
-	},
-	escapeshellcmd: {
-		description: 'Escape shell metacharacters',
-		signature: '( string $command ): string'
-	},
-	exec: {
-		description: 'Execute an external program',
-		signature: '( string $command [, array $output [, int $return_var ]]): string'
-	},
-	passthru: {
-		description: 'Execute an external program and display raw output',
-		signature: '( string $command [, int $return_var ]): void'
-	},
-	proc_close: {
-		description: 'Close a process opened by proc_open and return the exit code of that process',
-		signature: '( resource $process ): int'
-	},
-	proc_get_status: {
-		description: 'Get information about a process opened by proc_open',
-		signature: '( resource $process ): array'
-	},
-	proc_nice: {
-		description: 'Change the priority of the current process',
-		signature: '( int $increment ): bool'
-	},
-	proc_open: {
-		description: 'Execute a command and open file pointers for input/output',
-		signature: '( string $cmd , array $descriptorspec , array $pipes [, string $cwd [, array $env [, array $other_options ]]]): resource'
-	},
-	proc_terminate: {
-		description: 'Kills a process opened by proc_open',
-		signature: '( resource $process [, int $signal = 15 ]): bool'
-	},
-	shell_exec: {
-		description: 'Execute command via shell and return the complete output as a string',
-		signature: '( string $cmd ): string'
-	},
-	system: {
-		description: 'Execute an external program and display the output',
-		signature: '( string $command [, int $return_var ]): string'
-	},
-	ftok: {
-		description: 'Convert a pathname and a project identifier to a System V IPC key',
-		signature: '( string $pathname , string $proj ): int'
-	},
-	msg_get_queue: {
-		description: 'Create or attach to a message queue',
-		signature: '( int $key [, int $perms = 0666 ]): resource'
-	},
-	msg_queue_exists: {
-		description: 'Check whether a message queue exists',
-		signature: '( int $key ): bool'
-	},
-	msg_receive: {
-		description: 'Receive a message from a message queue',
-		signature: '( resource $queue , int $desiredmsgtype , int $msgtype , int $maxsize , mixed $message [, bool $unserialize [, int $flags = 0 [, int $errorcode ]]]): bool'
-	},
-	msg_remove_queue: {
-		description: 'Destroy a message queue',
-		signature: '( resource $queue ): bool'
-	},
-	msg_send: {
-		description: 'Send a message to a message queue',
-		signature: '( resource $queue , int $msgtype , mixed $message [, bool $serialize [, bool $blocking [, int $errorcode ]]]): bool'
-	},
-	msg_set_queue: {
-		description: 'Set information in the message queue data structure',
-		signature: '( resource $queue , array $data ): bool'
-	},
-	msg_stat_queue: {
-		description: 'Returns information from the message queue data structure',
-		signature: '( resource $queue ): array'
-	},
-	sem_acquire: {
-		description: 'Acquire a semaphore',
-		signature: '( resource $sem_identifier [, bool $nowait ]): bool'
-	},
-	sem_get: {
-		description: 'Get a semaphore id',
-		signature: '( int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]]): resource'
-	},
-	sem_release: {
-		description: 'Release a semaphore',
-		signature: '( resource $sem_identifier ): bool'
-	},
-	sem_remove: {
-		description: 'Remove a semaphore',
-		signature: '( resource $sem_identifier ): bool'
-	},
-	shm_attach: {
-		description: 'Creates or open a shared memory segment',
-		signature: '( int $key [, int $memsize [, int $perm = 0666 ]]): resource'
-	},
-	shm_detach: {
-		description: 'Disconnects from shared memory segment',
-		signature: '( resource $shm_identifier ): bool'
-	},
-	shm_get_var: {
-		description: 'Returns a variable from shared memory',
-		signature: '( resource $shm_identifier , int $variable_key ): mixed'
-	},
-	shm_has_var: {
-		description: 'Check whether a specific entry exists',
-		signature: '( resource $shm_identifier , int $variable_key ): bool'
-	},
-	shm_put_var: {
-		description: 'Inserts or updates a variable in shared memory',
-		signature: '( resource $shm_identifier , int $variable_key , mixed $variable ): bool'
-	},
-	shm_remove_var: {
-		description: 'Removes a variable from shared memory',
-		signature: '( resource $shm_identifier , int $variable_key ): bool'
-	},
-	shm_remove: {
-		description: 'Removes shared memory from Unix systems',
-		signature: '( resource $shm_identifier ): bool'
-	},
-	shmop_close: {
-		description: 'Close shared memory block',
-		signature: '( resource $shmid ): void'
-	},
-	shmop_delete: {
-		description: 'Delete shared memory block',
-		signature: '( resource $shmid ): bool'
-	},
-	shmop_open: {
-		description: 'Create or open shared memory block',
-		signature: '( int $key , string $flags , int $mode , int $size ): resource'
-	},
-	shmop_read: {
-		description: 'Read data from shared memory block',
-		signature: '( resource $shmid , int $start , int $count ): string'
-	},
-	shmop_size: {
-		description: 'Get size of shared memory block',
-		signature: '( resource $shmid ): int'
-	},
-	shmop_write: {
-		description: 'Write data into shared memory block',
-		signature: '( resource $shmid , string $data , int $offset ): int'
-	},
-	json_decode: {
-		description: 'Decodes a JSON string',
-		signature: '( string $json [, bool $assoc [, int $depth = 512 [, int $options = 0 ]]]): mixed'
-	},
-	json_encode: {
-		description: 'Returns the JSON representation of a value',
-		signature: '( mixed $value [, int $options = 0 [, int $depth = 512 ]]): string'
-	},
-	json_last_error_msg: {
-		description: 'Returns the error string of the last json_encode() or json_decode() call',
-		signature: '(void): string'
-	},
-	json_last_error: {
-		description: 'Returns the last error occurred',
-		signature: '(void): int'
-	},
-	connection_aborted: {
-		description: 'Check whether client disconnected',
-		signature: '(void): int'
-	},
-	connection_status: {
-		description: 'Returns connection status bitfield',
-		signature: '(void): int'
-	},
-	constant: {
-		description: 'Returns the value of a constant',
-		signature: '( string $name ): mixed'
-	},
-	define: {
-		description: 'Defines a named constant',
-		signature: '( string $name , mixed $value [, bool $case_insensitive ]): bool'
-	},
-	defined: {
-		description: 'Checks whether a given named constant exists',
-		signature: '( string $name ): bool'
-	},
-	die: {
-		description: 'Equivalent to exit',
-	},
-	eval: {
-		description: 'Evaluate a string as PHP code',
-		signature: '( string $code ): mixed'
-	},
-	exit: {
-		description: 'Output a message and terminate the current script',
-		signature: '( int $status ): void'
-	},
-	get_browser: {
-		description: 'Tells what the user\'s browser is capable of',
-		signature: '([ string $user_agent [, bool $return_array ]]): mixed'
-	},
-	__halt_compiler: {
-		description: 'Halts the compiler execution',
-		signature: '(void): void'
-	},
-	highlight_file: {
-		description: 'Syntax highlighting of a file',
-		signature: '( string $filename [, bool $return ]): mixed'
-	},
-	highlight_string: {
-		description: 'Syntax highlighting of a string',
-		signature: '( string $str [, bool $return ]): mixed'
-	},
-	hrtime: {
-		description: 'Get the system\'s high resolution time',
-		signature: '([ bool $get_as_number ]): mixed'
-	},
-	ignore_user_abort: {
-		description: 'Set whether a client disconnect should abort script execution',
-		signature: '([ bool $value ]): int'
-	},
-	pack: {
-		description: 'Pack data into binary string',
-		signature: '( string $format [, mixed $... ]): string'
-	},
-	php_check_syntax: {
-		description: 'Check the PHP syntax of (and execute) the specified file',
-		signature: '( string $filename [, string $error_message ]): bool'
-	},
-	php_strip_whitespace: {
-		description: 'Return source with stripped comments and whitespace',
-		signature: '( string $filename ): string'
-	},
-	sapi_windows_cp_conv: {
-		description: 'Convert string from one codepage to another',
-		signature: '( int|string $in_codepage , int|string $out_codepage , string $subject ): string'
-	},
-	sapi_windows_cp_get: {
-		description: 'Get process codepage',
-		signature: '( string $kind ): int'
-	},
-	sapi_windows_cp_is_utf8: {
-		description: 'Indicates whether the codepage is UTF-8 compatible',
-		signature: '(void): bool'
-	},
-	sapi_windows_cp_set: {
-		description: 'Set process codepage',
-		signature: '( int $cp ): bool'
-	},
-	sapi_windows_vt100_support: {
-		description: 'Get or set VT100 support for the specified stream associated to an output buffer of a Windows console.',
-		signature: '( resource $stream [, bool $enable ]): bool'
-	},
-	show_source: {
-		description: 'Alias of highlight_file',
-	},
-	sleep: {
-		description: 'Delay execution',
-		signature: '( int $seconds ): int'
-	},
-	sys_getloadavg: {
-		description: 'Gets system load average',
-		signature: '(void): array'
-	},
-	time_nanosleep: {
-		description: 'Delay for a number of seconds and nanoseconds',
-		signature: '( int $seconds , int $nanoseconds ): mixed'
-	},
-	time_sleep_until: {
-		description: 'Make the script sleep until the specified time',
-		signature: '( float $timestamp ): bool'
-	},
-	uniqid: {
-		description: 'Generate a unique ID',
-		signature: '([ string $prefix = "" [, bool $more_entropy ]]): string'
-	},
-	unpack: {
-		description: 'Unpack data from binary string',
-		signature: '( string $format , string $data [, int $offset = 0 ]): array'
-	},
-	usleep: {
-		description: 'Delay execution in microseconds',
-		signature: '( int $micro_seconds ): void'
-	},
-	class_implements: {
-		description: 'Return the interfaces which are implemented by the given class or interface',
-		signature: '( mixed $class [, bool $autoload ]): array'
-	},
-	class_parents: {
-		description: 'Return the parent classes of the given class',
-		signature: '( mixed $class [, bool $autoload ]): array'
-	},
-	class_uses: {
-		description: 'Return the traits used by the given class',
-		signature: '( mixed $class [, bool $autoload ]): array'
-	},
-	iterator_apply: {
-		description: 'Call a function for every element in an iterator',
-		signature: '( Traversable $iterator , callable $function [, array $args ]): int'
-	},
-	iterator_count: {
-		description: 'Count the elements in an iterator',
-		signature: '( Traversable $iterator ): int'
-	},
-	iterator_to_array: {
-		description: 'Copy the iterator into an array',
-		signature: '( Traversable $iterator [, bool $use_keys ]): array'
-	},
-	spl_autoload_call: {
-		description: 'Try all registered __autoload() functions to load the requested class',
-		signature: '( string $class_name ): void'
-	},
-	spl_autoload_extensions: {
-		description: 'Register and return default file extensions for spl_autoload',
-		signature: '([ string $file_extensions ]): string'
-	},
-	spl_autoload_functions: {
-		description: 'Return all registered __autoload() functions',
-		signature: '(void): array'
-	},
-	spl_autoload_register: {
-		description: 'Register given function as __autoload() implementation',
-		signature: '([ callable $autoload_function [, bool $throw [, bool $prepend ]]]): bool'
-	},
-	spl_autoload_unregister: {
-		description: 'Unregister given function as __autoload() implementation',
-		signature: '( mixed $autoload_function ): bool'
-	},
-	spl_autoload: {
-		description: 'Default implementation for __autoload()',
-		signature: '( string $class_name [, string $file_extensions = spl_autoload_extensions() ]): void'
-	},
-	spl_classes: {
-		description: 'Return available SPL classes',
-		signature: '(void): array'
-	},
-	spl_object_hash: {
-		description: 'Return hash id for given object',
-		signature: '( object $obj ): string'
-	},
-	spl_object_id: {
-		description: 'Return the integer object handle for given object',
-		signature: '( object $obj ): int'
-	},
-	set_socket_blocking: {
-		description: 'Alias of stream_set_blocking',
-	},
-	stream_bucket_append: {
-		description: 'Append bucket to brigade',
-		signature: '( resource $brigade , object $bucket ): void'
-	},
-	stream_bucket_make_writeable: {
-		description: 'Return a bucket object from the brigade for operating on',
-		signature: '( resource $brigade ): object'
-	},
-	stream_bucket_new: {
-		description: 'Create a new bucket for use on the current stream',
-		signature: '( resource $stream , string $buffer ): object'
-	},
-	stream_bucket_prepend: {
-		description: 'Prepend bucket to brigade',
-		signature: '( resource $brigade , object $bucket ): void'
-	},
-	stream_context_create: {
-		description: 'Creates a stream context',
-		signature: '([ array $options [, array $params ]]): resource'
-	},
-	stream_context_get_default: {
-		description: 'Retrieve the default stream context',
-		signature: '([ array $options ]): resource'
-	},
-	stream_context_get_options: {
-		description: 'Retrieve options for a stream/wrapper/context',
-		signature: '( resource $stream_or_context ): array'
-	},
-	stream_context_get_params: {
-		description: 'Retrieves parameters from a context',
-		signature: '( resource $stream_or_context ): array'
-	},
-	stream_context_set_default: {
-		description: 'Set the default stream context',
-		signature: '( array $options ): resource'
-	},
-	stream_context_set_option: {
-		description: 'Sets an option for a stream/wrapper/context',
-		signature: '( resource $stream_or_context , string $wrapper , string $option , mixed $value , array $options ): bool'
-	},
-	stream_context_set_params: {
-		description: 'Set parameters for a stream/wrapper/context',
-		signature: '( resource $stream_or_context , array $params ): bool'
-	},
-	stream_copy_to_stream: {
-		description: 'Copies data from one stream to another',
-		signature: '( resource $source , resource $dest [, int $maxlength = -1 [, int $offset = 0 ]]): int'
-	},
-	stream_filter_append: {
-		description: 'Attach a filter to a stream',
-		signature: '( resource $stream , string $filtername [, int $read_write [, mixed $params ]]): resource'
-	},
-	stream_filter_prepend: {
-		description: 'Attach a filter to a stream',
-		signature: '( resource $stream , string $filtername [, int $read_write [, mixed $params ]]): resource'
-	},
-	stream_filter_register: {
-		description: 'Register a user defined stream filter',
-		signature: '( string $filtername , string $classname ): bool'
-	},
-	stream_filter_remove: {
-		description: 'Remove a filter from a stream',
-		signature: '( resource $stream_filter ): bool'
-	},
-	stream_get_contents: {
-		description: 'Reads remainder of a stream into a string',
-		signature: '( resource $handle [, int $maxlength = -1 [, int $offset = -1 ]]): string'
-	},
-	stream_get_filters: {
-		description: 'Retrieve list of registered filters',
-		signature: '(void): array'
-	},
-	stream_get_line: {
-		description: 'Gets line from stream resource up to a given delimiter',
-		signature: '( resource $handle , int $length [, string $ending ]): string'
-	},
-	stream_get_meta_data: {
-		description: 'Retrieves header/meta data from streams/file pointers',
-		signature: '( resource $stream ): array'
-	},
-	stream_get_transports: {
-		description: 'Retrieve list of registered socket transports',
-		signature: '(void): array'
-	},
-	stream_get_wrappers: {
-		description: 'Retrieve list of registered streams',
-		signature: '(void): array'
-	},
-	stream_is_local: {
-		description: 'Checks if a stream is a local stream',
-		signature: '( mixed $stream_or_url ): bool'
-	},
-	stream_isatty: {
-		description: 'Check if a stream is a TTY',
-		signature: '( resource $stream ): bool'
-	},
-	stream_notification_callback: {
-		description: 'A callback function for the notification context parameter',
-		signature: '( int $notification_code , int $severity , string $message , int $message_code , int $bytes_transferred , int $bytes_max ): callable'
-	},
-	stream_register_wrapper: {
-		description: 'Alias of stream_wrapper_register',
-	},
-	stream_resolve_include_path: {
-		description: 'Resolve filename against the include path',
-		signature: '( string $filename ): string'
-	},
-	stream_select: {
-		description: 'Runs the equivalent of the select() system call on the given   arrays of streams with a timeout specified by tv_sec and tv_usec',
-		signature: '( array $read , array $write , array $except , int $tv_sec [, int $tv_usec = 0 ]): int'
-	},
-	stream_set_blocking: {
-		description: 'Set blocking/non-blocking mode on a stream',
-		signature: '( resource $stream , bool $mode ): bool'
-	},
-	stream_set_chunk_size: {
-		description: 'Set the stream chunk size',
-		signature: '( resource $fp , int $chunk_size ): int'
-	},
-	stream_set_read_buffer: {
-		description: 'Set read file buffering on the given stream',
-		signature: '( resource $stream , int $buffer ): int'
-	},
-	stream_set_timeout: {
-		description: 'Set timeout period on a stream',
-		signature: '( resource $stream , int $seconds [, int $microseconds = 0 ]): bool'
-	},
-	stream_set_write_buffer: {
-		description: 'Sets write file buffering on the given stream',
-		signature: '( resource $stream , int $buffer ): int'
-	},
-	stream_socket_accept: {
-		description: 'Accept a connection on a socket created by stream_socket_server',
-		signature: '( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string $peername ]]): resource'
-	},
-	stream_socket_client: {
-		description: 'Open Internet or Unix domain socket connection',
-		signature: '( string $remote_socket [, int $errno [, string $errstr [, float $timeout = ini_get("default_socket_timeout") [, int $flags = STREAM_CLIENT_CONNECT [, resource $context ]]]]]): resource'
-	},
-	stream_socket_enable_crypto: {
-		description: 'Turns encryption on/off on an already connected socket',
-		signature: '( resource $stream , bool $enable [, int $crypto_type [, resource $session_stream ]]): mixed'
-	},
-	stream_socket_get_name: {
-		description: 'Retrieve the name of the local or remote sockets',
-		signature: '( resource $handle , bool $want_peer ): string'
-	},
-	stream_socket_pair: {
-		description: 'Creates a pair of connected, indistinguishable socket streams',
-		signature: '( int $domain , int $type , int $protocol ): array'
-	},
-	stream_socket_recvfrom: {
-		description: 'Receives data from a socket, connected or not',
-		signature: '( resource $socket , int $length [, int $flags = 0 [, string $address ]]): string'
-	},
-	stream_socket_sendto: {
-		description: 'Sends a message to a socket, whether it is connected or not',
-		signature: '( resource $socket , string $data [, int $flags = 0 [, string $address ]]): int'
-	},
-	stream_socket_server: {
-		description: 'Create an Internet or Unix domain server socket',
-		signature: '( string $local_socket [, int $errno [, string $errstr [, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN [, resource $context ]]]]): resource'
-	},
-	stream_socket_shutdown: {
-		description: 'Shutdown a full-duplex connection',
-		signature: '( resource $stream , int $how ): bool'
-	},
-	stream_supports_lock: {
-		description: 'Tells whether the stream supports locking',
-		signature: '( resource $stream ): bool'
-	},
-	stream_wrapper_register: {
-		description: 'Register a URL wrapper implemented as a PHP class',
-		signature: '( string $protocol , string $classname [, int $flags = 0 ]): bool'
-	},
-	stream_wrapper_restore: {
-		description: 'Restores a previously unregistered built-in wrapper',
-		signature: '( string $protocol ): bool'
-	},
-	stream_wrapper_unregister: {
-		description: 'Unregister a URL wrapper',
-		signature: '( string $protocol ): bool'
-	},
-	token_get_all: {
-		description: 'Split given source into PHP tokens',
-		signature: '( string $source [, int $flags = 0 ]): array'
-	},
-	token_name: {
-		description: 'Get the symbolic name of a given PHP token',
-		signature: '( int $token ): string'
-	},
-	base64_decode: {
-		description: 'Decodes data encoded with MIME base64',
-		signature: '( string $data [, bool $strict ]): string'
-	},
-	base64_encode: {
-		description: 'Encodes data with MIME base64',
-		signature: '( string $data ): string'
-	},
-	get_headers: {
-		description: 'Fetches all the headers sent by the server in response to an HTTP request',
-		signature: '( string $url [, int $format = 0 [, resource $context ]]): array'
-	},
-	get_meta_tags: {
-		description: 'Extracts all meta tag content attributes from a file and returns an array',
-		signature: '( string $filename [, bool $use_include_path ]): array'
-	},
-	http_build_query: {
-		description: 'Generate URL-encoded query string',
-		signature: '( mixed $query_data [, string $numeric_prefix [, string $arg_separator [, int $enc_type ]]]): string'
-	},
-	parse_url: {
-		description: 'Parse a URL and return its components',
-		signature: '( string $url [, int $component = -1 ]): mixed'
-	},
-	rawurldecode: {
-		description: 'Decode URL-encoded strings',
-		signature: '( string $str ): string'
-	},
-	rawurlencode: {
-		description: 'URL-encode according to RFC 3986',
-		signature: '( string $str ): string'
-	},
-	urldecode: {
-		description: 'Decodes URL-encoded string',
-		signature: '( string $str ): string'
-	},
-	urlencode: {
-		description: 'URL-encodes string',
-		signature: '( string $str ): string'
-	},
-	curl_close: {
-		description: 'Close a cURL session',
-		signature: '( resource $ch ): void'
-	},
-	curl_copy_handle: {
-		description: 'Copy a cURL handle along with all of its preferences',
-		signature: '( resource $ch ): resource'
-	},
-	curl_errno: {
-		description: 'Return the last error number',
-		signature: '( resource $ch ): int'
-	},
-	curl_error: {
-		description: 'Return a string containing the last error for the current session',
-		signature: '( resource $ch ): string'
-	},
-	curl_escape: {
-		description: 'URL encodes the given string',
-		signature: '( resource $ch , string $str ): string'
-	},
-	curl_exec: {
-		description: 'Perform a cURL session',
-		signature: '( resource $ch ): mixed'
-	},
-	curl_file_create: {
-		description: 'Create a CURLFile object',
-		signature: '( string $filename [, string $mimetype [, string $postname ]]): CURLFile'
-	},
-	curl_getinfo: {
-		description: 'Get information regarding a specific transfer',
-		signature: '( resource $ch [, int $opt ]): mixed'
-	},
-	curl_init: {
-		description: 'Initialize a cURL session',
-		signature: '([ string $url ]): resource'
-	},
-	curl_multi_add_handle: {
-		description: 'Add a normal cURL handle to a cURL multi handle',
-		signature: '( resource $mh , resource $ch ): int'
-	},
-	curl_multi_close: {
-		description: 'Close a set of cURL handles',
-		signature: '( resource $mh ): void'
-	},
-	curl_multi_errno: {
-		description: 'Return the last multi curl error number',
-		signature: '( resource $mh ): int'
-	},
-	curl_multi_exec: {
-		description: 'Run the sub-connections of the current cURL handle',
-		signature: '( resource $mh , int $still_running ): int'
-	},
-	curl_multi_getcontent: {
-		description: 'Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set',
-		signature: '( resource $ch ): string'
-	},
-	curl_multi_info_read: {
-		description: 'Get information about the current transfers',
-		signature: '( resource $mh [, int $msgs_in_queue ]): array'
-	},
-	curl_multi_init: {
-		description: 'Returns a new cURL multi handle',
-		signature: '(void): resource'
-	},
-	curl_multi_remove_handle: {
-		description: 'Remove a multi handle from a set of cURL handles',
-		signature: '( resource $mh , resource $ch ): int'
-	},
-	curl_multi_select: {
-		description: 'Wait for activity on any curl_multi connection',
-		signature: '( resource $mh [, float $timeout = 1.0 ]): int'
-	},
-	curl_multi_setopt: {
-		description: 'Set an option for the cURL multi handle',
-		signature: '( resource $mh , int $option , mixed $value ): bool'
-	},
-	curl_multi_strerror: {
-		description: 'Return string describing error code',
-		signature: '( int $errornum ): string'
-	},
-	curl_pause: {
-		description: 'Pause and unpause a connection',
-		signature: '( resource $ch , int $bitmask ): int'
-	},
-	curl_reset: {
-		description: 'Reset all options of a libcurl session handle',
-		signature: '( resource $ch ): void'
-	},
-	curl_setopt_array: {
-		description: 'Set multiple options for a cURL transfer',
-		signature: '( resource $ch , array $options ): bool'
-	},
-	curl_setopt: {
-		description: 'Set an option for a cURL transfer',
-		signature: '( resource $ch , int $option , mixed $value ): bool'
-	},
-	curl_share_close: {
-		description: 'Close a cURL share handle',
-		signature: '( resource $sh ): void'
-	},
-	curl_share_errno: {
-		description: 'Return the last share curl error number',
-		signature: '( resource $sh ): int'
-	},
-	curl_share_init: {
-		description: 'Initialize a cURL share handle',
-		signature: '(void): resource'
-	},
-	curl_share_setopt: {
-		description: 'Set an option for a cURL share handle',
-		signature: '( resource $sh , int $option , string $value ): bool'
-	},
-	curl_share_strerror: {
-		description: 'Return string describing the given error code',
-		signature: '( int $errornum ): string'
-	},
-	curl_strerror: {
-		description: 'Return string describing the given error code',
-		signature: '( int $errornum ): string'
-	},
-	curl_unescape: {
-		description: 'Decodes the given URL encoded string',
-		signature: '( resource $ch , string $str ): string'
-	},
-	curl_version: {
-		description: 'Gets cURL version information',
-		signature: '([ int $age = CURLVERSION_NOW ]): array'
-	},
-	ftp_alloc: {
-		description: 'Allocates space for a file to be uploaded',
-		signature: '( resource $ftp_stream , int $filesize [, string $result ]): bool'
-	},
-	ftp_append: {
-		description: 'Append the contents of a file to another file on the FTP server',
-		signature: '( resource $ftp , string $remote_file , string $local_file [, int $mode ]): bool'
-	},
-	ftp_cdup: {
-		description: 'Changes to the parent directory',
-		signature: '( resource $ftp_stream ): bool'
-	},
-	ftp_chdir: {
-		description: 'Changes the current directory on a FTP server',
-		signature: '( resource $ftp_stream , string $directory ): bool'
-	},
-	ftp_chmod: {
-		description: 'Set permissions on a file via FTP',
-		signature: '( resource $ftp_stream , int $mode , string $filename ): int'
-	},
-	ftp_close: {
-		description: 'Closes an FTP connection',
-		signature: '( resource $ftp_stream ): resource'
-	},
-	ftp_connect: {
-		description: 'Opens an FTP connection',
-		signature: '( string $host [, int $port = 21 [, int $timeout = 90 ]]): resource'
-	},
-	ftp_delete: {
-		description: 'Deletes a file on the FTP server',
-		signature: '( resource $ftp_stream , string $path ): bool'
-	},
-	ftp_exec: {
-		description: 'Requests execution of a command on the FTP server',
-		signature: '( resource $ftp_stream , string $command ): bool'
-	},
-	ftp_fget: {
-		description: 'Downloads a file from the FTP server and saves to an open file',
-		signature: '( resource $ftp_stream , resource $handle , string $remote_file [, int $mode [, int $resumepos = 0 ]]): bool'
-	},
-	ftp_fput: {
-		description: 'Uploads from an open file to the FTP server',
-		signature: '( resource $ftp_stream , string $remote_file , resource $handle [, int $mode [, int $startpos = 0 ]]): bool'
-	},
-	ftp_get_option: {
-		description: 'Retrieves various runtime behaviours of the current FTP stream',
-		signature: '( resource $ftp_stream , int $option ): mixed'
-	},
-	ftp_get: {
-		description: 'Downloads a file from the FTP server',
-		signature: '( resource $ftp_stream , string $local_file , string $remote_file [, int $mode [, int $resumepos = 0 ]]): bool'
-	},
-	ftp_login: {
-		description: 'Logs in to an FTP connection',
-		signature: '( resource $ftp_stream , string $username , string $password ): bool'
-	},
-	ftp_mdtm: {
-		description: 'Returns the last modified time of the given file',
-		signature: '( resource $ftp_stream , string $remote_file ): int'
-	},
-	ftp_mkdir: {
-		description: 'Creates a directory',
-		signature: '( resource $ftp_stream , string $directory ): string'
-	},
-	ftp_mlsd: {
-		description: 'Returns a list of files in the given directory',
-		signature: '( resource $ftp_stream , string $directory ): array'
-	},
-	ftp_nb_continue: {
-		description: 'Continues retrieving/sending a file (non-blocking)',
-		signature: '( resource $ftp_stream ): int'
-	},
-	ftp_nb_fget: {
-		description: 'Retrieves a file from the FTP server and writes it to an open file (non-blocking)',
-		signature: '( resource $ftp_stream , resource $handle , string $remote_file [, int $mode [, int $resumepos = 0 ]]): int'
-	},
-	ftp_nb_fput: {
-		description: 'Stores a file from an open file to the FTP server (non-blocking)',
-		signature: '( resource $ftp_stream , string $remote_file , resource $handle [, int $mode [, int $startpos = 0 ]]): int'
-	},
-	ftp_nb_get: {
-		description: 'Retrieves a file from the FTP server and writes it to a local file (non-blocking)',
-		signature: '( resource $ftp_stream , string $local_file , string $remote_file [, int $mode [, int $resumepos = 0 ]]): int'
-	},
-	ftp_nb_put: {
-		description: 'Stores a file on the FTP server (non-blocking)',
-		signature: '( resource $ftp_stream , string $remote_file , string $local_file [, int $mode [, int $startpos = 0 ]]): int'
-	},
-	ftp_nlist: {
-		description: 'Returns a list of files in the given directory',
-		signature: '( resource $ftp_stream , string $directory ): array'
-	},
-	ftp_pasv: {
-		description: 'Turns passive mode on or off',
-		signature: '( resource $ftp_stream , bool $pasv ): bool'
-	},
-	ftp_put: {
-		description: 'Uploads a file to the FTP server',
-		signature: '( resource $ftp_stream , string $remote_file , string $local_file [, int $mode [, int $startpos = 0 ]]): bool'
-	},
-	ftp_pwd: {
-		description: 'Returns the current directory name',
-		signature: '( resource $ftp_stream ): string'
-	},
-	ftp_quit: {
-		description: 'Alias of ftp_close',
-	},
-	ftp_raw: {
-		description: 'Sends an arbitrary command to an FTP server',
-		signature: '( resource $ftp_stream , string $command ): array'
-	},
-	ftp_rawlist: {
-		description: 'Returns a detailed list of files in the given directory',
-		signature: '( resource $ftp_stream , string $directory [, bool $recursive ]): array'
-	},
-	ftp_rename: {
-		description: 'Renames a file or a directory on the FTP server',
-		signature: '( resource $ftp_stream , string $oldname , string $newname ): bool'
-	},
-	ftp_rmdir: {
-		description: 'Removes a directory',
-		signature: '( resource $ftp_stream , string $directory ): bool'
-	},
-	ftp_set_option: {
-		description: 'Set miscellaneous runtime FTP options',
-		signature: '( resource $ftp_stream , int $option , mixed $value ): bool'
-	},
-	ftp_site: {
-		description: 'Sends a SITE command to the server',
-		signature: '( resource $ftp_stream , string $command ): bool'
-	},
-	ftp_size: {
-		description: 'Returns the size of the given file',
-		signature: '( resource $ftp_stream , string $remote_file ): int'
-	},
-	ftp_ssl_connect: {
-		description: 'Opens a Secure SSL-FTP connection',
-		signature: '( string $host [, int $port = 21 [, int $timeout = 90 ]]): resource'
-	},
-	ftp_systype: {
-		description: 'Returns the system type identifier of the remote FTP server',
-		signature: '( resource $ftp_stream ): string'
-	},
-	checkdnsrr: {
-		description: 'Check DNS records corresponding to a given Internet host name or IP address',
-		signature: '( string $host [, string $type = "MX" ]): bool'
-	},
-	closelog: {
-		description: 'Close connection to system logger',
-		signature: '(void): bool'
-	},
-	define_syslog_variables: {
-		description: 'Initializes all syslog related variables',
-		signature: '(void): void'
-	},
-	dns_check_record: {
-		description: 'Alias of checkdnsrr',
-	},
-	dns_get_mx: {
-		description: 'Alias of getmxrr',
-	},
-	dns_get_record: {
-		description: 'Fetch DNS Resource Records associated with a hostname',
-		signature: '( string $hostname [, int $type = DNS_ANY [, array $authns [, array $addtl [, bool $raw ]]]]): array'
-	},
-	fsockopen: {
-		description: 'Open Internet or Unix domain socket connection',
-		signature: '( string $hostname [, int $port = -1 [, int $errno [, string $errstr [, float $timeout = ini_get("default_socket_timeout") ]]]]): resource'
-	},
-	gethostbyaddr: {
-		description: 'Get the Internet host name corresponding to a given IP address',
-		signature: '( string $ip_address ): string'
-	},
-	gethostbyname: {
-		description: 'Get the IPv4 address corresponding to a given Internet host name',
-		signature: '( string $hostname ): string'
-	},
-	gethostbynamel: {
-		description: 'Get a list of IPv4 addresses corresponding to a given Internet host   name',
-		signature: '( string $hostname ): array'
-	},
-	gethostname: {
-		description: 'Gets the host name',
-		signature: '(void): string'
-	},
-	getmxrr: {
-		description: 'Get MX records corresponding to a given Internet host name',
-		signature: '( string $hostname , array $mxhosts [, array $weight ]): bool'
-	},
-	getprotobyname: {
-		description: 'Get protocol number associated with protocol name',
-		signature: '( string $name ): int'
-	},
-	getprotobynumber: {
-		description: 'Get protocol name associated with protocol number',
-		signature: '( int $number ): string'
-	},
-	getservbyname: {
-		description: 'Get port number associated with an Internet service and protocol',
-		signature: '( string $service , string $protocol ): int'
-	},
-	getservbyport: {
-		description: 'Get Internet service which corresponds to port and protocol',
-		signature: '( int $port , string $protocol ): string'
-	},
-	header_register_callback: {
-		description: 'Call a header function',
-		signature: '( callable $callback ): bool'
-	},
-	header_remove: {
-		description: 'Remove previously set headers',
-		signature: '([ string $name ]): void'
-	},
-	header: {
-		description: 'Send a raw HTTP header',
-		signature: '( string $header [, bool $replace [, int $http_response_code ]]): void'
-	},
-	headers_list: {
-		description: 'Returns a list of response headers sent (or ready to send)',
-		signature: '(void): array'
-	},
-	headers_sent: {
-		description: 'Checks if or where headers have been sent',
-		signature: '([ string $file [, int $line ]]): bool'
-	},
-	http_response_code: {
-		description: 'Get or Set the HTTP response code',
-		signature: '([ int $response_code ]): mixed'
-	},
-	inet_ntop: {
-		description: 'Converts a packed internet address to a human readable representation',
-		signature: '( string $in_addr ): string'
-	},
-	inet_pton: {
-		description: 'Converts a human readable IP address to its packed in_addr representation',
-		signature: '( string $address ): string'
-	},
-	ip2long: {
-		description: 'Converts a string containing an (IPv4) Internet Protocol dotted address into a long integer',
-		signature: '( string $ip_address ): int'
-	},
-	long2ip: {
-		description: 'Converts an long integer address into a string in (IPv4) Internet standard dotted format',
-		signature: '( int $proper_address ): string'
-	},
-	openlog: {
-		description: 'Open connection to system logger',
-		signature: '( string $ident , int $option , int $facility ): bool'
-	},
-	pfsockopen: {
-		description: 'Open persistent Internet or Unix domain socket connection',
-		signature: '( string $hostname [, int $port = -1 [, int $errno [, string $errstr [, float $timeout = ini_get("default_socket_timeout") ]]]]): resource'
-	},
-	setcookie: {
-		description: 'Send a cookie',
-		signature: '( string $name [, string $value = "" [, int $expires = 0 [, string $path = "" [, string $domain = "" [, bool $secure [, bool $httponly [, array $options = [] ]]]]]]]): bool'
-	},
-	setrawcookie: {
-		description: 'Send a cookie without urlencoding the cookie value',
-		signature: '( string $name [, string $value [, int $expires = 0 [, string $path [, string $domain [, bool $secure [, bool $httponly [, array $options = [] ]]]]]]]): bool'
-	},
-	socket_get_status: {
-		description: 'Alias of stream_get_meta_data',
-	},
-	socket_set_blocking: {
-		description: 'Alias of stream_set_blocking',
-	},
-	socket_set_timeout: {
-		description: 'Alias of stream_set_timeout',
-	},
-	syslog: {
-		description: 'Generate a system log message',
-		signature: '( int $priority , string $message ): bool'
-	},
-	socket_accept: {
-		description: 'Accepts a connection on a socket',
-		signature: '( resource $socket ): resource'
-	},
-	socket_addrinfo_bind: {
-		description: 'Create and bind to a socket from a given addrinfo',
-		signature: '( resource $addr ): resource'
-	},
-	socket_addrinfo_connect: {
-		description: 'Create and connect to a socket from a given addrinfo',
-		signature: '( resource $addr ): resource'
-	},
-	socket_addrinfo_explain: {
-		description: 'Get information about addrinfo',
-		signature: '( resource $addr ): array'
-	},
-	socket_addrinfo_lookup: {
-		description: 'Get array with contents of getaddrinfo about the given hostname',
-		signature: '( string $host [, string $service [, array $hints ]]): array'
-	},
-	socket_bind: {
-		description: 'Binds a name to a socket',
-		signature: '( resource $socket , string $address [, int $port = 0 ]): bool'
-	},
-	socket_clear_error: {
-		description: 'Clears the error on the socket or the last error code',
-		signature: '([ resource $socket ]): void'
-	},
-	socket_close: {
-		description: 'Closes a socket resource',
-		signature: '( resource $socket ): void'
-	},
-	socket_cmsg_space: {
-		description: 'Calculate message buffer size',
-		signature: '( int $level , int $type [, int $n = 0 ]): int'
-	},
-	socket_connect: {
-		description: 'Initiates a connection on a socket',
-		signature: '( resource $socket , string $address [, int $port = 0 ]): bool'
-	},
-	socket_create_listen: {
-		description: 'Opens a socket on port to accept connections',
-		signature: '( int $port [, int $backlog = 128 ]): resource'
-	},
-	socket_create_pair: {
-		description: 'Creates a pair of indistinguishable sockets and stores them in an array',
-		signature: '( int $domain , int $type , int $protocol , array $fd ): bool'
-	},
-	socket_create: {
-		description: 'Create a socket (endpoint for communication)',
-		signature: '( int $domain , int $type , int $protocol ): resource'
-	},
-	socket_export_stream: {
-		description: 'Export a socket extension resource into a stream that encapsulates a socket',
-		signature: '( resource $socket ): resource'
-	},
-	socket_get_option: {
-		description: 'Gets socket options for the socket',
-		signature: '( resource $socket , int $level , int $optname ): mixed'
-	},
-	socket_getopt: {
-		description: 'Alias of socket_get_option',
-	},
-	socket_getpeername: {
-		description: 'Queries the remote side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type',
-		signature: '( resource $socket , string $address [, int $port ]): bool'
-	},
-	socket_getsockname: {
-		description: 'Queries the local side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type',
-		signature: '( resource $socket , string $addr [, int $port ]): bool'
-	},
-	socket_import_stream: {
-		description: 'Import a stream',
-		signature: '( resource $stream ): resource'
-	},
-	socket_last_error: {
-		description: 'Returns the last error on the socket',
-		signature: '([ resource $socket ]): int'
-	},
-	socket_listen: {
-		description: 'Listens for a connection on a socket',
-		signature: '( resource $socket [, int $backlog = 0 ]): bool'
-	},
-	socket_read: {
-		description: 'Reads a maximum of length bytes from a socket',
-		signature: '( resource $socket , int $length [, int $type = PHP_BINARY_READ ]): string'
-	},
-	socket_recv: {
-		description: 'Receives data from a connected socket',
-		signature: '( resource $socket , string $buf , int $len , int $flags ): int'
-	},
-	socket_recvfrom: {
-		description: 'Receives data from a socket whether or not it is connection-oriented',
-		signature: '( resource $socket , string $buf , int $len , int $flags , string $name [, int $port ]): int'
-	},
-	socket_recvmsg: {
-		description: 'Read a message',
-		signature: '( resource $socket , array $message [, int $flags = 0 ]): int'
-	},
-	socket_select: {
-		description: 'Runs the select() system call on the given arrays of sockets with a specified timeout',
-		signature: '( array $read , array $write , array $except , int $tv_sec [, int $tv_usec = 0 ]): int'
-	},
-	socket_send: {
-		description: 'Sends data to a connected socket',
-		signature: '( resource $socket , string $buf , int $len , int $flags ): int'
-	},
-	socket_sendmsg: {
-		description: 'Send a message',
-		signature: '( resource $socket , array $message [, int $flags = 0 ]): int'
-	},
-	socket_sendto: {
-		description: 'Sends a message to a socket, whether it is connected or not',
-		signature: '( resource $socket , string $buf , int $len , int $flags , string $addr [, int $port = 0 ]): int'
-	},
-	socket_set_block: {
-		description: 'Sets blocking mode on a socket resource',
-		signature: '( resource $socket ): bool'
-	},
-	socket_set_nonblock: {
-		description: 'Sets nonblocking mode for file descriptor fd',
-		signature: '( resource $socket ): bool'
-	},
-	socket_set_option: {
-		description: 'Sets socket options for the socket',
-		signature: '( resource $socket , int $level , int $optname , mixed $optval ): bool'
-	},
-	socket_setopt: {
-		description: 'Alias of socket_set_option',
-	},
-	socket_shutdown: {
-		description: 'Shuts down a socket for receiving, sending, or both',
-		signature: '( resource $socket [, int $how = 2 ]): bool'
-	},
-	socket_strerror: {
-		description: 'Return a string describing a socket error',
-		signature: '( int $errno ): string'
-	},
-	socket_write: {
-		description: 'Write to a socket',
-		signature: '( resource $socket , string $buffer [, int $length = 0 ]): int'
-	},
-	apache_child_terminate: {
-		description: 'Terminate apache process after this request',
-		signature: '(void): bool'
-	},
-	apache_get_modules: {
-		description: 'Get a list of loaded Apache modules',
-		signature: '(void): array'
-	},
-	apache_get_version: {
-		description: 'Fetch Apache version',
-		signature: '(void): string'
-	},
-	apache_getenv: {
-		description: 'Get an Apache subprocess_env variable',
-		signature: '( string $variable [, bool $walk_to_top ]): string'
-	},
-	apache_lookup_uri: {
-		description: 'Perform a partial request for the specified URI and return all info about it',
-		signature: '( string $filename ): object'
-	},
-	apache_note: {
-		description: 'Get and set apache request notes',
-		signature: '( string $note_name [, string $note_value = "" ]): string'
-	},
-	apache_request_headers: {
-		description: 'Fetch all HTTP request headers',
-		signature: '(void): array'
-	},
-	apache_reset_timeout: {
-		description: 'Reset the Apache write timer',
-		signature: '(void): bool'
-	},
-	apache_response_headers: {
-		description: 'Fetch all HTTP response headers',
-		signature: '(void): array'
-	},
-	apache_setenv: {
-		description: 'Set an Apache subprocess_env variable',
-		signature: '( string $variable , string $value [, bool $walk_to_top ]): bool'
-	},
-	getallheaders: {
-		description: 'Fetch all HTTP request headers',
-		signature: '(void): array'
-	},
-	virtual: {
-		description: 'Perform an Apache sub-request',
-		signature: '( string $filename ): bool'
-	},
-	nsapi_request_headers: {
-		description: 'Fetch all HTTP request headers',
-		signature: '(void): array'
-	},
-	nsapi_response_headers: {
-		description: 'Fetch all HTTP response headers',
-		signature: '(void): array'
-	},
-	nsapi_virtual: {
-		description: 'Perform an NSAPI sub-request',
-		signature: '( string $uri ): bool'
-	},
-	session_abort: {
-		description: 'Discard session array changes and finish session',
-		signature: '(void): bool'
-	},
-	session_cache_expire: {
-		description: 'Return current cache expire',
-		signature: '([ string $new_cache_expire ]): int'
-	},
-	session_cache_limiter: {
-		description: 'Get and/or set the current cache limiter',
-		signature: '([ string $cache_limiter ]): string'
-	},
-	session_commit: {
-		description: 'Alias of session_write_close',
-	},
-	session_create_id: {
-		description: 'Create new session id',
-		signature: '([ string $prefix ]): string'
-	},
-	session_decode: {
-		description: 'Decodes session data from a session encoded string',
-		signature: '( string $data ): bool'
-	},
-	session_destroy: {
-		description: 'Destroys all data registered to a session',
-		signature: '(void): bool'
-	},
-	session_encode: {
-		description: 'Encodes the current session data as a session encoded string',
-		signature: '(void): string'
-	},
-	session_gc: {
-		description: 'Perform session data garbage collection',
-		signature: '(void): int'
-	},
-	session_get_cookie_params: {
-		description: 'Get the session cookie parameters',
-		signature: '(void): array'
-	},
-	session_id: {
-		description: 'Get and/or set the current session id',
-		signature: '([ string $id ]): string'
-	},
-	session_is_registered: {
-		description: 'Find out whether a global variable is registered in a session',
-		signature: '( string $name ): bool'
-	},
-	session_module_name: {
-		description: 'Get and/or set the current session module',
-		signature: '([ string $module ]): string'
-	},
-	session_name: {
-		description: 'Get and/or set the current session name',
-		signature: '([ string $name ]): string'
-	},
-	session_regenerate_id: {
-		description: 'Update the current session id with a newly generated one',
-		signature: '([ bool $delete_old_session ]): bool'
-	},
-	session_register_shutdown: {
-		description: 'Session shutdown function',
-		signature: '(void): void'
-	},
-	session_register: {
-		description: 'Register one or more global variables with the current session',
-		signature: '( mixed $name [, mixed $... ]): bool'
-	},
-	session_reset: {
-		description: 'Re-initialize session array with original values',
-		signature: '(void): bool'
-	},
-	session_save_path: {
-		description: 'Get and/or set the current session save path',
-		signature: '([ string $path ]): string'
-	},
-	session_set_cookie_params: {
-		description: 'Set the session cookie parameters',
-		signature: '( int $lifetime [, string $path [, string $domain [, bool $secure [, bool $httponly , array $options ]]]]): bool'
-	},
-	session_set_save_handler: {
-		description: 'Sets user-level session storage functions',
-		signature: '( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid [, callable $validate_sid [, callable $update_timestamp , object $sessionhandler [, bool $register_shutdown ]]]]): bool'
-	},
-	session_start: {
-		description: 'Start new or resume existing session',
-		signature: '([ array $options = array() ]): bool'
-	},
-	session_status: {
-		description: 'Returns the current session status',
-		signature: '(void): int'
-	},
-	session_unregister: {
-		description: 'Unregister a global variable from the current session',
-		signature: '( string $name ): bool'
-	},
-	session_unset: {
-		description: 'Free all session variables',
-		signature: '(void): bool'
-	},
-	session_write_close: {
-		description: 'Write session data and end session',
-		signature: '(void): bool'
-	},
-	preg_filter: {
-		description: 'Perform a regular expression search and replace',
-		signature: '( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
-	},
-	preg_grep: {
-		description: 'Return array entries that match the pattern',
-		signature: '( string $pattern , array $input [, int $flags = 0 ]): array'
-	},
-	preg_last_error: {
-		description: 'Returns the error code of the last PCRE regex execution',
-		signature: '(void): int'
-	},
-	preg_match_all: {
-		description: 'Perform a global regular expression match',
-		signature: '( string $pattern , string $subject [, array $matches [, int $flags [, int $offset = 0 ]]]): int'
-	},
-	preg_match: {
-		description: 'Perform a regular expression match',
-		signature: '( string $pattern , string $subject [, array $matches [, int $flags = 0 [, int $offset = 0 ]]]): int'
-	},
-	preg_quote: {
-		description: 'Quote regular expression characters',
-		signature: '( string $str [, string $delimiter ]): string'
-	},
-	preg_replace_callback_array: {
-		description: 'Perform a regular expression search and replace using callbacks',
-		signature: '( array $patterns_and_callbacks , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
-	},
-	preg_replace_callback: {
-		description: 'Perform a regular expression search and replace using a callback',
-		signature: '( mixed $pattern , callable $callback , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
-	},
-	preg_replace: {
-		description: 'Perform a regular expression search and replace',
-		signature: '( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
-	},
-	preg_split: {
-		description: 'Split string by a regular expression',
-		signature: '( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]]): array'
-	},
-	addcslashes: {
-		description: 'Quote string with slashes in a C style',
-		signature: '( string $str , string $charlist ): string'
-	},
-	addslashes: {
-		description: 'Quote string with slashes',
-		signature: '( string $str ): string'
-	},
-	bin2hex: {
-		description: 'Convert binary data into hexadecimal representation',
-		signature: '( string $str ): string'
-	},
-	chop: {
-		description: 'Alias of rtrim',
-	},
-	chr: {
-		description: 'Generate a single-byte string from a number',
-		signature: '( int $bytevalue ): string'
-	},
-	chunk_split: {
-		description: 'Split a string into smaller chunks',
-		signature: '( string $body [, int $chunklen = 76 [, string $end = "\r\n" ]]): string'
-	},
-	convert_cyr_string: {
-		description: 'Convert from one Cyrillic character set to another',
-		signature: '( string $str , string $from , string $to ): string'
-	},
-	convert_uudecode: {
-		description: 'Decode a uuencoded string',
-		signature: '( string $data ): string'
-	},
-	convert_uuencode: {
-		description: 'Uuencode a string',
-		signature: '( string $data ): string'
-	},
-	count_chars: {
-		description: 'Return information about characters used in a string',
-		signature: '( string $string [, int $mode = 0 ]): mixed'
-	},
-	crc32: {
-		description: 'Calculates the crc32 polynomial of a string',
-		signature: '( string $str ): int'
-	},
-	crypt: {
-		description: 'One-way string hashing',
-		signature: '( string $str [, string $salt ]): string'
-	},
-	echo: {
-		description: 'Output one or more strings',
-		signature: '( string $arg1 [, string $... ]): void'
-	},
-	explode: {
-		description: 'Split a string by a string',
-		signature: '( string $delimiter , string $string [, int $limit = PHP_INT_MAX ]): array'
-	},
-	fprintf: {
-		description: 'Write a formatted string to a stream',
-		signature: '( resource $handle , string $format [, mixed $... ]): int'
-	},
-	get_html_translation_table: {
-		description: 'Returns the translation table used by htmlspecialchars and htmlentities',
-		signature: '([ int $table = HTML_SPECIALCHARS [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = "UTF-8" ]]]): array'
-	},
-	hebrev: {
-		description: 'Convert logical Hebrew text to visual text',
-		signature: '( string $hebrew_text [, int $max_chars_per_line = 0 ]): string'
-	},
-	hebrevc: {
-		description: 'Convert logical Hebrew text to visual text with newline conversion',
-		signature: '( string $hebrew_text [, int $max_chars_per_line = 0 ]): string'
-	},
-	hex2bin: {
-		description: 'Decodes a hexadecimally encoded binary string',
-		signature: '( string $data ): string'
-	},
-	html_entity_decode: {
-		description: 'Convert HTML entities to their corresponding characters',
-		signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") ]]): string'
-	},
-	htmlentities: {
-		description: 'Convert all applicable characters to HTML entities',
-		signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode ]]]): string'
-	},
-	htmlspecialchars_decode: {
-		description: 'Convert special HTML entities back to characters',
-		signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 ]): string'
-	},
-	htmlspecialchars: {
-		description: 'Convert special characters to HTML entities',
-		signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode ]]]): string'
-	},
-	implode: {
-		description: 'Join array elements with a string',
-		signature: '( string $glue , array $pieces ): string'
-	},
-	join: {
-		description: 'Alias of implode',
-	},
-	lcfirst: {
-		description: 'Make a string\'s first character lowercase',
-		signature: '( string $str ): string'
-	},
-	levenshtein: {
-		description: 'Calculate Levenshtein distance between two strings',
-		signature: '( string $str1 , string $str2 , int $cost_ins , int $cost_rep , int $cost_del ): int'
-	},
-	localeconv: {
-		description: 'Get numeric formatting information',
-		signature: '(void): array'
-	},
-	ltrim: {
-		description: 'Strip whitespace (or other characters) from the beginning of a string',
-		signature: '( string $str [, string $character_mask ]): string'
-	},
-	md5_file: {
-		description: 'Calculates the md5 hash of a given file',
-		signature: '( string $filename [, bool $raw_output ]): string'
-	},
-	md5: {
-		description: 'Calculate the md5 hash of a string',
-		signature: '( string $str [, bool $raw_output ]): string'
-	},
-	metaphone: {
-		description: 'Calculate the metaphone key of a string',
-		signature: '( string $str [, int $phonemes = 0 ]): string'
-	},
-	money_format: {
-		description: 'Formats a number as a currency string',
-		signature: '( string $format , float $number ): string'
-	},
-	nl_langinfo: {
-		description: 'Query language and locale information',
-		signature: '( int $item ): string'
-	},
-	nl2br: {
-		description: 'Inserts HTML line breaks before all newlines in a string',
-		signature: '( string $string [, bool $is_xhtml ]): string'
-	},
-	number_format: {
-		description: 'Format a number with grouped thousands',
-		signature: '( float $number , int $decimals = 0 , string $dec_point = "." , string $thousands_sep = "," ): string'
-	},
-	ord: {
-		description: 'Convert the first byte of a string to a value between 0 and 255',
-		signature: '( string $string ): int'
-	},
-	parse_str: {
-		description: 'Parses the string into variables',
-		signature: '( string $encoded_string [, array $result ]): void'
-	},
-	print: {
-		description: 'Output a string',
-		signature: '( string $arg ): int'
-	},
-	printf: {
-		description: 'Output a formatted string',
-		signature: '( string $format [, mixed $... ]): int'
-	},
-	quoted_printable_decode: {
-		description: 'Convert a quoted-printable string to an 8 bit string',
-		signature: '( string $str ): string'
-	},
-	quoted_printable_encode: {
-		description: 'Convert a 8 bit string to a quoted-printable string',
-		signature: '( string $str ): string'
-	},
-	quotemeta: {
-		description: 'Quote meta characters',
-		signature: '( string $str ): string'
-	},
-	rtrim: {
-		description: 'Strip whitespace (or other characters) from the end of a string',
-		signature: '( string $str [, string $character_mask ]): string'
-	},
-	setlocale: {
-		description: 'Set locale information',
-		signature: '( int $category , array $locale [, string $... ]): string'
-	},
-	sha1_file: {
-		description: 'Calculate the sha1 hash of a file',
-		signature: '( string $filename [, bool $raw_output ]): string'
-	},
-	sha1: {
-		description: 'Calculate the sha1 hash of a string',
-		signature: '( string $str [, bool $raw_output ]): string'
-	},
-	similar_text: {
-		description: 'Calculate the similarity between two strings',
-		signature: '( string $first , string $second [, float $percent ]): int'
-	},
-	soundex: {
-		description: 'Calculate the soundex key of a string',
-		signature: '( string $str ): string'
-	},
-	sprintf: {
-		description: 'Return a formatted string',
-		signature: '( string $format [, mixed $... ]): string'
-	},
-	sscanf: {
-		description: 'Parses input from a string according to a format',
-		signature: '( string $str , string $format [, mixed $... ]): mixed'
-	},
-	str_getcsv: {
-		description: 'Parse a CSV string into an array',
-		signature: '( string $input [, string $delimiter = "," [, string $enclosure = \'"\' [, string $escape = "\\" ]]]): array'
-	},
-	str_ireplace: {
-		description: 'Case-insensitive version of str_replace',
-		signature: '( mixed $search , mixed $replace , mixed $subject [, int $count ]): mixed'
-	},
-	str_pad: {
-		description: 'Pad a string to a certain length with another string',
-		signature: '( string $input , int $pad_length [, string $pad_string = " " [, int $pad_type = STR_PAD_RIGHT ]]): string'
-	},
-	str_repeat: {
-		description: 'Repeat a string',
-		signature: '( string $input , int $multiplier ): string'
-	},
-	str_replace: {
-		description: 'Replace all occurrences of the search string with the replacement string',
-		signature: '( mixed $search , mixed $replace , mixed $subject [, int $count ]): mixed'
-	},
-	str_rot13: {
-		description: 'Perform the rot13 transform on a string',
-		signature: '( string $str ): string'
-	},
-	str_shuffle: {
-		description: 'Randomly shuffles a string',
-		signature: '( string $str ): string'
-	},
-	str_split: {
-		description: 'Convert a string to an array',
-		signature: '( string $string [, int $split_length = 1 ]): array'
-	},
-	str_word_count: {
-		description: 'Return information about words used in a string',
-		signature: '( string $string [, int $format = 0 [, string $charlist ]]): mixed'
-	},
-	strcasecmp: {
-		description: 'Binary safe case-insensitive string comparison',
-		signature: '( string $str1 , string $str2 ): int'
-	},
-	strchr: {
-		description: 'Alias of strstr',
-	},
-	strcmp: {
-		description: 'Binary safe string comparison',
-		signature: '( string $str1 , string $str2 ): int'
-	},
-	strcoll: {
-		description: 'Locale based string comparison',
-		signature: '( string $str1 , string $str2 ): int'
-	},
-	strcspn: {
-		description: 'Find length of initial segment not matching mask',
-		signature: '( string $subject , string $mask [, int $start [, int $length ]]): int'
-	},
-	strip_tags: {
-		description: 'Strip HTML and PHP tags from a string',
-		signature: '( string $str [, string $allowable_tags ]): string'
-	},
-	stripcslashes: {
-		description: 'Un-quote string quoted with addcslashes',
-		signature: '( string $str ): string'
-	},
-	stripos: {
-		description: 'Find the position of the first occurrence of a case-insensitive substring in a string',
-		signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
-	},
-	stripslashes: {
-		description: 'Un-quotes a quoted string',
-		signature: '( string $str ): string'
-	},
-	stristr: {
-		description: 'Case-insensitive strstr',
-		signature: '( string $haystack , mixed $needle [, bool $before_needle ]): string'
-	},
-	strlen: {
-		description: 'Get string length',
-		signature: '( string $string ): int'
-	},
-	strnatcasecmp: {
-		description: 'Case insensitive string comparisons using a "natural order" algorithm',
-		signature: '( string $str1 , string $str2 ): int'
-	},
-	strnatcmp: {
-		description: 'String comparisons using a "natural order" algorithm',
-		signature: '( string $str1 , string $str2 ): int'
-	},
-	strncasecmp: {
-		description: 'Binary safe case-insensitive string comparison of the first n characters',
-		signature: '( string $str1 , string $str2 , int $len ): int'
-	},
-	strncmp: {
-		description: 'Binary safe string comparison of the first n characters',
-		signature: '( string $str1 , string $str2 , int $len ): int'
-	},
-	strpbrk: {
-		description: 'Search a string for any of a set of characters',
-		signature: '( string $haystack , string $char_list ): string'
-	},
-	strpos: {
-		description: 'Find the position of the first occurrence of a substring in a string',
-		signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
-	},
-	strrchr: {
-		description: 'Find the last occurrence of a character in a string',
-		signature: '( string $haystack , mixed $needle ): string'
-	},
-	strrev: {
-		description: 'Reverse a string',
-		signature: '( string $string ): string'
-	},
-	strripos: {
-		description: 'Find the position of the last occurrence of a case-insensitive substring in a string',
-		signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
-	},
-	strrpos: {
-		description: 'Find the position of the last occurrence of a substring in a string',
-		signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
-	},
-	strspn: {
-		description: 'Finds the length of the initial segment of a string consisting   entirely of characters contained within a given mask',
-		signature: '( string $subject , string $mask [, int $start [, int $length ]]): int'
-	},
-	strstr: {
-		description: 'Find the first occurrence of a string',
-		signature: '( string $haystack , mixed $needle [, bool $before_needle ]): string'
-	},
-	strtok: {
-		description: 'Tokenize string',
-		signature: '( string $str , string $token ): string'
-	},
-	strtolower: {
-		description: 'Make a string lowercase',
-		signature: '( string $string ): string'
-	},
-	strtoupper: {
-		description: 'Make a string uppercase',
-		signature: '( string $string ): string'
-	},
-	strtr: {
-		description: 'Translate characters or replace substrings',
-		signature: '( string $str , string $from , string $to , array $replace_pairs ): string'
-	},
-	substr_compare: {
-		description: 'Binary safe comparison of two strings from an offset, up to length characters',
-		signature: '( string $main_str , string $str , int $offset [, int $length [, bool $case_insensitivity ]]): int'
-	},
-	substr_count: {
-		description: 'Count the number of substring occurrences',
-		signature: '( string $haystack , string $needle [, int $offset = 0 [, int $length ]]): int'
-	},
-	substr_replace: {
-		description: 'Replace text within a portion of a string',
-		signature: '( mixed $string , mixed $replacement , mixed $start [, mixed $length ]): mixed'
-	},
-	substr: {
-		description: 'Return part of a string',
-		signature: '( string $string , int $start [, int $length ]): string'
-	},
-	trim: {
-		description: 'Strip whitespace (or other characters) from the beginning and end of a string',
-		signature: '( string $str [, string $character_mask = " \t\n\r\0\x0B" ]): string'
-	},
-	ucfirst: {
-		description: 'Make a string\'s first character uppercase',
-		signature: '( string $str ): string'
-	},
-	ucwords: {
-		description: 'Uppercase the first character of each word in a string',
-		signature: '( string $str [, string $delimiters = " \t\r\n\f\v" ]): string'
-	},
-	vfprintf: {
-		description: 'Write a formatted string to a stream',
-		signature: '( resource $handle , string $format , array $args ): int'
-	},
-	vprintf: {
-		description: 'Output a formatted string',
-		signature: '( string $format , array $args ): int'
-	},
-	vsprintf: {
-		description: 'Return a formatted string',
-		signature: '( string $format , array $args ): string'
-	},
-	wordwrap: {
-		description: 'Wraps a string to a given number of characters',
-		signature: '( string $str [, int $width = 75 [, string $break = "\n" [, bool $cut ]]]): string'
-	},
-	array_change_key_case: {
-		description: 'Changes the case of all keys in an array',
-		signature: '( array $array [, int $case = CASE_LOWER ]): array'
-	},
-	array_chunk: {
-		description: 'Split an array into chunks',
-		signature: '( array $array , int $size [, bool $preserve_keys ]): array'
-	},
-	array_column: {
-		description: 'Return the values from a single column in the input array',
-		signature: '( array $input , mixed $column_key [, mixed $index_key ]): array'
-	},
-	array_combine: {
-		description: 'Creates an array by using one array for keys and another for its values',
-		signature: '( array $keys , array $values ): array'
-	},
-	array_count_values: {
-		description: 'Counts all the values of an array',
-		signature: '( array $array ): array'
-	},
-	array_diff_assoc: {
-		description: 'Computes the difference of arrays with additional index check',
-		signature: '( array $array1 , array $array2 [, array $... ]): array'
-	},
-	array_diff_key: {
-		description: 'Computes the difference of arrays using keys for comparison',
-		signature: '( array $array1 , array $array2 [, array $... ]): array'
-	},
-	array_diff_uassoc: {
-		description: 'Computes the difference of arrays with additional index check which is performed by a user supplied callback function',
-		signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
-	},
-	array_diff_ukey: {
-		description: 'Computes the difference of arrays using a callback function on the keys for comparison',
-		signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
-	},
-	array_diff: {
-		description: 'Computes the difference of arrays',
-		signature: '( array $array1 , array $array2 [, array $... ]): array'
-	},
-	array_fill_keys: {
-		description: 'Fill an array with values, specifying keys',
-		signature: '( array $keys , mixed $value ): array'
-	},
-	array_fill: {
-		description: 'Fill an array with values',
-		signature: '( int $start_index , int $num , mixed $value ): array'
-	},
-	array_filter: {
-		description: 'Filters elements of an array using a callback function',
-		signature: '( array $array [, callable $callback [, int $flag = 0 ]]): array'
-	},
-	array_flip: {
-		description: 'Exchanges all keys with their associated values in an array',
-		signature: '( array $array ): string'
-	},
-	array_intersect_assoc: {
-		description: 'Computes the intersection of arrays with additional index check',
-		signature: '( array $array1 , array $array2 [, array $... ]): array'
-	},
-	array_intersect_key: {
-		description: 'Computes the intersection of arrays using keys for comparison',
-		signature: '( array $array1 , array $array2 [, array $... ]): array'
-	},
-	array_intersect_uassoc: {
-		description: 'Computes the intersection of arrays with additional index check, compares indexes by a callback function',
-		signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
-	},
-	array_intersect_ukey: {
-		description: 'Computes the intersection of arrays using a callback function on the keys for comparison',
-		signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
-	},
-	array_intersect: {
-		description: 'Computes the intersection of arrays',
-		signature: '( array $array1 , array $array2 [, array $... ]): array'
-	},
-	array_key_exists: {
-		description: 'Checks if the given key or index exists in the array',
-		signature: '( mixed $key , array $array ): bool'
-	},
-	array_key_first: {
-		description: 'Gets the first key of an array',
-		signature: '( array $array ): mixed'
-	},
-	array_key_last: {
-		description: 'Gets the last key of an array',
-		signature: '( array $array ): mixed'
-	},
-	array_keys: {
-		description: 'Return all the keys or a subset of the keys of an array',
-		signature: '( array $array , mixed $search_value [, bool $strict ]): array'
-	},
-	array_map: {
-		description: 'Applies the callback to the elements of the given arrays',
-		signature: '( callable $callback , array $array1 [, array $... ]): array'
-	},
-	array_merge_recursive: {
-		description: 'Merge one or more arrays recursively',
-		signature: '( array $array1 [, array $... ]): array'
-	},
-	array_merge: {
-		description: 'Merge one or more arrays',
-		signature: '( array $array1 [, array $... ]): array'
-	},
-	array_multisort: {
-		description: 'Sort multiple or multi-dimensional arrays',
-		signature: '( array $array1 [, mixed $array1_sort_order = SORT_ASC [, mixed $array1_sort_flags = SORT_REGULAR [, mixed $... ]]]): string'
-	},
-	array_pad: {
-		description: 'Pad array to the specified length with a value',
-		signature: '( array $array , int $size , mixed $value ): array'
-	},
-	array_pop: {
-		description: 'Pop the element off the end of array',
-		signature: '( array $array ): array'
-	},
-	array_product: {
-		description: 'Calculate the product of values in an array',
-		signature: '( array $array ): number'
-	},
-	array_push: {
-		description: 'Push one or more elements onto the end of array',
-		signature: '( array $array [, mixed $... ]): int'
-	},
-	array_rand: {
-		description: 'Pick one or more random keys out of an array',
-		signature: '( array $array [, int $num = 1 ]): mixed'
-	},
-	array_reduce: {
-		description: 'Iteratively reduce the array to a single value using a callback function',
-		signature: '( array $array , callable $callback [, mixed $initial ]): mixed'
-	},
-	array_replace_recursive: {
-		description: 'Replaces elements from passed arrays into the first array recursively',
-		signature: '( array $array1 [, array $... ]): array'
-	},
-	array_replace: {
-		description: 'Replaces elements from passed arrays into the first array',
-		signature: '( array $array1 [, array $... ]): array'
-	},
-	array_reverse: {
-		description: 'Return an array with elements in reverse order',
-		signature: '( array $array [, bool $preserve_keys ]): array'
-	},
-	array_search: {
-		description: 'Searches the array for a given value and returns the first corresponding key if successful',
-		signature: '( mixed $needle , array $haystack [, bool $strict ]): mixed'
-	},
-	array_shift: {
-		description: 'Shift an element off the beginning of array',
-		signature: '( array $array ): array'
-	},
-	array_slice: {
-		description: 'Extract a slice of the array',
-		signature: '( array $array , int $offset [, int $length [, bool $preserve_keys ]]): array'
-	},
-	array_splice: {
-		description: 'Remove a portion of the array and replace it with something else',
-		signature: '( array $input , int $offset [, int $length = count($input) [, mixed $replacement = array() ]]): array'
-	},
-	array_sum: {
-		description: 'Calculate the sum of values in an array',
-		signature: '( array $array ): number'
-	},
-	array_udiff_assoc: {
-		description: 'Computes the difference of arrays with additional index check, compares data by a callback function',
-		signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
-	},
-	array_udiff_uassoc: {
-		description: 'Computes the difference of arrays with additional index check, compares data and indexes by a callback function',
-		signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func , callable $key_compare_func ]): array'
-	},
-	array_udiff: {
-		description: 'Computes the difference of arrays by using a callback function for data comparison',
-		signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
-	},
-	array_uintersect_assoc: {
-		description: 'Computes the intersection of arrays with additional index check, compares data by a callback function',
-		signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
-	},
-	array_uintersect_uassoc: {
-		description: 'Computes the intersection of arrays with additional index check, compares data and indexes by separate callback functions',
-		signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func , callable $key_compare_func ]): array'
-	},
-	array_uintersect: {
-		description: 'Computes the intersection of arrays, compares data by a callback function',
-		signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
-	},
-	array_unique: {
-		description: 'Removes duplicate values from an array',
-		signature: '( array $array [, int $sort_flags = SORT_STRING ]): array'
-	},
-	array_unshift: {
-		description: 'Prepend one or more elements to the beginning of an array',
-		signature: '( array $array [, mixed $... ]): int'
-	},
-	array_values: {
-		description: 'Return all the values of an array',
-		signature: '( array $array ): array'
-	},
-	array_walk_recursive: {
-		description: 'Apply a user function recursively to every member of an array',
-		signature: '( array $array , callable $callback [, mixed $userdata ]): bool'
-	},
-	array_walk: {
-		description: 'Apply a user supplied function to every member of an array',
-		signature: '( array $array , callable $callback [, mixed $userdata ]): bool'
-	},
-	array: {
-		description: 'Create an array',
-		signature: '([ mixed $... ]): array'
-	},
-	arsort: {
-		description: 'Sort an array in reverse order and maintain index association',
-		signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
-	},
-	asort: {
-		description: 'Sort an array and maintain index association',
-		signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
-	},
-	compact: {
-		description: 'Create array containing variables and their values',
-		signature: '( mixed $varname1 [, mixed $... ]): array'
-	},
-	count: {
-		description: 'Count all elements in an array, or something in an object',
-		signature: '( mixed $array_or_countable [, int $mode = COUNT_NORMAL ]): int'
-	},
-	current: {
-		description: 'Return the current element in an array',
-		signature: '( array $array ): mixed'
-	},
-	each: {
-		description: 'Return the current key and value pair from an array and advance the array cursor',
-		signature: '( array $array ): array'
-	},
-	end: {
-		description: 'Set the internal pointer of an array to its last element',
-		signature: '( array $array ): mixed'
-	},
-	extract: {
-		description: 'Import variables into the current symbol table from an array',
-		signature: '( array $array [, int $flags = EXTR_OVERWRITE [, string $prefix ]]): int'
-	},
-	in_array: {
-		description: 'Checks if a value exists in an array',
-		signature: '( mixed $needle , array $haystack [, bool $strict ]): bool'
-	},
-	key_exists: {
-		description: 'Alias of array_key_exists',
-	},
-	key: {
-		description: 'Fetch a key from an array',
-		signature: '( array $array ): mixed'
-	},
-	krsort: {
-		description: 'Sort an array by key in reverse order',
-		signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
-	},
-	ksort: {
-		description: 'Sort an array by key',
-		signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
-	},
-	list: {
-		description: 'Assign variables as if they were an array',
-		signature: '( mixed $var1 [, mixed $... ]): array'
-	},
-	natcasesort: {
-		description: 'Sort an array using a case insensitive "natural order" algorithm',
-		signature: '( array $array ): bool'
-	},
-	natsort: {
-		description: 'Sort an array using a "natural order" algorithm',
-		signature: '( array $array ): bool'
-	},
-	next: {
-		description: 'Advance the internal pointer of an array',
-		signature: '( array $array ): mixed'
-	},
-	pos: {
-		description: 'Alias of current',
-	},
-	prev: {
-		description: 'Rewind the internal array pointer',
-		signature: '( array $array ): mixed'
-	},
-	range: {
-		description: 'Create an array containing a range of elements',
-		signature: '( mixed $start , mixed $end [, number $step = 1 ]): array'
-	},
-	reset: {
-		description: 'Set the internal pointer of an array to its first element',
-		signature: '( array $array ): mixed'
-	},
-	rsort: {
-		description: 'Sort an array in reverse order',
-		signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
-	},
-	shuffle: {
-		description: 'Shuffle an array',
-		signature: '( array $array ): bool'
-	},
-	sizeof: {
-		description: 'Alias of count',
-	},
-	sort: {
-		description: 'Sort an array',
-		signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
-	},
-	uasort: {
-		description: 'Sort an array with a user-defined comparison function and maintain index association',
-		signature: '( array $array , callable $value_compare_func ): bool'
-	},
-	uksort: {
-		description: 'Sort an array by keys using a user-defined comparison function',
-		signature: '( array $array , callable $key_compare_func ): bool'
-	},
-	usort: {
-		description: 'Sort an array by values using a user-defined comparison function',
-		signature: '( array $array , callable $value_compare_func ): bool'
-	},
-	__autoload: {
-		description: 'Attempt to load undefined class',
-		signature: '( string $class ): void'
-	},
-	call_user_method_array: {
-		description: 'Call a user method given with an array of parameters',
-		signature: '( string $method_name , object $obj , array $params ): mixed'
-	},
-	call_user_method: {
-		description: 'Call a user method on an specific object',
-		signature: '( string $method_name , object $obj [, mixed $... ]): mixed'
-	},
-	class_alias: {
-		description: 'Creates an alias for a class',
-		signature: '( string $original , string $alias [, bool $autoload ]): bool'
-	},
-	class_exists: {
-		description: 'Checks if the class has been defined',
-		signature: '( string $class_name [, bool $autoload ]): bool'
-	},
-	get_called_class: {
-		description: 'The "Late Static Binding" class name',
-		signature: '(void): string'
-	},
-	get_class_methods: {
-		description: 'Gets the class methods\' names',
-		signature: '( mixed $class_name ): array'
-	},
-	get_class_vars: {
-		description: 'Get the default properties of the class',
-		signature: '( string $class_name ): array'
-	},
-	get_class: {
-		description: 'Returns the name of the class of an object',
-		signature: '([ object $object ]): string'
-	},
-	get_declared_classes: {
-		description: 'Returns an array with the name of the defined classes',
-		signature: '(void): array'
-	},
-	get_declared_interfaces: {
-		description: 'Returns an array of all declared interfaces',
-		signature: '(void): array'
-	},
-	get_declared_traits: {
-		description: 'Returns an array of all declared traits',
-		signature: '(void): array'
-	},
-	get_object_vars: {
-		description: 'Gets the properties of the given object',
-		signature: '( object $object ): array'
-	},
-	get_parent_class: {
-		description: 'Retrieves the parent class name for object or class',
-		signature: '([ mixed $object ]): string'
-	},
-	interface_exists: {
-		description: 'Checks if the interface has been defined',
-		signature: '( string $interface_name [, bool $autoload ]): bool'
-	},
-	is_a: {
-		description: 'Checks if the object is of this class or has this class as one of its parents',
-		signature: '( mixed $object , string $class_name [, bool $allow_string ]): bool'
-	},
-	is_subclass_of: {
-		description: 'Checks if the object has this class as one of its parents or implements it',
-		signature: '( mixed $object , string $class_name [, bool $allow_string ]): bool'
-	},
-	method_exists: {
-		description: 'Checks if the class method exists',
-		signature: '( mixed $object , string $method_name ): bool'
-	},
-	property_exists: {
-		description: 'Checks if the object or class has a property',
-		signature: '( mixed $class , string $property ): bool'
-	},
-	trait_exists: {
-		description: 'Checks if the trait exists',
-		signature: '( string $traitname [, bool $autoload ]): bool'
-	},
-	ctype_alnum: {
-		description: 'Check for alphanumeric character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_alpha: {
-		description: 'Check for alphabetic character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_cntrl: {
-		description: 'Check for control character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_digit: {
-		description: 'Check for numeric character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_graph: {
-		description: 'Check for any printable character(s) except space',
-		signature: '( string $text ): string'
-	},
-	ctype_lower: {
-		description: 'Check for lowercase character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_print: {
-		description: 'Check for printable character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_punct: {
-		description: 'Check for any printable character which is not whitespace or an   alphanumeric character',
-		signature: '( string $text ): string'
-	},
-	ctype_space: {
-		description: 'Check for whitespace character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_upper: {
-		description: 'Check for uppercase character(s)',
-		signature: '( string $text ): string'
-	},
-	ctype_xdigit: {
-		description: 'Check for character(s) representing a hexadecimal digit',
-		signature: '( string $text ): string'
-	},
-	filter_has_var: {
-		description: 'Checks if variable of specified type exists',
-		signature: '( int $type , string $variable_name ): bool'
-	},
-	filter_id: {
-		description: 'Returns the filter ID belonging to a named filter',
-		signature: '( string $filtername ): int'
-	},
-	filter_input_array: {
-		description: 'Gets external variables and optionally filters them',
-		signature: '( int $type [, mixed $definition [, bool $add_empty ]]): mixed'
-	},
-	filter_input: {
-		description: 'Gets a specific external variable by name and optionally filters it',
-		signature: '( int $type , string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]]): mixed'
-	},
-	filter_list: {
-		description: 'Returns a list of all supported filters',
-		signature: '(void): array'
-	},
-	filter_var_array: {
-		description: 'Gets multiple variables and optionally filters them',
-		signature: '( array $data [, mixed $definition [, bool $add_empty ]]): mixed'
-	},
-	filter_var: {
-		description: 'Filters a variable with a specified filter',
-		signature: '( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]]): mixed'
-	},
-	call_user_func_array: {
-		description: 'Call a callback with an array of parameters',
-		signature: '( callable $callback , array $param_arr ): mixed'
-	},
-	call_user_func: {
-		description: 'Call the callback given by the first parameter',
-		signature: '( callable $callback [, mixed $... ]): mixed'
-	},
-	create_function: {
-		description: 'Create an anonymous (lambda-style) function',
-		signature: '( string $args , string $code ): string'
-	},
-	forward_static_call_array: {
-		description: 'Call a static method and pass the arguments as array',
-		signature: '( callable $function , array $parameters ): mixed'
-	},
-	forward_static_call: {
-		description: 'Call a static method',
-		signature: '( callable $function [, mixed $... ]): mixed'
-	},
-	func_get_arg: {
-		description: 'Return an item from the argument list',
-		signature: '( int $arg_num ): mixed'
-	},
-	func_get_args: {
-		description: 'Returns an array comprising a function\'s argument list',
-		signature: '(void): array'
-	},
-	func_num_args: {
-		description: 'Returns the number of arguments passed to the function',
-		signature: '(void): int'
-	},
-	function_exists: {
-		description: 'Return TRUE if the given function has been defined',
-		signature: '( string $function_name ): bool'
-	},
-	get_defined_functions: {
-		description: 'Returns an array of all defined functions',
-		signature: '([ bool $exclude_disabled ]): array'
-	},
-	register_shutdown_function: {
-		description: 'Register a function for execution on shutdown',
-		signature: '( callable $callback [, mixed $... ]): void'
-	},
-	register_tick_function: {
-		description: 'Register a function for execution on each tick',
-		signature: '( callable $function [, mixed $... ]): bool'
-	},
-	unregister_tick_function: {
-		description: 'De-register a function for execution on each tick',
-		signature: '( callable $function ): void'
-	},
-	boolval: {
-		description: 'Get the boolean value of a variable',
-		signature: '( mixed $var ): boolean'
-	},
-	debug_zval_dump: {
-		description: 'Dumps a string representation of an internal zend value to output',
-		signature: '( mixed $variable [, mixed $... ]): void'
-	},
-	doubleval: {
-		description: 'Alias of floatval',
-	},
-	empty: {
-		description: 'Determine whether a variable is empty',
-		signature: '( mixed $var ): bool'
-	},
-	floatval: {
-		description: 'Get float value of a variable',
-		signature: '( mixed $var ): float'
-	},
-	get_defined_vars: {
-		description: 'Returns an array of all defined variables',
-		signature: '(void): array'
-	},
-	get_resource_type: {
-		description: 'Returns the resource type',
-		signature: '( resource $handle ): string'
-	},
-	gettype: {
-		description: 'Get the type of a variable',
-		signature: '( mixed $var ): string'
-	},
-	import_request_variables: {
-		description: 'Import GET/POST/Cookie variables into the global scope',
-		signature: '( string $types [, string $prefix ]): bool'
-	},
-	intval: {
-		description: 'Get the integer value of a variable',
-		signature: '( mixed $var [, int $base = 10 ]): integer'
-	},
-	is_array: {
-		description: 'Finds whether a variable is an array',
-		signature: '( mixed $var ): bool'
-	},
-	is_bool: {
-		description: 'Finds out whether a variable is a boolean',
-		signature: '( mixed $var ): bool'
-	},
-	is_callable: {
-		description: 'Verify that the contents of a variable can be called as a function',
-		signature: '( mixed $var [, bool $syntax_only [, string $callable_name ]]): bool'
-	},
-	is_countable: {
-		description: 'Verify that the contents of a variable is a countable value',
-		signature: '( mixed $var ): array'
-	},
-	is_double: {
-		description: 'Alias of is_float',
-	},
-	is_float: {
-		description: 'Finds whether the type of a variable is float',
-		signature: '( mixed $var ): bool'
-	},
-	is_int: {
-		description: 'Find whether the type of a variable is integer',
-		signature: '( mixed $var ): bool'
-	},
-	is_integer: {
-		description: 'Alias of is_int',
-	},
-	is_iterable: {
-		description: 'Verify that the contents of a variable is an iterable value',
-		signature: '( mixed $var ): array'
-	},
-	is_long: {
-		description: 'Alias of is_int',
-	},
-	is_null: {
-		description: 'Finds whether a variable is NULL',
-		signature: '( mixed $var ): bool'
-	},
-	is_numeric: {
-		description: 'Finds whether a variable is a number or a numeric string',
-		signature: '( mixed $var ): bool'
-	},
-	is_object: {
-		description: 'Finds whether a variable is an object',
-		signature: '( mixed $var ): bool'
-	},
-	is_real: {
-		description: 'Alias of is_float',
-	},
-	is_resource: {
-		description: 'Finds whether a variable is a resource',
-		signature: '( mixed $var ): bool'
-	},
-	is_scalar: {
-		description: 'Finds whether a variable is a scalar',
-		signature: '( mixed $var ): resource'
-	},
-	is_string: {
-		description: 'Find whether the type of a variable is string',
-		signature: '( mixed $var ): bool'
-	},
-	isset: {
-		description: 'Determine if a variable is declared and is different than NULL',
-		signature: '( mixed $var [, mixed $... ]): bool'
-	},
-	print_r: {
-		description: 'Prints human-readable information about a variable',
-		signature: '( mixed $expression [, bool $return ]): mixed'
-	},
-	serialize: {
-		description: 'Generates a storable representation of a value',
-		signature: '( mixed $value ): string'
-	},
-	settype: {
-		description: 'Set the type of a variable',
-		signature: '( mixed $var , string $type ): bool'
-	},
-	strval: {
-		description: 'Get string value of a variable',
-		signature: '( mixed $var ): string'
-	},
-	unserialize: {
-		description: 'Creates a PHP value from a stored representation',
-		signature: '( string $str [, array $options ]): mixed'
-	},
-	unset: {
-		description: 'Unset a given variable',
-		signature: '( mixed $var [, mixed $... ]): void'
-	},
-	var_dump: {
-		description: 'Dumps information about a variable',
-		signature: '( mixed $expression [, mixed $... ]): string'
-	},
-	var_export: {
-		description: 'Outputs or returns a parsable string representation of a variable',
-		signature: '( mixed $expression [, bool $return ]): mixed'
-	},
-	xmlrpc_decode_request: {
-		description: 'Decodes XML into native PHP types',
-		signature: '( string $xml , string $method [, string $encoding ]): mixed'
-	},
-	xmlrpc_decode: {
-		description: 'Decodes XML into native PHP types',
-		signature: '( string $xml [, string $encoding = "iso-8859-1" ]): mixed'
-	},
-	xmlrpc_encode_request: {
-		description: 'Generates XML for a method request',
-		signature: '( string $method , mixed $params [, array $output_options ]): string'
-	},
-	xmlrpc_encode: {
-		description: 'Generates XML for a PHP value',
-		signature: '( mixed $value ): string'
-	},
-	xmlrpc_get_type: {
-		description: 'Gets xmlrpc type for a PHP value',
-		signature: '( mixed $value ): string'
-	},
-	xmlrpc_is_fault: {
-		description: 'Determines if an array value represents an XMLRPC fault',
-		signature: '( array $arg ): bool'
-	},
-	xmlrpc_parse_method_descriptions: {
-		description: 'Decodes XML into a list of method descriptions',
-		signature: '( string $xml ): array'
-	},
-	xmlrpc_server_add_introspection_data: {
-		description: 'Adds introspection documentation',
-		signature: '( resource $server , array $desc ): int'
-	},
-	xmlrpc_server_call_method: {
-		description: 'Parses XML requests and call methods',
-		signature: '( resource $server , string $xml , mixed $user_data [, array $output_options ]): string'
-	},
-	xmlrpc_server_create: {
-		description: 'Creates an xmlrpc server',
-		signature: '(void): resource'
-	},
-	xmlrpc_server_destroy: {
-		description: 'Destroys server resources',
-		signature: '( resource $server ): bool'
-	},
-	xmlrpc_server_register_introspection_callback: {
-		description: 'Register a PHP function to generate documentation',
-		signature: '( resource $server , string $function ): bool'
-	},
-	xmlrpc_server_register_method: {
-		description: 'Register a PHP function to resource method matching method_name',
-		signature: '( resource $server , string $method_name , string $function ): bool'
-	},
-	xmlrpc_set_type: {
-		description: 'Sets xmlrpc type, base64 or datetime, for a PHP string value',
-		signature: '( string $value , string $type ): bool'
-	},
-	com_create_guid: {
-		description: 'Generate a globally unique identifier (GUID)',
-		signature: '(void): string'
-	},
-	com_event_sink: {
-		description: 'Connect events from a COM object to a PHP object',
-		signature: '( variant $comobject , object $sinkobject [, mixed $sinkinterface ]): bool'
-	},
-	com_get_active_object: {
-		description: 'Returns a handle to an already running instance of a COM object',
-		signature: '( string $progid [, int $code_page ]): variant'
-	},
-	com_load_typelib: {
-		description: 'Loads a Typelib',
-		signature: '( string $typelib_name [, bool $case_sensitive ]): bool'
-	},
-	com_message_pump: {
-		description: 'Process COM messages, sleeping for up to timeoutms milliseconds',
-		signature: '([ int $timeoutms = 0 ]): bool'
-	},
-	com_print_typeinfo: {
-		description: 'Print out a PHP class definition for a dispatchable interface',
-		signature: '( object $comobject [, string $dispinterface [, bool $wantsink ]]): bool'
-	},
-	variant_abs: {
-		description: 'Returns the absolute value of a variant',
-		signature: '( mixed $val ): mixed'
-	},
-	variant_add: {
-		description: '"Adds" two variant values together and returns the result',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_and: {
-		description: 'Performs a bitwise AND operation between two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_cast: {
-		description: 'Convert a variant into a new variant object of another type',
-		signature: '( variant $variant , int $type ): variant'
-	},
-	variant_cat: {
-		description: 'Concatenates two variant values together and returns the result',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_cmp: {
-		description: 'Compares two variants',
-		signature: '( mixed $left , mixed $right [, int $lcid [, int $flags ]]): int'
-	},
-	variant_date_from_timestamp: {
-		description: 'Returns a variant date representation of a Unix timestamp',
-		signature: '( int $timestamp ): variant'
-	},
-	variant_date_to_timestamp: {
-		description: 'Converts a variant date/time value to Unix timestamp',
-		signature: '( variant $variant ): int'
-	},
-	variant_div: {
-		description: 'Returns the result from dividing two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_eqv: {
-		description: 'Performs a bitwise equivalence on two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_fix: {
-		description: 'Returns the integer portion of a variant',
-		signature: '( mixed $variant ): mixed'
-	},
-	variant_get_type: {
-		description: 'Returns the type of a variant object',
-		signature: '( variant $variant ): int'
-	},
-	variant_idiv: {
-		description: 'Converts variants to integers and then returns the result from dividing them',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_imp: {
-		description: 'Performs a bitwise implication on two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_int: {
-		description: 'Returns the integer portion of a variant',
-		signature: '( mixed $variant ): mixed'
-	},
-	variant_mod: {
-		description: 'Divides two variants and returns only the remainder',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_mul: {
-		description: 'Multiplies the values of the two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_neg: {
-		description: 'Performs logical negation on a variant',
-		signature: '( mixed $variant ): mixed'
-	},
-	variant_not: {
-		description: 'Performs bitwise not negation on a variant',
-		signature: '( mixed $variant ): mixed'
-	},
-	variant_or: {
-		description: 'Performs a logical disjunction on two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_pow: {
-		description: 'Returns the result of performing the power function with two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_round: {
-		description: 'Rounds a variant to the specified number of decimal places',
-		signature: '( mixed $variant , int $decimals ): mixed'
-	},
-	variant_set_type: {
-		description: 'Convert a variant into another type "in-place"',
-		signature: '( variant $variant , int $type ): void'
-	},
-	variant_set: {
-		description: 'Assigns a new value for a variant object',
-		signature: '( variant $variant , mixed $value ): void'
-	},
-	variant_sub: {
-		description: 'Subtracts the value of the right variant from the left variant value',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	variant_xor: {
-		description: 'Performs a logical exclusion on two variants',
-		signature: '( mixed $left , mixed $right ): mixed'
-	},
-	libxml_clear_errors: {
-		description: 'Clear libxml error buffer',
-		signature: '(void): void'
-	},
-	libxml_disable_entity_loader: {
-		description: 'Disable the ability to load external entities',
-		signature: '([ bool $disable ]): bool'
-	},
-	libxml_get_errors: {
-		description: 'Retrieve array of errors',
-		signature: '(void): array'
-	},
-	libxml_get_last_error: {
-		description: 'Retrieve last error from libxml',
-		signature: '(void): LibXMLError'
-	},
-	libxml_set_external_entity_loader: {
-		description: 'Changes the default external entity loader',
-		signature: '( callable $resolver_function ): bool'
-	},
-	libxml_set_streams_context: {
-		description: 'Set the streams context for the next libxml document load or write',
-		signature: '( resource $streams_context ): void'
-	},
-	libxml_use_internal_errors: {
-		description: 'Disable libxml errors and allow user to fetch error information as needed',
-		signature: '([ bool $use_errors ]): bool'
-	},
-	simplexml_import_dom: {
-		description: 'Get a SimpleXMLElement object from a DOM node',
-		signature: '( DOMNode $node [, string $class_name = "SimpleXMLElement" ]): SimpleXMLElement'
-	},
-	simplexml_load_file: {
-		description: 'Interprets an XML file into an object',
-		signature: '( string $filename [, string $class_name = "SimpleXMLElement" [, int $options = 0 [, string $ns = "" [, bool $is_prefix ]]]]): SimpleXMLElement'
-	},
-	simplexml_load_string: {
-		description: 'Interprets a string of XML into an object',
-		signature: '( string $data [, string $class_name = "SimpleXMLElement" [, int $options = 0 [, string $ns = "" [, bool $is_prefix ]]]]): SimpleXMLElement'
-	},
-	utf8_decode: {
-		description: 'Converts a string with ISO-8859-1 characters encoded with UTF-8   to single-byte ISO-8859-1',
-		signature: '( string $data ): string'
-	},
-	utf8_encode: {
-		description: 'Encodes an ISO-8859-1 string to UTF-8',
-		signature: '( string $data ): string'
-	},
-	xml_error_string: {
-		description: 'Get XML parser error string',
-		signature: '( int $code ): string'
-	},
-	xml_get_current_byte_index: {
-		description: 'Get current byte index for an XML parser',
-		signature: '( resource $parser ): int'
-	},
-	xml_get_current_column_number: {
-		description: 'Get current column number for an XML parser',
-		signature: '( resource $parser ): int'
-	},
-	xml_get_current_line_number: {
-		description: 'Get current line number for an XML parser',
-		signature: '( resource $parser ): int'
-	},
-	xml_get_error_code: {
-		description: 'Get XML parser error code',
-		signature: '( resource $parser ): int'
-	},
-	xml_parse_into_struct: {
-		description: 'Parse XML data into an array structure',
-		signature: '( resource $parser , string $data , array $values [, array $index ]): int'
-	},
-	xml_parse: {
-		description: 'Start parsing an XML document',
-		signature: '( resource $parser , string $data [, bool $is_final ]): int'
-	},
-	xml_parser_create_ns: {
-		description: 'Create an XML parser with namespace support',
-		signature: '([ string $encoding [, string $separator = ":" ]]): resource'
-	},
-	xml_parser_create: {
-		description: 'Create an XML parser',
-		signature: '([ string $encoding ]): resource'
-	},
-	xml_parser_free: {
-		description: 'Free an XML parser',
-		signature: '( resource $parser ): bool'
-	},
-	xml_parser_get_option: {
-		description: 'Get options from an XML parser',
-		signature: '( resource $parser , int $option ): mixed'
-	},
-	xml_parser_set_option: {
-		description: 'Set options in an XML parser',
-		signature: '( resource $parser , int $option , mixed $value ): bool'
-	},
-	xml_set_character_data_handler: {
-		description: 'Set up character data handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_default_handler: {
-		description: 'Set up default handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_element_handler: {
-		description: 'Set up start and end element handlers',
-		signature: '( resource $parser , callable $start_element_handler , callable $end_element_handler ): bool'
-	},
-	xml_set_end_namespace_decl_handler: {
-		description: 'Set up end namespace declaration handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_external_entity_ref_handler: {
-		description: 'Set up external entity reference handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_notation_decl_handler: {
-		description: 'Set up notation declaration handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_object: {
-		description: 'Use XML Parser within an object',
-		signature: '( resource $parser , object $object ): bool'
-	},
-	xml_set_processing_instruction_handler: {
-		description: 'Set up processing instruction (PI) handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_start_namespace_decl_handler: {
-		description: 'Set up start namespace declaration handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xml_set_unparsed_entity_decl_handler: {
-		description: 'Set up unparsed entity declaration handler',
-		signature: '( resource $parser , callable $handler ): bool'
-	},
-	xmlwriter_end_attribute: {
-		description: 'End attribute',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_cdata: {
-		description: 'End current CDATA',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_comment: {
-		description: 'Create end comment',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_document: {
-		description: 'End current document',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_dtd_attlist: {
-		description: 'End current DTD AttList',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_dtd_element: {
-		description: 'End current DTD element',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_dtd_entity: {
-		description: 'End current DTD Entity',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_dtd: {
-		description: 'End current DTD',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_element: {
-		description: 'End current element',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_end_pi: {
-		description: 'End current PI',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_flush: {
-		description: 'Flush current buffer',
-		signature: '([ bool $empty , resource $xmlwriter ]): mixed'
-	},
-	xmlwriter_full_end_element: {
-		description: 'End current element',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_open_memory: {
-		description: 'Create new xmlwriter using memory for string output',
-		signature: '(void): resource'
-	},
-	xmlwriter_open_uri: {
-		description: 'Create new xmlwriter using source uri for output',
-		signature: '( string $uri ): resource'
-	},
-	xmlwriter_output_memory: {
-		description: 'Returns current buffer',
-		signature: '([ bool $flush , resource $xmlwriter ]): string'
-	},
-	xmlwriter_set_indent_string: {
-		description: 'Set string used for indenting',
-		signature: '( string $indentString , resource $xmlwriter ): bool'
-	},
-	xmlwriter_set_indent: {
-		description: 'Toggle indentation on/off',
-		signature: '( bool $indent , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_attribute_ns: {
-		description: 'Create start namespaced attribute',
-		signature: '( string $prefix , string $name , string $uri , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_attribute: {
-		description: 'Create start attribute',
-		signature: '( string $name , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_cdata: {
-		description: 'Create start CDATA tag',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_comment: {
-		description: 'Create start comment',
-		signature: '( resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_document: {
-		description: 'Create document tag',
-		signature: '([ string $version = 1.0 [, string $encoding [, string $standalone , resource $xmlwriter ]]]): bool'
-	},
-	xmlwriter_start_dtd_attlist: {
-		description: 'Create start DTD AttList',
-		signature: '( string $name , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_dtd_element: {
-		description: 'Create start DTD element',
-		signature: '( string $qualifiedName , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_dtd_entity: {
-		description: 'Create start DTD Entity',
-		signature: '( string $name , bool $isparam , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_dtd: {
-		description: 'Create start DTD tag',
-		signature: '( string $qualifiedName [, string $publicId [, string $systemId , resource $xmlwriter ]]): bool'
-	},
-	xmlwriter_start_element_ns: {
-		description: 'Create start namespaced element tag',
-		signature: '( string $prefix , string $name , string $uri , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_element: {
-		description: 'Create start element tag',
-		signature: '( string $name , resource $xmlwriter ): bool'
-	},
-	xmlwriter_start_pi: {
-		description: 'Create start PI tag',
-		signature: '( string $target , resource $xmlwriter ): bool'
-	},
-	xmlwriter_text: {
-		description: 'Write text',
-		signature: '( string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_attribute_ns: {
-		description: 'Write full namespaced attribute',
-		signature: '( string $prefix , string $name , string $uri , string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_attribute: {
-		description: 'Write full attribute',
-		signature: '( string $name , string $value , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_cdata: {
-		description: 'Write full CDATA tag',
-		signature: '( string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_comment: {
-		description: 'Write full comment tag',
-		signature: '( string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_dtd_attlist: {
-		description: 'Write full DTD AttList tag',
-		signature: '( string $name , string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_dtd_element: {
-		description: 'Write full DTD element tag',
-		signature: '( string $name , string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_dtd_entity: {
-		description: 'Write full DTD Entity tag',
-		signature: '( string $name , string $content , bool $pe , string $pubid , string $sysid , string $ndataid , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_dtd: {
-		description: 'Write full DTD tag',
-		signature: '( string $name [, string $publicId [, string $systemId [, string $subset , resource $xmlwriter ]]]): bool'
-	},
-	xmlwriter_write_element_ns: {
-		description: 'Write full namespaced element tag',
-		signature: '( string $prefix , string $name , string $uri [, string $content , resource $xmlwriter ]): bool'
-	},
-	xmlwriter_write_element: {
-		description: 'Write full element tag',
-		signature: '( string $name [, string $content , resource $xmlwriter ]): bool'
-	},
-	xmlwriter_write_pi: {
-		description: 'Writes a PI',
-		signature: '( string $target , string $content , resource $xmlwriter ): bool'
-	},
-	xmlwriter_write_raw: {
-		description: 'Write a raw XML text',
-		signature: '( string $content , resource $xmlwriter ): bool'
-	},
+    debug_backtrace: {
+        description: 'Generates a backtrace',
+        signature: '([ int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT [, int $limit = 0 ]]): array'
+    },
+    debug_print_backtrace: {
+        description: 'Prints a backtrace',
+        signature: '([ int $options = 0 [, int $limit = 0 ]]): void'
+    },
+    error_clear_last: {
+        description: 'Clear the most recent error',
+        signature: '(void): void'
+    },
+    error_get_last: {
+        description: 'Get the last occurred error',
+        signature: '(void): array'
+    },
+    error_log: {
+        description: 'Send an error message to the defined error handling routines',
+        signature: '( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]]): bool'
+    },
+    error_reporting: {
+        description: 'Sets which PHP errors are reported',
+        signature: '([ int $level ]): int'
+    },
+    restore_error_handler: {
+        description: 'Restores the previous error handler function',
+        signature: '(void): bool'
+    },
+    restore_exception_handler: {
+        description: 'Restores the previously defined exception handler function',
+        signature: '(void): bool'
+    },
+    set_error_handler: {
+        description: 'Sets a user-defined error handler function',
+        signature: '( callable $error_handler [, int $error_types = E_ALL | E_STRICT ]): mixed'
+    },
+    set_exception_handler: {
+        description: 'Sets a user-defined exception handler function',
+        signature: '( callable $exception_handler ): callable'
+    },
+    trigger_error: {
+        description: 'Generates a user-level error/warning/notice message',
+        signature: '( string $error_msg [, int $error_type = E_USER_NOTICE ]): bool'
+    },
+    user_error: {
+        description: 'Alias of trigger_error',
+    },
+    opcache_compile_file: {
+        description: 'Compiles and caches a PHP script without executing it',
+        signature: '( string $file ): bool'
+    },
+    opcache_get_configuration: {
+        description: 'Get configuration information about the cache',
+        signature: '(void): array'
+    },
+    opcache_get_status: {
+        description: 'Get status information about the cache',
+        signature: '([ bool $get_scripts ]): array'
+    },
+    opcache_invalidate: {
+        description: 'Invalidates a cached script',
+        signature: '( string $script [, bool $force ]): bool'
+    },
+    opcache_is_script_cached: {
+        description: 'Tells whether a script is cached in OPCache',
+        signature: '( string $file ): bool'
+    },
+    opcache_reset: {
+        description: 'Resets the contents of the opcode cache',
+        signature: '(void): bool'
+    },
+    flush: {
+        description: 'Flush system output buffer',
+        signature: '(void): void'
+    },
+    ob_clean: {
+        description: 'Clean (erase) the output buffer',
+        signature: '(void): void'
+    },
+    ob_end_clean: {
+        description: 'Clean (erase) the output buffer and turn off output buffering',
+        signature: '(void): bool'
+    },
+    ob_end_flush: {
+        description: 'Flush (send) the output buffer and turn off output buffering',
+        signature: '(void): bool'
+    },
+    ob_flush: {
+        description: 'Flush (send) the output buffer',
+        signature: '(void): void'
+    },
+    ob_get_clean: {
+        description: 'Get current buffer contents and delete current output buffer',
+        signature: '(void): string'
+    },
+    ob_get_contents: {
+        description: 'Return the contents of the output buffer',
+        signature: '(void): string'
+    },
+    ob_get_flush: {
+        description: 'Flush the output buffer, return it as a string and turn off output buffering',
+        signature: '(void): string'
+    },
+    ob_get_length: {
+        description: 'Return the length of the output buffer',
+        signature: '(void): int'
+    },
+    ob_get_level: {
+        description: 'Return the nesting level of the output buffering mechanism',
+        signature: '(void): int'
+    },
+    ob_get_status: {
+        description: 'Get status of output buffers',
+        signature: '([ bool $full_status = FALSE ]): array'
+    },
+    ob_gzhandler: {
+        description: 'ob_start callback function to gzip output buffer',
+        signature: '( string $buffer , int $mode ): string'
+    },
+    ob_implicit_flush: {
+        description: 'Turn implicit flush on/off',
+        signature: '([ int $flag = 1 ]): void'
+    },
+    ob_list_handlers: {
+        description: 'List all output handlers in use',
+        signature: '(void): array'
+    },
+    ob_start: {
+        description: 'Turn on output buffering',
+        signature: '([ callable $output_callback [, int $chunk_size = 0 [, int $flags ]]]): bool'
+    },
+    output_add_rewrite_var: {
+        description: 'Add URL rewriter values',
+        signature: '( string $name , string $value ): bool'
+    },
+    output_reset_rewrite_vars: {
+        description: 'Reset URL rewriter values',
+        signature: '(void): bool'
+    },
+    assert_options: {
+        description: 'Set/get the various assert flags',
+        signature: '( int $what [, mixed $value ]): mixed'
+    },
+    assert: {
+        description: 'Checks if assertion is FALSE',
+        signature: '( mixed $assertion [, string $description [, Throwable $exception ]]): bool'
+    },
+    cli_get_process_title: {
+        description: 'Returns the current process title',
+        signature: '(void): string'
+    },
+    cli_set_process_title: {
+        description: 'Sets the process title',
+        signature: '( string $title ): bool'
+    },
+    dl: {
+        description: 'Loads a PHP extension at runtime',
+        signature: '( string $library ): bool'
+    },
+    extension_loaded: {
+        description: 'Find out whether an extension is loaded',
+        signature: '( string $name ): bool'
+    },
+    gc_collect_cycles: {
+        description: 'Forces collection of any existing garbage cycles',
+        signature: '(void): int'
+    },
+    gc_disable: {
+        description: 'Deactivates the circular reference collector',
+        signature: '(void): void'
+    },
+    gc_enable: {
+        description: 'Activates the circular reference collector',
+        signature: '(void): void'
+    },
+    gc_enabled: {
+        description: 'Returns status of the circular reference collector',
+        signature: '(void): bool'
+    },
+    gc_mem_caches: {
+        description: 'Reclaims memory used by the Zend Engine memory manager',
+        signature: '(void): int'
+    },
+    gc_status: {
+        description: 'Gets information about the garbage collector',
+        signature: '(void): array'
+    },
+    get_cfg_var: {
+        description: 'Gets the value of a PHP configuration option',
+        signature: '( string $option ): mixed'
+    },
+    get_current_user: {
+        description: 'Gets the name of the owner of the current PHP script',
+        signature: '(void): string'
+    },
+    get_defined_constants: {
+        description: 'Returns an associative array with the names of all the constants and their values',
+        signature: '([ bool $categorize ]): array'
+    },
+    get_extension_funcs: {
+        description: 'Returns an array with the names of the functions of a module',
+        signature: '( string $module_name ): array'
+    },
+    get_include_path: {
+        description: 'Gets the current include_path configuration option',
+        signature: '(void): string'
+    },
+    get_included_files: {
+        description: 'Returns an array with the names of included or required files',
+        signature: '(void): array'
+    },
+    get_loaded_extensions: {
+        description: 'Returns an array with the names of all modules compiled and loaded',
+        signature: '([ bool $zend_extensions ]): array'
+    },
+    get_magic_quotes_gpc: {
+        description: 'Gets the current configuration setting of magic_quotes_gpc',
+        signature: '(void): bool'
+    },
+    get_magic_quotes_runtime: {
+        description: 'Gets the current active configuration setting of magic_quotes_runtime',
+        signature: '(void): bool'
+    },
+    get_required_files: {
+        description: 'Alias of get_included_files',
+    },
+    get_resources: {
+        description: 'Returns active resources',
+        signature: '([ string $type ]): resource'
+    },
+    getenv: {
+        description: 'Gets the value of an environment variable',
+        signature: '( string $varname [, bool $local_only ]): array'
+    },
+    getlastmod: {
+        description: 'Gets time of last page modification',
+        signature: '(void): int'
+    },
+    getmygid: {
+        description: 'Get PHP script owner\'s GID',
+        signature: '(void): int'
+    },
+    getmyinode: {
+        description: 'Gets the inode of the current script',
+        signature: '(void): int'
+    },
+    getmypid: {
+        description: 'Gets PHP\'s process ID',
+        signature: '(void): int'
+    },
+    getmyuid: {
+        description: 'Gets PHP script owner\'s UID',
+        signature: '(void): int'
+    },
+    getopt: {
+        description: 'Gets options from the command line argument list',
+        signature: '( string $options [, array $longopts [, int $optind ]]): array'
+    },
+    getrusage: {
+        description: 'Gets the current resource usages',
+        signature: '([ int $who = 0 ]): array'
+    },
+    ini_alter: {
+        description: 'Alias of ini_set',
+    },
+    ini_get_all: {
+        description: 'Gets all configuration options',
+        signature: '([ string $extension [, bool $details ]]): array'
+    },
+    ini_get: {
+        description: 'Gets the value of a configuration option',
+        signature: '( string $varname ): string'
+    },
+    ini_restore: {
+        description: 'Restores the value of a configuration option',
+        signature: '( string $varname ): void'
+    },
+    ini_set: {
+        description: 'Sets the value of a configuration option',
+        signature: '( string $varname , string $newvalue ): string'
+    },
+    magic_quotes_runtime: {
+        description: 'Alias of set_magic_quotes_runtime',
+    },
+    main: {
+        description: 'Dummy for main',
+    },
+    memory_get_peak_usage: {
+        description: 'Returns the peak of memory allocated by PHP',
+        signature: '([ bool $real_usage ]): int'
+    },
+    memory_get_usage: {
+        description: 'Returns the amount of memory allocated to PHP',
+        signature: '([ bool $real_usage ]): int'
+    },
+    php_ini_loaded_file: {
+        description: 'Retrieve a path to the loaded php.ini file',
+        signature: '(void): string'
+    },
+    php_ini_scanned_files: {
+        description: 'Return a list of .ini files parsed from the additional ini dir',
+        signature: '(void): string'
+    },
+    php_logo_guid: {
+        description: 'Gets the logo guid',
+        signature: '(void): string'
+    },
+    php_sapi_name: {
+        description: 'Returns the type of interface between web server and PHP',
+        signature: '(void): string'
+    },
+    php_uname: {
+        description: 'Returns information about the operating system PHP is running on',
+        signature: '([ string $mode = "a" ]): string'
+    },
+    phpcredits: {
+        description: 'Prints out the credits for PHP',
+        signature: '([ int $flag = CREDITS_ALL ]): bool'
+    },
+    phpinfo: {
+        description: 'Outputs information about PHP\'s configuration',
+        signature: '([ int $what = INFO_ALL ]): bool'
+    },
+    phpversion: {
+        description: 'Gets the current PHP version',
+        signature: '([ string $extension ]): string'
+    },
+    putenv: {
+        description: 'Sets the value of an environment variable',
+        signature: '( string $setting ): bool'
+    },
+    restore_include_path: {
+        description: 'Restores the value of the include_path configuration option',
+        signature: '(void): void'
+    },
+    set_include_path: {
+        description: 'Sets the include_path configuration option',
+        signature: '( string $new_include_path ): string'
+    },
+    set_magic_quotes_runtime: {
+        description: 'Sets the current active configuration setting of magic_quotes_runtime',
+        signature: '( bool $new_setting ): bool'
+    },
+    set_time_limit: {
+        description: 'Limits the maximum execution time',
+        signature: '( int $seconds ): bool'
+    },
+    sys_get_temp_dir: {
+        description: 'Returns directory path used for temporary files',
+        signature: '(void): string'
+    },
+    version_compare: {
+        description: 'Compares two "PHP-standardized" version number strings',
+        signature: '( string $version1 , string $version2 , string $operator ): bool'
+    },
+    zend_logo_guid: {
+        description: 'Gets the Zend guid',
+        signature: '(void): string'
+    },
+    zend_thread_id: {
+        description: 'Returns a unique identifier for the current thread',
+        signature: '(void): int'
+    },
+    zend_version: {
+        description: 'Gets the version of the current Zend engine',
+        signature: '(void): string'
+    },
+    bzclose: {
+        description: 'Close a bzip2 file',
+        signature: '( resource $bz ): int'
+    },
+    bzcompress: {
+        description: 'Compress a string into bzip2 encoded data',
+        signature: '( string $source [, int $blocksize = 4 [, int $workfactor = 0 ]]): mixed'
+    },
+    bzdecompress: {
+        description: 'Decompresses bzip2 encoded data',
+        signature: '( string $source [, int $small = 0 ]): mixed'
+    },
+    bzerrno: {
+        description: 'Returns a bzip2 error number',
+        signature: '( resource $bz ): int'
+    },
+    bzerror: {
+        description: 'Returns the bzip2 error number and error string in an array',
+        signature: '( resource $bz ): array'
+    },
+    bzerrstr: {
+        description: 'Returns a bzip2 error string',
+        signature: '( resource $bz ): string'
+    },
+    bzflush: {
+        description: 'Force a write of all buffered data',
+        signature: '( resource $bz ): bool'
+    },
+    bzopen: {
+        description: 'Opens a bzip2 compressed file',
+        signature: '( mixed $file , string $mode ): resource'
+    },
+    bzread: {
+        description: 'Binary safe bzip2 file read',
+        signature: '( resource $bz [, int $length = 1024 ]): string'
+    },
+    bzwrite: {
+        description: 'Binary safe bzip2 file write',
+        signature: '( resource $bz , string $data [, int $length ]): int'
+    },
+    PharException: {
+        description: 'The PharException class provides a phar-specific exception class    for try/catch blocks',
+    },
+    zip_close: {
+        description: 'Close a ZIP file archive',
+        signature: '( resource $zip ): void'
+    },
+    zip_entry_close: {
+        description: 'Close a directory entry',
+        signature: '( resource $zip_entry ): bool'
+    },
+    zip_entry_compressedsize: {
+        description: 'Retrieve the compressed size of a directory entry',
+        signature: '( resource $zip_entry ): int'
+    },
+    zip_entry_compressionmethod: {
+        description: 'Retrieve the compression method of a directory entry',
+        signature: '( resource $zip_entry ): string'
+    },
+    zip_entry_filesize: {
+        description: 'Retrieve the actual file size of a directory entry',
+        signature: '( resource $zip_entry ): int'
+    },
+    zip_entry_name: {
+        description: 'Retrieve the name of a directory entry',
+        signature: '( resource $zip_entry ): string'
+    },
+    zip_entry_open: {
+        description: 'Open a directory entry for reading',
+        signature: '( resource $zip , resource $zip_entry [, string $mode ]): bool'
+    },
+    zip_entry_read: {
+        description: 'Read from an open directory entry',
+        signature: '( resource $zip_entry [, int $length = 1024 ]): string'
+    },
+    zip_open: {
+        description: 'Open a ZIP file archive',
+        signature: '( string $filename ): resource'
+    },
+    zip_read: {
+        description: 'Read next entry in a ZIP file archive',
+        signature: '( resource $zip ): resource'
+    },
+    deflate_add: {
+        description: 'Incrementally deflate data',
+        signature: '( resource $context , string $data [, int $flush_mode = ZLIB_SYNC_FLUSH ]): string'
+    },
+    deflate_init: {
+        description: 'Initialize an incremental deflate context',
+        signature: '( int $encoding [, array $options = array() ]): resource'
+    },
+    gzclose: {
+        description: 'Close an open gz-file pointer',
+        signature: '( resource $zp ): bool'
+    },
+    gzcompress: {
+        description: 'Compress a string',
+        signature: '( string $data [, int $level = -1 [, int $encoding = ZLIB_ENCODING_DEFLATE ]]): string'
+    },
+    gzdecode: {
+        description: 'Decodes a gzip compressed string',
+        signature: '( string $data [, int $length ]): string'
+    },
+    gzdeflate: {
+        description: 'Deflate a string',
+        signature: '( string $data [, int $level = -1 [, int $encoding = ZLIB_ENCODING_RAW ]]): string'
+    },
+    gzencode: {
+        description: 'Create a gzip compressed string',
+        signature: '( string $data [, int $level = -1 [, int $encoding_mode = FORCE_GZIP ]]): string'
+    },
+    gzeof: {
+        description: 'Test for EOF on a gz-file pointer',
+        signature: '( resource $zp ): int'
+    },
+    gzfile: {
+        description: 'Read entire gz-file into an array',
+        signature: '( string $filename [, int $use_include_path = 0 ]): array'
+    },
+    gzgetc: {
+        description: 'Get character from gz-file pointer',
+        signature: '( resource $zp ): string'
+    },
+    gzgets: {
+        description: 'Get line from file pointer',
+        signature: '( resource $zp [, int $length ]): string'
+    },
+    gzgetss: {
+        description: 'Get line from gz-file pointer and strip HTML tags',
+        signature: '( resource $zp , int $length [, string $allowable_tags ]): string'
+    },
+    gzinflate: {
+        description: 'Inflate a deflated string',
+        signature: '( string $data [, int $length = 0 ]): string'
+    },
+    gzopen: {
+        description: 'Open gz-file',
+        signature: '( string $filename , string $mode [, int $use_include_path = 0 ]): resource'
+    },
+    gzpassthru: {
+        description: 'Output all remaining data on a gz-file pointer',
+        signature: '( resource $zp ): int'
+    },
+    gzputs: {
+        description: 'Alias of gzwrite',
+    },
+    gzread: {
+        description: 'Binary-safe gz-file read',
+        signature: '( resource $zp , int $length ): string'
+    },
+    gzrewind: {
+        description: 'Rewind the position of a gz-file pointer',
+        signature: '( resource $zp ): bool'
+    },
+    gzseek: {
+        description: 'Seek on a gz-file pointer',
+        signature: '( resource $zp , int $offset [, int $whence = SEEK_SET ]): int'
+    },
+    gztell: {
+        description: 'Tell gz-file pointer read/write position',
+        signature: '( resource $zp ): int'
+    },
+    gzuncompress: {
+        description: 'Uncompress a compressed string',
+        signature: '( string $data [, int $length = 0 ]): string'
+    },
+    gzwrite: {
+        description: 'Binary-safe gz-file write',
+        signature: '( resource $zp , string $string [, int $length ]): int'
+    },
+    inflate_add: {
+        description: 'Incrementally inflate encoded data',
+        signature: '( resource $context , string $encoded_data [, int $flush_mode = ZLIB_SYNC_FLUSH ]): string'
+    },
+    inflate_get_read_len: {
+        description: 'Get number of bytes read so far',
+        signature: '( resource $resource ): int'
+    },
+    inflate_get_status: {
+        description: 'Get decompression status',
+        signature: '( resource $resource ): int'
+    },
+    inflate_init: {
+        description: 'Initialize an incremental inflate context',
+        signature: '( int $encoding [, array $options = array() ]): resource'
+    },
+    readgzfile: {
+        description: 'Output a gz-file',
+        signature: '( string $filename [, int $use_include_path = 0 ]): int'
+    },
+    zlib_decode: {
+        description: 'Uncompress any raw/gzip/zlib encoded data',
+        signature: '( string $data [, string $max_decoded_len ]): string'
+    },
+    zlib_encode: {
+        description: 'Compress data with the specified encoding',
+        signature: '( string $data , int $encoding [, int $level = -1 ]): string'
+    },
+    zlib_get_coding_type: {
+        description: 'Returns the coding type used for output compression',
+        signature: '(void): string'
+    },
+    random_bytes: {
+        description: 'Generates cryptographically secure pseudo-random bytes',
+        signature: '( int $length ): string'
+    },
+    random_int: {
+        description: 'Generates cryptographically secure pseudo-random integers',
+        signature: '( int $min , int $max ): int'
+    },
+    hash_algos: {
+        description: 'Return a list of registered hashing algorithms',
+        signature: '(void): array'
+    },
+    hash_copy: {
+        description: 'Copy hashing context',
+        signature: '( HashContext $context ): HashContext'
+    },
+    hash_equals: {
+        description: 'Timing attack safe string comparison',
+        signature: '( string $known_string , string $user_string ): bool'
+    },
+    hash_file: {
+        description: 'Generate a hash value using the contents of a given file',
+        signature: '( string $algo , string $filename [, bool $raw_output ]): string'
+    },
+    hash_final: {
+        description: 'Finalize an incremental hash and return resulting digest',
+        signature: '( HashContext $context [, bool $raw_output ]): string'
+    },
+    hash_hkdf: {
+        description: 'Generate a HKDF key derivation of a supplied key input',
+        signature: '( string $algo , string $ikm [, int $length = 0 [, string $info = \'\' [, string $salt = \'\' ]]]): string'
+    },
+    hash_hmac_algos: {
+        description: 'Return a list of registered hashing algorithms suitable for hash_hmac',
+        signature: '(void): array'
+    },
+    hash_hmac_file: {
+        description: 'Generate a keyed hash value using the HMAC method and the contents of a given file',
+        signature: '( string $algo , string $filename , string $key [, bool $raw_output ]): string'
+    },
+    hash_hmac: {
+        description: 'Generate a keyed hash value using the HMAC method',
+        signature: '( string $algo , string $data , string $key [, bool $raw_output ]): string'
+    },
+    hash_init: {
+        description: 'Initialize an incremental hashing context',
+        signature: '( string $algo [, int $options = 0 [, string $key ]]): HashContext'
+    },
+    hash_pbkdf2: {
+        description: 'Generate a PBKDF2 key derivation of a supplied password',
+        signature: '( string $algo , string $password , string $salt , int $iterations [, int $length = 0 [, bool $raw_output ]]): string'
+    },
+    hash_update_file: {
+        description: 'Pump data into an active hashing context from a file',
+        signature: '( HashContext $hcontext , string $filename [, resource $scontext ]): bool'
+    },
+    hash_update_stream: {
+        description: 'Pump data into an active hashing context from an open stream',
+        signature: '( HashContext $context , resource $handle [, int $length = -1 ]): int'
+    },
+    hash_update: {
+        description: 'Pump data into an active hashing context',
+        signature: '( HashContext $context , string $data ): bool'
+    },
+    hash: {
+        description: 'Generate a hash value (message digest)',
+        signature: '( string $algo , string $data [, bool $raw_output ]): string'
+    },
+    openssl_cipher_iv_length: {
+        description: 'Gets the cipher iv length',
+        signature: '( string $method ): int'
+    },
+    openssl_csr_export_to_file: {
+        description: 'Exports a CSR to a file',
+        signature: '( mixed $csr , string $outfilename [, bool $notext ]): bool'
+    },
+    openssl_csr_export: {
+        description: 'Exports a CSR as a string',
+        signature: '( mixed $csr , string $out [, bool $notext ]): bool'
+    },
+    openssl_csr_get_public_key: {
+        description: 'Returns the public key of a CSR',
+        signature: '( mixed $csr [, bool $use_shortnames ]): resource'
+    },
+    openssl_csr_get_subject: {
+        description: 'Returns the subject of a CSR',
+        signature: '( mixed $csr [, bool $use_shortnames ]): array'
+    },
+    openssl_csr_new: {
+        description: 'Generates a CSR',
+        signature: '( array $dn , resource $privkey [, array $configargs [, array $extraattribs ]]): mixed'
+    },
+    openssl_csr_sign: {
+        description: 'Sign a CSR with another certificate (or itself) and generate a certificate',
+        signature: '( mixed $csr , mixed $cacert , mixed $priv_key , int $days [, array $configargs [, int $serial = 0 ]]): resource'
+    },
+    openssl_decrypt: {
+        description: 'Decrypts data',
+        signature: '( string $data , string $method , string $key [, int $options = 0 [, string $iv = "" [, string $tag = "" [, string $aad = "" ]]]]): string'
+    },
+    openssl_dh_compute_key: {
+        description: 'Computes shared secret for public value of remote DH public key and local DH key',
+        signature: '( string $pub_key , resource $dh_key ): string'
+    },
+    openssl_digest: {
+        description: 'Computes a digest',
+        signature: '( string $data , string $method [, bool $raw_output ]): string'
+    },
+    openssl_encrypt: {
+        description: 'Encrypts data',
+        signature: '( string $data , string $method , string $key [, int $options = 0 [, string $iv = "" [, string $tag = NULL [, string $aad = "" [, int $tag_length = 16 ]]]]]): string'
+    },
+    openssl_error_string: {
+        description: 'Return openSSL error message',
+        signature: '(void): string'
+    },
+    openssl_free_key: {
+        description: 'Free key resource',
+        signature: '( resource $key_identifier ): void'
+    },
+    openssl_get_cert_locations: {
+        description: 'Retrieve the available certificate locations',
+        signature: '(void): array'
+    },
+    openssl_get_cipher_methods: {
+        description: 'Gets available cipher methods',
+        signature: '([ bool $aliases ]): array'
+    },
+    openssl_get_curve_names: {
+        description: 'Gets list of available curve names for ECC',
+        signature: '(void): array'
+    },
+    openssl_get_md_methods: {
+        description: 'Gets available digest methods',
+        signature: '([ bool $aliases ]): array'
+    },
+    openssl_get_privatekey: {
+        description: 'Alias of openssl_pkey_get_private',
+    },
+    openssl_get_publickey: {
+        description: 'Alias of openssl_pkey_get_public',
+    },
+    openssl_open: {
+        description: 'Open sealed data',
+        signature: '( string $sealed_data , string $open_data , string $env_key , mixed $priv_key_id [, string $method = "RC4" [, string $iv ]]): bool'
+    },
+    openssl_pbkdf2: {
+        description: 'Generates a PKCS5 v2 PBKDF2 string',
+        signature: '( string $password , string $salt , int $key_length , int $iterations [, string $digest_algorithm = "sha1" ]): string'
+    },
+    openssl_pkcs12_export_to_file: {
+        description: 'Exports a PKCS#12 Compatible Certificate Store File',
+        signature: '( mixed $x509 , string $filename , mixed $priv_key , string $pass [, array $args ]): bool'
+    },
+    openssl_pkcs12_export: {
+        description: 'Exports a PKCS#12 Compatible Certificate Store File to variable',
+        signature: '( mixed $x509 , string $out , mixed $priv_key , string $pass [, array $args ]): bool'
+    },
+    openssl_pkcs12_read: {
+        description: 'Parse a PKCS#12 Certificate Store into an array',
+        signature: '( string $pkcs12 , array $certs , string $pass ): bool'
+    },
+    openssl_pkcs7_decrypt: {
+        description: 'Decrypts an S/MIME encrypted message',
+        signature: '( string $infilename , string $outfilename , mixed $recipcert [, mixed $recipkey ]): bool'
+    },
+    openssl_pkcs7_encrypt: {
+        description: 'Encrypt an S/MIME message',
+        signature: '( string $infile , string $outfile , mixed $recipcerts , array $headers [, int $flags = 0 [, int $cipherid = OPENSSL_CIPHER_RC2_40 ]]): bool'
+    },
+    openssl_pkcs7_read: {
+        description: 'Export the PKCS7 file to an array of PEM certificates',
+        signature: '( string $infilename , array $certs ): bool'
+    },
+    openssl_pkcs7_sign: {
+        description: 'Sign an S/MIME message',
+        signature: '( string $infilename , string $outfilename , mixed $signcert , mixed $privkey , array $headers [, int $flags = PKCS7_DETACHED [, string $extracerts ]]): bool'
+    },
+    openssl_pkcs7_verify: {
+        description: 'Verifies the signature of an S/MIME signed message',
+        signature: '( string $filename , int $flags [, string $outfilename [, array $cainfo [, string $extracerts [, string $content [, string $p7bfilename ]]]]]): mixed'
+    },
+    openssl_pkey_export_to_file: {
+        description: 'Gets an exportable representation of a key into a file',
+        signature: '( mixed $key , string $outfilename [, string $passphrase [, array $configargs ]]): bool'
+    },
+    openssl_pkey_export: {
+        description: 'Gets an exportable representation of a key into a string',
+        signature: '( mixed $key , string $out [, string $passphrase [, array $configargs ]]): bool'
+    },
+    openssl_pkey_free: {
+        description: 'Frees a private key',
+        signature: '( resource $key ): void'
+    },
+    openssl_pkey_get_details: {
+        description: 'Returns an array with the key details',
+        signature: '( resource $key ): array'
+    },
+    openssl_pkey_get_private: {
+        description: 'Get a private key',
+        signature: '( mixed $key [, string $passphrase = "" ]): resource'
+    },
+    openssl_pkey_get_public: {
+        description: 'Extract public key from certificate and prepare it for use',
+        signature: '( mixed $certificate ): resource'
+    },
+    openssl_pkey_new: {
+        description: 'Generates a new private key',
+        signature: '([ array $configargs ]): resource'
+    },
+    openssl_private_decrypt: {
+        description: 'Decrypts data with private key',
+        signature: '( string $data , string $decrypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
+    },
+    openssl_private_encrypt: {
+        description: 'Encrypts data with private key',
+        signature: '( string $data , string $crypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
+    },
+    openssl_public_decrypt: {
+        description: 'Decrypts data with public key',
+        signature: '( string $data , string $decrypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
+    },
+    openssl_public_encrypt: {
+        description: 'Encrypts data with public key',
+        signature: '( string $data , string $crypted , mixed $key [, int $padding = OPENSSL_PKCS1_PADDING ]): bool'
+    },
+    openssl_random_pseudo_bytes: {
+        description: 'Generate a pseudo-random string of bytes',
+        signature: '( int $length [, bool $crypto_strong ]): string'
+    },
+    openssl_seal: {
+        description: 'Seal (encrypt) data',
+        signature: '( string $data , string $sealed_data , array $env_keys , array $pub_key_ids [, string $method = "RC4" [, string $iv ]]): int'
+    },
+    openssl_sign: {
+        description: 'Generate signature',
+        signature: '( string $data , string $signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ]): bool'
+    },
+    openssl_spki_export_challenge: {
+        description: 'Exports the challenge assoicated with a signed public key and challenge',
+        signature: '( string $spkac ): string'
+    },
+    openssl_spki_export: {
+        description: 'Exports a valid PEM formatted public key signed public key and challenge',
+        signature: '( string $spkac ): string'
+    },
+    openssl_spki_new: {
+        description: 'Generate a new signed public key and challenge',
+        signature: '( resource $privkey , string $challenge [, int $algorithm = 0 ]): string'
+    },
+    openssl_spki_verify: {
+        description: 'Verifies a signed public key and challenge',
+        signature: '( string $spkac ): string'
+    },
+    openssl_verify: {
+        description: 'Verify signature',
+        signature: '( string $data , string $signature , mixed $pub_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ]): int'
+    },
+    openssl_x509_check_private_key: {
+        description: 'Checks if a private key corresponds to a certificate',
+        signature: '( mixed $cert , mixed $key ): bool'
+    },
+    openssl_x509_checkpurpose: {
+        description: 'Verifies if a certificate can be used for a particular purpose',
+        signature: '( mixed $x509cert , int $purpose [, array $cainfo = array() [, string $untrustedfile ]]): int'
+    },
+    openssl_x509_export_to_file: {
+        description: 'Exports a certificate to file',
+        signature: '( mixed $x509 , string $outfilename [, bool $notext ]): bool'
+    },
+    openssl_x509_export: {
+        description: 'Exports a certificate as a string',
+        signature: '( mixed $x509 , string $output [, bool $notext ]): bool'
+    },
+    openssl_x509_fingerprint: {
+        description: 'Calculates the fingerprint, or digest, of a given X.509 certificate',
+        signature: '( mixed $x509 [, string $hash_algorithm = "sha1" [, bool $raw_output ]]): string'
+    },
+    openssl_x509_free: {
+        description: 'Free certificate resource',
+        signature: '( resource $x509cert ): void'
+    },
+    openssl_x509_parse: {
+        description: 'Parse an X509 certificate and return the information as an array',
+        signature: '( mixed $x509cert [, bool $shortnames ]): array'
+    },
+    openssl_x509_read: {
+        description: 'Parse an X.509 certificate and return a resource identifier for  it',
+        signature: '( mixed $x509certdata ): resource'
+    },
+    password_get_info: {
+        description: 'Returns information about the given hash',
+        signature: '( string $hash ): array'
+    },
+    password_hash: {
+        description: 'Creates a password hash',
+        signature: '( string $password , int $algo [, array $options ]): integer'
+    },
+    password_needs_rehash: {
+        description: 'Checks if the given hash matches the given options',
+        signature: '( string $hash , int $algo [, array $options ]): bool'
+    },
+    password_verify: {
+        description: 'Verifies that a password matches a hash',
+        signature: '( string $password , string $hash ): bool'
+    },
+    sodium_add: {
+        description: 'Add large numbers',
+        signature: '( string $val , string $addv ): void'
+    },
+    sodium_base642bin: {
+        description: 'Description',
+        signature: '( string $b64 , int $id [, string $ignore ]): string'
+    },
+    sodium_bin2base64: {
+        description: 'Description',
+        signature: '( string $bin , int $id ): string'
+    },
+    sodium_bin2hex: {
+        description: 'Encode to hexadecimal',
+        signature: '( string $bin ): string'
+    },
+    sodium_compare: {
+        description: 'Compare large numbers',
+        signature: '( string $buf1 , string $buf2 ): int'
+    },
+    sodium_crypto_aead_aes256gcm_decrypt: {
+        description: 'Decrypt in combined mode with precalculation',
+        signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_aes256gcm_encrypt: {
+        description: 'Encrypt in combined mode with precalculation',
+        signature: '( string $msg , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_aes256gcm_is_available: {
+        description: 'Check if hardware supports AES256-GCM',
+        signature: '(void): bool'
+    },
+    sodium_crypto_aead_aes256gcm_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_aead_chacha20poly1305_decrypt: {
+        description: 'Verify that the ciphertext includes a valid tag',
+        signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_chacha20poly1305_encrypt: {
+        description: 'Encrypt a message',
+        signature: '( string $msg , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_chacha20poly1305_ietf_decrypt: {
+        description: 'Verify that the ciphertext includes a valid tag',
+        signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_chacha20poly1305_ietf_encrypt: {
+        description: 'Encrypt a message',
+        signature: '( string $msg , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_chacha20poly1305_ietf_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_aead_chacha20poly1305_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_aead_xchacha20poly1305_ietf_decrypt: {
+        description: 'Description',
+        signature: '( string $ciphertext , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_xchacha20poly1305_ietf_encrypt: {
+        description: 'Description',
+        signature: '( string $msg , string $ad , string $nonce , string $key ): string'
+    },
+    sodium_crypto_aead_xchacha20poly1305_ietf_keygen: {
+        description: 'Description',
+        signature: '(void): string'
+    },
+    sodium_crypto_auth_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_auth_verify: {
+        description: 'Verifies that the tag is valid for the message',
+        signature: '( string $signature , string $msg , string $key ): bool'
+    },
+    sodium_crypto_auth: {
+        description: 'Compute a tag for the message',
+        signature: '( string $msg , string $key ): string'
+    },
+    sodium_crypto_box_keypair_from_secretkey_and_publickey: {
+        description: 'Description',
+        signature: '( string $secret_key , string $public_key ): string'
+    },
+    sodium_crypto_box_keypair: {
+        description: 'Randomly generate a secret key and a corresponding public key',
+        signature: '(void): string'
+    },
+    sodium_crypto_box_open: {
+        description: 'Verify and decrypt a ciphertext',
+        signature: '( string $ciphertext , string $nonce , string $key ): string'
+    },
+    sodium_crypto_box_publickey_from_secretkey: {
+        description: 'Description',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_box_publickey: {
+        description: 'Description',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_box_seal_open: {
+        description: 'Decrypt the ciphertext',
+        signature: '( string $ciphertext , string $key ): string'
+    },
+    sodium_crypto_box_seal: {
+        description: 'Encrypt a message',
+        signature: '( string $msg , string $key ): string'
+    },
+    sodium_crypto_box_secretkey: {
+        description: 'Description',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_box_seed_keypair: {
+        description: 'Deterministically derive the key pair from a single key',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_box: {
+        description: 'Encrypt a message',
+        signature: '( string $msg , string $nonce , string $key ): string'
+    },
+    sodium_crypto_generichash_final: {
+        description: 'Complete the hash',
+        signature: '( string $state [, int $length = SODIUM_CRYPTO_GENERICHASH_BYTES ]): string'
+    },
+    sodium_crypto_generichash_init: {
+        description: 'Initialize a hash',
+        signature: '([ string $key [, int $length = SODIUM_CRYPTO_GENERICHASH_BYTES ]]): string'
+    },
+    sodium_crypto_generichash_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_generichash_update: {
+        description: 'Add message to a hash',
+        signature: '( string $state , string $msg ): bool'
+    },
+    sodium_crypto_generichash: {
+        description: 'Get a hash of the message',
+        signature: '( string $msg [, string $key [, int $length = SODIUM_CRYPTO_GENERICHASH_BYTES ]]): string'
+    },
+    sodium_crypto_kdf_derive_from_key: {
+        description: 'Derive a subkey',
+        signature: '( int $subkey_len , int $subkey_id , string $context , string $key ): string'
+    },
+    sodium_crypto_kdf_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_kx_client_session_keys: {
+        description: 'Description',
+        signature: '( string $client_keypair , string $server_key ): array'
+    },
+    sodium_crypto_kx_keypair: {
+        description: 'Creates a new sodium keypair',
+        signature: '(void): string'
+    },
+    sodium_crypto_kx_publickey: {
+        description: 'Description',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_kx_secretkey: {
+        description: 'Description',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_kx_seed_keypair: {
+        description: 'Description',
+        signature: '( string $string ): string'
+    },
+    sodium_crypto_kx_server_session_keys: {
+        description: 'Description',
+        signature: '( string $server_keypair , string $client_key ): array'
+    },
+    sodium_crypto_pwhash_scryptsalsa208sha256_str_verify: {
+        description: 'Verify that the password is a valid password verification string',
+        signature: '( string $hash , string $password ): bool'
+    },
+    sodium_crypto_pwhash_scryptsalsa208sha256_str: {
+        description: 'Get an ASCII encoded hash',
+        signature: '( string $password , int $opslimit , int $memlimit ): string'
+    },
+    sodium_crypto_pwhash_scryptsalsa208sha256: {
+        description: 'Derives a key from a password',
+        signature: '( int $length , string $password , string $salt , int $opslimit , int $memlimit ): string'
+    },
+    sodium_crypto_pwhash_str_needs_rehash: {
+        description: 'Description',
+        signature: '( string $password , int $opslimit , int $memlimit ): bool'
+    },
+    sodium_crypto_pwhash_str_verify: {
+        description: 'Verifies that a password matches a hash',
+        signature: '( string $hash , string $password ): bool'
+    },
+    sodium_crypto_pwhash_str: {
+        description: 'Get an ASCII-encoded hash',
+        signature: '( string $password , int $opslimit , int $memlimit ): string'
+    },
+    sodium_crypto_pwhash: {
+        description: 'Derive a key from a password',
+        signature: '( int $length , string $password , string $salt , int $opslimit , int $memlimit [, int $alg ]): string'
+    },
+    sodium_crypto_scalarmult_base: {
+        description: 'Alias of sodium_crypto_box_publickey_from_secretkey',
+    },
+    sodium_crypto_scalarmult: {
+        description: 'Compute a shared secret given a user\'s secret key and another user\'s public key',
+        signature: '( string $n , string $p ): string'
+    },
+    sodium_crypto_secretbox_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_secretbox_open: {
+        description: 'Verify and decrypt a ciphertext',
+        signature: '( string $ciphertext , string $nonce , string $key ): string'
+    },
+    sodium_crypto_secretbox: {
+        description: 'Encrypt a message',
+        signature: '( string $string , string $nonce , string $key ): string'
+    },
+    sodium_crypto_secretstream_xchacha20poly1305_init_pull: {
+        description: 'Description',
+        signature: '( string $header , string $key ): string'
+    },
+    sodium_crypto_secretstream_xchacha20poly1305_init_push: {
+        description: 'Description',
+        signature: '( string $key ): array'
+    },
+    sodium_crypto_secretstream_xchacha20poly1305_keygen: {
+        description: 'Description',
+        signature: '(void): string'
+    },
+    sodium_crypto_secretstream_xchacha20poly1305_pull: {
+        description: 'Description',
+        signature: '( string $state , string $c [, string $ad ]): array'
+    },
+    sodium_crypto_secretstream_xchacha20poly1305_push: {
+        description: 'Description',
+        signature: '( string $state , string $msg [, string $ad [, int $tag ]]): string'
+    },
+    sodium_crypto_secretstream_xchacha20poly1305_rekey: {
+        description: 'Description',
+        signature: '( string $state ): void'
+    },
+    sodium_crypto_shorthash_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_shorthash: {
+        description: 'Compute a fixed-size fingerprint for the message',
+        signature: '( string $msg , string $key ): string'
+    },
+    sodium_crypto_sign_detached: {
+        description: 'Sign the message',
+        signature: '( string $msg , string $secretkey ): string'
+    },
+    sodium_crypto_sign_ed25519_pk_to_curve25519: {
+        description: 'Convert an Ed25519 public key to a Curve25519 public key',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_sign_ed25519_sk_to_curve25519: {
+        description: 'Convert an Ed25519 secret key to a Curve25519 secret key',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_sign_keypair_from_secretkey_and_publickey: {
+        description: 'Description',
+        signature: '( string $secret_key , string $public_key ): string'
+    },
+    sodium_crypto_sign_keypair: {
+        description: 'Randomly generate a secret key and a corresponding public key',
+        signature: '(void): string'
+    },
+    sodium_crypto_sign_open: {
+        description: 'Check that the signed message has a valid signature',
+        signature: '( string $string , string $public_key ): string'
+    },
+    sodium_crypto_sign_publickey_from_secretkey: {
+        description: 'Extract the public key from the secret key',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_sign_publickey: {
+        description: 'Description',
+        signature: '( string $keypair ): string'
+    },
+    sodium_crypto_sign_secretkey: {
+        description: 'Description',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_sign_seed_keypair: {
+        description: 'Deterministically derive the key pair from a single key',
+        signature: '( string $key ): string'
+    },
+    sodium_crypto_sign_verify_detached: {
+        description: 'Verify signature for the message',
+        signature: '( string $signature , string $msg , string $public_key ): bool'
+    },
+    sodium_crypto_sign: {
+        description: 'Sign a message',
+        signature: '( string $msg , string $secret_key ): string'
+    },
+    sodium_crypto_stream_keygen: {
+        description: 'Get random bytes for key',
+        signature: '(void): string'
+    },
+    sodium_crypto_stream_xor: {
+        description: 'Encrypt a message',
+        signature: '( string $msg , string $nonce , string $key ): string'
+    },
+    sodium_crypto_stream: {
+        description: 'Generate a deterministic sequence of bytes from a seed',
+        signature: '( int $length , string $nonce , string $key ): string'
+    },
+    sodium_hex2bin: {
+        description: 'Decodes a hexadecimally encoded binary string',
+        signature: '( string $hex [, string $ignore ]): string'
+    },
+    sodium_increment: {
+        description: 'Increment large number',
+        signature: '( string $val ): void'
+    },
+    sodium_memcmp: {
+        description: 'Test for equality in constant-time',
+        signature: '( string $buf1 , string $buf2 ): int'
+    },
+    sodium_memzero: {
+        description: 'Overwrite buf with zeros',
+        signature: '( string $buf ): void'
+    },
+    sodium_pad: {
+        description: 'Add padding data',
+        signature: '( string $unpadded , int $length ): string'
+    },
+    sodium_unpad: {
+        description: 'Remove padding data',
+        signature: '( string $padded , int $length ): string'
+    },
+    dba_close: {
+        description: 'Close a DBA database',
+        signature: '( resource $handle ): void'
+    },
+    dba_delete: {
+        description: 'Delete DBA entry specified by key',
+        signature: '( string $key , resource $handle ): bool'
+    },
+    dba_exists: {
+        description: 'Check whether key exists',
+        signature: '( string $key , resource $handle ): bool'
+    },
+    dba_fetch: {
+        description: 'Fetch data specified by key',
+        signature: '( string $key , resource $handle , int $skip ): string'
+    },
+    dba_firstkey: {
+        description: 'Fetch first key',
+        signature: '( resource $handle ): string'
+    },
+    dba_handlers: {
+        description: 'List all the handlers available',
+        signature: '([ bool $full_info ]): array'
+    },
+    dba_insert: {
+        description: 'Insert entry',
+        signature: '( string $key , string $value , resource $handle ): bool'
+    },
+    dba_key_split: {
+        description: 'Splits a key in string representation into array representation',
+        signature: '( mixed $key ): mixed'
+    },
+    dba_list: {
+        description: 'List all open database files',
+        signature: '(void): array'
+    },
+    dba_nextkey: {
+        description: 'Fetch next key',
+        signature: '( resource $handle ): string'
+    },
+    dba_open: {
+        description: 'Open database',
+        signature: '( string $path , string $mode [, string $handler [, mixed $... ]]): resource'
+    },
+    dba_optimize: {
+        description: 'Optimize database',
+        signature: '( resource $handle ): bool'
+    },
+    dba_popen: {
+        description: 'Open database persistently',
+        signature: '( string $path , string $mode [, string $handler [, mixed $... ]]): resource'
+    },
+    dba_replace: {
+        description: 'Replace or insert entry',
+        signature: '( string $key , string $value , resource $handle ): bool'
+    },
+    dba_sync: {
+        description: 'Synchronize database',
+        signature: '( resource $handle ): bool'
+    },
+    pdo_drivers: {
+        description: 'Return an array of available PDO drivers',
+        signature: '(void): array'
+    },
+    cal_days_in_month: {
+        description: 'Return the number of days in a month for a given year and calendar',
+        signature: '( int $calendar , int $month , int $year ): int'
+    },
+    cal_from_jd: {
+        description: 'Converts from Julian Day Count to a supported calendar',
+        signature: '( int $jd , int $calendar ): array'
+    },
+    cal_info: {
+        description: 'Returns information about a particular calendar',
+        signature: '([ int $calendar = -1 ]): array'
+    },
+    cal_to_jd: {
+        description: 'Converts from a supported calendar to Julian Day Count',
+        signature: '( int $calendar , int $month , int $day , int $year ): int'
+    },
+    easter_date: {
+        description: 'Get Unix timestamp for midnight on Easter of a given year',
+        signature: '([ int $year = date("Y") ]): int'
+    },
+    easter_days: {
+        description: 'Get number of days after March 21 on which Easter falls for a given year',
+        signature: '([ int $year = date("Y") [, int $method = CAL_EASTER_DEFAULT ]]): int'
+    },
+    frenchtojd: {
+        description: 'Converts a date from the French Republican Calendar to a Julian Day Count',
+        signature: '( int $month , int $day , int $year ): int'
+    },
+    gregoriantojd: {
+        description: 'Converts a Gregorian date to Julian Day Count',
+        signature: '( int $month , int $day , int $year ): int'
+    },
+    jddayofweek: {
+        description: 'Returns the day of the week',
+        signature: '( int $julianday [, int $mode = CAL_DOW_DAYNO ]): mixed'
+    },
+    jdmonthname: {
+        description: 'Returns a month name',
+        signature: '( int $julianday , int $mode ): string'
+    },
+    jdtofrench: {
+        description: 'Converts a Julian Day Count to the French Republican Calendar',
+        signature: '( int $juliandaycount ): string'
+    },
+    jdtogregorian: {
+        description: 'Converts Julian Day Count to Gregorian date',
+        signature: '( int $julianday ): string'
+    },
+    jdtojewish: {
+        description: 'Converts a Julian day count to a Jewish calendar date',
+        signature: '( int $juliandaycount [, bool $hebrew [, int $fl = 0 ]]): string'
+    },
+    jdtojulian: {
+        description: 'Converts a Julian Day Count to a Julian Calendar Date',
+        signature: '( int $julianday ): string'
+    },
+    jdtounix: {
+        description: 'Convert Julian Day to Unix timestamp',
+        signature: '( int $jday ): int'
+    },
+    jewishtojd: {
+        description: 'Converts a date in the Jewish Calendar to Julian Day Count',
+        signature: '( int $month , int $day , int $year ): int'
+    },
+    juliantojd: {
+        description: 'Converts a Julian Calendar date to Julian Day Count',
+        signature: '( int $month , int $day , int $year ): int'
+    },
+    unixtojd: {
+        description: 'Convert Unix timestamp to Julian Day',
+        signature: '([ int $timestamp = time() ]): int'
+    },
+    date_add: {
+        description: 'Adds an amount of days, months, years, hours, minutes and seconds to a   DateTime object',
+        signature: '( DateInterval $interval , DateTime $object ): DateTime'
+    },
+    date_create: {
+        description: 'Returns new DateTime object',
+        signature: '([ string $time = "now" [, DateTimeZone $timezone ]]): DateTime'
+    },
+    date_create_from_format: {
+        description: 'Parses a time string according to a specified format',
+        signature: '( string $format , string $time [, DateTimeZone $timezone ]): DateTime'
+    },
+    date_get_last_errors: {
+        description: 'Returns the warnings and errors',
+        signature: '(void): array'
+    },
+    date_modify: {
+        description: 'Alters the timestamp',
+        signature: '( string $modify , DateTime $object ): DateTime'
+    },
+    date_date_set: {
+        description: 'Sets the date',
+        signature: '( int $year , int $month , int $day , DateTime $object ): DateTime'
+    },
+    date_isodate_set: {
+        description: 'Sets the ISO date',
+        signature: '( int $year , int $week [, int $day = 1 , DateTime $object ]): DateTime'
+    },
+    date_time_set: {
+        description: 'Sets the time',
+        signature: '( int $hour , int $minute [, int $second = 0 [, int $microseconds = 0 , DateTime $object ]]): DateTime'
+    },
+    date_timestamp_set: {
+        description: 'Sets the date and time based on an Unix timestamp',
+        signature: '( int $unixtimestamp , DateTime $object ): DateTime'
+    },
+    date_timezone_set: {
+        description: 'Sets the time zone for the DateTime object',
+        signature: '( DateTimeZone $timezone , DateTime $object ): object'
+    },
+    date_sub: {
+        description: 'Subtracts an amount of days, months, years, hours, minutes and seconds from   a DateTime object',
+        signature: '( DateInterval $interval , DateTime $object ): DateTime'
+    },
+    date_create_immutable: {
+        description: 'Returns new DateTimeImmutable object',
+        signature: '([ string $time = "now" [, DateTimeZone $timezone ]]): DateTimeImmutable'
+    },
+    date_create_immutable_from_format: {
+        description: 'Parses a time string according to a specified format',
+        signature: '( string $format , string $time [, DateTimeZone $timezone ]): DateTimeImmutable'
+    },
+    date_diff: {
+        description: 'Returns the difference between two DateTime objects',
+        signature: '( DateTimeInterface $datetime2 [, bool $absolute , DateTimeInterface $datetime1 ]): DateInterval'
+    },
+    date_format: {
+        description: 'Returns date formatted according to given format',
+        signature: '( DateTimeInterface $object , string $format ): string'
+    },
+    date_offset_get: {
+        description: 'Returns the timezone offset',
+        signature: '( DateTimeInterface $object ): int'
+    },
+    date_timestamp_get: {
+        description: 'Gets the Unix timestamp',
+        signature: '( DateTimeInterface $object ): int'
+    },
+    date_timezone_get: {
+        description: 'Return time zone relative to given DateTime',
+        signature: '( DateTimeInterface $object ): DateTimeZone'
+    },
+    timezone_open: {
+        description: 'Creates new DateTimeZone object',
+        signature: '( string $timezone ): DateTimeZone'
+    },
+    timezone_location_get: {
+        description: 'Returns location information for a timezone',
+        signature: '( DateTimeZone $object ): array'
+    },
+    timezone_name_get: {
+        description: 'Returns the name of the timezone',
+        signature: '( DateTimeZone $object ): string'
+    },
+    timezone_offset_get: {
+        description: 'Returns the timezone offset from GMT',
+        signature: '( DateTimeInterface $datetime , DateTimeZone $object ): int'
+    },
+    timezone_transitions_get: {
+        description: 'Returns all transitions for the timezone',
+        signature: '([ int $timestamp_begin [, int $timestamp_end , DateTimeZone $object ]]): array'
+    },
+    timezone_abbreviations_list: {
+        description: 'Returns associative array containing dst, offset and the timezone name',
+        signature: '(void): array'
+    },
+    timezone_identifiers_list: {
+        description: 'Returns a numerically indexed array containing all defined timezone identifiers',
+        signature: '([ int $what = DateTimeZone::ALL [, string $country ]]): array'
+    },
+    checkdate: {
+        description: 'Validate a Gregorian date',
+        signature: '( int $month , int $day , int $year ): bool'
+    },
+    date_default_timezone_get: {
+        description: 'Gets the default timezone used by all date/time functions in a script',
+        signature: '(void): string'
+    },
+    date_default_timezone_set: {
+        description: 'Sets the default timezone used by all date/time functions in a script',
+        signature: '( string $timezone_identifier ): bool'
+    },
+    date_interval_create_from_date_string: {
+        description: 'Alias of DateInterval::createFromDateString',
+    },
+    date_interval_format: {
+        description: 'Alias of DateInterval::format',
+    },
+    date_parse_from_format: {
+        description: 'Get info about given date formatted according to the specified format',
+        signature: '( string $format , string $date ): array'
+    },
+    date_parse: {
+        description: 'Returns associative array with detailed info about given date',
+        signature: '( string $date ): array'
+    },
+    date_sun_info: {
+        description: 'Returns an array with information about sunset/sunrise and twilight begin/end',
+        signature: '( int $time , float $latitude , float $longitude ): array'
+    },
+    date_sunrise: {
+        description: 'Returns time of sunrise for a given day and location',
+        signature: '( int $timestamp [, int $format = SUNFUNCS_RET_STRING [, float $latitude = ini_get("date.default_latitude") [, float $longitude = ini_get("date.default_longitude") [, float $zenith = ini_get("date.sunrise_zenith") [, float $gmt_offset = 0 ]]]]]): mixed'
+    },
+    date_sunset: {
+        description: 'Returns time of sunset for a given day and location',
+        signature: '( int $timestamp [, int $format = SUNFUNCS_RET_STRING [, float $latitude = ini_get("date.default_latitude") [, float $longitude = ini_get("date.default_longitude") [, float $zenith = ini_get("date.sunset_zenith") [, float $gmt_offset = 0 ]]]]]): mixed'
+    },
+    date: {
+        description: 'Format a local time/date',
+        signature: '( string $format [, int $timestamp = time() ]): string'
+    },
+    getdate: {
+        description: 'Get date/time information',
+        signature: '([ int $timestamp = time() ]): array'
+    },
+    gettimeofday: {
+        description: 'Get current time',
+        signature: '([ bool $return_float ]): mixed'
+    },
+    gmdate: {
+        description: 'Format a GMT/UTC date/time',
+        signature: '( string $format [, int $timestamp = time() ]): string'
+    },
+    gmmktime: {
+        description: 'Get Unix timestamp for a GMT date',
+        signature: '([ int $hour = gmdate("H") [, int $minute = gmdate("i") [, int $second = gmdate("s") [, int $month = gmdate("n") [, int $day = gmdate("j") [, int $year = gmdate("Y") [, int $is_dst = -1 ]]]]]]]): int'
+    },
+    gmstrftime: {
+        description: 'Format a GMT/UTC time/date according to locale settings',
+        signature: '( string $format [, int $timestamp = time() ]): string'
+    },
+    idate: {
+        description: 'Format a local time/date as integer',
+        signature: '( string $format [, int $timestamp = time() ]): int'
+    },
+    localtime: {
+        description: 'Get the local time',
+        signature: '([ int $timestamp = time() [, bool $is_associative ]]): array'
+    },
+    microtime: {
+        description: 'Return current Unix timestamp with microseconds',
+        signature: '([ bool $get_as_float ]): mixed'
+    },
+    mktime: {
+        description: 'Get Unix timestamp for a date',
+        signature: '([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]]): int'
+    },
+    strftime: {
+        description: 'Format a local time/date according to locale settings',
+        signature: '( string $format [, int $timestamp = time() ]): string'
+    },
+    strptime: {
+        description: 'Parse a time/date generated with strftime',
+        signature: '( string $date , string $format ): array'
+    },
+    strtotime: {
+        description: 'Parse about any English textual datetime description into a Unix timestamp',
+        signature: '( string $time [, int $now = time() ]): int'
+    },
+    time: {
+        description: 'Return current Unix timestamp',
+        signature: '(void): int'
+    },
+    timezone_name_from_abbr: {
+        description: 'Returns the timezone name from abbreviation',
+        signature: '( string $abbr [, int $gmtOffset = -1 [, int $isdst = -1 ]]): string'
+    },
+    timezone_version_get: {
+        description: 'Gets the version of the timezonedb',
+        signature: '(void): string'
+    },
+    chdir: {
+        description: 'Change directory',
+        signature: '( string $directory ): bool'
+    },
+    chroot: {
+        description: 'Change the root directory',
+        signature: '( string $directory ): bool'
+    },
+    closedir: {
+        description: 'Close directory handle',
+        signature: '([ resource $dir_handle ]): void'
+    },
+    dir: {
+        description: 'Return an instance of the Directory class',
+        signature: '( string $directory [, resource $context ]): Directory'
+    },
+    getcwd: {
+        description: 'Gets the current working directory',
+        signature: '(void): string'
+    },
+    opendir: {
+        description: 'Open directory handle',
+        signature: '( string $path [, resource $context ]): resource'
+    },
+    readdir: {
+        description: 'Read entry from directory handle',
+        signature: '([ resource $dir_handle ]): string'
+    },
+    rewinddir: {
+        description: 'Rewind directory handle',
+        signature: '([ resource $dir_handle ]): void'
+    },
+    scandir: {
+        description: 'List files and directories inside the specified path',
+        signature: '( string $directory [, int $sorting_order = SCANDIR_SORT_ASCENDING [, resource $context ]]): array'
+    },
+    finfo_buffer: {
+        description: 'Return information about a string buffer',
+        signature: '( resource $finfo , string $string [, int $options = FILEINFO_NONE [, resource $context ]]): string'
+    },
+    finfo_close: {
+        description: 'Close fileinfo resource',
+        signature: '( resource $finfo ): bool'
+    },
+    finfo_file: {
+        description: 'Return information about a file',
+        signature: '( resource $finfo , string $file_name [, int $options = FILEINFO_NONE [, resource $context ]]): string'
+    },
+    finfo_open: {
+        description: 'Create a new fileinfo resource',
+        signature: '([ int $options = FILEINFO_NONE [, string $magic_file ]]): resource'
+    },
+    finfo_set_flags: {
+        description: 'Set libmagic configuration options',
+        signature: '( resource $finfo , int $options ): bool'
+    },
+    mime_content_type: {
+        description: 'Detect MIME Content-type for a file',
+        signature: '( string $filename ): string'
+    },
+    basename: {
+        description: 'Returns trailing name component of path',
+        signature: '( string $path [, string $suffix ]): string'
+    },
+    chgrp: {
+        description: 'Changes file group',
+        signature: '( string $filename , mixed $group ): bool'
+    },
+    chmod: {
+        description: 'Changes file mode',
+        signature: '( string $filename , int $mode ): bool'
+    },
+    chown: {
+        description: 'Changes file owner',
+        signature: '( string $filename , mixed $user ): bool'
+    },
+    clearstatcache: {
+        description: 'Clears file status cache',
+        signature: '([ bool $clear_realpath_cache [, string $filename ]]): void'
+    },
+    copy: {
+        description: 'Copies file',
+        signature: '( string $source , string $dest [, resource $context ]): bool'
+    },
+    delete: {
+        description: 'See unlink or unset',
+    },
+    dirname: {
+        description: 'Returns a parent directory\'s path',
+        signature: '( string $path [, int $levels = 1 ]): string'
+    },
+    disk_free_space: {
+        description: 'Returns available space on filesystem or disk partition',
+        signature: '( string $directory ): float'
+    },
+    disk_total_space: {
+        description: 'Returns the total size of a filesystem or disk partition',
+        signature: '( string $directory ): float'
+    },
+    diskfreespace: {
+        description: 'Alias of disk_free_space',
+    },
+    fclose: {
+        description: 'Closes an open file pointer',
+        signature: '( resource $handle ): bool'
+    },
+    feof: {
+        description: 'Tests for end-of-file on a file pointer',
+        signature: '( resource $handle ): bool'
+    },
+    fflush: {
+        description: 'Flushes the output to a file',
+        signature: '( resource $handle ): bool'
+    },
+    fgetc: {
+        description: 'Gets character from file pointer',
+        signature: '( resource $handle ): string'
+    },
+    fgetcsv: {
+        description: 'Gets line from file pointer and parse for CSV fields',
+        signature: '( resource $handle [, int $length = 0 [, string $delimiter = "," [, string $enclosure = \'"\' [, string $escape = "\\" ]]]]): array'
+    },
+    fgets: {
+        description: 'Gets line from file pointer',
+        signature: '( resource $handle [, int $length ]): string'
+    },
+    fgetss: {
+        description: 'Gets line from file pointer and strip HTML tags',
+        signature: '( resource $handle [, int $length [, string $allowable_tags ]]): string'
+    },
+    file_exists: {
+        description: 'Checks whether a file or directory exists',
+        signature: '( string $filename ): bool'
+    },
+    file_get_contents: {
+        description: 'Reads entire file into a string',
+        signature: '( string $filename [, bool $use_include_path [, resource $context [, int $offset = 0 [, int $maxlen ]]]]): string'
+    },
+    file_put_contents: {
+        description: 'Write data to a file',
+        signature: '( string $filename , mixed $data [, int $flags = 0 [, resource $context ]]): int'
+    },
+    file: {
+        description: 'Reads entire file into an array',
+        signature: '( string $filename [, int $flags = 0 [, resource $context ]]): array'
+    },
+    fileatime: {
+        description: 'Gets last access time of file',
+        signature: '( string $filename ): int'
+    },
+    filectime: {
+        description: 'Gets inode change time of file',
+        signature: '( string $filename ): int'
+    },
+    filegroup: {
+        description: 'Gets file group',
+        signature: '( string $filename ): int'
+    },
+    fileinode: {
+        description: 'Gets file inode',
+        signature: '( string $filename ): int'
+    },
+    filemtime: {
+        description: 'Gets file modification time',
+        signature: '( string $filename ): int'
+    },
+    fileowner: {
+        description: 'Gets file owner',
+        signature: '( string $filename ): int'
+    },
+    fileperms: {
+        description: 'Gets file permissions',
+        signature: '( string $filename ): int'
+    },
+    filesize: {
+        description: 'Gets file size',
+        signature: '( string $filename ): int'
+    },
+    filetype: {
+        description: 'Gets file type',
+        signature: '( string $filename ): string'
+    },
+    flock: {
+        description: 'Portable advisory file locking',
+        signature: '( resource $handle , int $operation [, int $wouldblock ]): bool'
+    },
+    fnmatch: {
+        description: 'Match filename against a pattern',
+        signature: '( string $pattern , string $string [, int $flags = 0 ]): bool'
+    },
+    fopen: {
+        description: 'Opens file or URL',
+        signature: '( string $filename , string $mode [, bool $use_include_path [, resource $context ]]): resource'
+    },
+    fpassthru: {
+        description: 'Output all remaining data on a file pointer',
+        signature: '( resource $handle ): int'
+    },
+    fputcsv: {
+        description: 'Format line as CSV and write to file pointer',
+        signature: '( resource $handle , array $fields [, string $delimiter = "," [, string $enclosure = \'"\' [, string $escape_char = "\\" ]]]): int'
+    },
+    fputs: {
+        description: 'Alias of fwrite',
+    },
+    fread: {
+        description: 'Binary-safe file read',
+        signature: '( resource $handle , int $length ): string'
+    },
+    fscanf: {
+        description: 'Parses input from a file according to a format',
+        signature: '( resource $handle , string $format [, mixed $... ]): mixed'
+    },
+    fseek: {
+        description: 'Seeks on a file pointer',
+        signature: '( resource $handle , int $offset [, int $whence = SEEK_SET ]): int'
+    },
+    fstat: {
+        description: 'Gets information about a file using an open file pointer',
+        signature: '( resource $handle ): array'
+    },
+    ftell: {
+        description: 'Returns the current position of the file read/write pointer',
+        signature: '( resource $handle ): int'
+    },
+    ftruncate: {
+        description: 'Truncates a file to a given length',
+        signature: '( resource $handle , int $size ): bool'
+    },
+    fwrite: {
+        description: 'Binary-safe file write',
+        signature: '( resource $handle , string $string [, int $length ]): int'
+    },
+    glob: {
+        description: 'Find pathnames matching a pattern',
+        signature: '( string $pattern [, int $flags = 0 ]): array'
+    },
+    is_dir: {
+        description: 'Tells whether the filename is a directory',
+        signature: '( string $filename ): bool'
+    },
+    is_executable: {
+        description: 'Tells whether the filename is executable',
+        signature: '( string $filename ): bool'
+    },
+    is_file: {
+        description: 'Tells whether the filename is a regular file',
+        signature: '( string $filename ): bool'
+    },
+    is_link: {
+        description: 'Tells whether the filename is a symbolic link',
+        signature: '( string $filename ): bool'
+    },
+    is_readable: {
+        description: 'Tells whether a file exists and is readable',
+        signature: '( string $filename ): bool'
+    },
+    is_uploaded_file: {
+        description: 'Tells whether the file was uploaded via HTTP POST',
+        signature: '( string $filename ): bool'
+    },
+    is_writable: {
+        description: 'Tells whether the filename is writable',
+        signature: '( string $filename ): bool'
+    },
+    is_writeable: {
+        description: 'Alias of is_writable',
+    },
+    lchgrp: {
+        description: 'Changes group ownership of symlink',
+        signature: '( string $filename , mixed $group ): bool'
+    },
+    lchown: {
+        description: 'Changes user ownership of symlink',
+        signature: '( string $filename , mixed $user ): bool'
+    },
+    link: {
+        description: 'Create a hard link',
+        signature: '( string $target , string $link ): bool'
+    },
+    linkinfo: {
+        description: 'Gets information about a link',
+        signature: '( string $path ): int'
+    },
+    lstat: {
+        description: 'Gives information about a file or symbolic link',
+        signature: '( string $filename ): array'
+    },
+    mkdir: {
+        description: 'Makes directory',
+        signature: '( string $pathname [, int $mode = 0777 [, bool $recursive [, resource $context ]]]): bool'
+    },
+    move_uploaded_file: {
+        description: 'Moves an uploaded file to a new location',
+        signature: '( string $filename , string $destination ): bool'
+    },
+    parse_ini_file: {
+        description: 'Parse a configuration file',
+        signature: '( string $filename [, bool $process_sections [, int $scanner_mode = INI_SCANNER_NORMAL ]]): array'
+    },
+    parse_ini_string: {
+        description: 'Parse a configuration string',
+        signature: '( string $ini [, bool $process_sections [, int $scanner_mode = INI_SCANNER_NORMAL ]]): array'
+    },
+    pathinfo: {
+        description: 'Returns information about a file path',
+        signature: '( string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]): mixed'
+    },
+    pclose: {
+        description: 'Closes process file pointer',
+        signature: '( resource $handle ): int'
+    },
+    popen: {
+        description: 'Opens process file pointer',
+        signature: '( string $command , string $mode ): resource'
+    },
+    readfile: {
+        description: 'Outputs a file',
+        signature: '( string $filename [, bool $use_include_path [, resource $context ]]): int'
+    },
+    readlink: {
+        description: 'Returns the target of a symbolic link',
+        signature: '( string $path ): string'
+    },
+    realpath_cache_get: {
+        description: 'Get realpath cache entries',
+        signature: '(void): array'
+    },
+    realpath_cache_size: {
+        description: 'Get realpath cache size',
+        signature: '(void): int'
+    },
+    realpath: {
+        description: 'Returns canonicalized absolute pathname',
+        signature: '( string $path ): string'
+    },
+    rename: {
+        description: 'Renames a file or directory',
+        signature: '( string $oldname , string $newname [, resource $context ]): bool'
+    },
+    rewind: {
+        description: 'Rewind the position of a file pointer',
+        signature: '( resource $handle ): bool'
+    },
+    rmdir: {
+        description: 'Removes directory',
+        signature: '( string $dirname [, resource $context ]): bool'
+    },
+    set_file_buffer: {
+        description: 'Alias of stream_set_write_buffer',
+    },
+    stat: {
+        description: 'Gives information about a file',
+        signature: '( string $filename ): array'
+    },
+    symlink: {
+        description: 'Creates a symbolic link',
+        signature: '( string $target , string $link ): bool'
+    },
+    tempnam: {
+        description: 'Create file with unique file name',
+        signature: '( string $dir , string $prefix ): string'
+    },
+    tmpfile: {
+        description: 'Creates a temporary file',
+        signature: '(void): resource'
+    },
+    touch: {
+        description: 'Sets access and modification time of file',
+        signature: '( string $filename [, int $time = time() [, int $atime ]]): bool'
+    },
+    umask: {
+        description: 'Changes the current umask',
+        signature: '([ int $mask ]): int'
+    },
+    unlink: {
+        description: 'Deletes a file',
+        signature: '( string $filename [, resource $context ]): bool'
+    },
+    iconv_get_encoding: {
+        description: 'Retrieve internal configuration variables of iconv extension',
+        signature: '([ string $type = "all" ]): mixed'
+    },
+    iconv_mime_decode_headers: {
+        description: 'Decodes multiple MIME header fields at once',
+        signature: '( string $encoded_headers [, int $mode = 0 [, string $charset = ini_get("iconv.internal_encoding") ]]): array'
+    },
+    iconv_mime_decode: {
+        description: 'Decodes a MIME header field',
+        signature: '( string $encoded_header [, int $mode = 0 [, string $charset = ini_get("iconv.internal_encoding") ]]): string'
+    },
+    iconv_mime_encode: {
+        description: 'Composes a MIME header field',
+        signature: '( string $field_name , string $field_value [, array $preferences ]): string'
+    },
+    iconv_set_encoding: {
+        description: 'Set current setting for character encoding conversion',
+        signature: '( string $type , string $charset ): bool'
+    },
+    iconv_strlen: {
+        description: 'Returns the character count of string',
+        signature: '( string $str [, string $charset = ini_get("iconv.internal_encoding") ]): int'
+    },
+    iconv_strpos: {
+        description: 'Finds position of first occurrence of a needle within a haystack',
+        signature: '( string $haystack , string $needle [, int $offset = 0 [, string $charset = ini_get("iconv.internal_encoding") ]]): int'
+    },
+    iconv_strrpos: {
+        description: 'Finds the last occurrence of a needle within a haystack',
+        signature: '( string $haystack , string $needle [, string $charset = ini_get("iconv.internal_encoding") ]): int'
+    },
+    iconv_substr: {
+        description: 'Cut out part of a string',
+        signature: '( string $str , int $offset [, int $length = iconv_strlen($str, $charset) [, string $charset = ini_get("iconv.internal_encoding") ]]): string'
+    },
+    iconv: {
+        description: 'Convert string to requested character encoding',
+        signature: '( string $in_charset , string $out_charset , string $str ): string'
+    },
+    ob_iconv_handler: {
+        description: 'Convert character encoding as output buffer handler',
+        signature: '( string $contents , int $status ): string'
+    },
+    collator_asort: {
+        description: 'Sort array maintaining index association',
+        signature: '( array $arr [, int $sort_flag , Collator $coll ]): bool'
+    },
+    collator_compare: {
+        description: 'Compare two Unicode strings',
+        signature: '( string $str1 , string $str2 , Collator $coll ): int'
+    },
+    collator_create: {
+        description: 'Create a collator',
+        signature: '( string $locale ): Collator'
+    },
+    collator_get_attribute: {
+        description: 'Get collation attribute value',
+        signature: '( int $attr , Collator $coll ): int'
+    },
+    collator_get_error_code: {
+        description: 'Get collator\'s last error code',
+        signature: '( Collator $coll ): int'
+    },
+    collator_get_error_message: {
+        description: 'Get text for collator\'s last error code',
+        signature: '( Collator $coll ): string'
+    },
+    collator_get_locale: {
+        description: 'Get the locale name of the collator',
+        signature: '( int $type , Collator $coll ): string'
+    },
+    collator_get_sort_key: {
+        description: 'Get sorting key for a string',
+        signature: '( string $str , Collator $coll ): string'
+    },
+    collator_get_strength: {
+        description: 'Get current collation strength',
+        signature: '( Collator $coll ): int'
+    },
+    collator_set_attribute: {
+        description: 'Set collation attribute',
+        signature: '( int $attr , int $val , Collator $coll ): bool'
+    },
+    collator_set_strength: {
+        description: 'Set collation strength',
+        signature: '( int $strength , Collator $coll ): bool'
+    },
+    collator_sort_with_sort_keys: {
+        description: 'Sort array using specified collator and sort keys',
+        signature: '( array $arr , Collator $coll ): bool'
+    },
+    collator_sort: {
+        description: 'Sort array using specified collator',
+        signature: '( array $arr [, int $sort_flag , Collator $coll ]): bool'
+    },
+    numfmt_create: {
+        description: 'Create a number formatter',
+        signature: '( string $locale , int $style [, string $pattern ]): NumberFormatter'
+    },
+    numfmt_format_currency: {
+        description: 'Format a currency value',
+        signature: '( float $value , string $currency , NumberFormatter $fmt ): string'
+    },
+    numfmt_format: {
+        description: 'Format a number',
+        signature: '( number $value [, int $type , NumberFormatter $fmt ]): string'
+    },
+    numfmt_get_attribute: {
+        description: 'Get an attribute',
+        signature: '( int $attr , NumberFormatter $fmt ): int'
+    },
+    numfmt_get_error_code: {
+        description: 'Get formatter\'s last error code',
+        signature: '( NumberFormatter $fmt ): int'
+    },
+    numfmt_get_error_message: {
+        description: 'Get formatter\'s last error message',
+        signature: '( NumberFormatter $fmt ): string'
+    },
+    numfmt_get_locale: {
+        description: 'Get formatter locale',
+        signature: '([ int $type , NumberFormatter $fmt ]): string'
+    },
+    numfmt_get_pattern: {
+        description: 'Get formatter pattern',
+        signature: '( NumberFormatter $fmt ): string'
+    },
+    numfmt_get_symbol: {
+        description: 'Get a symbol value',
+        signature: '( int $attr , NumberFormatter $fmt ): string'
+    },
+    numfmt_get_text_attribute: {
+        description: 'Get a text attribute',
+        signature: '( int $attr , NumberFormatter $fmt ): string'
+    },
+    numfmt_parse_currency: {
+        description: 'Parse a currency number',
+        signature: '( string $value , string $currency [, int $position , NumberFormatter $fmt ]): float'
+    },
+    numfmt_parse: {
+        description: 'Parse a number',
+        signature: '( string $value [, int $type [, int $position , NumberFormatter $fmt ]]): mixed'
+    },
+    numfmt_set_attribute: {
+        description: 'Set an attribute',
+        signature: '( int $attr , int $value , NumberFormatter $fmt ): bool'
+    },
+    numfmt_set_pattern: {
+        description: 'Set formatter pattern',
+        signature: '( string $pattern , NumberFormatter $fmt ): bool'
+    },
+    numfmt_set_symbol: {
+        description: 'Set a symbol value',
+        signature: '( int $attr , string $value , NumberFormatter $fmt ): bool'
+    },
+    numfmt_set_text_attribute: {
+        description: 'Set a text attribute',
+        signature: '( int $attr , string $value , NumberFormatter $fmt ): bool'
+    },
+    locale_accept_from_http: {
+        description: 'Tries to find out best available locale based on HTTP "Accept-Language" header',
+        signature: '( string $header ): string'
+    },
+    locale_canonicalize: {
+        description: 'Canonicalize the locale string',
+        signature: '( string $locale ): string'
+    },
+    locale_compose: {
+        description: 'Returns a correctly ordered and delimited locale ID',
+        signature: '( array $subtags ): string'
+    },
+    locale_filter_matches: {
+        description: 'Checks if a language tag filter matches with locale',
+        signature: '( string $langtag , string $locale [, bool $canonicalize ]): bool'
+    },
+    locale_get_all_variants: {
+        description: 'Gets the variants for the input locale',
+        signature: '( string $locale ): array'
+    },
+    locale_get_default: {
+        description: 'Gets the default locale value from the INTL global \'default_locale\'',
+        signature: '(void): string'
+    },
+    locale_get_display_language: {
+        description: 'Returns an appropriately localized display name for language of the inputlocale',
+        signature: '( string $locale [, string $in_locale ]): string'
+    },
+    locale_get_display_name: {
+        description: 'Returns an appropriately localized display name for the input locale',
+        signature: '( string $locale [, string $in_locale ]): string'
+    },
+    locale_get_display_region: {
+        description: 'Returns an appropriately localized display name for region of the input locale',
+        signature: '( string $locale [, string $in_locale ]): string'
+    },
+    locale_get_display_script: {
+        description: 'Returns an appropriately localized display name for script of the input locale',
+        signature: '( string $locale [, string $in_locale ]): string'
+    },
+    locale_get_display_variant: {
+        description: 'Returns an appropriately localized display name for variants of the input locale',
+        signature: '( string $locale [, string $in_locale ]): string'
+    },
+    locale_get_keywords: {
+        description: 'Gets the keywords for the input locale',
+        signature: '( string $locale ): array'
+    },
+    locale_get_primary_language: {
+        description: 'Gets the primary language for the input locale',
+        signature: '( string $locale ): string'
+    },
+    locale_get_region: {
+        description: 'Gets the region for the input locale',
+        signature: '( string $locale ): string'
+    },
+    locale_get_script: {
+        description: 'Gets the script for the input locale',
+        signature: '( string $locale ): string'
+    },
+    locale_lookup: {
+        description: 'Searches the language tag list for the best match to the language',
+        signature: '( array $langtag , string $locale [, bool $canonicalize [, string $default ]]): string'
+    },
+    locale_parse: {
+        description: 'Returns a key-value array of locale ID subtag elements',
+        signature: '( string $locale ): array'
+    },
+    locale_set_default: {
+        description: 'Sets the default runtime locale',
+        signature: '( string $locale ): bool'
+    },
+    normalizer_get_raw_decomposition: {
+        description: 'Gets the Decomposition_Mapping property for the given UTF-8 encoded code point',
+        signature: '( string $input ): string'
+    },
+    normalizer_is_normalized: {
+        description: 'Checks if the provided string is already in the specified normalization   form',
+        signature: '( string $input [, int $form = Normalizer::FORM_C ]): bool'
+    },
+    normalizer_normalize: {
+        description: 'Normalizes the input provided and returns the normalized string',
+        signature: '( string $input [, int $form = Normalizer::FORM_C ]): string'
+    },
+    msgfmt_create: {
+        description: 'Constructs a new Message Formatter',
+        signature: '( string $locale , string $pattern ): MessageFormatter'
+    },
+    msgfmt_format_message: {
+        description: 'Quick format message',
+        signature: '( string $locale , string $pattern , array $args ): string'
+    },
+    msgfmt_format: {
+        description: 'Format the message',
+        signature: '( array $args , MessageFormatter $fmt ): string'
+    },
+    msgfmt_get_error_code: {
+        description: 'Get the error code from last operation',
+        signature: '( MessageFormatter $fmt ): int'
+    },
+    msgfmt_get_error_message: {
+        description: 'Get the error text from the last operation',
+        signature: '( MessageFormatter $fmt ): string'
+    },
+    msgfmt_get_locale: {
+        description: 'Get the locale for which the formatter was created',
+        signature: '( NumberFormatter $formatter ): string'
+    },
+    msgfmt_get_pattern: {
+        description: 'Get the pattern used by the formatter',
+        signature: '( MessageFormatter $fmt ): string'
+    },
+    msgfmt_parse_message: {
+        description: 'Quick parse input string',
+        signature: '( string $locale , string $pattern , string $source , string $value ): array'
+    },
+    msgfmt_parse: {
+        description: 'Parse input string according to pattern',
+        signature: '( string $value , MessageFormatter $fmt ): array'
+    },
+    msgfmt_set_pattern: {
+        description: 'Set the pattern used by the formatter',
+        signature: '( string $pattern , MessageFormatter $fmt ): bool'
+    },
+    intlcal_get_error_code: {
+        description: 'Get last error code on the object',
+        signature: '( IntlCalendar $calendar ): int'
+    },
+    intlcal_get_error_message: {
+        description: 'Get last error message on the object',
+        signature: '( IntlCalendar $calendar ): string'
+    },
+    intltz_get_error_code: {
+        description: 'Get last error code on the object',
+        signature: '(void): int'
+    },
+    intltz_get_error_message: {
+        description: 'Get last error message on the object',
+        signature: '(void): string'
+    },
+    datefmt_create: {
+        description: 'Create a date formatter',
+        signature: '( string $locale , int $datetype , int $timetype [, mixed $timezone = NULL [, mixed $calendar = NULL [, string $pattern = "" ]]]): IntlDateFormatter'
+    },
+    datefmt_format: {
+        description: 'Format the date/time value as a string',
+        signature: '( mixed $value , IntlDateFormatter $fmt ): string'
+    },
+    datefmt_format_object: {
+        description: 'Formats an object',
+        signature: '( object $object [, mixed $format = NULL [, string $locale = NULL ]]): string'
+    },
+    datefmt_get_calendar: {
+        description: 'Get the calendar type used for the IntlDateFormatter',
+        signature: '( IntlDateFormatter $fmt ): int'
+    },
+    datefmt_get_datetype: {
+        description: 'Get the datetype used for the IntlDateFormatter',
+        signature: '( IntlDateFormatter $fmt ): int'
+    },
+    datefmt_get_error_code: {
+        description: 'Get the error code from last operation',
+        signature: '( IntlDateFormatter $fmt ): int'
+    },
+    datefmt_get_error_message: {
+        description: 'Get the error text from the last operation',
+        signature: '( IntlDateFormatter $fmt ): string'
+    },
+    datefmt_get_locale: {
+        description: 'Get the locale used by formatter',
+        signature: '([ int $which , IntlDateFormatter $fmt ]): string'
+    },
+    datefmt_get_pattern: {
+        description: 'Get the pattern used for the IntlDateFormatter',
+        signature: '( IntlDateFormatter $fmt ): string'
+    },
+    datefmt_get_timetype: {
+        description: 'Get the timetype used for the IntlDateFormatter',
+        signature: '( IntlDateFormatter $fmt ): int'
+    },
+    datefmt_get_timezone_id: {
+        description: 'Get the timezone-id used for the IntlDateFormatter',
+        signature: '( IntlDateFormatter $fmt ): string'
+    },
+    datefmt_get_calendar_object: {
+        description: 'Get copy of formatterʼs calendar object',
+        signature: '(void): IntlCalendar'
+    },
+    datefmt_get_timezone: {
+        description: 'Get formatterʼs timezone',
+        signature: '(void): IntlTimeZone'
+    },
+    datefmt_is_lenient: {
+        description: 'Get the lenient used for the IntlDateFormatter',
+        signature: '( IntlDateFormatter $fmt ): bool'
+    },
+    datefmt_localtime: {
+        description: 'Parse string to a field-based time value',
+        signature: '( string $value [, int $position , IntlDateFormatter $fmt ]): array'
+    },
+    datefmt_parse: {
+        description: 'Parse string to a timestamp value',
+        signature: '( string $value [, int $position , IntlDateFormatter $fmt ]): int'
+    },
+    datefmt_set_calendar: {
+        description: 'Sets the calendar type used by the formatter',
+        signature: '( mixed $which , IntlDateFormatter $fmt ): bool'
+    },
+    datefmt_set_lenient: {
+        description: 'Set the leniency of the parser',
+        signature: '( bool $lenient , IntlDateFormatter $fmt ): bool'
+    },
+    datefmt_set_pattern: {
+        description: 'Set the pattern used for the IntlDateFormatter',
+        signature: '( string $pattern , IntlDateFormatter $fmt ): bool'
+    },
+    datefmt_set_timezone_id: {
+        description: 'Sets the time zone to use',
+        signature: '( string $zone , IntlDateFormatter $fmt ): bool'
+    },
+    datefmt_set_timezone: {
+        description: 'Sets formatterʼs timezone',
+        signature: '( mixed $zone , IntlDateFormatter $fmt ): bool'
+    },
+    resourcebundle_count: {
+        description: 'Get number of elements in the bundle',
+        signature: '( ResourceBundle $r ): int'
+    },
+    resourcebundle_create: {
+        description: 'Create a resource bundle',
+        signature: '( string $locale , string $bundlename [, bool $fallback ]): ResourceBundle'
+    },
+    resourcebundle_get_error_code: {
+        description: 'Get bundle\'s last error code',
+        signature: '( ResourceBundle $r ): int'
+    },
+    resourcebundle_get_error_message: {
+        description: 'Get bundle\'s last error message',
+        signature: '( ResourceBundle $r ): string'
+    },
+    resourcebundle_get: {
+        description: 'Get data from the bundle',
+        signature: '( string|int $index [, bool $fallback , ResourceBundle $r ]): mixed'
+    },
+    resourcebundle_locales: {
+        description: 'Get supported locales',
+        signature: '( string $bundlename ): array'
+    },
+    transliterator_create: {
+        description: 'Create a transliterator',
+        signature: '( string $id [, int $direction ]): Transliterator'
+    },
+    transliterator_create_from_rules: {
+        description: 'Create transliterator from rules',
+        signature: '( string $rules [, int $direction , string $id ]): Transliterator'
+    },
+    transliterator_create_inverse: {
+        description: 'Create an inverse transliterator',
+        signature: '(void): Transliterator'
+    },
+    transliterator_get_error_code: {
+        description: 'Get last error code',
+        signature: '(void): int'
+    },
+    transliterator_get_error_message: {
+        description: 'Get last error message',
+        signature: '(void): string'
+    },
+    transliterator_list_ids: {
+        description: 'Get transliterator IDs',
+        signature: '(void): array'
+    },
+    transliterator_transliterate: {
+        description: 'Transliterate a string',
+        signature: '( string $subject [, int $start [, int $end , mixed $transliterator ]]): string'
+    },
+    intl_get_error_code: {
+        description: 'Get the last error code',
+        signature: '(void): int'
+    },
+    intl_get_error_message: {
+        description: 'Get description of the last error',
+        signature: '(void): string'
+    },
+    grapheme_extract: {
+        description: 'Function to extract a sequence of default grapheme clusters from a text buffer, which must be encoded in UTF-8',
+        signature: '( string $haystack , int $size [, int $extract_type [, int $start = 0 [, int $next ]]]): string'
+    },
+    grapheme_stripos: {
+        description: 'Find position (in grapheme units) of first occurrence of a case-insensitive string',
+        signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
+    },
+    grapheme_stristr: {
+        description: 'Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack',
+        signature: '( string $haystack , string $needle [, bool $before_needle ]): string'
+    },
+    grapheme_strlen: {
+        description: 'Get string length in grapheme units',
+        signature: '( string $input ): int'
+    },
+    grapheme_strpos: {
+        description: 'Find position (in grapheme units) of first occurrence of a string',
+        signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
+    },
+    grapheme_strripos: {
+        description: 'Find position (in grapheme units) of last occurrence of a case-insensitive string',
+        signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
+    },
+    grapheme_strrpos: {
+        description: 'Find position (in grapheme units) of last occurrence of a string',
+        signature: '( string $haystack , string $needle [, int $offset = 0 ]): int'
+    },
+    grapheme_strstr: {
+        description: 'Returns part of haystack string from the first occurrence of needle to the end of haystack',
+        signature: '( string $haystack , string $needle [, bool $before_needle ]): string'
+    },
+    grapheme_substr: {
+        description: 'Return part of a string',
+        signature: '( string $string , int $start [, int $length ]): string'
+    },
+    idn_to_ascii: {
+        description: 'Convert domain name to IDNA ASCII form',
+        signature: '( string $domain [, int $options = IDNA_DEFAULT [, int $variant = INTL_IDNA_VARIANT_UTS46 [, array $idna_info ]]]): string'
+    },
+    idn_to_utf8: {
+        description: 'Convert domain name from IDNA ASCII to Unicode',
+        signature: '( string $domain [, int $options = IDNA_DEFAULT [, int $variant = INTL_IDNA_VARIANT_UTS46 [, array $idna_info ]]]): string'
+    },
+    intl_error_name: {
+        description: 'Get symbolic name for a given error code',
+        signature: '( int $error_code ): string'
+    },
+    intl_is_failure: {
+        description: 'Check whether the given error code indicates failure',
+        signature: '( int $error_code ): bool'
+    },
+    mb_check_encoding: {
+        description: 'Check if the string is valid for the specified encoding',
+        signature: '([ string $var [, string $encoding = mb_internal_encoding() ]]): bool'
+    },
+    mb_chr: {
+        description: 'Get a specific character',
+        signature: '( int $cp [, string $encoding ]): string'
+    },
+    mb_convert_case: {
+        description: 'Perform case folding on a string',
+        signature: '( string $str , int $mode [, string $encoding = mb_internal_encoding() ]): string'
+    },
+    mb_convert_encoding: {
+        description: 'Convert character encoding',
+        signature: '( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ]): string'
+    },
+    mb_convert_kana: {
+        description: 'Convert "kana" one from another ("zen-kaku", "han-kaku" and more)',
+        signature: '( string $str [, string $option = "KV" [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_convert_variables: {
+        description: 'Convert character code in variable(s)',
+        signature: '( string $to_encoding , mixed $from_encoding , mixed $vars [, mixed $... ]): string'
+    },
+    mb_decode_mimeheader: {
+        description: 'Decode string in MIME header field',
+        signature: '( string $str ): string'
+    },
+    mb_decode_numericentity: {
+        description: 'Decode HTML numeric string reference to character',
+        signature: '( string $str , array $convmap [, string $encoding = mb_internal_encoding() [, bool $is_hex ]]): string'
+    },
+    mb_detect_encoding: {
+        description: 'Detect character encoding',
+        signature: '( string $str [, mixed $encoding_list = mb_detect_order() [, bool $strict ]]): string'
+    },
+    mb_detect_order: {
+        description: 'Set/Get character encoding detection order',
+        signature: '([ mixed $encoding_list = mb_detect_order() ]): mixed'
+    },
+    mb_encode_mimeheader: {
+        description: 'Encode string for MIME header',
+        signature: '( string $str [, string $charset = determined by mb_language() [, string $transfer_encoding = "B" [, string $linefeed = "\r\n" [, int $indent = 0 ]]]]): string'
+    },
+    mb_encode_numericentity: {
+        description: 'Encode character to HTML numeric string reference',
+        signature: '( string $str , array $convmap [, string $encoding = mb_internal_encoding() [, bool $is_hex ]]): string'
+    },
+    mb_encoding_aliases: {
+        description: 'Get aliases of a known encoding type',
+        signature: '( string $encoding ): array'
+    },
+    mb_ereg_match: {
+        description: 'Regular expression match for multibyte string',
+        signature: '( string $pattern , string $string [, string $option = "msr" ]): bool'
+    },
+    mb_ereg_replace_callback: {
+        description: 'Perform a regular expression search and replace with multibyte support using a callback',
+        signature: '( string $pattern , callable $callback , string $string [, string $option = "msr" ]): string'
+    },
+    mb_ereg_replace: {
+        description: 'Replace regular expression with multibyte support',
+        signature: '( string $pattern , string $replacement , string $string [, string $option = "msr" ]): string'
+    },
+    mb_ereg_search_getpos: {
+        description: 'Returns start point for next regular expression match',
+        signature: '(void): int'
+    },
+    mb_ereg_search_getregs: {
+        description: 'Retrieve the result from the last multibyte regular expression match',
+        signature: '(void): array'
+    },
+    mb_ereg_search_init: {
+        description: 'Setup string and regular expression for a multibyte regular expression match',
+        signature: '( string $string [, string $pattern [, string $option = "msr" ]]): bool'
+    },
+    mb_ereg_search_pos: {
+        description: 'Returns position and length of a matched part of the multibyte regular expression for a predefined multibyte string',
+        signature: '([ string $pattern [, string $option = "ms" ]]): array'
+    },
+    mb_ereg_search_regs: {
+        description: 'Returns the matched part of a multibyte regular expression',
+        signature: '([ string $pattern [, string $option = "ms" ]]): array'
+    },
+    mb_ereg_search_setpos: {
+        description: 'Set start point of next regular expression match',
+        signature: '( int $position ): bool'
+    },
+    mb_ereg_search: {
+        description: 'Multibyte regular expression match for predefined multibyte string',
+        signature: '([ string $pattern [, string $option = "ms" ]]): bool'
+    },
+    mb_ereg: {
+        description: 'Regular expression match with multibyte support',
+        signature: '( string $pattern , string $string [, array $regs ]): int'
+    },
+    mb_eregi_replace: {
+        description: 'Replace regular expression with multibyte support ignoring case',
+        signature: '( string $pattern , string $replace , string $string [, string $option = "msri" ]): string'
+    },
+    mb_eregi: {
+        description: 'Regular expression match ignoring case with multibyte support',
+        signature: '( string $pattern , string $string [, array $regs ]): int'
+    },
+    mb_get_info: {
+        description: 'Get internal settings of mbstring',
+        signature: '([ string $type = "all" ]): mixed'
+    },
+    mb_http_input: {
+        description: 'Detect HTTP input character encoding',
+        signature: '([ string $type = "" ]): mixed'
+    },
+    mb_http_output: {
+        description: 'Set/Get HTTP output character encoding',
+        signature: '([ string $encoding = mb_http_output() ]): mixed'
+    },
+    mb_internal_encoding: {
+        description: 'Set/Get internal character encoding',
+        signature: '([ string $encoding = mb_internal_encoding() ]): mixed'
+    },
+    mb_language: {
+        description: 'Set/Get current language',
+        signature: '([ string $language = mb_language() ]): mixed'
+    },
+    mb_list_encodings: {
+        description: 'Returns an array of all supported encodings',
+        signature: '(void): array'
+    },
+    mb_ord: {
+        description: 'Get code point of character',
+        signature: '( string $str [, string $encoding ]): int'
+    },
+    mb_output_handler: {
+        description: 'Callback function converts character encoding in output buffer',
+        signature: '( string $contents , int $status ): string'
+    },
+    mb_parse_str: {
+        description: 'Parse GET/POST/COOKIE data and set global variable',
+        signature: '( string $encoded_string [, array $result ]): array'
+    },
+    mb_preferred_mime_name: {
+        description: 'Get MIME charset string',
+        signature: '( string $encoding ): string'
+    },
+    mb_regex_encoding: {
+        description: 'Set/Get character encoding for multibyte regex',
+        signature: '([ string $encoding = mb_regex_encoding() ]): mixed'
+    },
+    mb_regex_set_options: {
+        description: 'Set/Get the default options for mbregex functions',
+        signature: '([ string $options = mb_regex_set_options() ]): string'
+    },
+    mb_scrub: {
+        description: 'Description',
+        signature: '( string $str [, string $encoding ]): string'
+    },
+    mb_send_mail: {
+        description: 'Send encoded mail',
+        signature: '( string $to , string $subject , string $message [, mixed $additional_headers [, string $additional_parameter ]]): bool'
+    },
+    mb_split: {
+        description: 'Split multibyte string using regular expression',
+        signature: '( string $pattern , string $string [, int $limit = -1 ]): array'
+    },
+    mb_strcut: {
+        description: 'Get part of string',
+        signature: '( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_strimwidth: {
+        description: 'Get truncated string with specified width',
+        signature: '( string $str , int $start , int $width [, string $trimmarker = "" [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_stripos: {
+        description: 'Finds position of first occurrence of a string within another, case insensitive',
+        signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): int'
+    },
+    mb_stristr: {
+        description: 'Finds first occurrence of a string within another, case insensitive',
+        signature: '( string $haystack , string $needle [, bool $before_needle [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_strlen: {
+        description: 'Get string length',
+        signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
+    },
+    mb_strpos: {
+        description: 'Find position of first occurrence of string in a string',
+        signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_strrchr: {
+        description: 'Finds the last occurrence of a character in a string within another',
+        signature: '( string $haystack , string $needle [, bool $part [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_strrichr: {
+        description: 'Finds the last occurrence of a character in a string within another, case insensitive',
+        signature: '( string $haystack , string $needle [, bool $part [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_strripos: {
+        description: 'Finds position of last occurrence of a string within another, case insensitive',
+        signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): int'
+    },
+    mb_strrpos: {
+        description: 'Find position of last occurrence of a string in a string',
+        signature: '( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]]): int'
+    },
+    mb_strstr: {
+        description: 'Finds first occurrence of a string within another',
+        signature: '( string $haystack , string $needle [, bool $before_needle [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    mb_strtolower: {
+        description: 'Make a string lowercase',
+        signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
+    },
+    mb_strtoupper: {
+        description: 'Make a string uppercase',
+        signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
+    },
+    mb_strwidth: {
+        description: 'Return width of string',
+        signature: '( string $str [, string $encoding = mb_internal_encoding() ]): string'
+    },
+    mb_substitute_character: {
+        description: 'Set/Get substitution character',
+        signature: '([ mixed $substchar = mb_substitute_character() ]): integer'
+    },
+    mb_substr_count: {
+        description: 'Count the number of substring occurrences',
+        signature: '( string $haystack , string $needle [, string $encoding = mb_internal_encoding() ]): string'
+    },
+    mb_substr: {
+        description: 'Get part of string',
+        signature: '( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]]): string'
+    },
+    exif_imagetype: {
+        description: 'Determine the type of an image',
+        signature: '( string $filename ): int'
+    },
+    exif_read_data: {
+        description: 'Reads the EXIF headers from an image file',
+        signature: '( mixed $stream [, string $sections [, bool $arrays [, bool $thumbnail ]]]): array'
+    },
+    exif_tagname: {
+        description: 'Get the header name for an index',
+        signature: '( int $index ): string'
+    },
+    exif_thumbnail: {
+        description: 'Retrieve the embedded thumbnail of an image',
+        signature: '( mixed $stream [, int $width [, int $height [, int $imagetype ]]]): string'
+    },
+    read_exif_data: {
+        description: 'Alias of exif_read_data',
+    },
+    ezmlm_hash: {
+        description: 'Calculate the hash value needed by EZMLM',
+        signature: '( string $addr ): int'
+    },
+    mail: {
+        description: 'Send mail',
+        signature: '( string $to , string $subject , string $message [, mixed $additional_headers [, string $additional_parameters ]]): bool'
+    },
+    bcadd: {
+        description: 'Add two arbitrary precision numbers',
+        signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): string'
+    },
+    bccomp: {
+        description: 'Compare two arbitrary precision numbers',
+        signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): int'
+    },
+    bcdiv: {
+        description: 'Divide two arbitrary precision numbers',
+        signature: '( string $dividend , string $divisor [, int $scale = 0 ]): string'
+    },
+    bcmod: {
+        description: 'Get modulus of an arbitrary precision number',
+        signature: '( string $dividend , string $divisor [, int $scale = 0 ]): string'
+    },
+    bcmul: {
+        description: 'Multiply two arbitrary precision numbers',
+        signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): string'
+    },
+    bcpow: {
+        description: 'Raise an arbitrary precision number to another',
+        signature: '( string $base , string $exponent [, int $scale = 0 ]): string'
+    },
+    bcpowmod: {
+        description: 'Raise an arbitrary precision number to another, reduced by a specified modulus',
+        signature: '( string $base , string $exponent , string $modulus [, int $scale = 0 ]): string'
+    },
+    bcscale: {
+        description: 'Set or get default scale parameter for all bc math functions',
+        signature: '( int $scale ): int'
+    },
+    bcsqrt: {
+        description: 'Get the square root of an arbitrary precision number',
+        signature: '( string $operand [, int $scale = 0 ]): string'
+    },
+    bcsub: {
+        description: 'Subtract one arbitrary precision number from another',
+        signature: '( string $left_operand , string $right_operand [, int $scale = 0 ]): string'
+    },
+    abs: {
+        description: 'Absolute value',
+        signature: '( mixed $number ): number'
+    },
+    acos: {
+        description: 'Arc cosine',
+        signature: '( float $arg ): float'
+    },
+    acosh: {
+        description: 'Inverse hyperbolic cosine',
+        signature: '( float $arg ): float'
+    },
+    asin: {
+        description: 'Arc sine',
+        signature: '( float $arg ): float'
+    },
+    asinh: {
+        description: 'Inverse hyperbolic sine',
+        signature: '( float $arg ): float'
+    },
+    atan2: {
+        description: 'Arc tangent of two variables',
+        signature: '( float $y , float $x ): float'
+    },
+    atan: {
+        description: 'Arc tangent',
+        signature: '( float $arg ): float'
+    },
+    atanh: {
+        description: 'Inverse hyperbolic tangent',
+        signature: '( float $arg ): float'
+    },
+    base_convert: {
+        description: 'Convert a number between arbitrary bases',
+        signature: '( string $number , int $frombase , int $tobase ): string'
+    },
+    bindec: {
+        description: 'Binary to decimal',
+        signature: '( string $binary_string ): float'
+    },
+    ceil: {
+        description: 'Round fractions up',
+        signature: '( float $value ): float'
+    },
+    cos: {
+        description: 'Cosine',
+        signature: '( float $arg ): float'
+    },
+    cosh: {
+        description: 'Hyperbolic cosine',
+        signature: '( float $arg ): float'
+    },
+    decbin: {
+        description: 'Decimal to binary',
+        signature: '( int $number ): string'
+    },
+    dechex: {
+        description: 'Decimal to hexadecimal',
+        signature: '( int $number ): string'
+    },
+    decoct: {
+        description: 'Decimal to octal',
+        signature: '( int $number ): string'
+    },
+    deg2rad: {
+        description: 'Converts the number in degrees to the radian equivalent',
+        signature: '( float $number ): float'
+    },
+    exp: {
+        description: 'Calculates the exponent of e',
+        signature: '( float $arg ): float'
+    },
+    expm1: {
+        description: 'Returns exp(number) - 1, computed in a way that is accurate even   when the value of number is close to zero',
+        signature: '( float $arg ): float'
+    },
+    floor: {
+        description: 'Round fractions down',
+        signature: '( float $value ): float'
+    },
+    fmod: {
+        description: 'Returns the floating point remainder (modulo) of the division  of the arguments',
+        signature: '( float $x , float $y ): float'
+    },
+    getrandmax: {
+        description: 'Show largest possible random value',
+        signature: '(void): int'
+    },
+    hexdec: {
+        description: 'Hexadecimal to decimal',
+        signature: '( string $hex_string ): number'
+    },
+    hypot: {
+        description: 'Calculate the length of the hypotenuse of a right-angle triangle',
+        signature: '( float $x , float $y ): float'
+    },
+    intdiv: {
+        description: 'Integer division',
+        signature: '( int $dividend , int $divisor ): int'
+    },
+    is_finite: {
+        description: 'Finds whether a value is a legal finite number',
+        signature: '( float $val ): bool'
+    },
+    is_infinite: {
+        description: 'Finds whether a value is infinite',
+        signature: '( float $val ): bool'
+    },
+    is_nan: {
+        description: 'Finds whether a value is not a number',
+        signature: '( float $val ): bool'
+    },
+    lcg_value: {
+        description: 'Combined linear congruential generator',
+        signature: '(void): float'
+    },
+    log10: {
+        description: 'Base-10 logarithm',
+        signature: '( float $arg ): float'
+    },
+    log1p: {
+        description: 'Returns log(1 + number), computed in a way that is accurate even when   the value of number is close to zero',
+        signature: '( float $number ): float'
+    },
+    log: {
+        description: 'Natural logarithm',
+        signature: '( float $arg [, float $base = M_E ]): float'
+    },
+    max: {
+        description: 'Find highest value',
+        signature: '( array $values , mixed $value1 [, mixed $... ]): string'
+    },
+    min: {
+        description: 'Find lowest value',
+        signature: '( array $values , mixed $value1 [, mixed $... ]): string'
+    },
+    mt_getrandmax: {
+        description: 'Show largest possible random value',
+        signature: '(void): int'
+    },
+    mt_rand: {
+        description: 'Generate a random value via the Mersenne Twister Random Number Generator',
+        signature: '( int $min , int $max ): int'
+    },
+    mt_srand: {
+        description: 'Seeds the Mersenne Twister Random Number Generator',
+        signature: '([ int $seed [, int $mode = MT_RAND_MT19937 ]]): void'
+    },
+    octdec: {
+        description: 'Octal to decimal',
+        signature: '( string $octal_string ): number'
+    },
+    pi: {
+        description: 'Get value of pi',
+        signature: '(void): float'
+    },
+    pow: {
+        description: 'Exponential expression',
+        signature: '( number $base , number $exp ): number'
+    },
+    rad2deg: {
+        description: 'Converts the radian number to the equivalent number in degrees',
+        signature: '( float $number ): float'
+    },
+    rand: {
+        description: 'Generate a random integer',
+        signature: '( int $min , int $max ): int'
+    },
+    round: {
+        description: 'Rounds a float',
+        signature: '( float $val [, int $precision = 0 [, int $mode = PHP_ROUND_HALF_UP ]]): float'
+    },
+    sin: {
+        description: 'Sine',
+        signature: '( float $arg ): float'
+    },
+    sinh: {
+        description: 'Hyperbolic sine',
+        signature: '( float $arg ): float'
+    },
+    sqrt: {
+        description: 'Square root',
+        signature: '( float $arg ): float'
+    },
+    srand: {
+        description: 'Seed the random number generator',
+        signature: '([ int $seed ]): void'
+    },
+    tan: {
+        description: 'Tangent',
+        signature: '( float $arg ): float'
+    },
+    tanh: {
+        description: 'Hyperbolic tangent',
+        signature: '( float $arg ): float'
+    },
+    pcntl_alarm: {
+        description: 'Set an alarm clock for delivery of a signal',
+        signature: '( int $seconds ): int'
+    },
+    pcntl_async_signals: {
+        description: 'Enable/disable asynchronous signal handling or return the old setting',
+        signature: '([ bool $on ]): bool'
+    },
+    pcntl_errno: {
+        description: 'Alias of pcntl_get_last_error',
+    },
+    pcntl_exec: {
+        description: 'Executes specified program in current process space',
+        signature: '( string $path [, array $args [, array $envs ]]): void'
+    },
+    pcntl_fork: {
+        description: 'Forks the currently running process',
+        signature: '(void): int'
+    },
+    pcntl_get_last_error: {
+        description: 'Retrieve the error number set by the last pcntl function which failed',
+        signature: '(void): int'
+    },
+    pcntl_getpriority: {
+        description: 'Get the priority of any process',
+        signature: '([ int $pid = getmypid() [, int $process_identifier = PRIO_PROCESS ]]): int'
+    },
+    pcntl_setpriority: {
+        description: 'Change the priority of any process',
+        signature: '( int $priority [, int $pid = getmypid() [, int $process_identifier = PRIO_PROCESS ]]): bool'
+    },
+    pcntl_signal_dispatch: {
+        description: 'Calls signal handlers for pending signals',
+        signature: '(void): bool'
+    },
+    pcntl_signal_get_handler: {
+        description: 'Get the current handler for specified signal',
+        signature: '( int $signo ): mixed'
+    },
+    pcntl_signal: {
+        description: 'Installs a signal handler',
+        signature: '( int $signo , callable|int $handler [, bool $restart_syscalls ]): bool'
+    },
+    pcntl_sigprocmask: {
+        description: 'Sets and retrieves blocked signals',
+        signature: '( int $how , array $set [, array $oldset ]): bool'
+    },
+    pcntl_sigtimedwait: {
+        description: 'Waits for signals, with a timeout',
+        signature: '( array $set [, array $siginfo [, int $seconds = 0 [, int $nanoseconds = 0 ]]]): int'
+    },
+    pcntl_sigwaitinfo: {
+        description: 'Waits for signals',
+        signature: '( array $set [, array $siginfo ]): int'
+    },
+    pcntl_strerror: {
+        description: 'Retrieve the system error message associated with the given errno',
+        signature: '( int $errno ): string'
+    },
+    pcntl_wait: {
+        description: 'Waits on or returns the status of a forked child',
+        signature: '( int $status [, int $options = 0 [, array $rusage ]]): int'
+    },
+    pcntl_waitpid: {
+        description: 'Waits on or returns the status of a forked child',
+        signature: '( int $pid , int $status [, int $options = 0 [, array $rusage ]]): int'
+    },
+    pcntl_wexitstatus: {
+        description: 'Returns the return code of a terminated child',
+        signature: '( int $status ): int'
+    },
+    pcntl_wifexited: {
+        description: 'Checks if status code represents a normal exit',
+        signature: '( int $status ): bool'
+    },
+    pcntl_wifsignaled: {
+        description: 'Checks whether the status code represents a termination due to a signal',
+        signature: '( int $status ): bool'
+    },
+    pcntl_wifstopped: {
+        description: 'Checks whether the child process is currently stopped',
+        signature: '( int $status ): bool'
+    },
+    pcntl_wstopsig: {
+        description: 'Returns the signal which caused the child to stop',
+        signature: '( int $status ): int'
+    },
+    pcntl_wtermsig: {
+        description: 'Returns the signal which caused the child to terminate',
+        signature: '( int $status ): int'
+    },
+    posix_access: {
+        description: 'Determine accessibility of a file',
+        signature: '( string $file [, int $mode = POSIX_F_OK ]): bool'
+    },
+    posix_ctermid: {
+        description: 'Get path name of controlling terminal',
+        signature: '(void): string'
+    },
+    posix_errno: {
+        description: 'Alias of posix_get_last_error',
+    },
+    posix_get_last_error: {
+        description: 'Retrieve the error number set by the last posix function that failed',
+        signature: '(void): int'
+    },
+    posix_getcwd: {
+        description: 'Pathname of current directory',
+        signature: '(void): string'
+    },
+    posix_getegid: {
+        description: 'Return the effective group ID of the current process',
+        signature: '(void): int'
+    },
+    posix_geteuid: {
+        description: 'Return the effective user ID of the current process',
+        signature: '(void): int'
+    },
+    posix_getgid: {
+        description: 'Return the real group ID of the current process',
+        signature: '(void): int'
+    },
+    posix_getgrgid: {
+        description: 'Return info about a group by group id',
+        signature: '( int $gid ): array'
+    },
+    posix_getgrnam: {
+        description: 'Return info about a group by name',
+        signature: '( string $name ): array'
+    },
+    posix_getgroups: {
+        description: 'Return the group set of the current process',
+        signature: '(void): array'
+    },
+    posix_getlogin: {
+        description: 'Return login name',
+        signature: '(void): string'
+    },
+    posix_getpgid: {
+        description: 'Get process group id for job control',
+        signature: '( int $pid ): int'
+    },
+    posix_getpgrp: {
+        description: 'Return the current process group identifier',
+        signature: '(void): int'
+    },
+    posix_getpid: {
+        description: 'Return the current process identifier',
+        signature: '(void): int'
+    },
+    posix_getppid: {
+        description: 'Return the parent process identifier',
+        signature: '(void): int'
+    },
+    posix_getpwnam: {
+        description: 'Return info about a user by username',
+        signature: '( string $username ): array'
+    },
+    posix_getpwuid: {
+        description: 'Return info about a user by user id',
+        signature: '( int $uid ): array'
+    },
+    posix_getrlimit: {
+        description: 'Return info about system resource limits',
+        signature: '(void): array'
+    },
+    posix_getsid: {
+        description: 'Get the current sid of the process',
+        signature: '( int $pid ): int'
+    },
+    posix_getuid: {
+        description: 'Return the real user ID of the current process',
+        signature: '(void): int'
+    },
+    posix_initgroups: {
+        description: 'Calculate the group access list',
+        signature: '( string $name , int $base_group_id ): bool'
+    },
+    posix_isatty: {
+        description: 'Determine if a file descriptor is an interactive terminal',
+        signature: '( mixed $fd ): bool'
+    },
+    posix_kill: {
+        description: 'Send a signal to a process',
+        signature: '( int $pid , int $sig ): bool'
+    },
+    posix_mkfifo: {
+        description: 'Create a fifo special file (a named pipe)',
+        signature: '( string $pathname , int $mode ): bool'
+    },
+    posix_mknod: {
+        description: 'Create a special or ordinary file (POSIX.1)',
+        signature: '( string $pathname , int $mode [, int $major = 0 [, int $minor = 0 ]]): bool'
+    },
+    posix_setegid: {
+        description: 'Set the effective GID of the current process',
+        signature: '( int $gid ): bool'
+    },
+    posix_seteuid: {
+        description: 'Set the effective UID of the current process',
+        signature: '( int $uid ): bool'
+    },
+    posix_setgid: {
+        description: 'Set the GID of the current process',
+        signature: '( int $gid ): bool'
+    },
+    posix_setpgid: {
+        description: 'Set process group id for job control',
+        signature: '( int $pid , int $pgid ): bool'
+    },
+    posix_setrlimit: {
+        description: 'Set system resource limits',
+        signature: '( int $resource , int $softlimit , int $hardlimit ): bool'
+    },
+    posix_setsid: {
+        description: 'Make the current process a session leader',
+        signature: '(void): int'
+    },
+    posix_setuid: {
+        description: 'Set the UID of the current process',
+        signature: '( int $uid ): bool'
+    },
+    posix_strerror: {
+        description: 'Retrieve the system error message associated with the given errno',
+        signature: '( int $errno ): string'
+    },
+    posix_times: {
+        description: 'Get process times',
+        signature: '(void): array'
+    },
+    posix_ttyname: {
+        description: 'Determine terminal device name',
+        signature: '( mixed $fd ): string'
+    },
+    posix_uname: {
+        description: 'Get system name',
+        signature: '(void): array'
+    },
+    escapeshellarg: {
+        description: 'Escape a string to be used as a shell argument',
+        signature: '( string $arg ): string'
+    },
+    escapeshellcmd: {
+        description: 'Escape shell metacharacters',
+        signature: '( string $command ): string'
+    },
+    exec: {
+        description: 'Execute an external program',
+        signature: '( string $command [, array $output [, int $return_var ]]): string'
+    },
+    passthru: {
+        description: 'Execute an external program and display raw output',
+        signature: '( string $command [, int $return_var ]): void'
+    },
+    proc_close: {
+        description: 'Close a process opened by proc_open and return the exit code of that process',
+        signature: '( resource $process ): int'
+    },
+    proc_get_status: {
+        description: 'Get information about a process opened by proc_open',
+        signature: '( resource $process ): array'
+    },
+    proc_nice: {
+        description: 'Change the priority of the current process',
+        signature: '( int $increment ): bool'
+    },
+    proc_open: {
+        description: 'Execute a command and open file pointers for input/output',
+        signature: '( string $cmd , array $descriptorspec , array $pipes [, string $cwd [, array $env [, array $other_options ]]]): resource'
+    },
+    proc_terminate: {
+        description: 'Kills a process opened by proc_open',
+        signature: '( resource $process [, int $signal = 15 ]): bool'
+    },
+    shell_exec: {
+        description: 'Execute command via shell and return the complete output as a string',
+        signature: '( string $cmd ): string'
+    },
+    system: {
+        description: 'Execute an external program and display the output',
+        signature: '( string $command [, int $return_var ]): string'
+    },
+    ftok: {
+        description: 'Convert a pathname and a project identifier to a System V IPC key',
+        signature: '( string $pathname , string $proj ): int'
+    },
+    msg_get_queue: {
+        description: 'Create or attach to a message queue',
+        signature: '( int $key [, int $perms = 0666 ]): resource'
+    },
+    msg_queue_exists: {
+        description: 'Check whether a message queue exists',
+        signature: '( int $key ): bool'
+    },
+    msg_receive: {
+        description: 'Receive a message from a message queue',
+        signature: '( resource $queue , int $desiredmsgtype , int $msgtype , int $maxsize , mixed $message [, bool $unserialize [, int $flags = 0 [, int $errorcode ]]]): bool'
+    },
+    msg_remove_queue: {
+        description: 'Destroy a message queue',
+        signature: '( resource $queue ): bool'
+    },
+    msg_send: {
+        description: 'Send a message to a message queue',
+        signature: '( resource $queue , int $msgtype , mixed $message [, bool $serialize [, bool $blocking [, int $errorcode ]]]): bool'
+    },
+    msg_set_queue: {
+        description: 'Set information in the message queue data structure',
+        signature: '( resource $queue , array $data ): bool'
+    },
+    msg_stat_queue: {
+        description: 'Returns information from the message queue data structure',
+        signature: '( resource $queue ): array'
+    },
+    sem_acquire: {
+        description: 'Acquire a semaphore',
+        signature: '( resource $sem_identifier [, bool $nowait ]): bool'
+    },
+    sem_get: {
+        description: 'Get a semaphore id',
+        signature: '( int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]]): resource'
+    },
+    sem_release: {
+        description: 'Release a semaphore',
+        signature: '( resource $sem_identifier ): bool'
+    },
+    sem_remove: {
+        description: 'Remove a semaphore',
+        signature: '( resource $sem_identifier ): bool'
+    },
+    shm_attach: {
+        description: 'Creates or open a shared memory segment',
+        signature: '( int $key [, int $memsize [, int $perm = 0666 ]]): resource'
+    },
+    shm_detach: {
+        description: 'Disconnects from shared memory segment',
+        signature: '( resource $shm_identifier ): bool'
+    },
+    shm_get_var: {
+        description: 'Returns a variable from shared memory',
+        signature: '( resource $shm_identifier , int $variable_key ): mixed'
+    },
+    shm_has_var: {
+        description: 'Check whether a specific entry exists',
+        signature: '( resource $shm_identifier , int $variable_key ): bool'
+    },
+    shm_put_var: {
+        description: 'Inserts or updates a variable in shared memory',
+        signature: '( resource $shm_identifier , int $variable_key , mixed $variable ): bool'
+    },
+    shm_remove_var: {
+        description: 'Removes a variable from shared memory',
+        signature: '( resource $shm_identifier , int $variable_key ): bool'
+    },
+    shm_remove: {
+        description: 'Removes shared memory from Unix systems',
+        signature: '( resource $shm_identifier ): bool'
+    },
+    shmop_close: {
+        description: 'Close shared memory block',
+        signature: '( resource $shmid ): void'
+    },
+    shmop_delete: {
+        description: 'Delete shared memory block',
+        signature: '( resource $shmid ): bool'
+    },
+    shmop_open: {
+        description: 'Create or open shared memory block',
+        signature: '( int $key , string $flags , int $mode , int $size ): resource'
+    },
+    shmop_read: {
+        description: 'Read data from shared memory block',
+        signature: '( resource $shmid , int $start , int $count ): string'
+    },
+    shmop_size: {
+        description: 'Get size of shared memory block',
+        signature: '( resource $shmid ): int'
+    },
+    shmop_write: {
+        description: 'Write data into shared memory block',
+        signature: '( resource $shmid , string $data , int $offset ): int'
+    },
+    json_decode: {
+        description: 'Decodes a JSON string',
+        signature: '( string $json [, bool $assoc [, int $depth = 512 [, int $options = 0 ]]]): mixed'
+    },
+    json_encode: {
+        description: 'Returns the JSON representation of a value',
+        signature: '( mixed $value [, int $options = 0 [, int $depth = 512 ]]): string'
+    },
+    json_last_error_msg: {
+        description: 'Returns the error string of the last json_encode() or json_decode() call',
+        signature: '(void): string'
+    },
+    json_last_error: {
+        description: 'Returns the last error occurred',
+        signature: '(void): int'
+    },
+    connection_aborted: {
+        description: 'Check whether client disconnected',
+        signature: '(void): int'
+    },
+    connection_status: {
+        description: 'Returns connection status bitfield',
+        signature: '(void): int'
+    },
+    constant: {
+        description: 'Returns the value of a constant',
+        signature: '( string $name ): mixed'
+    },
+    define: {
+        description: 'Defines a named constant',
+        signature: '( string $name , mixed $value [, bool $case_insensitive ]): bool'
+    },
+    defined: {
+        description: 'Checks whether a given named constant exists',
+        signature: '( string $name ): bool'
+    },
+    die: {
+        description: 'Equivalent to exit',
+    },
+    eval: {
+        description: 'Evaluate a string as PHP code',
+        signature: '( string $code ): mixed'
+    },
+    exit: {
+        description: 'Output a message and terminate the current script',
+        signature: '( int $status ): void'
+    },
+    get_browser: {
+        description: 'Tells what the user\'s browser is capable of',
+        signature: '([ string $user_agent [, bool $return_array ]]): mixed'
+    },
+    __halt_compiler: {
+        description: 'Halts the compiler execution',
+        signature: '(void): void'
+    },
+    highlight_file: {
+        description: 'Syntax highlighting of a file',
+        signature: '( string $filename [, bool $return ]): mixed'
+    },
+    highlight_string: {
+        description: 'Syntax highlighting of a string',
+        signature: '( string $str [, bool $return ]): mixed'
+    },
+    hrtime: {
+        description: 'Get the system\'s high resolution time',
+        signature: '([ bool $get_as_number ]): mixed'
+    },
+    ignore_user_abort: {
+        description: 'Set whether a client disconnect should abort script execution',
+        signature: '([ bool $value ]): int'
+    },
+    pack: {
+        description: 'Pack data into binary string',
+        signature: '( string $format [, mixed $... ]): string'
+    },
+    php_check_syntax: {
+        description: 'Check the PHP syntax of (and execute) the specified file',
+        signature: '( string $filename [, string $error_message ]): bool'
+    },
+    php_strip_whitespace: {
+        description: 'Return source with stripped comments and whitespace',
+        signature: '( string $filename ): string'
+    },
+    sapi_windows_cp_conv: {
+        description: 'Convert string from one codepage to another',
+        signature: '( int|string $in_codepage , int|string $out_codepage , string $subject ): string'
+    },
+    sapi_windows_cp_get: {
+        description: 'Get process codepage',
+        signature: '( string $kind ): int'
+    },
+    sapi_windows_cp_is_utf8: {
+        description: 'Indicates whether the codepage is UTF-8 compatible',
+        signature: '(void): bool'
+    },
+    sapi_windows_cp_set: {
+        description: 'Set process codepage',
+        signature: '( int $cp ): bool'
+    },
+    sapi_windows_vt100_support: {
+        description: 'Get or set VT100 support for the specified stream associated to an output buffer of a Windows console.',
+        signature: '( resource $stream [, bool $enable ]): bool'
+    },
+    show_source: {
+        description: 'Alias of highlight_file',
+    },
+    sleep: {
+        description: 'Delay execution',
+        signature: '( int $seconds ): int'
+    },
+    sys_getloadavg: {
+        description: 'Gets system load average',
+        signature: '(void): array'
+    },
+    time_nanosleep: {
+        description: 'Delay for a number of seconds and nanoseconds',
+        signature: '( int $seconds , int $nanoseconds ): mixed'
+    },
+    time_sleep_until: {
+        description: 'Make the script sleep until the specified time',
+        signature: '( float $timestamp ): bool'
+    },
+    uniqid: {
+        description: 'Generate a unique ID',
+        signature: '([ string $prefix = "" [, bool $more_entropy ]]): string'
+    },
+    unpack: {
+        description: 'Unpack data from binary string',
+        signature: '( string $format , string $data [, int $offset = 0 ]): array'
+    },
+    usleep: {
+        description: 'Delay execution in microseconds',
+        signature: '( int $micro_seconds ): void'
+    },
+    class_implements: {
+        description: 'Return the interfaces which are implemented by the given class or interface',
+        signature: '( mixed $class [, bool $autoload ]): array'
+    },
+    class_parents: {
+        description: 'Return the parent classes of the given class',
+        signature: '( mixed $class [, bool $autoload ]): array'
+    },
+    class_uses: {
+        description: 'Return the traits used by the given class',
+        signature: '( mixed $class [, bool $autoload ]): array'
+    },
+    iterator_apply: {
+        description: 'Call a function for every element in an iterator',
+        signature: '( Traversable $iterator , callable $function [, array $args ]): int'
+    },
+    iterator_count: {
+        description: 'Count the elements in an iterator',
+        signature: '( Traversable $iterator ): int'
+    },
+    iterator_to_array: {
+        description: 'Copy the iterator into an array',
+        signature: '( Traversable $iterator [, bool $use_keys ]): array'
+    },
+    spl_autoload_call: {
+        description: 'Try all registered __autoload() functions to load the requested class',
+        signature: '( string $class_name ): void'
+    },
+    spl_autoload_extensions: {
+        description: 'Register and return default file extensions for spl_autoload',
+        signature: '([ string $file_extensions ]): string'
+    },
+    spl_autoload_functions: {
+        description: 'Return all registered __autoload() functions',
+        signature: '(void): array'
+    },
+    spl_autoload_register: {
+        description: 'Register given function as __autoload() implementation',
+        signature: '([ callable $autoload_function [, bool $throw [, bool $prepend ]]]): bool'
+    },
+    spl_autoload_unregister: {
+        description: 'Unregister given function as __autoload() implementation',
+        signature: '( mixed $autoload_function ): bool'
+    },
+    spl_autoload: {
+        description: 'Default implementation for __autoload()',
+        signature: '( string $class_name [, string $file_extensions = spl_autoload_extensions() ]): void'
+    },
+    spl_classes: {
+        description: 'Return available SPL classes',
+        signature: '(void): array'
+    },
+    spl_object_hash: {
+        description: 'Return hash id for given object',
+        signature: '( object $obj ): string'
+    },
+    spl_object_id: {
+        description: 'Return the integer object handle for given object',
+        signature: '( object $obj ): int'
+    },
+    set_socket_blocking: {
+        description: 'Alias of stream_set_blocking',
+    },
+    stream_bucket_append: {
+        description: 'Append bucket to brigade',
+        signature: '( resource $brigade , object $bucket ): void'
+    },
+    stream_bucket_make_writeable: {
+        description: 'Return a bucket object from the brigade for operating on',
+        signature: '( resource $brigade ): object'
+    },
+    stream_bucket_new: {
+        description: 'Create a new bucket for use on the current stream',
+        signature: '( resource $stream , string $buffer ): object'
+    },
+    stream_bucket_prepend: {
+        description: 'Prepend bucket to brigade',
+        signature: '( resource $brigade , object $bucket ): void'
+    },
+    stream_context_create: {
+        description: 'Creates a stream context',
+        signature: '([ array $options [, array $params ]]): resource'
+    },
+    stream_context_get_default: {
+        description: 'Retrieve the default stream context',
+        signature: '([ array $options ]): resource'
+    },
+    stream_context_get_options: {
+        description: 'Retrieve options for a stream/wrapper/context',
+        signature: '( resource $stream_or_context ): array'
+    },
+    stream_context_get_params: {
+        description: 'Retrieves parameters from a context',
+        signature: '( resource $stream_or_context ): array'
+    },
+    stream_context_set_default: {
+        description: 'Set the default stream context',
+        signature: '( array $options ): resource'
+    },
+    stream_context_set_option: {
+        description: 'Sets an option for a stream/wrapper/context',
+        signature: '( resource $stream_or_context , string $wrapper , string $option , mixed $value , array $options ): bool'
+    },
+    stream_context_set_params: {
+        description: 'Set parameters for a stream/wrapper/context',
+        signature: '( resource $stream_or_context , array $params ): bool'
+    },
+    stream_copy_to_stream: {
+        description: 'Copies data from one stream to another',
+        signature: '( resource $source , resource $dest [, int $maxlength = -1 [, int $offset = 0 ]]): int'
+    },
+    stream_filter_append: {
+        description: 'Attach a filter to a stream',
+        signature: '( resource $stream , string $filtername [, int $read_write [, mixed $params ]]): resource'
+    },
+    stream_filter_prepend: {
+        description: 'Attach a filter to a stream',
+        signature: '( resource $stream , string $filtername [, int $read_write [, mixed $params ]]): resource'
+    },
+    stream_filter_register: {
+        description: 'Register a user defined stream filter',
+        signature: '( string $filtername , string $classname ): bool'
+    },
+    stream_filter_remove: {
+        description: 'Remove a filter from a stream',
+        signature: '( resource $stream_filter ): bool'
+    },
+    stream_get_contents: {
+        description: 'Reads remainder of a stream into a string',
+        signature: '( resource $handle [, int $maxlength = -1 [, int $offset = -1 ]]): string'
+    },
+    stream_get_filters: {
+        description: 'Retrieve list of registered filters',
+        signature: '(void): array'
+    },
+    stream_get_line: {
+        description: 'Gets line from stream resource up to a given delimiter',
+        signature: '( resource $handle , int $length [, string $ending ]): string'
+    },
+    stream_get_meta_data: {
+        description: 'Retrieves header/meta data from streams/file pointers',
+        signature: '( resource $stream ): array'
+    },
+    stream_get_transports: {
+        description: 'Retrieve list of registered socket transports',
+        signature: '(void): array'
+    },
+    stream_get_wrappers: {
+        description: 'Retrieve list of registered streams',
+        signature: '(void): array'
+    },
+    stream_is_local: {
+        description: 'Checks if a stream is a local stream',
+        signature: '( mixed $stream_or_url ): bool'
+    },
+    stream_isatty: {
+        description: 'Check if a stream is a TTY',
+        signature: '( resource $stream ): bool'
+    },
+    stream_notification_callback: {
+        description: 'A callback function for the notification context parameter',
+        signature: '( int $notification_code , int $severity , string $message , int $message_code , int $bytes_transferred , int $bytes_max ): callable'
+    },
+    stream_register_wrapper: {
+        description: 'Alias of stream_wrapper_register',
+    },
+    stream_resolve_include_path: {
+        description: 'Resolve filename against the include path',
+        signature: '( string $filename ): string'
+    },
+    stream_select: {
+        description: 'Runs the equivalent of the select() system call on the given   arrays of streams with a timeout specified by tv_sec and tv_usec',
+        signature: '( array $read , array $write , array $except , int $tv_sec [, int $tv_usec = 0 ]): int'
+    },
+    stream_set_blocking: {
+        description: 'Set blocking/non-blocking mode on a stream',
+        signature: '( resource $stream , bool $mode ): bool'
+    },
+    stream_set_chunk_size: {
+        description: 'Set the stream chunk size',
+        signature: '( resource $fp , int $chunk_size ): int'
+    },
+    stream_set_read_buffer: {
+        description: 'Set read file buffering on the given stream',
+        signature: '( resource $stream , int $buffer ): int'
+    },
+    stream_set_timeout: {
+        description: 'Set timeout period on a stream',
+        signature: '( resource $stream , int $seconds [, int $microseconds = 0 ]): bool'
+    },
+    stream_set_write_buffer: {
+        description: 'Sets write file buffering on the given stream',
+        signature: '( resource $stream , int $buffer ): int'
+    },
+    stream_socket_accept: {
+        description: 'Accept a connection on a socket created by stream_socket_server',
+        signature: '( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string $peername ]]): resource'
+    },
+    stream_socket_client: {
+        description: 'Open Internet or Unix domain socket connection',
+        signature: '( string $remote_socket [, int $errno [, string $errstr [, float $timeout = ini_get("default_socket_timeout") [, int $flags = STREAM_CLIENT_CONNECT [, resource $context ]]]]]): resource'
+    },
+    stream_socket_enable_crypto: {
+        description: 'Turns encryption on/off on an already connected socket',
+        signature: '( resource $stream , bool $enable [, int $crypto_type [, resource $session_stream ]]): mixed'
+    },
+    stream_socket_get_name: {
+        description: 'Retrieve the name of the local or remote sockets',
+        signature: '( resource $handle , bool $want_peer ): string'
+    },
+    stream_socket_pair: {
+        description: 'Creates a pair of connected, indistinguishable socket streams',
+        signature: '( int $domain , int $type , int $protocol ): array'
+    },
+    stream_socket_recvfrom: {
+        description: 'Receives data from a socket, connected or not',
+        signature: '( resource $socket , int $length [, int $flags = 0 [, string $address ]]): string'
+    },
+    stream_socket_sendto: {
+        description: 'Sends a message to a socket, whether it is connected or not',
+        signature: '( resource $socket , string $data [, int $flags = 0 [, string $address ]]): int'
+    },
+    stream_socket_server: {
+        description: 'Create an Internet or Unix domain server socket',
+        signature: '( string $local_socket [, int $errno [, string $errstr [, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN [, resource $context ]]]]): resource'
+    },
+    stream_socket_shutdown: {
+        description: 'Shutdown a full-duplex connection',
+        signature: '( resource $stream , int $how ): bool'
+    },
+    stream_supports_lock: {
+        description: 'Tells whether the stream supports locking',
+        signature: '( resource $stream ): bool'
+    },
+    stream_wrapper_register: {
+        description: 'Register a URL wrapper implemented as a PHP class',
+        signature: '( string $protocol , string $classname [, int $flags = 0 ]): bool'
+    },
+    stream_wrapper_restore: {
+        description: 'Restores a previously unregistered built-in wrapper',
+        signature: '( string $protocol ): bool'
+    },
+    stream_wrapper_unregister: {
+        description: 'Unregister a URL wrapper',
+        signature: '( string $protocol ): bool'
+    },
+    token_get_all: {
+        description: 'Split given source into PHP tokens',
+        signature: '( string $source [, int $flags = 0 ]): array'
+    },
+    token_name: {
+        description: 'Get the symbolic name of a given PHP token',
+        signature: '( int $token ): string'
+    },
+    base64_decode: {
+        description: 'Decodes data encoded with MIME base64',
+        signature: '( string $data [, bool $strict ]): string'
+    },
+    base64_encode: {
+        description: 'Encodes data with MIME base64',
+        signature: '( string $data ): string'
+    },
+    get_headers: {
+        description: 'Fetches all the headers sent by the server in response to an HTTP request',
+        signature: '( string $url [, int $format = 0 [, resource $context ]]): array'
+    },
+    get_meta_tags: {
+        description: 'Extracts all meta tag content attributes from a file and returns an array',
+        signature: '( string $filename [, bool $use_include_path ]): array'
+    },
+    http_build_query: {
+        description: 'Generate URL-encoded query string',
+        signature: '( mixed $query_data [, string $numeric_prefix [, string $arg_separator [, int $enc_type ]]]): string'
+    },
+    parse_url: {
+        description: 'Parse a URL and return its components',
+        signature: '( string $url [, int $component = -1 ]): mixed'
+    },
+    rawurldecode: {
+        description: 'Decode URL-encoded strings',
+        signature: '( string $str ): string'
+    },
+    rawurlencode: {
+        description: 'URL-encode according to RFC 3986',
+        signature: '( string $str ): string'
+    },
+    urldecode: {
+        description: 'Decodes URL-encoded string',
+        signature: '( string $str ): string'
+    },
+    urlencode: {
+        description: 'URL-encodes string',
+        signature: '( string $str ): string'
+    },
+    curl_close: {
+        description: 'Close a cURL session',
+        signature: '( resource $ch ): void'
+    },
+    curl_copy_handle: {
+        description: 'Copy a cURL handle along with all of its preferences',
+        signature: '( resource $ch ): resource'
+    },
+    curl_errno: {
+        description: 'Return the last error number',
+        signature: '( resource $ch ): int'
+    },
+    curl_error: {
+        description: 'Return a string containing the last error for the current session',
+        signature: '( resource $ch ): string'
+    },
+    curl_escape: {
+        description: 'URL encodes the given string',
+        signature: '( resource $ch , string $str ): string'
+    },
+    curl_exec: {
+        description: 'Perform a cURL session',
+        signature: '( resource $ch ): mixed'
+    },
+    curl_file_create: {
+        description: 'Create a CURLFile object',
+        signature: '( string $filename [, string $mimetype [, string $postname ]]): CURLFile'
+    },
+    curl_getinfo: {
+        description: 'Get information regarding a specific transfer',
+        signature: '( resource $ch [, int $opt ]): mixed'
+    },
+    curl_init: {
+        description: 'Initialize a cURL session',
+        signature: '([ string $url ]): resource'
+    },
+    curl_multi_add_handle: {
+        description: 'Add a normal cURL handle to a cURL multi handle',
+        signature: '( resource $mh , resource $ch ): int'
+    },
+    curl_multi_close: {
+        description: 'Close a set of cURL handles',
+        signature: '( resource $mh ): void'
+    },
+    curl_multi_errno: {
+        description: 'Return the last multi curl error number',
+        signature: '( resource $mh ): int'
+    },
+    curl_multi_exec: {
+        description: 'Run the sub-connections of the current cURL handle',
+        signature: '( resource $mh , int $still_running ): int'
+    },
+    curl_multi_getcontent: {
+        description: 'Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set',
+        signature: '( resource $ch ): string'
+    },
+    curl_multi_info_read: {
+        description: 'Get information about the current transfers',
+        signature: '( resource $mh [, int $msgs_in_queue ]): array'
+    },
+    curl_multi_init: {
+        description: 'Returns a new cURL multi handle',
+        signature: '(void): resource'
+    },
+    curl_multi_remove_handle: {
+        description: 'Remove a multi handle from a set of cURL handles',
+        signature: '( resource $mh , resource $ch ): int'
+    },
+    curl_multi_select: {
+        description: 'Wait for activity on any curl_multi connection',
+        signature: '( resource $mh [, float $timeout = 1.0 ]): int'
+    },
+    curl_multi_setopt: {
+        description: 'Set an option for the cURL multi handle',
+        signature: '( resource $mh , int $option , mixed $value ): bool'
+    },
+    curl_multi_strerror: {
+        description: 'Return string describing error code',
+        signature: '( int $errornum ): string'
+    },
+    curl_pause: {
+        description: 'Pause and unpause a connection',
+        signature: '( resource $ch , int $bitmask ): int'
+    },
+    curl_reset: {
+        description: 'Reset all options of a libcurl session handle',
+        signature: '( resource $ch ): void'
+    },
+    curl_setopt_array: {
+        description: 'Set multiple options for a cURL transfer',
+        signature: '( resource $ch , array $options ): bool'
+    },
+    curl_setopt: {
+        description: 'Set an option for a cURL transfer',
+        signature: '( resource $ch , int $option , mixed $value ): bool'
+    },
+    curl_share_close: {
+        description: 'Close a cURL share handle',
+        signature: '( resource $sh ): void'
+    },
+    curl_share_errno: {
+        description: 'Return the last share curl error number',
+        signature: '( resource $sh ): int'
+    },
+    curl_share_init: {
+        description: 'Initialize a cURL share handle',
+        signature: '(void): resource'
+    },
+    curl_share_setopt: {
+        description: 'Set an option for a cURL share handle',
+        signature: '( resource $sh , int $option , string $value ): bool'
+    },
+    curl_share_strerror: {
+        description: 'Return string describing the given error code',
+        signature: '( int $errornum ): string'
+    },
+    curl_strerror: {
+        description: 'Return string describing the given error code',
+        signature: '( int $errornum ): string'
+    },
+    curl_unescape: {
+        description: 'Decodes the given URL encoded string',
+        signature: '( resource $ch , string $str ): string'
+    },
+    curl_version: {
+        description: 'Gets cURL version information',
+        signature: '([ int $age = CURLVERSION_NOW ]): array'
+    },
+    ftp_alloc: {
+        description: 'Allocates space for a file to be uploaded',
+        signature: '( resource $ftp_stream , int $filesize [, string $result ]): bool'
+    },
+    ftp_append: {
+        description: 'Append the contents of a file to another file on the FTP server',
+        signature: '( resource $ftp , string $remote_file , string $local_file [, int $mode ]): bool'
+    },
+    ftp_cdup: {
+        description: 'Changes to the parent directory',
+        signature: '( resource $ftp_stream ): bool'
+    },
+    ftp_chdir: {
+        description: 'Changes the current directory on a FTP server',
+        signature: '( resource $ftp_stream , string $directory ): bool'
+    },
+    ftp_chmod: {
+        description: 'Set permissions on a file via FTP',
+        signature: '( resource $ftp_stream , int $mode , string $filename ): int'
+    },
+    ftp_close: {
+        description: 'Closes an FTP connection',
+        signature: '( resource $ftp_stream ): resource'
+    },
+    ftp_connect: {
+        description: 'Opens an FTP connection',
+        signature: '( string $host [, int $port = 21 [, int $timeout = 90 ]]): resource'
+    },
+    ftp_delete: {
+        description: 'Deletes a file on the FTP server',
+        signature: '( resource $ftp_stream , string $path ): bool'
+    },
+    ftp_exec: {
+        description: 'Requests execution of a command on the FTP server',
+        signature: '( resource $ftp_stream , string $command ): bool'
+    },
+    ftp_fget: {
+        description: 'Downloads a file from the FTP server and saves to an open file',
+        signature: '( resource $ftp_stream , resource $handle , string $remote_file [, int $mode [, int $resumepos = 0 ]]): bool'
+    },
+    ftp_fput: {
+        description: 'Uploads from an open file to the FTP server',
+        signature: '( resource $ftp_stream , string $remote_file , resource $handle [, int $mode [, int $startpos = 0 ]]): bool'
+    },
+    ftp_get_option: {
+        description: 'Retrieves various runtime behaviours of the current FTP stream',
+        signature: '( resource $ftp_stream , int $option ): mixed'
+    },
+    ftp_get: {
+        description: 'Downloads a file from the FTP server',
+        signature: '( resource $ftp_stream , string $local_file , string $remote_file [, int $mode [, int $resumepos = 0 ]]): bool'
+    },
+    ftp_login: {
+        description: 'Logs in to an FTP connection',
+        signature: '( resource $ftp_stream , string $username , string $password ): bool'
+    },
+    ftp_mdtm: {
+        description: 'Returns the last modified time of the given file',
+        signature: '( resource $ftp_stream , string $remote_file ): int'
+    },
+    ftp_mkdir: {
+        description: 'Creates a directory',
+        signature: '( resource $ftp_stream , string $directory ): string'
+    },
+    ftp_mlsd: {
+        description: 'Returns a list of files in the given directory',
+        signature: '( resource $ftp_stream , string $directory ): array'
+    },
+    ftp_nb_continue: {
+        description: 'Continues retrieving/sending a file (non-blocking)',
+        signature: '( resource $ftp_stream ): int'
+    },
+    ftp_nb_fget: {
+        description: 'Retrieves a file from the FTP server and writes it to an open file (non-blocking)',
+        signature: '( resource $ftp_stream , resource $handle , string $remote_file [, int $mode [, int $resumepos = 0 ]]): int'
+    },
+    ftp_nb_fput: {
+        description: 'Stores a file from an open file to the FTP server (non-blocking)',
+        signature: '( resource $ftp_stream , string $remote_file , resource $handle [, int $mode [, int $startpos = 0 ]]): int'
+    },
+    ftp_nb_get: {
+        description: 'Retrieves a file from the FTP server and writes it to a local file (non-blocking)',
+        signature: '( resource $ftp_stream , string $local_file , string $remote_file [, int $mode [, int $resumepos = 0 ]]): int'
+    },
+    ftp_nb_put: {
+        description: 'Stores a file on the FTP server (non-blocking)',
+        signature: '( resource $ftp_stream , string $remote_file , string $local_file [, int $mode [, int $startpos = 0 ]]): int'
+    },
+    ftp_nlist: {
+        description: 'Returns a list of files in the given directory',
+        signature: '( resource $ftp_stream , string $directory ): array'
+    },
+    ftp_pasv: {
+        description: 'Turns passive mode on or off',
+        signature: '( resource $ftp_stream , bool $pasv ): bool'
+    },
+    ftp_put: {
+        description: 'Uploads a file to the FTP server',
+        signature: '( resource $ftp_stream , string $remote_file , string $local_file [, int $mode [, int $startpos = 0 ]]): bool'
+    },
+    ftp_pwd: {
+        description: 'Returns the current directory name',
+        signature: '( resource $ftp_stream ): string'
+    },
+    ftp_quit: {
+        description: 'Alias of ftp_close',
+    },
+    ftp_raw: {
+        description: 'Sends an arbitrary command to an FTP server',
+        signature: '( resource $ftp_stream , string $command ): array'
+    },
+    ftp_rawlist: {
+        description: 'Returns a detailed list of files in the given directory',
+        signature: '( resource $ftp_stream , string $directory [, bool $recursive ]): array'
+    },
+    ftp_rename: {
+        description: 'Renames a file or a directory on the FTP server',
+        signature: '( resource $ftp_stream , string $oldname , string $newname ): bool'
+    },
+    ftp_rmdir: {
+        description: 'Removes a directory',
+        signature: '( resource $ftp_stream , string $directory ): bool'
+    },
+    ftp_set_option: {
+        description: 'Set miscellaneous runtime FTP options',
+        signature: '( resource $ftp_stream , int $option , mixed $value ): bool'
+    },
+    ftp_site: {
+        description: 'Sends a SITE command to the server',
+        signature: '( resource $ftp_stream , string $command ): bool'
+    },
+    ftp_size: {
+        description: 'Returns the size of the given file',
+        signature: '( resource $ftp_stream , string $remote_file ): int'
+    },
+    ftp_ssl_connect: {
+        description: 'Opens a Secure SSL-FTP connection',
+        signature: '( string $host [, int $port = 21 [, int $timeout = 90 ]]): resource'
+    },
+    ftp_systype: {
+        description: 'Returns the system type identifier of the remote FTP server',
+        signature: '( resource $ftp_stream ): string'
+    },
+    checkdnsrr: {
+        description: 'Check DNS records corresponding to a given Internet host name or IP address',
+        signature: '( string $host [, string $type = "MX" ]): bool'
+    },
+    closelog: {
+        description: 'Close connection to system logger',
+        signature: '(void): bool'
+    },
+    define_syslog_variables: {
+        description: 'Initializes all syslog related variables',
+        signature: '(void): void'
+    },
+    dns_check_record: {
+        description: 'Alias of checkdnsrr',
+    },
+    dns_get_mx: {
+        description: 'Alias of getmxrr',
+    },
+    dns_get_record: {
+        description: 'Fetch DNS Resource Records associated with a hostname',
+        signature: '( string $hostname [, int $type = DNS_ANY [, array $authns [, array $addtl [, bool $raw ]]]]): array'
+    },
+    fsockopen: {
+        description: 'Open Internet or Unix domain socket connection',
+        signature: '( string $hostname [, int $port = -1 [, int $errno [, string $errstr [, float $timeout = ini_get("default_socket_timeout") ]]]]): resource'
+    },
+    gethostbyaddr: {
+        description: 'Get the Internet host name corresponding to a given IP address',
+        signature: '( string $ip_address ): string'
+    },
+    gethostbyname: {
+        description: 'Get the IPv4 address corresponding to a given Internet host name',
+        signature: '( string $hostname ): string'
+    },
+    gethostbynamel: {
+        description: 'Get a list of IPv4 addresses corresponding to a given Internet host   name',
+        signature: '( string $hostname ): array'
+    },
+    gethostname: {
+        description: 'Gets the host name',
+        signature: '(void): string'
+    },
+    getmxrr: {
+        description: 'Get MX records corresponding to a given Internet host name',
+        signature: '( string $hostname , array $mxhosts [, array $weight ]): bool'
+    },
+    getprotobyname: {
+        description: 'Get protocol number associated with protocol name',
+        signature: '( string $name ): int'
+    },
+    getprotobynumber: {
+        description: 'Get protocol name associated with protocol number',
+        signature: '( int $number ): string'
+    },
+    getservbyname: {
+        description: 'Get port number associated with an Internet service and protocol',
+        signature: '( string $service , string $protocol ): int'
+    },
+    getservbyport: {
+        description: 'Get Internet service which corresponds to port and protocol',
+        signature: '( int $port , string $protocol ): string'
+    },
+    header_register_callback: {
+        description: 'Call a header function',
+        signature: '( callable $callback ): bool'
+    },
+    header_remove: {
+        description: 'Remove previously set headers',
+        signature: '([ string $name ]): void'
+    },
+    header: {
+        description: 'Send a raw HTTP header',
+        signature: '( string $header [, bool $replace [, int $http_response_code ]]): void'
+    },
+    headers_list: {
+        description: 'Returns a list of response headers sent (or ready to send)',
+        signature: '(void): array'
+    },
+    headers_sent: {
+        description: 'Checks if or where headers have been sent',
+        signature: '([ string $file [, int $line ]]): bool'
+    },
+    http_response_code: {
+        description: 'Get or Set the HTTP response code',
+        signature: '([ int $response_code ]): mixed'
+    },
+    inet_ntop: {
+        description: 'Converts a packed internet address to a human readable representation',
+        signature: '( string $in_addr ): string'
+    },
+    inet_pton: {
+        description: 'Converts a human readable IP address to its packed in_addr representation',
+        signature: '( string $address ): string'
+    },
+    ip2long: {
+        description: 'Converts a string containing an (IPv4) Internet Protocol dotted address into a long integer',
+        signature: '( string $ip_address ): int'
+    },
+    long2ip: {
+        description: 'Converts an long integer address into a string in (IPv4) Internet standard dotted format',
+        signature: '( int $proper_address ): string'
+    },
+    openlog: {
+        description: 'Open connection to system logger',
+        signature: '( string $ident , int $option , int $facility ): bool'
+    },
+    pfsockopen: {
+        description: 'Open persistent Internet or Unix domain socket connection',
+        signature: '( string $hostname [, int $port = -1 [, int $errno [, string $errstr [, float $timeout = ini_get("default_socket_timeout") ]]]]): resource'
+    },
+    setcookie: {
+        description: 'Send a cookie',
+        signature: '( string $name [, string $value = "" [, int $expires = 0 [, string $path = "" [, string $domain = "" [, bool $secure [, bool $httponly [, array $options = [] ]]]]]]]): bool'
+    },
+    setrawcookie: {
+        description: 'Send a cookie without urlencoding the cookie value',
+        signature: '( string $name [, string $value [, int $expires = 0 [, string $path [, string $domain [, bool $secure [, bool $httponly [, array $options = [] ]]]]]]]): bool'
+    },
+    socket_get_status: {
+        description: 'Alias of stream_get_meta_data',
+    },
+    socket_set_blocking: {
+        description: 'Alias of stream_set_blocking',
+    },
+    socket_set_timeout: {
+        description: 'Alias of stream_set_timeout',
+    },
+    syslog: {
+        description: 'Generate a system log message',
+        signature: '( int $priority , string $message ): bool'
+    },
+    socket_accept: {
+        description: 'Accepts a connection on a socket',
+        signature: '( resource $socket ): resource'
+    },
+    socket_addrinfo_bind: {
+        description: 'Create and bind to a socket from a given addrinfo',
+        signature: '( resource $addr ): resource'
+    },
+    socket_addrinfo_connect: {
+        description: 'Create and connect to a socket from a given addrinfo',
+        signature: '( resource $addr ): resource'
+    },
+    socket_addrinfo_explain: {
+        description: 'Get information about addrinfo',
+        signature: '( resource $addr ): array'
+    },
+    socket_addrinfo_lookup: {
+        description: 'Get array with contents of getaddrinfo about the given hostname',
+        signature: '( string $host [, string $service [, array $hints ]]): array'
+    },
+    socket_bind: {
+        description: 'Binds a name to a socket',
+        signature: '( resource $socket , string $address [, int $port = 0 ]): bool'
+    },
+    socket_clear_error: {
+        description: 'Clears the error on the socket or the last error code',
+        signature: '([ resource $socket ]): void'
+    },
+    socket_close: {
+        description: 'Closes a socket resource',
+        signature: '( resource $socket ): void'
+    },
+    socket_cmsg_space: {
+        description: 'Calculate message buffer size',
+        signature: '( int $level , int $type [, int $n = 0 ]): int'
+    },
+    socket_connect: {
+        description: 'Initiates a connection on a socket',
+        signature: '( resource $socket , string $address [, int $port = 0 ]): bool'
+    },
+    socket_create_listen: {
+        description: 'Opens a socket on port to accept connections',
+        signature: '( int $port [, int $backlog = 128 ]): resource'
+    },
+    socket_create_pair: {
+        description: 'Creates a pair of indistinguishable sockets and stores them in an array',
+        signature: '( int $domain , int $type , int $protocol , array $fd ): bool'
+    },
+    socket_create: {
+        description: 'Create a socket (endpoint for communication)',
+        signature: '( int $domain , int $type , int $protocol ): resource'
+    },
+    socket_export_stream: {
+        description: 'Export a socket extension resource into a stream that encapsulates a socket',
+        signature: '( resource $socket ): resource'
+    },
+    socket_get_option: {
+        description: 'Gets socket options for the socket',
+        signature: '( resource $socket , int $level , int $optname ): mixed'
+    },
+    socket_getopt: {
+        description: 'Alias of socket_get_option',
+    },
+    socket_getpeername: {
+        description: 'Queries the remote side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type',
+        signature: '( resource $socket , string $address [, int $port ]): bool'
+    },
+    socket_getsockname: {
+        description: 'Queries the local side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type',
+        signature: '( resource $socket , string $addr [, int $port ]): bool'
+    },
+    socket_import_stream: {
+        description: 'Import a stream',
+        signature: '( resource $stream ): resource'
+    },
+    socket_last_error: {
+        description: 'Returns the last error on the socket',
+        signature: '([ resource $socket ]): int'
+    },
+    socket_listen: {
+        description: 'Listens for a connection on a socket',
+        signature: '( resource $socket [, int $backlog = 0 ]): bool'
+    },
+    socket_read: {
+        description: 'Reads a maximum of length bytes from a socket',
+        signature: '( resource $socket , int $length [, int $type = PHP_BINARY_READ ]): string'
+    },
+    socket_recv: {
+        description: 'Receives data from a connected socket',
+        signature: '( resource $socket , string $buf , int $len , int $flags ): int'
+    },
+    socket_recvfrom: {
+        description: 'Receives data from a socket whether or not it is connection-oriented',
+        signature: '( resource $socket , string $buf , int $len , int $flags , string $name [, int $port ]): int'
+    },
+    socket_recvmsg: {
+        description: 'Read a message',
+        signature: '( resource $socket , array $message [, int $flags = 0 ]): int'
+    },
+    socket_select: {
+        description: 'Runs the select() system call on the given arrays of sockets with a specified timeout',
+        signature: '( array $read , array $write , array $except , int $tv_sec [, int $tv_usec = 0 ]): int'
+    },
+    socket_send: {
+        description: 'Sends data to a connected socket',
+        signature: '( resource $socket , string $buf , int $len , int $flags ): int'
+    },
+    socket_sendmsg: {
+        description: 'Send a message',
+        signature: '( resource $socket , array $message [, int $flags = 0 ]): int'
+    },
+    socket_sendto: {
+        description: 'Sends a message to a socket, whether it is connected or not',
+        signature: '( resource $socket , string $buf , int $len , int $flags , string $addr [, int $port = 0 ]): int'
+    },
+    socket_set_block: {
+        description: 'Sets blocking mode on a socket resource',
+        signature: '( resource $socket ): bool'
+    },
+    socket_set_nonblock: {
+        description: 'Sets nonblocking mode for file descriptor fd',
+        signature: '( resource $socket ): bool'
+    },
+    socket_set_option: {
+        description: 'Sets socket options for the socket',
+        signature: '( resource $socket , int $level , int $optname , mixed $optval ): bool'
+    },
+    socket_setopt: {
+        description: 'Alias of socket_set_option',
+    },
+    socket_shutdown: {
+        description: 'Shuts down a socket for receiving, sending, or both',
+        signature: '( resource $socket [, int $how = 2 ]): bool'
+    },
+    socket_strerror: {
+        description: 'Return a string describing a socket error',
+        signature: '( int $errno ): string'
+    },
+    socket_write: {
+        description: 'Write to a socket',
+        signature: '( resource $socket , string $buffer [, int $length = 0 ]): int'
+    },
+    apache_child_terminate: {
+        description: 'Terminate apache process after this request',
+        signature: '(void): bool'
+    },
+    apache_get_modules: {
+        description: 'Get a list of loaded Apache modules',
+        signature: '(void): array'
+    },
+    apache_get_version: {
+        description: 'Fetch Apache version',
+        signature: '(void): string'
+    },
+    apache_getenv: {
+        description: 'Get an Apache subprocess_env variable',
+        signature: '( string $variable [, bool $walk_to_top ]): string'
+    },
+    apache_lookup_uri: {
+        description: 'Perform a partial request for the specified URI and return all info about it',
+        signature: '( string $filename ): object'
+    },
+    apache_note: {
+        description: 'Get and set apache request notes',
+        signature: '( string $note_name [, string $note_value = "" ]): string'
+    },
+    apache_request_headers: {
+        description: 'Fetch all HTTP request headers',
+        signature: '(void): array'
+    },
+    apache_reset_timeout: {
+        description: 'Reset the Apache write timer',
+        signature: '(void): bool'
+    },
+    apache_response_headers: {
+        description: 'Fetch all HTTP response headers',
+        signature: '(void): array'
+    },
+    apache_setenv: {
+        description: 'Set an Apache subprocess_env variable',
+        signature: '( string $variable , string $value [, bool $walk_to_top ]): bool'
+    },
+    getallheaders: {
+        description: 'Fetch all HTTP request headers',
+        signature: '(void): array'
+    },
+    virtual: {
+        description: 'Perform an Apache sub-request',
+        signature: '( string $filename ): bool'
+    },
+    nsapi_request_headers: {
+        description: 'Fetch all HTTP request headers',
+        signature: '(void): array'
+    },
+    nsapi_response_headers: {
+        description: 'Fetch all HTTP response headers',
+        signature: '(void): array'
+    },
+    nsapi_virtual: {
+        description: 'Perform an NSAPI sub-request',
+        signature: '( string $uri ): bool'
+    },
+    session_abort: {
+        description: 'Discard session array changes and finish session',
+        signature: '(void): bool'
+    },
+    session_cache_expire: {
+        description: 'Return current cache expire',
+        signature: '([ string $new_cache_expire ]): int'
+    },
+    session_cache_limiter: {
+        description: 'Get and/or set the current cache limiter',
+        signature: '([ string $cache_limiter ]): string'
+    },
+    session_commit: {
+        description: 'Alias of session_write_close',
+    },
+    session_create_id: {
+        description: 'Create new session id',
+        signature: '([ string $prefix ]): string'
+    },
+    session_decode: {
+        description: 'Decodes session data from a session encoded string',
+        signature: '( string $data ): bool'
+    },
+    session_destroy: {
+        description: 'Destroys all data registered to a session',
+        signature: '(void): bool'
+    },
+    session_encode: {
+        description: 'Encodes the current session data as a session encoded string',
+        signature: '(void): string'
+    },
+    session_gc: {
+        description: 'Perform session data garbage collection',
+        signature: '(void): int'
+    },
+    session_get_cookie_params: {
+        description: 'Get the session cookie parameters',
+        signature: '(void): array'
+    },
+    session_id: {
+        description: 'Get and/or set the current session id',
+        signature: '([ string $id ]): string'
+    },
+    session_is_registered: {
+        description: 'Find out whether a global variable is registered in a session',
+        signature: '( string $name ): bool'
+    },
+    session_module_name: {
+        description: 'Get and/or set the current session module',
+        signature: '([ string $module ]): string'
+    },
+    session_name: {
+        description: 'Get and/or set the current session name',
+        signature: '([ string $name ]): string'
+    },
+    session_regenerate_id: {
+        description: 'Update the current session id with a newly generated one',
+        signature: '([ bool $delete_old_session ]): bool'
+    },
+    session_register_shutdown: {
+        description: 'Session shutdown function',
+        signature: '(void): void'
+    },
+    session_register: {
+        description: 'Register one or more global variables with the current session',
+        signature: '( mixed $name [, mixed $... ]): bool'
+    },
+    session_reset: {
+        description: 'Re-initialize session array with original values',
+        signature: '(void): bool'
+    },
+    session_save_path: {
+        description: 'Get and/or set the current session save path',
+        signature: '([ string $path ]): string'
+    },
+    session_set_cookie_params: {
+        description: 'Set the session cookie parameters',
+        signature: '( int $lifetime [, string $path [, string $domain [, bool $secure [, bool $httponly , array $options ]]]]): bool'
+    },
+    session_set_save_handler: {
+        description: 'Sets user-level session storage functions',
+        signature: '( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid [, callable $validate_sid [, callable $update_timestamp , object $sessionhandler [, bool $register_shutdown ]]]]): bool'
+    },
+    session_start: {
+        description: 'Start new or resume existing session',
+        signature: '([ array $options = array() ]): bool'
+    },
+    session_status: {
+        description: 'Returns the current session status',
+        signature: '(void): int'
+    },
+    session_unregister: {
+        description: 'Unregister a global variable from the current session',
+        signature: '( string $name ): bool'
+    },
+    session_unset: {
+        description: 'Free all session variables',
+        signature: '(void): bool'
+    },
+    session_write_close: {
+        description: 'Write session data and end session',
+        signature: '(void): bool'
+    },
+    preg_filter: {
+        description: 'Perform a regular expression search and replace',
+        signature: '( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
+    },
+    preg_grep: {
+        description: 'Return array entries that match the pattern',
+        signature: '( string $pattern , array $input [, int $flags = 0 ]): array'
+    },
+    preg_last_error: {
+        description: 'Returns the error code of the last PCRE regex execution',
+        signature: '(void): int'
+    },
+    preg_match_all: {
+        description: 'Perform a global regular expression match',
+        signature: '( string $pattern , string $subject [, array $matches [, int $flags [, int $offset = 0 ]]]): int'
+    },
+    preg_match: {
+        description: 'Perform a regular expression match',
+        signature: '( string $pattern , string $subject [, array $matches [, int $flags = 0 [, int $offset = 0 ]]]): int'
+    },
+    preg_quote: {
+        description: 'Quote regular expression characters',
+        signature: '( string $str [, string $delimiter ]): string'
+    },
+    preg_replace_callback_array: {
+        description: 'Perform a regular expression search and replace using callbacks',
+        signature: '( array $patterns_and_callbacks , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
+    },
+    preg_replace_callback: {
+        description: 'Perform a regular expression search and replace using a callback',
+        signature: '( mixed $pattern , callable $callback , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
+    },
+    preg_replace: {
+        description: 'Perform a regular expression search and replace',
+        signature: '( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int $count ]]): mixed'
+    },
+    preg_split: {
+        description: 'Split string by a regular expression',
+        signature: '( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]]): array'
+    },
+    addcslashes: {
+        description: 'Quote string with slashes in a C style',
+        signature: '( string $str , string $charlist ): string'
+    },
+    addslashes: {
+        description: 'Quote string with slashes',
+        signature: '( string $str ): string'
+    },
+    bin2hex: {
+        description: 'Convert binary data into hexadecimal representation',
+        signature: '( string $str ): string'
+    },
+    chop: {
+        description: 'Alias of rtrim',
+    },
+    chr: {
+        description: 'Generate a single-byte string from a number',
+        signature: '( int $bytevalue ): string'
+    },
+    chunk_split: {
+        description: 'Split a string into smaller chunks',
+        signature: '( string $body [, int $chunklen = 76 [, string $end = "\r\n" ]]): string'
+    },
+    convert_cyr_string: {
+        description: 'Convert from one Cyrillic character set to another',
+        signature: '( string $str , string $from , string $to ): string'
+    },
+    convert_uudecode: {
+        description: 'Decode a uuencoded string',
+        signature: '( string $data ): string'
+    },
+    convert_uuencode: {
+        description: 'Uuencode a string',
+        signature: '( string $data ): string'
+    },
+    count_chars: {
+        description: 'Return information about characters used in a string',
+        signature: '( string $string [, int $mode = 0 ]): mixed'
+    },
+    crc32: {
+        description: 'Calculates the crc32 polynomial of a string',
+        signature: '( string $str ): int'
+    },
+    crypt: {
+        description: 'One-way string hashing',
+        signature: '( string $str [, string $salt ]): string'
+    },
+    echo: {
+        description: 'Output one or more strings',
+        signature: '( string $arg1 [, string $... ]): void'
+    },
+    explode: {
+        description: 'Split a string by a string',
+        signature: '( string $delimiter , string $string [, int $limit = PHP_INT_MAX ]): array'
+    },
+    fprintf: {
+        description: 'Write a formatted string to a stream',
+        signature: '( resource $handle , string $format [, mixed $... ]): int'
+    },
+    get_html_translation_table: {
+        description: 'Returns the translation table used by htmlspecialchars and htmlentities',
+        signature: '([ int $table = HTML_SPECIALCHARS [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = "UTF-8" ]]]): array'
+    },
+    hebrev: {
+        description: 'Convert logical Hebrew text to visual text',
+        signature: '( string $hebrew_text [, int $max_chars_per_line = 0 ]): string'
+    },
+    hebrevc: {
+        description: 'Convert logical Hebrew text to visual text with newline conversion',
+        signature: '( string $hebrew_text [, int $max_chars_per_line = 0 ]): string'
+    },
+    hex2bin: {
+        description: 'Decodes a hexadecimally encoded binary string',
+        signature: '( string $data ): string'
+    },
+    html_entity_decode: {
+        description: 'Convert HTML entities to their corresponding characters',
+        signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") ]]): string'
+    },
+    htmlentities: {
+        description: 'Convert all applicable characters to HTML entities',
+        signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode ]]]): string'
+    },
+    htmlspecialchars_decode: {
+        description: 'Convert special HTML entities back to characters',
+        signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 ]): string'
+    },
+    htmlspecialchars: {
+        description: 'Convert special characters to HTML entities',
+        signature: '( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode ]]]): string'
+    },
+    implode: {
+        description: 'Join array elements with a string',
+        signature: '( string $glue , array $pieces ): string'
+    },
+    join: {
+        description: 'Alias of implode',
+    },
+    lcfirst: {
+        description: 'Make a string\'s first character lowercase',
+        signature: '( string $str ): string'
+    },
+    levenshtein: {
+        description: 'Calculate Levenshtein distance between two strings',
+        signature: '( string $str1 , string $str2 , int $cost_ins , int $cost_rep , int $cost_del ): int'
+    },
+    localeconv: {
+        description: 'Get numeric formatting information',
+        signature: '(void): array'
+    },
+    ltrim: {
+        description: 'Strip whitespace (or other characters) from the beginning of a string',
+        signature: '( string $str [, string $character_mask ]): string'
+    },
+    md5_file: {
+        description: 'Calculates the md5 hash of a given file',
+        signature: '( string $filename [, bool $raw_output ]): string'
+    },
+    md5: {
+        description: 'Calculate the md5 hash of a string',
+        signature: '( string $str [, bool $raw_output ]): string'
+    },
+    metaphone: {
+        description: 'Calculate the metaphone key of a string',
+        signature: '( string $str [, int $phonemes = 0 ]): string'
+    },
+    money_format: {
+        description: 'Formats a number as a currency string',
+        signature: '( string $format , float $number ): string'
+    },
+    nl_langinfo: {
+        description: 'Query language and locale information',
+        signature: '( int $item ): string'
+    },
+    nl2br: {
+        description: 'Inserts HTML line breaks before all newlines in a string',
+        signature: '( string $string [, bool $is_xhtml ]): string'
+    },
+    number_format: {
+        description: 'Format a number with grouped thousands',
+        signature: '( float $number , int $decimals = 0 , string $dec_point = "." , string $thousands_sep = "," ): string'
+    },
+    ord: {
+        description: 'Convert the first byte of a string to a value between 0 and 255',
+        signature: '( string $string ): int'
+    },
+    parse_str: {
+        description: 'Parses the string into variables',
+        signature: '( string $encoded_string [, array $result ]): void'
+    },
+    print: {
+        description: 'Output a string',
+        signature: '( string $arg ): int'
+    },
+    printf: {
+        description: 'Output a formatted string',
+        signature: '( string $format [, mixed $... ]): int'
+    },
+    quoted_printable_decode: {
+        description: 'Convert a quoted-printable string to an 8 bit string',
+        signature: '( string $str ): string'
+    },
+    quoted_printable_encode: {
+        description: 'Convert a 8 bit string to a quoted-printable string',
+        signature: '( string $str ): string'
+    },
+    quotemeta: {
+        description: 'Quote meta characters',
+        signature: '( string $str ): string'
+    },
+    rtrim: {
+        description: 'Strip whitespace (or other characters) from the end of a string',
+        signature: '( string $str [, string $character_mask ]): string'
+    },
+    setlocale: {
+        description: 'Set locale information',
+        signature: '( int $category , array $locale [, string $... ]): string'
+    },
+    sha1_file: {
+        description: 'Calculate the sha1 hash of a file',
+        signature: '( string $filename [, bool $raw_output ]): string'
+    },
+    sha1: {
+        description: 'Calculate the sha1 hash of a string',
+        signature: '( string $str [, bool $raw_output ]): string'
+    },
+    similar_text: {
+        description: 'Calculate the similarity between two strings',
+        signature: '( string $first , string $second [, float $percent ]): int'
+    },
+    soundex: {
+        description: 'Calculate the soundex key of a string',
+        signature: '( string $str ): string'
+    },
+    sprintf: {
+        description: 'Return a formatted string',
+        signature: '( string $format [, mixed $... ]): string'
+    },
+    sscanf: {
+        description: 'Parses input from a string according to a format',
+        signature: '( string $str , string $format [, mixed $... ]): mixed'
+    },
+    str_getcsv: {
+        description: 'Parse a CSV string into an array',
+        signature: '( string $input [, string $delimiter = "," [, string $enclosure = \'"\' [, string $escape = "\\" ]]]): array'
+    },
+    str_ireplace: {
+        description: 'Case-insensitive version of str_replace',
+        signature: '( mixed $search , mixed $replace , mixed $subject [, int $count ]): mixed'
+    },
+    str_pad: {
+        description: 'Pad a string to a certain length with another string',
+        signature: '( string $input , int $pad_length [, string $pad_string = " " [, int $pad_type = STR_PAD_RIGHT ]]): string'
+    },
+    str_repeat: {
+        description: 'Repeat a string',
+        signature: '( string $input , int $multiplier ): string'
+    },
+    str_replace: {
+        description: 'Replace all occurrences of the search string with the replacement string',
+        signature: '( mixed $search , mixed $replace , mixed $subject [, int $count ]): mixed'
+    },
+    str_rot13: {
+        description: 'Perform the rot13 transform on a string',
+        signature: '( string $str ): string'
+    },
+    str_shuffle: {
+        description: 'Randomly shuffles a string',
+        signature: '( string $str ): string'
+    },
+    str_split: {
+        description: 'Convert a string to an array',
+        signature: '( string $string [, int $split_length = 1 ]): array'
+    },
+    str_word_count: {
+        description: 'Return information about words used in a string',
+        signature: '( string $string [, int $format = 0 [, string $charlist ]]): mixed'
+    },
+    strcasecmp: {
+        description: 'Binary safe case-insensitive string comparison',
+        signature: '( string $str1 , string $str2 ): int'
+    },
+    strchr: {
+        description: 'Alias of strstr',
+    },
+    strcmp: {
+        description: 'Binary safe string comparison',
+        signature: '( string $str1 , string $str2 ): int'
+    },
+    strcoll: {
+        description: 'Locale based string comparison',
+        signature: '( string $str1 , string $str2 ): int'
+    },
+    strcspn: {
+        description: 'Find length of initial segment not matching mask',
+        signature: '( string $subject , string $mask [, int $start [, int $length ]]): int'
+    },
+    strip_tags: {
+        description: 'Strip HTML and PHP tags from a string',
+        signature: '( string $str [, string $allowable_tags ]): string'
+    },
+    stripcslashes: {
+        description: 'Un-quote string quoted with addcslashes',
+        signature: '( string $str ): string'
+    },
+    stripos: {
+        description: 'Find the position of the first occurrence of a case-insensitive substring in a string',
+        signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
+    },
+    stripslashes: {
+        description: 'Un-quotes a quoted string',
+        signature: '( string $str ): string'
+    },
+    stristr: {
+        description: 'Case-insensitive strstr',
+        signature: '( string $haystack , mixed $needle [, bool $before_needle ]): string'
+    },
+    strlen: {
+        description: 'Get string length',
+        signature: '( string $string ): int'
+    },
+    strnatcasecmp: {
+        description: 'Case insensitive string comparisons using a "natural order" algorithm',
+        signature: '( string $str1 , string $str2 ): int'
+    },
+    strnatcmp: {
+        description: 'String comparisons using a "natural order" algorithm',
+        signature: '( string $str1 , string $str2 ): int'
+    },
+    strncasecmp: {
+        description: 'Binary safe case-insensitive string comparison of the first n characters',
+        signature: '( string $str1 , string $str2 , int $len ): int'
+    },
+    strncmp: {
+        description: 'Binary safe string comparison of the first n characters',
+        signature: '( string $str1 , string $str2 , int $len ): int'
+    },
+    strpbrk: {
+        description: 'Search a string for any of a set of characters',
+        signature: '( string $haystack , string $char_list ): string'
+    },
+    strpos: {
+        description: 'Find the position of the first occurrence of a substring in a string',
+        signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
+    },
+    strrchr: {
+        description: 'Find the last occurrence of a character in a string',
+        signature: '( string $haystack , mixed $needle ): string'
+    },
+    strrev: {
+        description: 'Reverse a string',
+        signature: '( string $string ): string'
+    },
+    strripos: {
+        description: 'Find the position of the last occurrence of a case-insensitive substring in a string',
+        signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
+    },
+    strrpos: {
+        description: 'Find the position of the last occurrence of a substring in a string',
+        signature: '( string $haystack , mixed $needle [, int $offset = 0 ]): int'
+    },
+    strspn: {
+        description: 'Finds the length of the initial segment of a string consisting   entirely of characters contained within a given mask',
+        signature: '( string $subject , string $mask [, int $start [, int $length ]]): int'
+    },
+    strstr: {
+        description: 'Find the first occurrence of a string',
+        signature: '( string $haystack , mixed $needle [, bool $before_needle ]): string'
+    },
+    strtok: {
+        description: 'Tokenize string',
+        signature: '( string $str , string $token ): string'
+    },
+    strtolower: {
+        description: 'Make a string lowercase',
+        signature: '( string $string ): string'
+    },
+    strtoupper: {
+        description: 'Make a string uppercase',
+        signature: '( string $string ): string'
+    },
+    strtr: {
+        description: 'Translate characters or replace substrings',
+        signature: '( string $str , string $from , string $to , array $replace_pairs ): string'
+    },
+    substr_compare: {
+        description: 'Binary safe comparison of two strings from an offset, up to length characters',
+        signature: '( string $main_str , string $str , int $offset [, int $length [, bool $case_insensitivity ]]): int'
+    },
+    substr_count: {
+        description: 'Count the number of substring occurrences',
+        signature: '( string $haystack , string $needle [, int $offset = 0 [, int $length ]]): int'
+    },
+    substr_replace: {
+        description: 'Replace text within a portion of a string',
+        signature: '( mixed $string , mixed $replacement , mixed $start [, mixed $length ]): mixed'
+    },
+    substr: {
+        description: 'Return part of a string',
+        signature: '( string $string , int $start [, int $length ]): string'
+    },
+    trim: {
+        description: 'Strip whitespace (or other characters) from the beginning and end of a string',
+        signature: '( string $str [, string $character_mask = " \t\n\r\0\x0B" ]): string'
+    },
+    ucfirst: {
+        description: 'Make a string\'s first character uppercase',
+        signature: '( string $str ): string'
+    },
+    ucwords: {
+        description: 'Uppercase the first character of each word in a string',
+        signature: '( string $str [, string $delimiters = " \t\r\n\f\v" ]): string'
+    },
+    vfprintf: {
+        description: 'Write a formatted string to a stream',
+        signature: '( resource $handle , string $format , array $args ): int'
+    },
+    vprintf: {
+        description: 'Output a formatted string',
+        signature: '( string $format , array $args ): int'
+    },
+    vsprintf: {
+        description: 'Return a formatted string',
+        signature: '( string $format , array $args ): string'
+    },
+    wordwrap: {
+        description: 'Wraps a string to a given number of characters',
+        signature: '( string $str [, int $width = 75 [, string $break = "\n" [, bool $cut ]]]): string'
+    },
+    array_change_key_case: {
+        description: 'Changes the case of all keys in an array',
+        signature: '( array $array [, int $case = CASE_LOWER ]): array'
+    },
+    array_chunk: {
+        description: 'Split an array into chunks',
+        signature: '( array $array , int $size [, bool $preserve_keys ]): array'
+    },
+    array_column: {
+        description: 'Return the values from a single column in the input array',
+        signature: '( array $input , mixed $column_key [, mixed $index_key ]): array'
+    },
+    array_combine: {
+        description: 'Creates an array by using one array for keys and another for its values',
+        signature: '( array $keys , array $values ): array'
+    },
+    array_count_values: {
+        description: 'Counts all the values of an array',
+        signature: '( array $array ): array'
+    },
+    array_diff_assoc: {
+        description: 'Computes the difference of arrays with additional index check',
+        signature: '( array $array1 , array $array2 [, array $... ]): array'
+    },
+    array_diff_key: {
+        description: 'Computes the difference of arrays using keys for comparison',
+        signature: '( array $array1 , array $array2 [, array $... ]): array'
+    },
+    array_diff_uassoc: {
+        description: 'Computes the difference of arrays with additional index check which is performed by a user supplied callback function',
+        signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
+    },
+    array_diff_ukey: {
+        description: 'Computes the difference of arrays using a callback function on the keys for comparison',
+        signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
+    },
+    array_diff: {
+        description: 'Computes the difference of arrays',
+        signature: '( array $array1 , array $array2 [, array $... ]): array'
+    },
+    array_fill_keys: {
+        description: 'Fill an array with values, specifying keys',
+        signature: '( array $keys , mixed $value ): array'
+    },
+    array_fill: {
+        description: 'Fill an array with values',
+        signature: '( int $start_index , int $num , mixed $value ): array'
+    },
+    array_filter: {
+        description: 'Filters elements of an array using a callback function',
+        signature: '( array $array [, callable $callback [, int $flag = 0 ]]): array'
+    },
+    array_flip: {
+        description: 'Exchanges all keys with their associated values in an array',
+        signature: '( array $array ): string'
+    },
+    array_intersect_assoc: {
+        description: 'Computes the intersection of arrays with additional index check',
+        signature: '( array $array1 , array $array2 [, array $... ]): array'
+    },
+    array_intersect_key: {
+        description: 'Computes the intersection of arrays using keys for comparison',
+        signature: '( array $array1 , array $array2 [, array $... ]): array'
+    },
+    array_intersect_uassoc: {
+        description: 'Computes the intersection of arrays with additional index check, compares indexes by a callback function',
+        signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
+    },
+    array_intersect_ukey: {
+        description: 'Computes the intersection of arrays using a callback function on the keys for comparison',
+        signature: '( array $array1 , array $array2 [, array $... , callable $key_compare_func ]): array'
+    },
+    array_intersect: {
+        description: 'Computes the intersection of arrays',
+        signature: '( array $array1 , array $array2 [, array $... ]): array'
+    },
+    array_key_exists: {
+        description: 'Checks if the given key or index exists in the array',
+        signature: '( mixed $key , array $array ): bool'
+    },
+    array_key_first: {
+        description: 'Gets the first key of an array',
+        signature: '( array $array ): mixed'
+    },
+    array_key_last: {
+        description: 'Gets the last key of an array',
+        signature: '( array $array ): mixed'
+    },
+    array_keys: {
+        description: 'Return all the keys or a subset of the keys of an array',
+        signature: '( array $array , mixed $search_value [, bool $strict ]): array'
+    },
+    array_map: {
+        description: 'Applies the callback to the elements of the given arrays',
+        signature: '( callable $callback , array $array1 [, array $... ]): array'
+    },
+    array_merge_recursive: {
+        description: 'Merge one or more arrays recursively',
+        signature: '( array $array1 [, array $... ]): array'
+    },
+    array_merge: {
+        description: 'Merge one or more arrays',
+        signature: '( array $array1 [, array $... ]): array'
+    },
+    array_multisort: {
+        description: 'Sort multiple or multi-dimensional arrays',
+        signature: '( array $array1 [, mixed $array1_sort_order = SORT_ASC [, mixed $array1_sort_flags = SORT_REGULAR [, mixed $... ]]]): string'
+    },
+    array_pad: {
+        description: 'Pad array to the specified length with a value',
+        signature: '( array $array , int $size , mixed $value ): array'
+    },
+    array_pop: {
+        description: 'Pop the element off the end of array',
+        signature: '( array $array ): array'
+    },
+    array_product: {
+        description: 'Calculate the product of values in an array',
+        signature: '( array $array ): number'
+    },
+    array_push: {
+        description: 'Push one or more elements onto the end of array',
+        signature: '( array $array [, mixed $... ]): int'
+    },
+    array_rand: {
+        description: 'Pick one or more random keys out of an array',
+        signature: '( array $array [, int $num = 1 ]): mixed'
+    },
+    array_reduce: {
+        description: 'Iteratively reduce the array to a single value using a callback function',
+        signature: '( array $array , callable $callback [, mixed $initial ]): mixed'
+    },
+    array_replace_recursive: {
+        description: 'Replaces elements from passed arrays into the first array recursively',
+        signature: '( array $array1 [, array $... ]): array'
+    },
+    array_replace: {
+        description: 'Replaces elements from passed arrays into the first array',
+        signature: '( array $array1 [, array $... ]): array'
+    },
+    array_reverse: {
+        description: 'Return an array with elements in reverse order',
+        signature: '( array $array [, bool $preserve_keys ]): array'
+    },
+    array_search: {
+        description: 'Searches the array for a given value and returns the first corresponding key if successful',
+        signature: '( mixed $needle , array $haystack [, bool $strict ]): mixed'
+    },
+    array_shift: {
+        description: 'Shift an element off the beginning of array',
+        signature: '( array $array ): array'
+    },
+    array_slice: {
+        description: 'Extract a slice of the array',
+        signature: '( array $array , int $offset [, int $length [, bool $preserve_keys ]]): array'
+    },
+    array_splice: {
+        description: 'Remove a portion of the array and replace it with something else',
+        signature: '( array $input , int $offset [, int $length = count($input) [, mixed $replacement = array() ]]): array'
+    },
+    array_sum: {
+        description: 'Calculate the sum of values in an array',
+        signature: '( array $array ): number'
+    },
+    array_udiff_assoc: {
+        description: 'Computes the difference of arrays with additional index check, compares data by a callback function',
+        signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
+    },
+    array_udiff_uassoc: {
+        description: 'Computes the difference of arrays with additional index check, compares data and indexes by a callback function',
+        signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func , callable $key_compare_func ]): array'
+    },
+    array_udiff: {
+        description: 'Computes the difference of arrays by using a callback function for data comparison',
+        signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
+    },
+    array_uintersect_assoc: {
+        description: 'Computes the intersection of arrays with additional index check, compares data by a callback function',
+        signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
+    },
+    array_uintersect_uassoc: {
+        description: 'Computes the intersection of arrays with additional index check, compares data and indexes by separate callback functions',
+        signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func , callable $key_compare_func ]): array'
+    },
+    array_uintersect: {
+        description: 'Computes the intersection of arrays, compares data by a callback function',
+        signature: '( array $array1 , array $array2 [, array $... , callable $value_compare_func ]): array'
+    },
+    array_unique: {
+        description: 'Removes duplicate values from an array',
+        signature: '( array $array [, int $sort_flags = SORT_STRING ]): array'
+    },
+    array_unshift: {
+        description: 'Prepend one or more elements to the beginning of an array',
+        signature: '( array $array [, mixed $... ]): int'
+    },
+    array_values: {
+        description: 'Return all the values of an array',
+        signature: '( array $array ): array'
+    },
+    array_walk_recursive: {
+        description: 'Apply a user function recursively to every member of an array',
+        signature: '( array $array , callable $callback [, mixed $userdata ]): bool'
+    },
+    array_walk: {
+        description: 'Apply a user supplied function to every member of an array',
+        signature: '( array $array , callable $callback [, mixed $userdata ]): bool'
+    },
+    array: {
+        description: 'Create an array',
+        signature: '([ mixed $... ]): array'
+    },
+    arsort: {
+        description: 'Sort an array in reverse order and maintain index association',
+        signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
+    },
+    asort: {
+        description: 'Sort an array and maintain index association',
+        signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
+    },
+    compact: {
+        description: 'Create array containing variables and their values',
+        signature: '( mixed $varname1 [, mixed $... ]): array'
+    },
+    count: {
+        description: 'Count all elements in an array, or something in an object',
+        signature: '( mixed $array_or_countable [, int $mode = COUNT_NORMAL ]): int'
+    },
+    current: {
+        description: 'Return the current element in an array',
+        signature: '( array $array ): mixed'
+    },
+    each: {
+        description: 'Return the current key and value pair from an array and advance the array cursor',
+        signature: '( array $array ): array'
+    },
+    end: {
+        description: 'Set the internal pointer of an array to its last element',
+        signature: '( array $array ): mixed'
+    },
+    extract: {
+        description: 'Import variables into the current symbol table from an array',
+        signature: '( array $array [, int $flags = EXTR_OVERWRITE [, string $prefix ]]): int'
+    },
+    in_array: {
+        description: 'Checks if a value exists in an array',
+        signature: '( mixed $needle , array $haystack [, bool $strict ]): bool'
+    },
+    key_exists: {
+        description: 'Alias of array_key_exists',
+    },
+    key: {
+        description: 'Fetch a key from an array',
+        signature: '( array $array ): mixed'
+    },
+    krsort: {
+        description: 'Sort an array by key in reverse order',
+        signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
+    },
+    ksort: {
+        description: 'Sort an array by key',
+        signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
+    },
+    list: {
+        description: 'Assign variables as if they were an array',
+        signature: '( mixed $var1 [, mixed $... ]): array'
+    },
+    natcasesort: {
+        description: 'Sort an array using a case insensitive "natural order" algorithm',
+        signature: '( array $array ): bool'
+    },
+    natsort: {
+        description: 'Sort an array using a "natural order" algorithm',
+        signature: '( array $array ): bool'
+    },
+    next: {
+        description: 'Advance the internal pointer of an array',
+        signature: '( array $array ): mixed'
+    },
+    pos: {
+        description: 'Alias of current',
+    },
+    prev: {
+        description: 'Rewind the internal array pointer',
+        signature: '( array $array ): mixed'
+    },
+    range: {
+        description: 'Create an array containing a range of elements',
+        signature: '( mixed $start , mixed $end [, number $step = 1 ]): array'
+    },
+    reset: {
+        description: 'Set the internal pointer of an array to its first element',
+        signature: '( array $array ): mixed'
+    },
+    rsort: {
+        description: 'Sort an array in reverse order',
+        signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
+    },
+    shuffle: {
+        description: 'Shuffle an array',
+        signature: '( array $array ): bool'
+    },
+    sizeof: {
+        description: 'Alias of count',
+    },
+    sort: {
+        description: 'Sort an array',
+        signature: '( array $array [, int $sort_flags = SORT_REGULAR ]): bool'
+    },
+    uasort: {
+        description: 'Sort an array with a user-defined comparison function and maintain index association',
+        signature: '( array $array , callable $value_compare_func ): bool'
+    },
+    uksort: {
+        description: 'Sort an array by keys using a user-defined comparison function',
+        signature: '( array $array , callable $key_compare_func ): bool'
+    },
+    usort: {
+        description: 'Sort an array by values using a user-defined comparison function',
+        signature: '( array $array , callable $value_compare_func ): bool'
+    },
+    __autoload: {
+        description: 'Attempt to load undefined class',
+        signature: '( string $class ): void'
+    },
+    call_user_method_array: {
+        description: 'Call a user method given with an array of parameters',
+        signature: '( string $method_name , object $obj , array $params ): mixed'
+    },
+    call_user_method: {
+        description: 'Call a user method on an specific object',
+        signature: '( string $method_name , object $obj [, mixed $... ]): mixed'
+    },
+    class_alias: {
+        description: 'Creates an alias for a class',
+        signature: '( string $original , string $alias [, bool $autoload ]): bool'
+    },
+    class_exists: {
+        description: 'Checks if the class has been defined',
+        signature: '( string $class_name [, bool $autoload ]): bool'
+    },
+    get_called_class: {
+        description: 'The "Late Static Binding" class name',
+        signature: '(void): string'
+    },
+    get_class_methods: {
+        description: 'Gets the class methods\' names',
+        signature: '( mixed $class_name ): array'
+    },
+    get_class_vars: {
+        description: 'Get the default properties of the class',
+        signature: '( string $class_name ): array'
+    },
+    get_class: {
+        description: 'Returns the name of the class of an object',
+        signature: '([ object $object ]): string'
+    },
+    get_declared_classes: {
+        description: 'Returns an array with the name of the defined classes',
+        signature: '(void): array'
+    },
+    get_declared_interfaces: {
+        description: 'Returns an array of all declared interfaces',
+        signature: '(void): array'
+    },
+    get_declared_traits: {
+        description: 'Returns an array of all declared traits',
+        signature: '(void): array'
+    },
+    get_object_vars: {
+        description: 'Gets the properties of the given object',
+        signature: '( object $object ): array'
+    },
+    get_parent_class: {
+        description: 'Retrieves the parent class name for object or class',
+        signature: '([ mixed $object ]): string'
+    },
+    interface_exists: {
+        description: 'Checks if the interface has been defined',
+        signature: '( string $interface_name [, bool $autoload ]): bool'
+    },
+    is_a: {
+        description: 'Checks if the object is of this class or has this class as one of its parents',
+        signature: '( mixed $object , string $class_name [, bool $allow_string ]): bool'
+    },
+    is_subclass_of: {
+        description: 'Checks if the object has this class as one of its parents or implements it',
+        signature: '( mixed $object , string $class_name [, bool $allow_string ]): bool'
+    },
+    method_exists: {
+        description: 'Checks if the class method exists',
+        signature: '( mixed $object , string $method_name ): bool'
+    },
+    property_exists: {
+        description: 'Checks if the object or class has a property',
+        signature: '( mixed $class , string $property ): bool'
+    },
+    trait_exists: {
+        description: 'Checks if the trait exists',
+        signature: '( string $traitname [, bool $autoload ]): bool'
+    },
+    ctype_alnum: {
+        description: 'Check for alphanumeric character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_alpha: {
+        description: 'Check for alphabetic character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_cntrl: {
+        description: 'Check for control character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_digit: {
+        description: 'Check for numeric character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_graph: {
+        description: 'Check for any printable character(s) except space',
+        signature: '( string $text ): string'
+    },
+    ctype_lower: {
+        description: 'Check for lowercase character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_print: {
+        description: 'Check for printable character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_punct: {
+        description: 'Check for any printable character which is not whitespace or an   alphanumeric character',
+        signature: '( string $text ): string'
+    },
+    ctype_space: {
+        description: 'Check for whitespace character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_upper: {
+        description: 'Check for uppercase character(s)',
+        signature: '( string $text ): string'
+    },
+    ctype_xdigit: {
+        description: 'Check for character(s) representing a hexadecimal digit',
+        signature: '( string $text ): string'
+    },
+    filter_has_var: {
+        description: 'Checks if variable of specified type exists',
+        signature: '( int $type , string $variable_name ): bool'
+    },
+    filter_id: {
+        description: 'Returns the filter ID belonging to a named filter',
+        signature: '( string $filtername ): int'
+    },
+    filter_input_array: {
+        description: 'Gets external variables and optionally filters them',
+        signature: '( int $type [, mixed $definition [, bool $add_empty ]]): mixed'
+    },
+    filter_input: {
+        description: 'Gets a specific external variable by name and optionally filters it',
+        signature: '( int $type , string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]]): mixed'
+    },
+    filter_list: {
+        description: 'Returns a list of all supported filters',
+        signature: '(void): array'
+    },
+    filter_var_array: {
+        description: 'Gets multiple variables and optionally filters them',
+        signature: '( array $data [, mixed $definition [, bool $add_empty ]]): mixed'
+    },
+    filter_var: {
+        description: 'Filters a variable with a specified filter',
+        signature: '( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]]): mixed'
+    },
+    call_user_func_array: {
+        description: 'Call a callback with an array of parameters',
+        signature: '( callable $callback , array $param_arr ): mixed'
+    },
+    call_user_func: {
+        description: 'Call the callback given by the first parameter',
+        signature: '( callable $callback [, mixed $... ]): mixed'
+    },
+    create_function: {
+        description: 'Create an anonymous (lambda-style) function',
+        signature: '( string $args , string $code ): string'
+    },
+    forward_static_call_array: {
+        description: 'Call a static method and pass the arguments as array',
+        signature: '( callable $function , array $parameters ): mixed'
+    },
+    forward_static_call: {
+        description: 'Call a static method',
+        signature: '( callable $function [, mixed $... ]): mixed'
+    },
+    func_get_arg: {
+        description: 'Return an item from the argument list',
+        signature: '( int $arg_num ): mixed'
+    },
+    func_get_args: {
+        description: 'Returns an array comprising a function\'s argument list',
+        signature: '(void): array'
+    },
+    func_num_args: {
+        description: 'Returns the number of arguments passed to the function',
+        signature: '(void): int'
+    },
+    function_exists: {
+        description: 'Return TRUE if the given function has been defined',
+        signature: '( string $function_name ): bool'
+    },
+    get_defined_functions: {
+        description: 'Returns an array of all defined functions',
+        signature: '([ bool $exclude_disabled ]): array'
+    },
+    register_shutdown_function: {
+        description: 'Register a function for execution on shutdown',
+        signature: '( callable $callback [, mixed $... ]): void'
+    },
+    register_tick_function: {
+        description: 'Register a function for execution on each tick',
+        signature: '( callable $function [, mixed $... ]): bool'
+    },
+    unregister_tick_function: {
+        description: 'De-register a function for execution on each tick',
+        signature: '( callable $function ): void'
+    },
+    boolval: {
+        description: 'Get the boolean value of a variable',
+        signature: '( mixed $var ): boolean'
+    },
+    debug_zval_dump: {
+        description: 'Dumps a string representation of an internal zend value to output',
+        signature: '( mixed $variable [, mixed $... ]): void'
+    },
+    doubleval: {
+        description: 'Alias of floatval',
+    },
+    empty: {
+        description: 'Determine whether a variable is empty',
+        signature: '( mixed $var ): bool'
+    },
+    floatval: {
+        description: 'Get float value of a variable',
+        signature: '( mixed $var ): float'
+    },
+    get_defined_vars: {
+        description: 'Returns an array of all defined variables',
+        signature: '(void): array'
+    },
+    get_resource_type: {
+        description: 'Returns the resource type',
+        signature: '( resource $handle ): string'
+    },
+    gettype: {
+        description: 'Get the type of a variable',
+        signature: '( mixed $var ): string'
+    },
+    import_request_variables: {
+        description: 'Import GET/POST/Cookie variables into the global scope',
+        signature: '( string $types [, string $prefix ]): bool'
+    },
+    intval: {
+        description: 'Get the integer value of a variable',
+        signature: '( mixed $var [, int $base = 10 ]): integer'
+    },
+    is_array: {
+        description: 'Finds whether a variable is an array',
+        signature: '( mixed $var ): bool'
+    },
+    is_bool: {
+        description: 'Finds out whether a variable is a boolean',
+        signature: '( mixed $var ): bool'
+    },
+    is_callable: {
+        description: 'Verify that the contents of a variable can be called as a function',
+        signature: '( mixed $var [, bool $syntax_only [, string $callable_name ]]): bool'
+    },
+    is_countable: {
+        description: 'Verify that the contents of a variable is a countable value',
+        signature: '( mixed $var ): array'
+    },
+    is_double: {
+        description: 'Alias of is_float',
+    },
+    is_float: {
+        description: 'Finds whether the type of a variable is float',
+        signature: '( mixed $var ): bool'
+    },
+    is_int: {
+        description: 'Find whether the type of a variable is integer',
+        signature: '( mixed $var ): bool'
+    },
+    is_integer: {
+        description: 'Alias of is_int',
+    },
+    is_iterable: {
+        description: 'Verify that the contents of a variable is an iterable value',
+        signature: '( mixed $var ): array'
+    },
+    is_long: {
+        description: 'Alias of is_int',
+    },
+    is_null: {
+        description: 'Finds whether a variable is NULL',
+        signature: '( mixed $var ): bool'
+    },
+    is_numeric: {
+        description: 'Finds whether a variable is a number or a numeric string',
+        signature: '( mixed $var ): bool'
+    },
+    is_object: {
+        description: 'Finds whether a variable is an object',
+        signature: '( mixed $var ): bool'
+    },
+    is_real: {
+        description: 'Alias of is_float',
+    },
+    is_resource: {
+        description: 'Finds whether a variable is a resource',
+        signature: '( mixed $var ): bool'
+    },
+    is_scalar: {
+        description: 'Finds whether a variable is a scalar',
+        signature: '( mixed $var ): resource'
+    },
+    is_string: {
+        description: 'Find whether the type of a variable is string',
+        signature: '( mixed $var ): bool'
+    },
+    isset: {
+        description: 'Determine if a variable is declared and is different than NULL',
+        signature: '( mixed $var [, mixed $... ]): bool'
+    },
+    print_r: {
+        description: 'Prints human-readable information about a variable',
+        signature: '( mixed $expression [, bool $return ]): mixed'
+    },
+    serialize: {
+        description: 'Generates a storable representation of a value',
+        signature: '( mixed $value ): string'
+    },
+    settype: {
+        description: 'Set the type of a variable',
+        signature: '( mixed $var , string $type ): bool'
+    },
+    strval: {
+        description: 'Get string value of a variable',
+        signature: '( mixed $var ): string'
+    },
+    unserialize: {
+        description: 'Creates a PHP value from a stored representation',
+        signature: '( string $str [, array $options ]): mixed'
+    },
+    unset: {
+        description: 'Unset a given variable',
+        signature: '( mixed $var [, mixed $... ]): void'
+    },
+    var_dump: {
+        description: 'Dumps information about a variable',
+        signature: '( mixed $expression [, mixed $... ]): string'
+    },
+    var_export: {
+        description: 'Outputs or returns a parsable string representation of a variable',
+        signature: '( mixed $expression [, bool $return ]): mixed'
+    },
+    xmlrpc_decode_request: {
+        description: 'Decodes XML into native PHP types',
+        signature: '( string $xml , string $method [, string $encoding ]): mixed'
+    },
+    xmlrpc_decode: {
+        description: 'Decodes XML into native PHP types',
+        signature: '( string $xml [, string $encoding = "iso-8859-1" ]): mixed'
+    },
+    xmlrpc_encode_request: {
+        description: 'Generates XML for a method request',
+        signature: '( string $method , mixed $params [, array $output_options ]): string'
+    },
+    xmlrpc_encode: {
+        description: 'Generates XML for a PHP value',
+        signature: '( mixed $value ): string'
+    },
+    xmlrpc_get_type: {
+        description: 'Gets xmlrpc type for a PHP value',
+        signature: '( mixed $value ): string'
+    },
+    xmlrpc_is_fault: {
+        description: 'Determines if an array value represents an XMLRPC fault',
+        signature: '( array $arg ): bool'
+    },
+    xmlrpc_parse_method_descriptions: {
+        description: 'Decodes XML into a list of method descriptions',
+        signature: '( string $xml ): array'
+    },
+    xmlrpc_server_add_introspection_data: {
+        description: 'Adds introspection documentation',
+        signature: '( resource $server , array $desc ): int'
+    },
+    xmlrpc_server_call_method: {
+        description: 'Parses XML requests and call methods',
+        signature: '( resource $server , string $xml , mixed $user_data [, array $output_options ]): string'
+    },
+    xmlrpc_server_create: {
+        description: 'Creates an xmlrpc server',
+        signature: '(void): resource'
+    },
+    xmlrpc_server_destroy: {
+        description: 'Destroys server resources',
+        signature: '( resource $server ): bool'
+    },
+    xmlrpc_server_register_introspection_callback: {
+        description: 'Register a PHP function to generate documentation',
+        signature: '( resource $server , string $function ): bool'
+    },
+    xmlrpc_server_register_method: {
+        description: 'Register a PHP function to resource method matching method_name',
+        signature: '( resource $server , string $method_name , string $function ): bool'
+    },
+    xmlrpc_set_type: {
+        description: 'Sets xmlrpc type, base64 or datetime, for a PHP string value',
+        signature: '( string $value , string $type ): bool'
+    },
+    com_create_guid: {
+        description: 'Generate a globally unique identifier (GUID)',
+        signature: '(void): string'
+    },
+    com_event_sink: {
+        description: 'Connect events from a COM object to a PHP object',
+        signature: '( variant $comobject , object $sinkobject [, mixed $sinkinterface ]): bool'
+    },
+    com_get_active_object: {
+        description: 'Returns a handle to an already running instance of a COM object',
+        signature: '( string $progid [, int $code_page ]): variant'
+    },
+    com_load_typelib: {
+        description: 'Loads a Typelib',
+        signature: '( string $typelib_name [, bool $case_sensitive ]): bool'
+    },
+    com_message_pump: {
+        description: 'Process COM messages, sleeping for up to timeoutms milliseconds',
+        signature: '([ int $timeoutms = 0 ]): bool'
+    },
+    com_print_typeinfo: {
+        description: 'Print out a PHP class definition for a dispatchable interface',
+        signature: '( object $comobject [, string $dispinterface [, bool $wantsink ]]): bool'
+    },
+    variant_abs: {
+        description: 'Returns the absolute value of a variant',
+        signature: '( mixed $val ): mixed'
+    },
+    variant_add: {
+        description: '"Adds" two variant values together and returns the result',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_and: {
+        description: 'Performs a bitwise AND operation between two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_cast: {
+        description: 'Convert a variant into a new variant object of another type',
+        signature: '( variant $variant , int $type ): variant'
+    },
+    variant_cat: {
+        description: 'Concatenates two variant values together and returns the result',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_cmp: {
+        description: 'Compares two variants',
+        signature: '( mixed $left , mixed $right [, int $lcid [, int $flags ]]): int'
+    },
+    variant_date_from_timestamp: {
+        description: 'Returns a variant date representation of a Unix timestamp',
+        signature: '( int $timestamp ): variant'
+    },
+    variant_date_to_timestamp: {
+        description: 'Converts a variant date/time value to Unix timestamp',
+        signature: '( variant $variant ): int'
+    },
+    variant_div: {
+        description: 'Returns the result from dividing two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_eqv: {
+        description: 'Performs a bitwise equivalence on two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_fix: {
+        description: 'Returns the integer portion of a variant',
+        signature: '( mixed $variant ): mixed'
+    },
+    variant_get_type: {
+        description: 'Returns the type of a variant object',
+        signature: '( variant $variant ): int'
+    },
+    variant_idiv: {
+        description: 'Converts variants to integers and then returns the result from dividing them',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_imp: {
+        description: 'Performs a bitwise implication on two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_int: {
+        description: 'Returns the integer portion of a variant',
+        signature: '( mixed $variant ): mixed'
+    },
+    variant_mod: {
+        description: 'Divides two variants and returns only the remainder',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_mul: {
+        description: 'Multiplies the values of the two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_neg: {
+        description: 'Performs logical negation on a variant',
+        signature: '( mixed $variant ): mixed'
+    },
+    variant_not: {
+        description: 'Performs bitwise not negation on a variant',
+        signature: '( mixed $variant ): mixed'
+    },
+    variant_or: {
+        description: 'Performs a logical disjunction on two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_pow: {
+        description: 'Returns the result of performing the power function with two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_round: {
+        description: 'Rounds a variant to the specified number of decimal places',
+        signature: '( mixed $variant , int $decimals ): mixed'
+    },
+    variant_set_type: {
+        description: 'Convert a variant into another type "in-place"',
+        signature: '( variant $variant , int $type ): void'
+    },
+    variant_set: {
+        description: 'Assigns a new value for a variant object',
+        signature: '( variant $variant , mixed $value ): void'
+    },
+    variant_sub: {
+        description: 'Subtracts the value of the right variant from the left variant value',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    variant_xor: {
+        description: 'Performs a logical exclusion on two variants',
+        signature: '( mixed $left , mixed $right ): mixed'
+    },
+    libxml_clear_errors: {
+        description: 'Clear libxml error buffer',
+        signature: '(void): void'
+    },
+    libxml_disable_entity_loader: {
+        description: 'Disable the ability to load external entities',
+        signature: '([ bool $disable ]): bool'
+    },
+    libxml_get_errors: {
+        description: 'Retrieve array of errors',
+        signature: '(void): array'
+    },
+    libxml_get_last_error: {
+        description: 'Retrieve last error from libxml',
+        signature: '(void): LibXMLError'
+    },
+    libxml_set_external_entity_loader: {
+        description: 'Changes the default external entity loader',
+        signature: '( callable $resolver_function ): bool'
+    },
+    libxml_set_streams_context: {
+        description: 'Set the streams context for the next libxml document load or write',
+        signature: '( resource $streams_context ): void'
+    },
+    libxml_use_internal_errors: {
+        description: 'Disable libxml errors and allow user to fetch error information as needed',
+        signature: '([ bool $use_errors ]): bool'
+    },
+    simplexml_import_dom: {
+        description: 'Get a SimpleXMLElement object from a DOM node',
+        signature: '( DOMNode $node [, string $class_name = "SimpleXMLElement" ]): SimpleXMLElement'
+    },
+    simplexml_load_file: {
+        description: 'Interprets an XML file into an object',
+        signature: '( string $filename [, string $class_name = "SimpleXMLElement" [, int $options = 0 [, string $ns = "" [, bool $is_prefix ]]]]): SimpleXMLElement'
+    },
+    simplexml_load_string: {
+        description: 'Interprets a string of XML into an object',
+        signature: '( string $data [, string $class_name = "SimpleXMLElement" [, int $options = 0 [, string $ns = "" [, bool $is_prefix ]]]]): SimpleXMLElement'
+    },
+    utf8_decode: {
+        description: 'Converts a string with ISO-8859-1 characters encoded with UTF-8   to single-byte ISO-8859-1',
+        signature: '( string $data ): string'
+    },
+    utf8_encode: {
+        description: 'Encodes an ISO-8859-1 string to UTF-8',
+        signature: '( string $data ): string'
+    },
+    xml_error_string: {
+        description: 'Get XML parser error string',
+        signature: '( int $code ): string'
+    },
+    xml_get_current_byte_index: {
+        description: 'Get current byte index for an XML parser',
+        signature: '( resource $parser ): int'
+    },
+    xml_get_current_column_number: {
+        description: 'Get current column number for an XML parser',
+        signature: '( resource $parser ): int'
+    },
+    xml_get_current_line_number: {
+        description: 'Get current line number for an XML parser',
+        signature: '( resource $parser ): int'
+    },
+    xml_get_error_code: {
+        description: 'Get XML parser error code',
+        signature: '( resource $parser ): int'
+    },
+    xml_parse_into_struct: {
+        description: 'Parse XML data into an array structure',
+        signature: '( resource $parser , string $data , array $values [, array $index ]): int'
+    },
+    xml_parse: {
+        description: 'Start parsing an XML document',
+        signature: '( resource $parser , string $data [, bool $is_final ]): int'
+    },
+    xml_parser_create_ns: {
+        description: 'Create an XML parser with namespace support',
+        signature: '([ string $encoding [, string $separator = ":" ]]): resource'
+    },
+    xml_parser_create: {
+        description: 'Create an XML parser',
+        signature: '([ string $encoding ]): resource'
+    },
+    xml_parser_free: {
+        description: 'Free an XML parser',
+        signature: '( resource $parser ): bool'
+    },
+    xml_parser_get_option: {
+        description: 'Get options from an XML parser',
+        signature: '( resource $parser , int $option ): mixed'
+    },
+    xml_parser_set_option: {
+        description: 'Set options in an XML parser',
+        signature: '( resource $parser , int $option , mixed $value ): bool'
+    },
+    xml_set_character_data_handler: {
+        description: 'Set up character data handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_default_handler: {
+        description: 'Set up default handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_element_handler: {
+        description: 'Set up start and end element handlers',
+        signature: '( resource $parser , callable $start_element_handler , callable $end_element_handler ): bool'
+    },
+    xml_set_end_namespace_decl_handler: {
+        description: 'Set up end namespace declaration handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_external_entity_ref_handler: {
+        description: 'Set up external entity reference handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_notation_decl_handler: {
+        description: 'Set up notation declaration handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_object: {
+        description: 'Use XML Parser within an object',
+        signature: '( resource $parser , object $object ): bool'
+    },
+    xml_set_processing_instruction_handler: {
+        description: 'Set up processing instruction (PI) handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_start_namespace_decl_handler: {
+        description: 'Set up start namespace declaration handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xml_set_unparsed_entity_decl_handler: {
+        description: 'Set up unparsed entity declaration handler',
+        signature: '( resource $parser , callable $handler ): bool'
+    },
+    xmlwriter_end_attribute: {
+        description: 'End attribute',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_cdata: {
+        description: 'End current CDATA',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_comment: {
+        description: 'Create end comment',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_document: {
+        description: 'End current document',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_dtd_attlist: {
+        description: 'End current DTD AttList',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_dtd_element: {
+        description: 'End current DTD element',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_dtd_entity: {
+        description: 'End current DTD Entity',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_dtd: {
+        description: 'End current DTD',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_element: {
+        description: 'End current element',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_end_pi: {
+        description: 'End current PI',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_flush: {
+        description: 'Flush current buffer',
+        signature: '([ bool $empty , resource $xmlwriter ]): mixed'
+    },
+    xmlwriter_full_end_element: {
+        description: 'End current element',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_open_memory: {
+        description: 'Create new xmlwriter using memory for string output',
+        signature: '(void): resource'
+    },
+    xmlwriter_open_uri: {
+        description: 'Create new xmlwriter using source uri for output',
+        signature: '( string $uri ): resource'
+    },
+    xmlwriter_output_memory: {
+        description: 'Returns current buffer',
+        signature: '([ bool $flush , resource $xmlwriter ]): string'
+    },
+    xmlwriter_set_indent_string: {
+        description: 'Set string used for indenting',
+        signature: '( string $indentString , resource $xmlwriter ): bool'
+    },
+    xmlwriter_set_indent: {
+        description: 'Toggle indentation on/off',
+        signature: '( bool $indent , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_attribute_ns: {
+        description: 'Create start namespaced attribute',
+        signature: '( string $prefix , string $name , string $uri , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_attribute: {
+        description: 'Create start attribute',
+        signature: '( string $name , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_cdata: {
+        description: 'Create start CDATA tag',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_comment: {
+        description: 'Create start comment',
+        signature: '( resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_document: {
+        description: 'Create document tag',
+        signature: '([ string $version = 1.0 [, string $encoding [, string $standalone , resource $xmlwriter ]]]): bool'
+    },
+    xmlwriter_start_dtd_attlist: {
+        description: 'Create start DTD AttList',
+        signature: '( string $name , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_dtd_element: {
+        description: 'Create start DTD element',
+        signature: '( string $qualifiedName , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_dtd_entity: {
+        description: 'Create start DTD Entity',
+        signature: '( string $name , bool $isparam , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_dtd: {
+        description: 'Create start DTD tag',
+        signature: '( string $qualifiedName [, string $publicId [, string $systemId , resource $xmlwriter ]]): bool'
+    },
+    xmlwriter_start_element_ns: {
+        description: 'Create start namespaced element tag',
+        signature: '( string $prefix , string $name , string $uri , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_element: {
+        description: 'Create start element tag',
+        signature: '( string $name , resource $xmlwriter ): bool'
+    },
+    xmlwriter_start_pi: {
+        description: 'Create start PI tag',
+        signature: '( string $target , resource $xmlwriter ): bool'
+    },
+    xmlwriter_text: {
+        description: 'Write text',
+        signature: '( string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_attribute_ns: {
+        description: 'Write full namespaced attribute',
+        signature: '( string $prefix , string $name , string $uri , string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_attribute: {
+        description: 'Write full attribute',
+        signature: '( string $name , string $value , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_cdata: {
+        description: 'Write full CDATA tag',
+        signature: '( string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_comment: {
+        description: 'Write full comment tag',
+        signature: '( string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_dtd_attlist: {
+        description: 'Write full DTD AttList tag',
+        signature: '( string $name , string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_dtd_element: {
+        description: 'Write full DTD element tag',
+        signature: '( string $name , string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_dtd_entity: {
+        description: 'Write full DTD Entity tag',
+        signature: '( string $name , string $content , bool $pe , string $pubid , string $sysid , string $ndataid , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_dtd: {
+        description: 'Write full DTD tag',
+        signature: '( string $name [, string $publicId [, string $systemId [, string $subset , resource $xmlwriter ]]]): bool'
+    },
+    xmlwriter_write_element_ns: {
+        description: 'Write full namespaced element tag',
+        signature: '( string $prefix , string $name , string $uri [, string $content , resource $xmlwriter ]): bool'
+    },
+    xmlwriter_write_element: {
+        description: 'Write full element tag',
+        signature: '( string $name [, string $content , resource $xmlwriter ]): bool'
+    },
+    xmlwriter_write_pi: {
+        description: 'Writes a PI',
+        signature: '( string $target , string $content , resource $xmlwriter ): bool'
+    },
+    xmlwriter_write_raw: {
+        description: 'Write a raw XML text',
+        signature: '( string $content , resource $xmlwriter ): bool'
+    },
 };
diff --git a/extensions/php-language-features/Source/features/phpGlobals.ts b/extensions/php-language-features/Source/features/phpGlobals.ts
index 889acd7a8422b..e366ccbe41165 100644
--- a/extensions/php-language-features/Source/features/phpGlobals.ts
+++ b/extensions/php-language-features/Source/features/phpGlobals.ts
@@ -2,323 +2,270 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 // file generated from PHP53Schema.xml using php-exclude_generate_php_globals.js
-
-export interface IEntry { description?: string; signature?: string }
-export interface IEntries { [name: string]: IEntry }
-
+export interface IEntry {
+    description?: string;
+    signature?: string;
+}
+export interface IEntries {
+    [name: string]: IEntry;
+}
 export const globalvariables: IEntries = {
-	$GLOBALS: {
-		description: 'An associative array containing references to all variables which are currently defined in the global scope of the script. The variable names are the keys of the array.',
-	},
-	$_SERVER: {
-		description: '$_SERVER is an array containing information such as headers, paths, and script locations. The entries in this array are created by the web server. There is no guarantee that every web server will provide any of these; servers may omit some, or provide others not listed here. That said, a large number of these variables are accounted for in the CGI/1.1 specification, so you should be able to expect those.',
-	},
-	$_GET: {
-		description: 'An associative array of variables passed to the current script via the URL parameters.',
-	},
-	$_POST: {
-		description: 'An associative array of variables passed to the current script via the HTTP POST method.',
-	},
-	$_FILES: {
-		description: 'An associative array of items uploaded to the current script via the HTTP POST method.',
-	},
-	$_REQUEST: {
-		description: 'An associative array that by default contains the contents of $_GET, $_POST and $_COOKIE.',
-	},
-	$_SESSION: {
-		description: 'An associative array containing session variables available to the current script. See the Session functions documentation for more information on how this is used.',
-	},
-	$_ENV: {
-		description: 'An associative array of variables passed to the current script via the environment method. \r\n\r\nThese variables are imported into PHP\'s global namespace from the environment under which the PHP parser is running. Many are provided by the shell under which PHP is running and different systems are likely running different kinds of shells, a definitive list is impossible. Please see your shell\'s documentation for a list of defined environment variables. \r\n\r\nOther environment variables include the CGI variables, placed there regardless of whether PHP is running as a server module or CGI processor.',
-	},
-	$_COOKIE: {
-		description: 'An associative array of variables passed to the current script via HTTP Cookies.',
-	},
-	$php_errormsg: {
-		description: '$php_errormsg is a variable containing the text of the last error message generated by PHP. This variable will only be available within the scope in which the error occurred, and only if the track_errors configuration option is turned on (it defaults to off).',
-	},
-	$HTTP_RAW_POST_DATA: {
-		description: '$HTTP_RAW_POST_DATA contains the raw POST data. See always_populate_raw_post_data',
-	},
-	$http_response_header: {
-		description: 'The $http_response_header array is similar to the get_headers() function. When using the HTTP wrapper, $http_response_header will be populated with the HTTP response headers. $http_response_header will be created in the local scope.',
-	},
-	$argc: {
-		description: 'Contains the number of arguments passed to the current script when running from the command line.',
-	},
-	$argv: {
-		description: 'Contains an array of all the arguments passed to the script when running from the command line.',
-	},
-	$this: {
-		description: 'Refers to the current object',
-	},
+    $GLOBALS: {
+        description: 'An associative array containing references to all variables which are currently defined in the global scope of the script. The variable names are the keys of the array.',
+    },
+    $_SERVER: {
+        description: '$_SERVER is an array containing information such as headers, paths, and script locations. The entries in this array are created by the web server. There is no guarantee that every web server will provide any of these; servers may omit some, or provide others not listed here. That said, a large number of these variables are accounted for in the CGI/1.1 specification, so you should be able to expect those.',
+    },
+    $_GET: {
+        description: 'An associative array of variables passed to the current script via the URL parameters.',
+    },
+    $_POST: {
+        description: 'An associative array of variables passed to the current script via the HTTP POST method.',
+    },
+    $_FILES: {
+        description: 'An associative array of items uploaded to the current script via the HTTP POST method.',
+    },
+    $_REQUEST: {
+        description: 'An associative array that by default contains the contents of $_GET, $_POST and $_COOKIE.',
+    },
+    $_SESSION: {
+        description: 'An associative array containing session variables available to the current script. See the Session functions documentation for more information on how this is used.',
+    },
+    $_ENV: {
+        description: 'An associative array of variables passed to the current script via the environment method. \r\n\r\nThese variables are imported into PHP\'s global namespace from the environment under which the PHP parser is running. Many are provided by the shell under which PHP is running and different systems are likely running different kinds of shells, a definitive list is impossible. Please see your shell\'s documentation for a list of defined environment variables. \r\n\r\nOther environment variables include the CGI variables, placed there regardless of whether PHP is running as a server module or CGI processor.',
+    },
+    $_COOKIE: {
+        description: 'An associative array of variables passed to the current script via HTTP Cookies.',
+    },
+    $php_errormsg: {
+        description: '$php_errormsg is a variable containing the text of the last error message generated by PHP. This variable will only be available within the scope in which the error occurred, and only if the track_errors configuration option is turned on (it defaults to off).',
+    },
+    $HTTP_RAW_POST_DATA: {
+        description: '$HTTP_RAW_POST_DATA contains the raw POST data. See always_populate_raw_post_data',
+    },
+    $http_response_header: {
+        description: 'The $http_response_header array is similar to the get_headers() function. When using the HTTP wrapper, $http_response_header will be populated with the HTTP response headers. $http_response_header will be created in the local scope.',
+    },
+    $argc: {
+        description: 'Contains the number of arguments passed to the current script when running from the command line.',
+    },
+    $argv: {
+        description: 'Contains an array of all the arguments passed to the script when running from the command line.',
+    },
+    $this: {
+        description: 'Refers to the current object',
+    },
 };
 export const compiletimeconstants: IEntries = {
-	__CLASS__: {
-		description: 'The class name. (Added in PHP 4.3.0) As of PHP 5 this constant returns the class name as it was declared (case-sensitive). In PHP 4 its value is always lowercased.',
-	},
-	__DIR__: {
-		description: 'The directory of the file. If used inside an include, the directory of the included file is returned. This is equivalent to dirname(__FILE__). This directory name does not have a trailing slash unless it is the root directory. (Added in PHP 5.3.0.)',
-	},
-	__FILE__: {
-		description: 'The full path and filename of the file. If used inside an include, the name of the included file is returned. Since PHP 4.0.2, __FILE__ always contains an absolute path with symlinks resolved whereas in older versions it contained relative path under some circumstances.',
-	},
-	__FUNCTION__: {
-		description: 'The function name. (Added in PHP 4.3.0) As of PHP 5 this constant returns the function name as it was declared (case-sensitive). In PHP 4 its value is always lowercased.',
-	},
-	__LINE__: {
-		description: 'The current line number of the file.',
-	},
-	__METHOD__: {
-		description: 'The class method name. (Added in PHP 5.0.0) The method name is returned as it was declared (case-sensitive).',
-	},
-	__NAMESPACE__: {
-		description: 'The name of the current namespace (case-sensitive). This constant is defined in compile-time (Added in PHP 5.3.0).',
-	},
-	TRUE: {
-	},
-	FALSE: {
-	},
-	NULL: {
-	},
-	M_PI: {
-		description: 'The constant Pi: 3.14159265358979323846',
-	},
-	M_E: {
-		description: 'The constant e: 2.7182818284590452354',
-	},
-	M_LOG2E: {
-		description: 'The constant log_2 e: 1.4426950408889634074',
-	},
-	M_LOG10E: {
-		description: 'The constant log_10 e: 0.43429448190325182765',
-	},
-	M_LN2: {
-		description: 'The constant log_e 2: 0.69314718055994530942',
-	},
-	M_LN10: {
-		description: 'The constant log_e 10: 2.30258509299404568402',
-	},
-	M_PI_2: {
-		description: 'The constant pi/2: 1.57079632679489661923',
-	},
-	M_PI_4: {
-		description: 'The constant pi/4: 0.78539816339744830962',
-	},
-	M_1_PI: {
-		description: 'The constant 1/pi: 0.31830988618379067154',
-	},
-	M_2_PI: {
-		description: 'The constant 2/pi: 0.63661977236758134308',
-	},
-	M_SQRTPI: {
-		description: 'The constant sqrt(pi): 1.77245385090551602729',
-	},
-	M_2_SQRTPI: {
-		description: 'The constant 2/sqrt(pi): 1.12837916709551257390',
-	},
-	M_SQRT2: {
-		description: 'The constant sqrt(2): 1.41421356237309504880',
-	},
-	M_SQRT3: {
-		description: 'The constant sqrt(3): 1.73205080756887729352',
-	},
-	M_SQRT1_2: {
-		description: 'The constant 1/sqrt(2): 0.7071067811865475244',
-	},
-	M_LNPI: {
-		description: 'The constant log_e(pi): 1.14472988584940017414',
-	},
-	M_EULER: {
-		description: 'Euler constant: 0.57721566490153286061',
-	},
-	PHP_ROUND_HALF_UP: {
-		description: 'Round halves up = 1',
-	},
-	PHP_ROUND_HALF_DOWN: {
-		description: 'Round halves down = 2',
-	},
-	PHP_ROUND_HALF_EVEN: {
-		description: 'Round halves to even numbers = 3',
-	},
-	PHP_ROUND_HALF_ODD: {
-		description: 'Round halvesto odd numbers = 4',
-	},
-	NAN: {
-		description: 'NAN (as a float): Not A Number',
-	},
-	INF: {
-		description: 'INF (as a float): The infinite',
-	},
-	PASSWORD_BCRYPT: {
-		description: 'PASSWORD_BCRYPT is used to create new password hashes using the CRYPT_BLOWFISH algorithm.',
-	},
-	PASSWORD_DEFAULT: {
-		description: 'The default algorithm to use for hashing if no algorithm is provided. This may change in newer PHP releases when newer, stronger hashing algorithms are supported.',
-	},
+    __CLASS__: {
+        description: 'The class name. (Added in PHP 4.3.0) As of PHP 5 this constant returns the class name as it was declared (case-sensitive). In PHP 4 its value is always lowercased.',
+    },
+    __DIR__: {
+        description: 'The directory of the file. If used inside an include, the directory of the included file is returned. This is equivalent to dirname(__FILE__). This directory name does not have a trailing slash unless it is the root directory. (Added in PHP 5.3.0.)',
+    },
+    __FILE__: {
+        description: 'The full path and filename of the file. If used inside an include, the name of the included file is returned. Since PHP 4.0.2, __FILE__ always contains an absolute path with symlinks resolved whereas in older versions it contained relative path under some circumstances.',
+    },
+    __FUNCTION__: {
+        description: 'The function name. (Added in PHP 4.3.0) As of PHP 5 this constant returns the function name as it was declared (case-sensitive). In PHP 4 its value is always lowercased.',
+    },
+    __LINE__: {
+        description: 'The current line number of the file.',
+    },
+    __METHOD__: {
+        description: 'The class method name. (Added in PHP 5.0.0) The method name is returned as it was declared (case-sensitive).',
+    },
+    __NAMESPACE__: {
+        description: 'The name of the current namespace (case-sensitive). This constant is defined in compile-time (Added in PHP 5.3.0).',
+    },
+    TRUE: {},
+    FALSE: {},
+    NULL: {},
+    M_PI: {
+        description: 'The constant Pi: 3.14159265358979323846',
+    },
+    M_E: {
+        description: 'The constant e: 2.7182818284590452354',
+    },
+    M_LOG2E: {
+        description: 'The constant log_2 e: 1.4426950408889634074',
+    },
+    M_LOG10E: {
+        description: 'The constant log_10 e: 0.43429448190325182765',
+    },
+    M_LN2: {
+        description: 'The constant log_e 2: 0.69314718055994530942',
+    },
+    M_LN10: {
+        description: 'The constant log_e 10: 2.30258509299404568402',
+    },
+    M_PI_2: {
+        description: 'The constant pi/2: 1.57079632679489661923',
+    },
+    M_PI_4: {
+        description: 'The constant pi/4: 0.78539816339744830962',
+    },
+    M_1_PI: {
+        description: 'The constant 1/pi: 0.31830988618379067154',
+    },
+    M_2_PI: {
+        description: 'The constant 2/pi: 0.63661977236758134308',
+    },
+    M_SQRTPI: {
+        description: 'The constant sqrt(pi): 1.77245385090551602729',
+    },
+    M_2_SQRTPI: {
+        description: 'The constant 2/sqrt(pi): 1.12837916709551257390',
+    },
+    M_SQRT2: {
+        description: 'The constant sqrt(2): 1.41421356237309504880',
+    },
+    M_SQRT3: {
+        description: 'The constant sqrt(3): 1.73205080756887729352',
+    },
+    M_SQRT1_2: {
+        description: 'The constant 1/sqrt(2): 0.7071067811865475244',
+    },
+    M_LNPI: {
+        description: 'The constant log_e(pi): 1.14472988584940017414',
+    },
+    M_EULER: {
+        description: 'Euler constant: 0.57721566490153286061',
+    },
+    PHP_ROUND_HALF_UP: {
+        description: 'Round halves up = 1',
+    },
+    PHP_ROUND_HALF_DOWN: {
+        description: 'Round halves down = 2',
+    },
+    PHP_ROUND_HALF_EVEN: {
+        description: 'Round halves to even numbers = 3',
+    },
+    PHP_ROUND_HALF_ODD: {
+        description: 'Round halvesto odd numbers = 4',
+    },
+    NAN: {
+        description: 'NAN (as a float): Not A Number',
+    },
+    INF: {
+        description: 'INF (as a float): The infinite',
+    },
+    PASSWORD_BCRYPT: {
+        description: 'PASSWORD_BCRYPT is used to create new password hashes using the CRYPT_BLOWFISH algorithm.',
+    },
+    PASSWORD_DEFAULT: {
+        description: 'The default algorithm to use for hashing if no algorithm is provided. This may change in newer PHP releases when newer, stronger hashing algorithms are supported.',
+    },
 };
 export const keywords: IEntries = {
-	define: {
-		description: 'Defines a named constant at runtime.',
-		signature: '( string $name , mixed $value [, bool $case_insensitive = false ] ): bool'
-	},
-	die: {
-		description: 'This language construct is equivalent to exit().',
-	},
-	echo: {
-		description: 'Outputs all parameters. \r\n\r\necho is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo, the parameters must not be enclosed within parentheses.\r\n\r\necho also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
-		signature: '( string $arg1 [, string $... ] ): void'
-	},
-	empty: {
-		description: 'Determine whether a variable is considered to be empty.',
-		signature: '( mixed $var ): bool'
-	},
-	exit: {
-		description: 'Terminates execution of the script. Shutdown functions and object destructors will always be executed even if exit() is called.',
-		signature: '([ string $status ] )\r\nvoid exit ( int $status ): void'
-	},
-	eval: {
-		description: 'Evaluates the string given in code_str as PHP code. Among other things, this can be useful for storing code in a database text field for later execution.\r\nThere are some factors to keep in mind when using eval(). Remember that the string passed must be valid PHP code, including things like terminating statements with a semicolon so the parser doesn\'t die on the line after the eval(), and properly escaping things in code_str. To mix HTML output and PHP code you can use a closing PHP tag to leave PHP mode.\r\nAlso remember that variables given values under eval() will retain these values in the main script afterwards.',
-		signature: '( string $code_str ): mixed'
-	},
-	include: {
-		description: 'The include statement includes and evaluates the specified file.',
-	},
-	include_once: {
-		description: 'The include_once statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
-	},
-	isset: {
-		description: 'Determine if a variable is set and is not NULL. \r\n\r\nIf a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte is not equivalent to the PHP NULL constant. \r\n\r\nIf multiple parameters are supplied then isset() will return TRUE only if all of the parameters are set. Evaluation goes from left to right and stops as soon as an unset variable is encountered.',
-		signature: '( mixed $var [, mixed $... ] ): bool'
-	},
-	list: {
-		description: 'Like array(), this is not really a function, but a language construct. list() is used to assign a list of variables in one operation.',
-		signature: '( mixed $varname [, mixed $... ] ): array'
-	},
-	require: {
-		description: 'require is identical to include except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include only emits a warning (E_WARNING) which allows the script to continue.',
-	},
-	require_once: {
-		description: 'The require_once statement is identical to require except PHP will check if the file has already been included, and if so, not include (require) it again.',
-	},
-	return: {
-		description: 'If called from within a function, the return statement immediately ends execution of the current function, and returns its argument as the value of the function call. return will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was included or required, then control is passed back to the calling file. Furthermore, if the current script file was included, then the value given to return will be returned as the value of the include call. If return is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
-	},
-	print: {
-		description: 'Outputs arg. \r\n\r\nprint() is not actually a real function (it is a language construct) so you are not required to use parentheses with its argument list.',
-		signature: '( string $arg ): int'
-	},
-	unset: {
-		description: 'unset() destroys the specified variables. \r\n\r\nThe behavior of unset() inside of a function can vary depending on what type of variable you are attempting to destroy. \r\n\r\nIf a globalized variable is unset() inside of a function, only the local variable is destroyed. The variable in the calling environment will retain the same value as before unset() was called.',
-		signature: '( mixed $var [, mixed $... ] ): void'
-	},
-	yield: {
-		description: 'The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.',
-	},
-	abstract: {
-	},
-	and: {
-	},
-	array: {
-	},
-	as: {
-	},
-	break: {
-	},
-	case: {
-	},
-	catch: {
-	},
-	class: {
-	},
-	clone: {
-	},
-	const: {
-	},
-	continue: {
-	},
-	declare: {
-	},
-	default: {
-	},
-	do: {
-	},
-	else: {
-	},
-	elseif: {
-	},
-	enddeclare: {
-	},
-	endfor: {
-	},
-	endforeach: {
-	},
-	endif: {
-	},
-	endswitch: {
-	},
-	endwhile: {
-	},
-	extends: {
-	},
-	final: {
-	},
-	finally: {
-	},
-	for: {
-	},
-	foreach: {
-	},
-	function: {
-	},
-	global: {
-	},
-	goto: {
-	},
-	if: {
-	},
-	implements: {
-	},
-	interface: {
-	},
-	instanceof: {
-	},
-	insteadOf: {
-	},
-	namespace: {
-	},
-	new: {
-	},
-	or: {
-	},
-	parent: {
-	},
-	private: {
-	},
-	protected: {
-	},
-	public: {
-	},
-	self: {
-	},
-	static: {
-	},
-	switch: {
-	},
-	throw: {
-	},
-	trait: {
-	},
-	try: {
-	},
-	use: {
-	},
-	var: {
-	},
-	while: {
-	},
-	xor: {
-	},
+    define: {
+        description: 'Defines a named constant at runtime.',
+        signature: '( string $name , mixed $value [, bool $case_insensitive = false ] ): bool'
+    },
+    die: {
+        description: 'This language construct is equivalent to exit().',
+    },
+    echo: {
+        description: 'Outputs all parameters. \r\n\r\necho is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo, the parameters must not be enclosed within parentheses.\r\n\r\necho also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
+        signature: '( string $arg1 [, string $... ] ): void'
+    },
+    empty: {
+        description: 'Determine whether a variable is considered to be empty.',
+        signature: '( mixed $var ): bool'
+    },
+    exit: {
+        description: 'Terminates execution of the script. Shutdown functions and object destructors will always be executed even if exit() is called.',
+        signature: '([ string $status ] )\r\nvoid exit ( int $status ): void'
+    },
+    eval: {
+        description: 'Evaluates the string given in code_str as PHP code. Among other things, this can be useful for storing code in a database text field for later execution.\r\nThere are some factors to keep in mind when using eval(). Remember that the string passed must be valid PHP code, including things like terminating statements with a semicolon so the parser doesn\'t die on the line after the eval(), and properly escaping things in code_str. To mix HTML output and PHP code you can use a closing PHP tag to leave PHP mode.\r\nAlso remember that variables given values under eval() will retain these values in the main script afterwards.',
+        signature: '( string $code_str ): mixed'
+    },
+    include: {
+        description: 'The include statement includes and evaluates the specified file.',
+    },
+    include_once: {
+        description: 'The include_once statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
+    },
+    isset: {
+        description: 'Determine if a variable is set and is not NULL. \r\n\r\nIf a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte is not equivalent to the PHP NULL constant. \r\n\r\nIf multiple parameters are supplied then isset() will return TRUE only if all of the parameters are set. Evaluation goes from left to right and stops as soon as an unset variable is encountered.',
+        signature: '( mixed $var [, mixed $... ] ): bool'
+    },
+    list: {
+        description: 'Like array(), this is not really a function, but a language construct. list() is used to assign a list of variables in one operation.',
+        signature: '( mixed $varname [, mixed $... ] ): array'
+    },
+    require: {
+        description: 'require is identical to include except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include only emits a warning (E_WARNING) which allows the script to continue.',
+    },
+    require_once: {
+        description: 'The require_once statement is identical to require except PHP will check if the file has already been included, and if so, not include (require) it again.',
+    },
+    return: {
+        description: 'If called from within a function, the return statement immediately ends execution of the current function, and returns its argument as the value of the function call. return will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was included or required, then control is passed back to the calling file. Furthermore, if the current script file was included, then the value given to return will be returned as the value of the include call. If return is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
+    },
+    print: {
+        description: 'Outputs arg. \r\n\r\nprint() is not actually a real function (it is a language construct) so you are not required to use parentheses with its argument list.',
+        signature: '( string $arg ): int'
+    },
+    unset: {
+        description: 'unset() destroys the specified variables. \r\n\r\nThe behavior of unset() inside of a function can vary depending on what type of variable you are attempting to destroy. \r\n\r\nIf a globalized variable is unset() inside of a function, only the local variable is destroyed. The variable in the calling environment will retain the same value as before unset() was called.',
+        signature: '( mixed $var [, mixed $... ] ): void'
+    },
+    yield: {
+        description: 'The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.',
+    },
+    abstract: {},
+    and: {},
+    array: {},
+    as: {},
+    break: {},
+    case: {},
+    catch: {},
+    class: {},
+    clone: {},
+    const: {},
+    continue: {},
+    declare: {},
+    default: {},
+    do: {},
+    else: {},
+    elseif: {},
+    enddeclare: {},
+    endfor: {},
+    endforeach: {},
+    endif: {},
+    endswitch: {},
+    endwhile: {},
+    extends: {},
+    final: {},
+    finally: {},
+    for: {},
+    foreach: {},
+    function: {},
+    global: {},
+    goto: {},
+    if: {},
+    implements: {},
+    interface: {},
+    instanceof: {},
+    insteadOf: {},
+    namespace: {},
+    new: {},
+    or: {},
+    parent: {},
+    private: {},
+    protected: {},
+    public: {},
+    self: {},
+    static: {},
+    switch: {},
+    throw: {},
+    trait: {},
+    try: {},
+    use: {},
+    var: {},
+    while: {},
+    xor: {},
 };
diff --git a/extensions/php-language-features/Source/features/signatureHelpProvider.ts b/extensions/php-language-features/Source/features/signatureHelpProvider.ts
index ea3fec492432d..cbf502db93887 100644
--- a/extensions/php-language-features/Source/features/signatureHelpProvider.ts
+++ b/extensions/php-language-features/Source/features/signatureHelpProvider.ts
@@ -2,11 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { SignatureHelpProvider, SignatureHelp, SignatureInformation, CancellationToken, TextDocument, Position, workspace } from 'vscode';
 import * as phpGlobals from './phpGlobals';
 import * as phpGlobalFunctions from './phpGlobalFunctions';
-
 const _NL = '\n'.charCodeAt(0);
 const _TAB = '\t'.charCodeAt(0);
 const _WSB = ' '.charCodeAt(0);
@@ -26,148 +24,139 @@ const _A = 'A'.charCodeAt(0);
 const _Z = 'Z'.charCodeAt(0);
 const _0 = '0'.charCodeAt(0);
 const _9 = '9'.charCodeAt(0);
-
 const BOF = 0;
-
-
 class BackwardIterator {
-	private lineNumber: number;
-	private offset: number;
-	private line: string;
-	private model: TextDocument;
-
-	constructor(model: TextDocument, offset: number, lineNumber: number) {
-		this.lineNumber = lineNumber;
-		this.offset = offset;
-		this.line = model.lineAt(this.lineNumber).text;
-		this.model = model;
-	}
-
-	public hasNext(): boolean {
-		return this.lineNumber >= 0;
-	}
-
-	public next(): number {
-		if (this.offset < 0) {
-			if (this.lineNumber > 0) {
-				this.lineNumber--;
-				this.line = this.model.lineAt(this.lineNumber).text;
-				this.offset = this.line.length - 1;
-				return _NL;
-			}
-			this.lineNumber = -1;
-			return BOF;
-		}
-		const ch = this.line.charCodeAt(this.offset);
-		this.offset--;
-		return ch;
-	}
-
+    private lineNumber: number;
+    private offset: number;
+    private line: string;
+    private model: TextDocument;
+    constructor(model: TextDocument, offset: number, lineNumber: number) {
+        this.lineNumber = lineNumber;
+        this.offset = offset;
+        this.line = model.lineAt(this.lineNumber).text;
+        this.model = model;
+    }
+    public hasNext(): boolean {
+        return this.lineNumber >= 0;
+    }
+    public next(): number {
+        if (this.offset < 0) {
+            if (this.lineNumber > 0) {
+                this.lineNumber--;
+                this.line = this.model.lineAt(this.lineNumber).text;
+                this.offset = this.line.length - 1;
+                return _NL;
+            }
+            this.lineNumber = -1;
+            return BOF;
+        }
+        const ch = this.line.charCodeAt(this.offset);
+        this.offset--;
+        return ch;
+    }
 }
-
-
 export default class PHPSignatureHelpProvider implements SignatureHelpProvider {
-
-	public provideSignatureHelp(document: TextDocument, position: Position, _token: CancellationToken): Promise | null {
-		const enable = workspace.getConfiguration('php').get('suggest.basic', true);
-		if (!enable) {
-			return null;
-		}
-
-		const iterator = new BackwardIterator(document, position.character - 1, position.line);
-
-		const paramCount = this.readArguments(iterator);
-		if (paramCount < 0) {
-			return null;
-		}
-
-		const ident = this.readIdent(iterator);
-		if (!ident) {
-			return null;
-		}
-
-		const entry = phpGlobalFunctions.globalfunctions[ident] || phpGlobals.keywords[ident];
-		if (!entry || !entry.signature) {
-			return null;
-		}
-		const paramsString = entry.signature.substring(0, entry.signature.lastIndexOf(')') + 1);
-		const signatureInfo = new SignatureInformation(ident + paramsString, entry.description);
-
-		const re = /\w*\s+\&?\$[\w_\.]+|void/g;
-		let match: RegExpExecArray | null = null;
-		while ((match = re.exec(paramsString)) !== null) {
-			signatureInfo.parameters.push({ label: match[0], documentation: '' });
-		}
-		const ret = new SignatureHelp();
-		ret.signatures.push(signatureInfo);
-		ret.activeSignature = 0;
-		ret.activeParameter = Math.min(paramCount, signatureInfo.parameters.length - 1);
-		return Promise.resolve(ret);
-	}
-
-	private readArguments(iterator: BackwardIterator): number {
-		let parentNesting = 0;
-		let bracketNesting = 0;
-		let curlyNesting = 0;
-		let paramCount = 0;
-		while (iterator.hasNext()) {
-			const ch = iterator.next();
-			switch (ch) {
-				case _LParent:
-					parentNesting--;
-					if (parentNesting < 0) {
-						return paramCount;
-					}
-					break;
-				case _RParent: parentNesting++; break;
-				case _LCurly: curlyNesting--; break;
-				case _RCurly: curlyNesting++; break;
-				case _LBracket: bracketNesting--; break;
-				case _RBracket: bracketNesting++; break;
-				case _DQuote:
-				case _Quote:
-					while (iterator.hasNext() && ch !== iterator.next()) {
-						// find the closing quote or double quote
-					}
-					break;
-				case _Comma:
-					if (!parentNesting && !bracketNesting && !curlyNesting) {
-						paramCount++;
-					}
-					break;
-			}
-		}
-		return -1;
-	}
-
-	private isIdentPart(ch: number): boolean {
-		if (ch === _USC || // _
-			ch >= _a && ch <= _z || // a-z
-			ch >= _A && ch <= _Z || // A-Z
-			ch >= _0 && ch <= _9 || // 0/9
-			ch >= 0x80 && ch <= 0xFFFF) { // nonascii
-
-			return true;
-		}
-		return false;
-	}
-
-	private readIdent(iterator: BackwardIterator): string {
-		let identStarted = false;
-		let ident = '';
-		while (iterator.hasNext()) {
-			const ch = iterator.next();
-			if (!identStarted && (ch === _WSB || ch === _TAB || ch === _NL)) {
-				continue;
-			}
-			if (this.isIdentPart(ch)) {
-				identStarted = true;
-				ident = String.fromCharCode(ch) + ident;
-			} else if (identStarted) {
-				return ident;
-			}
-		}
-		return ident;
-	}
-
+    public provideSignatureHelp(document: TextDocument, position: Position, _token: CancellationToken): Promise | null {
+        const enable = workspace.getConfiguration('php').get('suggest.basic', true);
+        if (!enable) {
+            return null;
+        }
+        const iterator = new BackwardIterator(document, position.character - 1, position.line);
+        const paramCount = this.readArguments(iterator);
+        if (paramCount < 0) {
+            return null;
+        }
+        const ident = this.readIdent(iterator);
+        if (!ident) {
+            return null;
+        }
+        const entry = phpGlobalFunctions.globalfunctions[ident] || phpGlobals.keywords[ident];
+        if (!entry || !entry.signature) {
+            return null;
+        }
+        const paramsString = entry.signature.substring(0, entry.signature.lastIndexOf(')') + 1);
+        const signatureInfo = new SignatureInformation(ident + paramsString, entry.description);
+        const re = /\w*\s+\&?\$[\w_\.]+|void/g;
+        let match: RegExpExecArray | null = null;
+        while ((match = re.exec(paramsString)) !== null) {
+            signatureInfo.parameters.push({ label: match[0], documentation: '' });
+        }
+        const ret = new SignatureHelp();
+        ret.signatures.push(signatureInfo);
+        ret.activeSignature = 0;
+        ret.activeParameter = Math.min(paramCount, signatureInfo.parameters.length - 1);
+        return Promise.resolve(ret);
+    }
+    private readArguments(iterator: BackwardIterator): number {
+        let parentNesting = 0;
+        let bracketNesting = 0;
+        let curlyNesting = 0;
+        let paramCount = 0;
+        while (iterator.hasNext()) {
+            const ch = iterator.next();
+            switch (ch) {
+                case _LParent:
+                    parentNesting--;
+                    if (parentNesting < 0) {
+                        return paramCount;
+                    }
+                    break;
+                case _RParent:
+                    parentNesting++;
+                    break;
+                case _LCurly:
+                    curlyNesting--;
+                    break;
+                case _RCurly:
+                    curlyNesting++;
+                    break;
+                case _LBracket:
+                    bracketNesting--;
+                    break;
+                case _RBracket:
+                    bracketNesting++;
+                    break;
+                case _DQuote:
+                case _Quote:
+                    while (iterator.hasNext() && ch !== iterator.next()) {
+                        // find the closing quote or double quote
+                    }
+                    break;
+                case _Comma:
+                    if (!parentNesting && !bracketNesting && !curlyNesting) {
+                        paramCount++;
+                    }
+                    break;
+            }
+        }
+        return -1;
+    }
+    private isIdentPart(ch: number): boolean {
+        if (ch === _USC || // _
+            ch >= _a && ch <= _z || // a-z
+            ch >= _A && ch <= _Z || // A-Z
+            ch >= _0 && ch <= _9 || // 0/9
+            ch >= 0x80 && ch <= 0xFFFF) { // nonascii
+            return true;
+        }
+        return false;
+    }
+    private readIdent(iterator: BackwardIterator): string {
+        let identStarted = false;
+        let ident = '';
+        while (iterator.hasNext()) {
+            const ch = iterator.next();
+            if (!identStarted && (ch === _WSB || ch === _TAB || ch === _NL)) {
+                continue;
+            }
+            if (this.isIdentPart(ch)) {
+                identStarted = true;
+                ident = String.fromCharCode(ch) + ident;
+            }
+            else if (identStarted) {
+                return ident;
+            }
+        }
+        return ident;
+    }
 }
diff --git a/extensions/php-language-features/Source/features/utils/async.ts b/extensions/php-language-features/Source/features/utils/async.ts
index 84a54c4fcd1dd..fe8b67f5780b4 100644
--- a/extensions/php-language-features/Source/features/utils/async.ts
+++ b/extensions/php-language-features/Source/features/utils/async.ts
@@ -2,11 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export interface ITask {
-	(): T;
+    (): T;
 }
-
 /**
  * A helper to prevent accumulation of sequential async tasks.
  *
@@ -28,55 +26,44 @@ export interface ITask {
  * 		}
  */
 export class Throttler {
-
-	private activePromise: Promise | null;
-	private queuedPromise: Promise | null;
-	private queuedPromiseFactory: ITask> | null;
-
-	constructor() {
-		this.activePromise = null;
-		this.queuedPromise = null;
-		this.queuedPromiseFactory = null;
-	}
-
-	public queue(promiseFactory: ITask>): Promise {
-		if (this.activePromise) {
-			this.queuedPromiseFactory = promiseFactory;
-
-			if (!this.queuedPromise) {
-				const onComplete = () => {
-					this.queuedPromise = null;
-
-					const result = this.queue(this.queuedPromiseFactory!);
-					this.queuedPromiseFactory = null;
-
-					return result;
-				};
-
-				this.queuedPromise = new Promise((resolve) => {
-					this.activePromise!.then(onComplete, onComplete).then(resolve);
-				});
-			}
-
-			return new Promise((resolve, reject) => {
-				this.queuedPromise!.then(resolve, reject);
-			});
-		}
-
-		this.activePromise = promiseFactory();
-
-		return new Promise((resolve, reject) => {
-			this.activePromise!.then((result: T) => {
-				this.activePromise = null;
-				resolve(result);
-			}, (err: any) => {
-				this.activePromise = null;
-				reject(err);
-			});
-		});
-	}
+    private activePromise: Promise | null;
+    private queuedPromise: Promise | null;
+    private queuedPromiseFactory: ITask> | null;
+    constructor() {
+        this.activePromise = null;
+        this.queuedPromise = null;
+        this.queuedPromiseFactory = null;
+    }
+    public queue(promiseFactory: ITask>): Promise {
+        if (this.activePromise) {
+            this.queuedPromiseFactory = promiseFactory;
+            if (!this.queuedPromise) {
+                const onComplete = () => {
+                    this.queuedPromise = null;
+                    const result = this.queue(this.queuedPromiseFactory!);
+                    this.queuedPromiseFactory = null;
+                    return result;
+                };
+                this.queuedPromise = new Promise((resolve) => {
+                    this.activePromise!.then(onComplete, onComplete).then(resolve);
+                });
+            }
+            return new Promise((resolve, reject) => {
+                this.queuedPromise!.then(resolve, reject);
+            });
+        }
+        this.activePromise = promiseFactory();
+        return new Promise((resolve, reject) => {
+            this.activePromise!.then((result: T) => {
+                this.activePromise = null;
+                resolve(result);
+            }, (err: any) => {
+                this.activePromise = null;
+                reject(err);
+            });
+        });
+    }
 }
-
 /**
  * A helper to delay execution of a task that is being requested often.
  *
@@ -101,67 +88,54 @@ export class Throttler {
  * 		}
  */
 export class Delayer {
-
-	public defaultDelay: number;
-	private timeout: NodeJS.Timer | null;
-	private completionPromise: Promise | null;
-	private onResolve: ((value: T | PromiseLike | undefined) => void) | null;
-	private task: ITask | null;
-
-	constructor(defaultDelay: number) {
-		this.defaultDelay = defaultDelay;
-		this.timeout = null;
-		this.completionPromise = null;
-		this.onResolve = null;
-		this.task = null;
-	}
-
-	public trigger(task: ITask, delay: number = this.defaultDelay): Promise {
-		this.task = task;
-		this.cancelTimeout();
-
-		if (!this.completionPromise) {
-			this.completionPromise = new Promise((resolve) => {
-				this.onResolve = resolve;
-			}).then(() => {
-				this.completionPromise = null;
-				this.onResolve = null;
-
-				const result = this.task!();
-				this.task = null;
-
-				return result;
-			});
-		}
-
-		this.timeout = setTimeout(() => {
-			this.timeout = null;
-			this.onResolve!(undefined);
-		}, delay);
-
-		return this.completionPromise;
-	}
-
-	public isTriggered(): boolean {
-		return this.timeout !== null;
-	}
-
-	public cancel(): void {
-		this.cancelTimeout();
-
-		if (this.completionPromise) {
-			this.completionPromise = null;
-		}
-	}
-
-	private cancelTimeout(): void {
-		if (this.timeout !== null) {
-			clearTimeout(this.timeout);
-			this.timeout = null;
-		}
-	}
+    public defaultDelay: number;
+    private timeout: NodeJS.Timer | null;
+    private completionPromise: Promise | null;
+    private onResolve: ((value: T | PromiseLike | undefined) => void) | null;
+    private task: ITask | null;
+    constructor(defaultDelay: number) {
+        this.defaultDelay = defaultDelay;
+        this.timeout = null;
+        this.completionPromise = null;
+        this.onResolve = null;
+        this.task = null;
+    }
+    public trigger(task: ITask, delay: number = this.defaultDelay): Promise {
+        this.task = task;
+        this.cancelTimeout();
+        if (!this.completionPromise) {
+            this.completionPromise = new Promise((resolve) => {
+                this.onResolve = resolve;
+            }).then(() => {
+                this.completionPromise = null;
+                this.onResolve = null;
+                const result = this.task!();
+                this.task = null;
+                return result;
+            });
+        }
+        this.timeout = setTimeout(() => {
+            this.timeout = null;
+            this.onResolve!(undefined);
+        }, delay);
+        return this.completionPromise;
+    }
+    public isTriggered(): boolean {
+        return this.timeout !== null;
+    }
+    public cancel(): void {
+        this.cancelTimeout();
+        if (this.completionPromise) {
+            this.completionPromise = null;
+        }
+    }
+    private cancelTimeout(): void {
+        if (this.timeout !== null) {
+            clearTimeout(this.timeout);
+            this.timeout = null;
+        }
+    }
 }
-
 /**
  * A helper to delay execution of a task that is being requested often, while
  * preventing accumulation of consecutive executions, while the task runs.
@@ -170,16 +144,12 @@ export class Delayer {
  * helpers, for an analogy.
  */
 export class ThrottledDelayer extends Delayer> {
-
-	private throttler: Throttler;
-
-	constructor(defaultDelay: number) {
-		super(defaultDelay);
-
-		this.throttler = new Throttler();
-	}
-
-	public override trigger(promiseFactory: ITask>, delay?: number): Promise> {
-		return super.trigger(() => this.throttler.queue(promiseFactory), delay);
-	}
+    private throttler: Throttler;
+    constructor(defaultDelay: number) {
+        super(defaultDelay);
+        this.throttler = new Throttler();
+    }
+    public override trigger(promiseFactory: ITask>, delay?: number): Promise> {
+        return super.trigger(() => this.throttler.queue(promiseFactory), delay);
+    }
 }
diff --git a/extensions/php-language-features/Source/features/utils/markedTextUtil.ts b/extensions/php-language-features/Source/features/utils/markedTextUtil.ts
index 856fad050e503..b8908001b2e29 100644
--- a/extensions/php-language-features/Source/features/utils/markedTextUtil.ts
+++ b/extensions/php-language-features/Source/features/utils/markedTextUtil.ts
@@ -2,9 +2,7 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { MarkedString } from 'vscode';
-
 export function textToMarkedString(text: string): MarkedString {
-	return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
-}
\ No newline at end of file
+    return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
+}
diff --git a/extensions/php-language-features/Source/features/validationProvider.ts b/extensions/php-language-features/Source/features/validationProvider.ts
index b6e38ee27ccb4..252ef456e071b 100644
--- a/extensions/php-language-features/Source/features/validationProvider.ts
+++ b/extensions/php-language-features/Source/features/validationProvider.ts
@@ -2,324 +2,302 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as cp from 'child_process';
 import { StringDecoder } from 'string_decoder';
 import which from 'which';
 import * as path from 'path';
 import * as vscode from 'vscode';
 import { ThrottledDelayer } from './utils/async';
-
 const enum Setting {
-	Run = 'php.validate.run',
-	Enable = 'php.validate.enable',
-	ExecutablePath = 'php.validate.executablePath',
+    Run = 'php.validate.run',
+    Enable = 'php.validate.enable',
+    ExecutablePath = 'php.validate.executablePath'
 }
-
 export class LineDecoder {
-	private stringDecoder: StringDecoder;
-	private remaining: string | null;
-
-	constructor(encoding: BufferEncoding = 'utf8') {
-		this.stringDecoder = new StringDecoder(encoding);
-		this.remaining = null;
-	}
-
-	public write(buffer: Buffer): string[] {
-		const result: string[] = [];
-		const value = this.remaining
-			? this.remaining + this.stringDecoder.write(buffer)
-			: this.stringDecoder.write(buffer);
-
-		if (value.length < 1) {
-			return result;
-		}
-		let start = 0;
-		let ch: number;
-		while (start < value.length && ((ch = value.charCodeAt(start)) === 13 || ch === 10)) {
-			start++;
-		}
-		let idx = start;
-		while (idx < value.length) {
-			ch = value.charCodeAt(idx);
-			if (ch === 13 || ch === 10) {
-				result.push(value.substring(start, idx));
-				idx++;
-				while (idx < value.length && ((ch = value.charCodeAt(idx)) === 13 || ch === 10)) {
-					idx++;
-				}
-				start = idx;
-			} else {
-				idx++;
-			}
-		}
-		this.remaining = start < value.length ? value.substr(start) : null;
-		return result;
-	}
-
-	public end(): string | null {
-		return this.remaining;
-	}
+    private stringDecoder: StringDecoder;
+    private remaining: string | null;
+    constructor(encoding: BufferEncoding = 'utf8') {
+        this.stringDecoder = new StringDecoder(encoding);
+        this.remaining = null;
+    }
+    public write(buffer: Buffer): string[] {
+        const result: string[] = [];
+        const value = this.remaining
+            ? this.remaining + this.stringDecoder.write(buffer)
+            : this.stringDecoder.write(buffer);
+        if (value.length < 1) {
+            return result;
+        }
+        let start = 0;
+        let ch: number;
+        while (start < value.length && ((ch = value.charCodeAt(start)) === 13 || ch === 10)) {
+            start++;
+        }
+        let idx = start;
+        while (idx < value.length) {
+            ch = value.charCodeAt(idx);
+            if (ch === 13 || ch === 10) {
+                result.push(value.substring(start, idx));
+                idx++;
+                while (idx < value.length && ((ch = value.charCodeAt(idx)) === 13 || ch === 10)) {
+                    idx++;
+                }
+                start = idx;
+            }
+            else {
+                idx++;
+            }
+        }
+        this.remaining = start < value.length ? value.substr(start) : null;
+        return result;
+    }
+    public end(): string | null {
+        return this.remaining;
+    }
 }
-
 enum RunTrigger {
-	onSave,
-	onType
+    onSave,
+    onType
 }
-
 namespace RunTrigger {
-	export const strings = {
-		onSave: 'onSave',
-		onType: 'onType'
-	};
-	export const from = function (value: string): RunTrigger {
-		if (value === 'onType') {
-			return RunTrigger.onType;
-		} else {
-			return RunTrigger.onSave;
-		}
-	};
+    export const strings = {
+        onSave: 'onSave',
+        onType: 'onType'
+    };
+    export const from = function (value: string): RunTrigger {
+        if (value === 'onType') {
+            return RunTrigger.onType;
+        }
+        else {
+            return RunTrigger.onSave;
+        }
+    };
 }
-
 export default class PHPValidationProvider {
-
-	private static MatchExpression: RegExp = /(?:(?:Parse|Fatal) error): (.*)(?: in )(.*?)(?: on line )(\d+)/;
-	private static BufferArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off'];
-	private static FileArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off', '-f'];
-
-	private validationEnabled: boolean;
-	private pauseValidation: boolean;
-	private config: IPhpConfig | undefined;
-	private loadConfigP: Promise;
-
-	private documentListener: vscode.Disposable | null = null;
-	private diagnosticCollection?: vscode.DiagnosticCollection;
-	private delayers?: { [key: string]: ThrottledDelayer };
-
-	constructor() {
-		this.validationEnabled = true;
-		this.pauseValidation = false;
-		this.loadConfigP = this.loadConfiguration();
-	}
-
-	public activate(subscriptions: vscode.Disposable[]) {
-		this.diagnosticCollection = vscode.languages.createDiagnosticCollection();
-		subscriptions.push(this);
-		subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => this.loadConfigP = this.loadConfiguration()));
-
-		vscode.workspace.onDidOpenTextDocument(this.triggerValidate, this, subscriptions);
-		vscode.workspace.onDidCloseTextDocument((textDocument) => {
-			this.diagnosticCollection!.delete(textDocument.uri);
-			if (this.delayers) {
-				delete this.delayers[textDocument.uri.toString()];
-			}
-		}, null, subscriptions);
-	}
-
-	public dispose(): void {
-		if (this.diagnosticCollection) {
-			this.diagnosticCollection.clear();
-			this.diagnosticCollection.dispose();
-		}
-		if (this.documentListener) {
-			this.documentListener.dispose();
-			this.documentListener = null;
-		}
-	}
-
-	private async loadConfiguration(): Promise {
-		const section = vscode.workspace.getConfiguration();
-		const oldExecutable = this.config?.executable;
-		this.validationEnabled = section.get(Setting.Enable, true);
-
-		this.config = await getConfig();
-
-		this.delayers = Object.create(null);
-		if (this.pauseValidation) {
-			this.pauseValidation = oldExecutable === this.config.executable;
-		}
-		if (this.documentListener) {
-			this.documentListener.dispose();
-			this.documentListener = null;
-		}
-		this.diagnosticCollection!.clear();
-		if (this.validationEnabled) {
-			if (this.config.trigger === RunTrigger.onType) {
-				this.documentListener = vscode.workspace.onDidChangeTextDocument((e) => {
-					this.triggerValidate(e.document);
-				});
-			} else {
-				this.documentListener = vscode.workspace.onDidSaveTextDocument(this.triggerValidate, this);
-			}
-			// Configuration has changed. Reevaluate all documents.
-			vscode.workspace.textDocuments.forEach(this.triggerValidate, this);
-		}
-	}
-
-	private async triggerValidate(textDocument: vscode.TextDocument): Promise {
-		await this.loadConfigP;
-		if (textDocument.languageId !== 'php' || this.pauseValidation || !this.validationEnabled) {
-			return;
-		}
-
-		if (vscode.workspace.isTrusted) {
-			const key = textDocument.uri.toString();
-			let delayer = this.delayers![key];
-			if (!delayer) {
-				delayer = new ThrottledDelayer(this.config?.trigger === RunTrigger.onType ? 250 : 0);
-				this.delayers![key] = delayer;
-			}
-			delayer.trigger(() => this.doValidate(textDocument));
-		}
-	}
-
-	private doValidate(textDocument: vscode.TextDocument): Promise {
-		return new Promise(resolve => {
-			const executable = this.config!.executable;
-			if (!executable) {
-				this.showErrorMessage(vscode.l10n.t("Cannot validate since a PHP installation could not be found. Use the setting 'php.validate.executablePath' to configure the PHP executable."));
-				this.pauseValidation = true;
-				resolve();
-				return;
-			}
-
-			if (!path.isAbsolute(executable)) {
-				// executable should either be resolved to an absolute path or undefined.
-				// This is just to be sure.
-				return;
-			}
-
-			const decoder = new LineDecoder();
-			const diagnostics: vscode.Diagnostic[] = [];
-			const processLine = (line: string) => {
-				const matches = line.match(PHPValidationProvider.MatchExpression);
-				if (matches) {
-					const message = matches[1];
-					const line = parseInt(matches[3]) - 1;
-					const diagnostic: vscode.Diagnostic = new vscode.Diagnostic(
-						new vscode.Range(line, 0, line, 2 ** 31 - 1), // See https://github.com/microsoft/vscode/issues/80288#issuecomment-650636442 for discussion
-						message
-					);
-					diagnostics.push(diagnostic);
-				}
-			};
-
-			const options = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) ? { cwd: vscode.workspace.workspaceFolders[0].uri.fsPath } : undefined;
-			let args: string[];
-			if (this.config!.trigger === RunTrigger.onSave) {
-				args = PHPValidationProvider.FileArgs.slice(0);
-				args.push(textDocument.fileName);
-			} else {
-				args = PHPValidationProvider.BufferArgs;
-			}
-			try {
-				const childProcess = cp.spawn(executable, args, options);
-				childProcess.on('error', (error: Error) => {
-					if (this.pauseValidation) {
-						resolve();
-						return;
-					}
-					this.showError(error, executable);
-					this.pauseValidation = true;
-					resolve();
-				});
-				if (childProcess.pid) {
-					if (this.config!.trigger === RunTrigger.onType) {
-						childProcess.stdin.write(textDocument.getText());
-						childProcess.stdin.end();
-					}
-					childProcess.stdout.on('data', (data: Buffer) => {
-						decoder.write(data).forEach(processLine);
-					});
-					childProcess.stdout.on('end', () => {
-						const line = decoder.end();
-						if (line) {
-							processLine(line);
-						}
-						this.diagnosticCollection!.set(textDocument.uri, diagnostics);
-						resolve();
-					});
-				} else {
-					resolve();
-				}
-			} catch (error) {
-				this.showError(error, executable);
-			}
-		});
-	}
-
-	private async showError(error: any, executable: string): Promise {
-		let message: string | null = null;
-		if (error.code === 'ENOENT') {
-			if (this.config!.executable) {
-				message = vscode.l10n.t("Cannot validate since {0} is not a valid php executable. Use the setting 'php.validate.executablePath' to configure the PHP executable.", executable);
-			} else {
-				message = vscode.l10n.t("Cannot validate since no PHP executable is set. Use the setting 'php.validate.executablePath' to configure the PHP executable.");
-			}
-		} else {
-			message = error.message ? error.message : vscode.l10n.t("Failed to run php using path: {0}. Reason is unknown.", executable);
-		}
-		if (!message) {
-			return;
-		}
-
-		return this.showErrorMessage(message);
-	}
-
-	private async showErrorMessage(message: string): Promise {
-		const openSettings = vscode.l10n.t("Open Settings");
-		if (await vscode.window.showInformationMessage(message, openSettings) === openSettings) {
-			vscode.commands.executeCommand('workbench.action.openSettings', Setting.ExecutablePath);
-		}
-	}
+    private static MatchExpression: RegExp = /(?:(?:Parse|Fatal) error): (.*)(?: in )(.*?)(?: on line )(\d+)/;
+    private static BufferArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off'];
+    private static FileArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off', '-f'];
+    private validationEnabled: boolean;
+    private pauseValidation: boolean;
+    private config: IPhpConfig | undefined;
+    private loadConfigP: Promise;
+    private documentListener: vscode.Disposable | null = null;
+    private diagnosticCollection?: vscode.DiagnosticCollection;
+    private delayers?: {
+        [key: string]: ThrottledDelayer;
+    };
+    constructor() {
+        this.validationEnabled = true;
+        this.pauseValidation = false;
+        this.loadConfigP = this.loadConfiguration();
+    }
+    public activate(subscriptions: vscode.Disposable[]) {
+        this.diagnosticCollection = vscode.languages.createDiagnosticCollection();
+        subscriptions.push(this);
+        subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => this.loadConfigP = this.loadConfiguration()));
+        vscode.workspace.onDidOpenTextDocument(this.triggerValidate, this, subscriptions);
+        vscode.workspace.onDidCloseTextDocument((textDocument) => {
+            this.diagnosticCollection!.delete(textDocument.uri);
+            if (this.delayers) {
+                delete this.delayers[textDocument.uri.toString()];
+            }
+        }, null, subscriptions);
+    }
+    public dispose(): void {
+        if (this.diagnosticCollection) {
+            this.diagnosticCollection.clear();
+            this.diagnosticCollection.dispose();
+        }
+        if (this.documentListener) {
+            this.documentListener.dispose();
+            this.documentListener = null;
+        }
+    }
+    private async loadConfiguration(): Promise {
+        const section = vscode.workspace.getConfiguration();
+        const oldExecutable = this.config?.executable;
+        this.validationEnabled = section.get(Setting.Enable, true);
+        this.config = await getConfig();
+        this.delayers = Object.create(null);
+        if (this.pauseValidation) {
+            this.pauseValidation = oldExecutable === this.config.executable;
+        }
+        if (this.documentListener) {
+            this.documentListener.dispose();
+            this.documentListener = null;
+        }
+        this.diagnosticCollection!.clear();
+        if (this.validationEnabled) {
+            if (this.config.trigger === RunTrigger.onType) {
+                this.documentListener = vscode.workspace.onDidChangeTextDocument((e) => {
+                    this.triggerValidate(e.document);
+                });
+            }
+            else {
+                this.documentListener = vscode.workspace.onDidSaveTextDocument(this.triggerValidate, this);
+            }
+            // Configuration has changed. Reevaluate all documents.
+            vscode.workspace.textDocuments.forEach(this.triggerValidate, this);
+        }
+    }
+    private async triggerValidate(textDocument: vscode.TextDocument): Promise {
+        await this.loadConfigP;
+        if (textDocument.languageId !== 'php' || this.pauseValidation || !this.validationEnabled) {
+            return;
+        }
+        if (vscode.workspace.isTrusted) {
+            const key = textDocument.uri.toString();
+            let delayer = this.delayers![key];
+            if (!delayer) {
+                delayer = new ThrottledDelayer(this.config?.trigger === RunTrigger.onType ? 250 : 0);
+                this.delayers![key] = delayer;
+            }
+            delayer.trigger(() => this.doValidate(textDocument));
+        }
+    }
+    private doValidate(textDocument: vscode.TextDocument): Promise {
+        return new Promise(resolve => {
+            const executable = this.config!.executable;
+            if (!executable) {
+                this.showErrorMessage(vscode.l10n.t("Cannot validate since a PHP installation could not be found. Use the setting 'php.validate.executablePath' to configure the PHP executable."));
+                this.pauseValidation = true;
+                resolve();
+                return;
+            }
+            if (!path.isAbsolute(executable)) {
+                // executable should either be resolved to an absolute path or undefined.
+                // This is just to be sure.
+                return;
+            }
+            const decoder = new LineDecoder();
+            const diagnostics: vscode.Diagnostic[] = [];
+            const processLine = (line: string) => {
+                const matches = line.match(PHPValidationProvider.MatchExpression);
+                if (matches) {
+                    const message = matches[1];
+                    const line = parseInt(matches[3]) - 1;
+                    const diagnostic: vscode.Diagnostic = new vscode.Diagnostic(new vscode.Range(line, 0, line, 2 ** 31 - 1), // See https://github.com/microsoft/vscode/issues/80288#issuecomment-650636442 for discussion
+                    message);
+                    diagnostics.push(diagnostic);
+                }
+            };
+            const options = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) ? { cwd: vscode.workspace.workspaceFolders[0].uri.fsPath } : undefined;
+            let args: string[];
+            if (this.config!.trigger === RunTrigger.onSave) {
+                args = PHPValidationProvider.FileArgs.slice(0);
+                args.push(textDocument.fileName);
+            }
+            else {
+                args = PHPValidationProvider.BufferArgs;
+            }
+            try {
+                const childProcess = cp.spawn(executable, args, options);
+                childProcess.on('error', (error: Error) => {
+                    if (this.pauseValidation) {
+                        resolve();
+                        return;
+                    }
+                    this.showError(error, executable);
+                    this.pauseValidation = true;
+                    resolve();
+                });
+                if (childProcess.pid) {
+                    if (this.config!.trigger === RunTrigger.onType) {
+                        childProcess.stdin.write(textDocument.getText());
+                        childProcess.stdin.end();
+                    }
+                    childProcess.stdout.on('data', (data: Buffer) => {
+                        decoder.write(data).forEach(processLine);
+                    });
+                    childProcess.stdout.on('end', () => {
+                        const line = decoder.end();
+                        if (line) {
+                            processLine(line);
+                        }
+                        this.diagnosticCollection!.set(textDocument.uri, diagnostics);
+                        resolve();
+                    });
+                }
+                else {
+                    resolve();
+                }
+            }
+            catch (error) {
+                this.showError(error, executable);
+            }
+        });
+    }
+    private async showError(error: any, executable: string): Promise {
+        let message: string | null = null;
+        if (error.code === 'ENOENT') {
+            if (this.config!.executable) {
+                message = vscode.l10n.t("Cannot validate since {0} is not a valid php executable. Use the setting 'php.validate.executablePath' to configure the PHP executable.", executable);
+            }
+            else {
+                message = vscode.l10n.t("Cannot validate since no PHP executable is set. Use the setting 'php.validate.executablePath' to configure the PHP executable.");
+            }
+        }
+        else {
+            message = error.message ? error.message : vscode.l10n.t("Failed to run php using path: {0}. Reason is unknown.", executable);
+        }
+        if (!message) {
+            return;
+        }
+        return this.showErrorMessage(message);
+    }
+    private async showErrorMessage(message: string): Promise {
+        const openSettings = vscode.l10n.t("Open Settings");
+        if (await vscode.window.showInformationMessage(message, openSettings) === openSettings) {
+            vscode.commands.executeCommand('workbench.action.openSettings', Setting.ExecutablePath);
+        }
+    }
 }
-
 interface IPhpConfig {
-	readonly executable: string | undefined;
-	readonly executableIsUserDefined: boolean | undefined;
-	readonly trigger: RunTrigger;
+    readonly executable: string | undefined;
+    readonly executableIsUserDefined: boolean | undefined;
+    readonly trigger: RunTrigger;
 }
-
 async function getConfig(): Promise {
-	const section = vscode.workspace.getConfiguration();
-
-	let executable: string | undefined;
-	let executableIsUserDefined: boolean | undefined;
-	const inspect = section.inspect(Setting.ExecutablePath);
-	if (inspect && inspect.workspaceValue) {
-		executable = inspect.workspaceValue;
-		executableIsUserDefined = false;
-	} else if (inspect && inspect.globalValue) {
-		executable = inspect.globalValue;
-		executableIsUserDefined = true;
-	} else {
-		executable = undefined;
-		executableIsUserDefined = undefined;
-	}
-
-	if (executable && !path.isAbsolute(executable)) {
-		const first = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0];
-		if (first) {
-			executable = vscode.Uri.joinPath(first.uri, executable).fsPath;
-		} else {
-			executable = undefined;
-		}
-	} else if (!executable) {
-		executable = await getPhpPath();
-	}
-
-	const trigger = RunTrigger.from(section.get(Setting.Run, RunTrigger.strings.onSave));
-	return {
-		executable,
-		executableIsUserDefined,
-		trigger
-	};
+    const section = vscode.workspace.getConfiguration();
+    let executable: string | undefined;
+    let executableIsUserDefined: boolean | undefined;
+    const inspect = section.inspect(Setting.ExecutablePath);
+    if (inspect && inspect.workspaceValue) {
+        executable = inspect.workspaceValue;
+        executableIsUserDefined = false;
+    }
+    else if (inspect && inspect.globalValue) {
+        executable = inspect.globalValue;
+        executableIsUserDefined = true;
+    }
+    else {
+        executable = undefined;
+        executableIsUserDefined = undefined;
+    }
+    if (executable && !path.isAbsolute(executable)) {
+        const first = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0];
+        if (first) {
+            executable = vscode.Uri.joinPath(first.uri, executable).fsPath;
+        }
+        else {
+            executable = undefined;
+        }
+    }
+    else if (!executable) {
+        executable = await getPhpPath();
+    }
+    const trigger = RunTrigger.from(section.get(Setting.Run, RunTrigger.strings.onSave));
+    return {
+        executable,
+        executableIsUserDefined,
+        trigger
+    };
 }
-
 async function getPhpPath(): Promise {
-	try {
-		return await which('php');
-	} catch (e) {
-		return undefined;
-	}
+    try {
+        return await which('php');
+    }
+    catch (e) {
+        return undefined;
+    }
 }
diff --git a/extensions/php-language-features/Source/phpMain.ts b/extensions/php-language-features/Source/phpMain.ts
index b35d523b350d6..e90b7ee57cf89 100644
--- a/extensions/php-language-features/Source/phpMain.ts
+++ b/extensions/php-language-features/Source/phpMain.ts
@@ -2,21 +2,16 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 import PHPCompletionItemProvider from './features/completionItemProvider';
 import PHPHoverProvider from './features/hoverProvider';
 import PHPSignatureHelpProvider from './features/signatureHelpProvider';
 import PHPValidationProvider from './features/validationProvider';
-
 export function activate(context: vscode.ExtensionContext): any {
-
-	const validator = new PHPValidationProvider();
-	validator.activate(context.subscriptions);
-
-	// add providers
-	context.subscriptions.push(vscode.languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '>', '$'));
-	context.subscriptions.push(vscode.languages.registerHoverProvider('php', new PHPHoverProvider()));
-	context.subscriptions.push(vscode.languages.registerSignatureHelpProvider('php', new PHPSignatureHelpProvider(), '(', ','));
+    const validator = new PHPValidationProvider();
+    validator.activate(context.subscriptions);
+    // add providers
+    context.subscriptions.push(vscode.languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '>', '$'));
+    context.subscriptions.push(vscode.languages.registerHoverProvider('php', new PHPHoverProvider()));
+    context.subscriptions.push(vscode.languages.registerSignatureHelpProvider('php', new PHPSignatureHelpProvider(), '(', ','));
 }
diff --git a/extensions/references-view/Source/calls/index.ts b/extensions/references-view/Source/calls/index.ts
index 46c789b5dbf15..a6804d92b67bf 100644
--- a/extensions/references-view/Source/calls/index.ts
+++ b/extensions/references-view/Source/calls/index.ts
@@ -2,77 +2,57 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolsTree } from '../tree';
 import { ContextKey } from '../utils';
 import { CallItem, CallsDirection, CallsTreeInput } from './model';
-
 export function register(tree: SymbolsTree, context: vscode.ExtensionContext): void {
-
-	const direction = new RichCallsDirection(context.workspaceState, CallsDirection.Incoming);
-
-	function showCallHierarchy() {
-		if (vscode.window.activeTextEditor) {
-			const input = new CallsTreeInput(new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), direction.value);
-			tree.setInput(input);
-		}
-	}
-
-	function setCallsDirection(value: CallsDirection, anchor: CallItem | unknown) {
-		direction.value = value;
-
-		let newInput: CallsTreeInput | undefined;
-		const oldInput = tree.getInput();
-		if (anchor instanceof CallItem) {
-			newInput = new CallsTreeInput(new vscode.Location(anchor.item.uri, anchor.item.selectionRange.start), direction.value);
-		} else if (oldInput instanceof CallsTreeInput) {
-			newInput = new CallsTreeInput(oldInput.location, direction.value);
-		}
-		if (newInput) {
-			tree.setInput(newInput);
-		}
-	}
-
-	context.subscriptions.push(
-		vscode.commands.registerCommand('references-view.showCallHierarchy', showCallHierarchy),
-		vscode.commands.registerCommand('references-view.showOutgoingCalls', (item: CallItem | unknown) => setCallsDirection(CallsDirection.Outgoing, item)),
-		vscode.commands.registerCommand('references-view.showIncomingCalls', (item: CallItem | unknown) => setCallsDirection(CallsDirection.Incoming, item)),
-		vscode.commands.registerCommand('references-view.removeCallItem', removeCallItem)
-	);
+    const direction = new RichCallsDirection(context.workspaceState, CallsDirection.Incoming);
+    function showCallHierarchy() {
+        if (vscode.window.activeTextEditor) {
+            const input = new CallsTreeInput(new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), direction.value);
+            tree.setInput(input);
+        }
+    }
+    function setCallsDirection(value: CallsDirection, anchor: CallItem | unknown) {
+        direction.value = value;
+        let newInput: CallsTreeInput | undefined;
+        const oldInput = tree.getInput();
+        if (anchor instanceof CallItem) {
+            newInput = new CallsTreeInput(new vscode.Location(anchor.item.uri, anchor.item.selectionRange.start), direction.value);
+        }
+        else if (oldInput instanceof CallsTreeInput) {
+            newInput = new CallsTreeInput(oldInput.location, direction.value);
+        }
+        if (newInput) {
+            tree.setInput(newInput);
+        }
+    }
+    context.subscriptions.push(vscode.commands.registerCommand('references-view.showCallHierarchy', showCallHierarchy), vscode.commands.registerCommand('references-view.showOutgoingCalls', (item: CallItem | unknown) => setCallsDirection(CallsDirection.Outgoing, item)), vscode.commands.registerCommand('references-view.showIncomingCalls', (item: CallItem | unknown) => setCallsDirection(CallsDirection.Incoming, item)), vscode.commands.registerCommand('references-view.removeCallItem', removeCallItem));
 }
-
 function removeCallItem(item: CallItem | unknown): void {
-	if (item instanceof CallItem) {
-		item.remove();
-	}
+    if (item instanceof CallItem) {
+        item.remove();
+    }
 }
-
 class RichCallsDirection {
-
-	private static _key = 'references-view.callHierarchyMode';
-
-	private _ctxMode = new ContextKey<'showIncoming' | 'showOutgoing'>('references-view.callHierarchyMode');
-
-	constructor(
-		private _mem: vscode.Memento,
-		private _value: CallsDirection = CallsDirection.Outgoing,
-	) {
-		const raw = _mem.get(RichCallsDirection._key);
-		if (typeof raw === 'number' && raw >= 0 && raw <= 1) {
-			this.value = raw;
-		} else {
-			this.value = _value;
-		}
-	}
-
-	get value() {
-		return this._value;
-	}
-
-	set value(value: CallsDirection) {
-		this._value = value;
-		this._ctxMode.set(this._value === CallsDirection.Incoming ? 'showIncoming' : 'showOutgoing');
-		this._mem.update(RichCallsDirection._key, value);
-	}
+    private static _key = 'references-view.callHierarchyMode';
+    private _ctxMode = new ContextKey<'showIncoming' | 'showOutgoing'>('references-view.callHierarchyMode');
+    constructor(private _mem: vscode.Memento, private _value: CallsDirection = CallsDirection.Outgoing) {
+        const raw = _mem.get(RichCallsDirection._key);
+        if (typeof raw === 'number' && raw >= 0 && raw <= 1) {
+            this.value = raw;
+        }
+        else {
+            this.value = _value;
+        }
+    }
+    get value() {
+        return this._value;
+    }
+    set value(value: CallsDirection) {
+        this._value = value;
+        this._ctxMode.set(this._value === CallsDirection.Incoming ? 'showIncoming' : 'showOutgoing');
+        this._mem.update(RichCallsDirection._key, value);
+    }
 }
diff --git a/extensions/references-view/Source/calls/model.ts b/extensions/references-view/Source/calls/model.ts
index 087e577b16064..cd56f585dbd7d 100644
--- a/extensions/references-view/Source/calls/model.ts
+++ b/extensions/references-view/Source/calls/model.ts
@@ -2,224 +2,173 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput } from '../references-view';
 import { asResourceUrl, del, getThemeIcon, tail } from '../utils';
-
 export class CallsTreeInput implements SymbolTreeInput {
-
-	readonly title: string;
-	readonly contextValue: string = 'callHierarchy';
-
-	constructor(
-		readonly location: vscode.Location,
-		readonly direction: CallsDirection,
-	) {
-		this.title = direction === CallsDirection.Incoming
-			? vscode.l10n.t('Callers Of')
-			: vscode.l10n.t('Calls From');
-	}
-
-	async resolve() {
-
-		const items = await Promise.resolve(vscode.commands.executeCommand('vscode.prepareCallHierarchy', this.location.uri, this.location.range.start));
-		const model = new CallsModel(this.direction, items ?? []);
-		const provider = new CallItemDataProvider(model);
-
-		if (model.roots.length === 0) {
-			return;
-		}
-
-		return {
-			provider,
-			get message() { return model.roots.length === 0 ? vscode.l10n.t('No results.') : undefined; },
-			navigation: model,
-			highlights: model,
-			dnd: model,
-			dispose() {
-				provider.dispose();
-			}
-		};
-	}
-
-	with(location: vscode.Location): CallsTreeInput {
-		return new CallsTreeInput(location, this.direction);
-	}
+    readonly title: string;
+    readonly contextValue: string = 'callHierarchy';
+    constructor(readonly location: vscode.Location, readonly direction: CallsDirection) {
+        this.title = direction === CallsDirection.Incoming
+            ? vscode.l10n.t('Callers Of')
+            : vscode.l10n.t('Calls From');
+    }
+    async resolve() {
+        const items = await Promise.resolve(vscode.commands.executeCommand('vscode.prepareCallHierarchy', this.location.uri, this.location.range.start));
+        const model = new CallsModel(this.direction, items ?? []);
+        const provider = new CallItemDataProvider(model);
+        if (model.roots.length === 0) {
+            return;
+        }
+        return {
+            provider,
+            get message() { return model.roots.length === 0 ? vscode.l10n.t('No results.') : undefined; },
+            navigation: model,
+            highlights: model,
+            dnd: model,
+            dispose() {
+                provider.dispose();
+            }
+        };
+    }
+    with(location: vscode.Location): CallsTreeInput {
+        return new CallsTreeInput(location, this.direction);
+    }
 }
-
-
 export const enum CallsDirection {
-	Incoming,
-	Outgoing
+    Incoming,
+    Outgoing
 }
-
-
-
 export class CallItem {
-
-	children?: CallItem[];
-
-	constructor(
-		readonly model: CallsModel,
-		readonly item: vscode.CallHierarchyItem,
-		readonly parent: CallItem | undefined,
-		readonly locations: vscode.Location[] | undefined
-	) { }
-
-	remove(): void {
-		this.model.remove(this);
-	}
+    children?: CallItem[];
+    constructor(readonly model: CallsModel, readonly item: vscode.CallHierarchyItem, readonly parent: CallItem | undefined, readonly locations: vscode.Location[] | undefined) { }
+    remove(): void {
+        this.model.remove(this);
+    }
 }
-
 class CallsModel implements SymbolItemNavigation, SymbolItemEditorHighlights, SymbolItemDragAndDrop {
-
-	readonly roots: CallItem[] = [];
-
-	private readonly _onDidChange = new vscode.EventEmitter();
-	readonly onDidChange = this._onDidChange.event;
-
-	constructor(readonly direction: CallsDirection, items: vscode.CallHierarchyItem[]) {
-		this.roots = items.map(item => new CallItem(this, item, undefined, undefined));
-	}
-
-	private async _resolveCalls(call: CallItem): Promise {
-		if (this.direction === CallsDirection.Incoming) {
-			const calls = await vscode.commands.executeCommand('vscode.provideIncomingCalls', call.item);
-			return calls ? calls.map(item => new CallItem(this, item.from, call, item.fromRanges.map(range => new vscode.Location(item.from.uri, range)))) : [];
-		} else {
-			const calls = await vscode.commands.executeCommand('vscode.provideOutgoingCalls', call.item);
-			return calls ? calls.map(item => new CallItem(this, item.to, call, item.fromRanges.map(range => new vscode.Location(call.item.uri, range)))) : [];
-		}
-	}
-
-	async getCallChildren(call: CallItem): Promise {
-		if (!call.children) {
-			call.children = await this._resolveCalls(call);
-		}
-		return call.children;
-	}
-
-	// -- navigation
-
-	location(item: CallItem) {
-		return new vscode.Location(item.item.uri, item.item.range);
-	}
-
-	nearest(uri: vscode.Uri, _position: vscode.Position): CallItem | undefined {
-		return this.roots.find(item => item.item.uri.toString() === uri.toString()) ?? this.roots[0];
-	}
-
-	next(from: CallItem): CallItem {
-		return this._move(from, true) ?? from;
-	}
-
-	previous(from: CallItem): CallItem {
-		return this._move(from, false) ?? from;
-	}
-
-	private _move(item: CallItem, fwd: boolean): CallItem | void {
-		if (item.children?.length) {
-			return fwd ? item.children[0] : tail(item.children);
-		}
-		const array = this.roots.includes(item) ? this.roots : item.parent?.children;
-		if (array?.length) {
-			const idx = array.indexOf(item);
-			const delta = fwd ? 1 : -1;
-			return array[idx + delta + array.length % array.length];
-		}
-	}
-
-	// --- dnd
-
-	getDragUri(item: CallItem): vscode.Uri | undefined {
-		return asResourceUrl(item.item.uri, item.item.range);
-	}
-
-	// --- highlights
-
-	getEditorHighlights(item: CallItem, uri: vscode.Uri): vscode.Range[] | undefined {
-		if (!item.locations) {
-			return item.item.uri.toString() === uri.toString() ? [item.item.selectionRange] : undefined;
-		}
-		return item.locations
-			.filter(loc => loc.uri.toString() === uri.toString())
-			.map(loc => loc.range);
-	}
-
-	remove(item: CallItem) {
-		const isInRoot = this.roots.includes(item);
-		const siblings = isInRoot ? this.roots : item.parent?.children;
-		if (siblings) {
-			del(siblings, item);
-			this._onDidChange.fire(this);
-		}
-	}
+    readonly roots: CallItem[] = [];
+    private readonly _onDidChange = new vscode.EventEmitter();
+    readonly onDidChange = this._onDidChange.event;
+    constructor(readonly direction: CallsDirection, items: vscode.CallHierarchyItem[]) {
+        this.roots = items.map(item => new CallItem(this, item, undefined, undefined));
+    }
+    private async _resolveCalls(call: CallItem): Promise {
+        if (this.direction === CallsDirection.Incoming) {
+            const calls = await vscode.commands.executeCommand('vscode.provideIncomingCalls', call.item);
+            return calls ? calls.map(item => new CallItem(this, item.from, call, item.fromRanges.map(range => new vscode.Location(item.from.uri, range)))) : [];
+        }
+        else {
+            const calls = await vscode.commands.executeCommand('vscode.provideOutgoingCalls', call.item);
+            return calls ? calls.map(item => new CallItem(this, item.to, call, item.fromRanges.map(range => new vscode.Location(call.item.uri, range)))) : [];
+        }
+    }
+    async getCallChildren(call: CallItem): Promise {
+        if (!call.children) {
+            call.children = await this._resolveCalls(call);
+        }
+        return call.children;
+    }
+    // -- navigation
+    location(item: CallItem) {
+        return new vscode.Location(item.item.uri, item.item.range);
+    }
+    nearest(uri: vscode.Uri, _position: vscode.Position): CallItem | undefined {
+        return this.roots.find(item => item.item.uri.toString() === uri.toString()) ?? this.roots[0];
+    }
+    next(from: CallItem): CallItem {
+        return this._move(from, true) ?? from;
+    }
+    previous(from: CallItem): CallItem {
+        return this._move(from, false) ?? from;
+    }
+    private _move(item: CallItem, fwd: boolean): CallItem | void {
+        if (item.children?.length) {
+            return fwd ? item.children[0] : tail(item.children);
+        }
+        const array = this.roots.includes(item) ? this.roots : item.parent?.children;
+        if (array?.length) {
+            const idx = array.indexOf(item);
+            const delta = fwd ? 1 : -1;
+            return array[idx + delta + array.length % array.length];
+        }
+    }
+    // --- dnd
+    getDragUri(item: CallItem): vscode.Uri | undefined {
+        return asResourceUrl(item.item.uri, item.item.range);
+    }
+    // --- highlights
+    getEditorHighlights(item: CallItem, uri: vscode.Uri): vscode.Range[] | undefined {
+        if (!item.locations) {
+            return item.item.uri.toString() === uri.toString() ? [item.item.selectionRange] : undefined;
+        }
+        return item.locations
+            .filter(loc => loc.uri.toString() === uri.toString())
+            .map(loc => loc.range);
+    }
+    remove(item: CallItem) {
+        const isInRoot = this.roots.includes(item);
+        const siblings = isInRoot ? this.roots : item.parent?.children;
+        if (siblings) {
+            del(siblings, item);
+            this._onDidChange.fire(this);
+        }
+    }
 }
-
 class CallItemDataProvider implements vscode.TreeDataProvider {
-
-	private readonly _emitter = new vscode.EventEmitter();
-	readonly onDidChangeTreeData = this._emitter.event;
-
-	private readonly _modelListener: vscode.Disposable;
-
-	constructor(private _model: CallsModel) {
-		this._modelListener = _model.onDidChange(e => this._emitter.fire(e instanceof CallItem ? e : undefined));
-	}
-
-	dispose(): void {
-		this._emitter.dispose();
-		this._modelListener.dispose();
-	}
-
-	getTreeItem(element: CallItem): vscode.TreeItem {
-
-		const item = new vscode.TreeItem(element.item.name);
-		item.description = element.item.detail;
-		item.tooltip = item.label && element.item.detail ? `${item.label} - ${element.item.detail}` : item.label ? `${item.label}` : element.item.detail;
-		item.contextValue = 'call-item';
-		item.iconPath = getThemeIcon(element.item.kind);
-
-		type OpenArgs = [vscode.Uri, vscode.TextDocumentShowOptions];
-		let openArgs: OpenArgs;
-
-		if (element.model.direction === CallsDirection.Outgoing) {
-
-			openArgs = [element.item.uri, { selection: element.item.selectionRange.with({ end: element.item.selectionRange.start }) }];
-
-		} else {
-			// incoming call -> reveal first call instead of caller
-			let firstLoctionStart: vscode.Position | undefined;
-			if (element.locations) {
-				for (const loc of element.locations) {
-					if (loc.uri.toString() === element.item.uri.toString()) {
-						firstLoctionStart = firstLoctionStart?.isBefore(loc.range.start) ? firstLoctionStart : loc.range.start;
-					}
-				}
-			}
-			if (!firstLoctionStart) {
-				firstLoctionStart = element.item.selectionRange.start;
-			}
-			openArgs = [element.item.uri, { selection: new vscode.Range(firstLoctionStart, firstLoctionStart) }];
-		}
-
-		item.command = {
-			command: 'vscode.open',
-			title: vscode.l10n.t('Open Call'),
-			arguments: openArgs
-		};
-		item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
-		return item;
-	}
-
-	getChildren(element?: CallItem | undefined) {
-		return element
-			? this._model.getCallChildren(element)
-			: this._model.roots;
-	}
-
-	getParent(element: CallItem) {
-		return element.parent;
-	}
+    private readonly _emitter = new vscode.EventEmitter();
+    readonly onDidChangeTreeData = this._emitter.event;
+    private readonly _modelListener: vscode.Disposable;
+    constructor(private _model: CallsModel) {
+        this._modelListener = _model.onDidChange(e => this._emitter.fire(e instanceof CallItem ? e : undefined));
+    }
+    dispose(): void {
+        this._emitter.dispose();
+        this._modelListener.dispose();
+    }
+    getTreeItem(element: CallItem): vscode.TreeItem {
+        const item = new vscode.TreeItem(element.item.name);
+        item.description = element.item.detail;
+        item.tooltip = item.label && element.item.detail ? `${item.label} - ${element.item.detail}` : item.label ? `${item.label}` : element.item.detail;
+        item.contextValue = 'call-item';
+        item.iconPath = getThemeIcon(element.item.kind);
+        type OpenArgs = [
+            vscode.Uri,
+            vscode.TextDocumentShowOptions
+        ];
+        let openArgs: OpenArgs;
+        if (element.model.direction === CallsDirection.Outgoing) {
+            openArgs = [element.item.uri, { selection: element.item.selectionRange.with({ end: element.item.selectionRange.start }) }];
+        }
+        else {
+            // incoming call -> reveal first call instead of caller
+            let firstLoctionStart: vscode.Position | undefined;
+            if (element.locations) {
+                for (const loc of element.locations) {
+                    if (loc.uri.toString() === element.item.uri.toString()) {
+                        firstLoctionStart = firstLoctionStart?.isBefore(loc.range.start) ? firstLoctionStart : loc.range.start;
+                    }
+                }
+            }
+            if (!firstLoctionStart) {
+                firstLoctionStart = element.item.selectionRange.start;
+            }
+            openArgs = [element.item.uri, { selection: new vscode.Range(firstLoctionStart, firstLoctionStart) }];
+        }
+        item.command = {
+            command: 'vscode.open',
+            title: vscode.l10n.t('Open Call'),
+            arguments: openArgs
+        };
+        item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
+        return item;
+    }
+    getChildren(element?: CallItem | undefined) {
+        return element
+            ? this._model.getCallChildren(element)
+            : this._model.roots;
+    }
+    getParent(element: CallItem) {
+        return element.parent;
+    }
 }
diff --git a/extensions/references-view/Source/extension.ts b/extensions/references-view/Source/extension.ts
index 4cd47866fe2fc..571c79dc81f18 100644
--- a/extensions/references-view/Source/extension.ts
+++ b/extensions/references-view/Source/extension.ts
@@ -2,29 +2,22 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as calls from './calls';
 import * as references from './references';
 import { SymbolTree, SymbolTreeInput } from './references-view';
 import { SymbolsTree } from './tree';
 import * as types from './types';
-
 export function activate(context: vscode.ExtensionContext): SymbolTree {
-
-	const tree = new SymbolsTree();
-
-	references.register(tree, context);
-	calls.register(tree, context);
-	types.register(tree, context);
-
-	function setInput(input: SymbolTreeInput) {
-		tree.setInput(input);
-	}
-
-	function getInput(): SymbolTreeInput | undefined {
-		return tree.getInput();
-	}
-
-	return { setInput, getInput };
+    const tree = new SymbolsTree();
+    references.register(tree, context);
+    calls.register(tree, context);
+    types.register(tree, context);
+    function setInput(input: SymbolTreeInput) {
+        tree.setInput(input);
+    }
+    function getInput(): SymbolTreeInput | undefined {
+        return tree.getInput();
+    }
+    return { setInput, getInput };
 }
diff --git a/extensions/references-view/Source/highlights.ts b/extensions/references-view/Source/highlights.ts
index fff533fa176c7..d3f0bf603706e 100644
--- a/extensions/references-view/Source/highlights.ts
+++ b/extensions/references-view/Source/highlights.ts
@@ -2,69 +2,55 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolItemEditorHighlights } from './references-view';
-
 export class EditorHighlights {
-
-	private readonly _decorationType = vscode.window.createTextEditorDecorationType({
-		backgroundColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
-		rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
-		overviewRulerLane: vscode.OverviewRulerLane.Center,
-		overviewRulerColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
-	});
-
-	private readonly disposables: vscode.Disposable[] = [];
-	private readonly _ignore = new Set();
-
-	constructor(private readonly _view: vscode.TreeView, private readonly _delegate: SymbolItemEditorHighlights) {
-		this.disposables.push(
-			vscode.workspace.onDidChangeTextDocument(e => this._ignore.add(e.document.uri.toString())),
-			vscode.window.onDidChangeActiveTextEditor(() => _view.visible && this.update()),
-			_view.onDidChangeVisibility(e => e.visible ? this._show() : this._hide()),
-			_view.onDidChangeSelection(() => {
-				if (_view.visible) {
-					this.update();
-				}
-			})
-		);
-		this._show();
-	}
-
-	dispose() {
-		vscode.Disposable.from(...this.disposables).dispose();
-		for (const editor of vscode.window.visibleTextEditors) {
-			editor.setDecorations(this._decorationType, []);
-		}
-	}
-
-	private _show(): void {
-		const { activeTextEditor: editor } = vscode.window;
-		if (!editor || !editor.viewColumn) {
-			return;
-		}
-		if (this._ignore.has(editor.document.uri.toString())) {
-			return;
-		}
-		const [anchor] = this._view.selection;
-		if (!anchor) {
-			return;
-		}
-		const ranges = this._delegate.getEditorHighlights(anchor, editor.document.uri);
-		if (ranges) {
-			editor.setDecorations(this._decorationType, ranges);
-		}
-	}
-
-	private _hide(): void {
-		for (const editor of vscode.window.visibleTextEditors) {
-			editor.setDecorations(this._decorationType, []);
-		}
-	}
-
-	update(): void {
-		this._hide();
-		this._show();
-	}
+    private readonly _decorationType = vscode.window.createTextEditorDecorationType({
+        backgroundColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
+        rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
+        overviewRulerLane: vscode.OverviewRulerLane.Center,
+        overviewRulerColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
+    });
+    private readonly disposables: vscode.Disposable[] = [];
+    private readonly _ignore = new Set();
+    constructor(private readonly _view: vscode.TreeView, private readonly _delegate: SymbolItemEditorHighlights) {
+        this.disposables.push(vscode.workspace.onDidChangeTextDocument(e => this._ignore.add(e.document.uri.toString())), vscode.window.onDidChangeActiveTextEditor(() => _view.visible && this.update()), _view.onDidChangeVisibility(e => e.visible ? this._show() : this._hide()), _view.onDidChangeSelection(() => {
+            if (_view.visible) {
+                this.update();
+            }
+        }));
+        this._show();
+    }
+    dispose() {
+        vscode.Disposable.from(...this.disposables).dispose();
+        for (const editor of vscode.window.visibleTextEditors) {
+            editor.setDecorations(this._decorationType, []);
+        }
+    }
+    private _show(): void {
+        const { activeTextEditor: editor } = vscode.window;
+        if (!editor || !editor.viewColumn) {
+            return;
+        }
+        if (this._ignore.has(editor.document.uri.toString())) {
+            return;
+        }
+        const [anchor] = this._view.selection;
+        if (!anchor) {
+            return;
+        }
+        const ranges = this._delegate.getEditorHighlights(anchor, editor.document.uri);
+        if (ranges) {
+            editor.setDecorations(this._decorationType, ranges);
+        }
+    }
+    private _hide(): void {
+        for (const editor of vscode.window.visibleTextEditors) {
+            editor.setDecorations(this._decorationType, []);
+        }
+    }
+    update(): void {
+        this._hide();
+        this._show();
+    }
 }
diff --git a/extensions/references-view/Source/navigation.ts b/extensions/references-view/Source/navigation.ts
index e592dfbf7eae6..abf0c12d16dc2 100644
--- a/extensions/references-view/Source/navigation.ts
+++ b/extensions/references-view/Source/navigation.ts
@@ -2,84 +2,70 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolItemNavigation } from './references-view';
 import { ContextKey } from './utils';
-
 export class Navigation {
-
-	private readonly _disposables: vscode.Disposable[] = [];
-	private readonly _ctxCanNavigate = new ContextKey('references-view.canNavigate');
-
-	private _delegate?: SymbolItemNavigation;
-
-	constructor(private readonly _view: vscode.TreeView) {
-		this._disposables.push(
-			vscode.commands.registerCommand('references-view.next', () => this.next(false)),
-			vscode.commands.registerCommand('references-view.prev', () => this.previous(false)),
-		);
-	}
-
-	dispose(): void {
-		vscode.Disposable.from(...this._disposables).dispose();
-	}
-
-	update(delegate: SymbolItemNavigation | undefined) {
-		this._delegate = delegate;
-		this._ctxCanNavigate.set(Boolean(this._delegate));
-	}
-
-	private _anchor(): undefined | unknown {
-		if (!this._delegate) {
-			return undefined;
-		}
-		const [sel] = this._view.selection;
-		if (sel) {
-			return sel;
-		}
-		if (!vscode.window.activeTextEditor) {
-			return undefined;
-		}
-		return this._delegate.nearest(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active);
-	}
-
-	private _open(loc: vscode.Location, preserveFocus: boolean) {
-		vscode.commands.executeCommand('vscode.open', loc.uri, {
-			selection: new vscode.Selection(loc.range.start, loc.range.start),
-			preserveFocus
-		});
-	}
-
-	previous(preserveFocus: boolean): void {
-		if (!this._delegate) {
-			return;
-		}
-		const item = this._anchor();
-		if (!item) {
-			return;
-		}
-		const newItem = this._delegate.previous(item);
-		const newLocation = this._delegate.location(newItem);
-		if (newLocation) {
-			this._view.reveal(newItem, { select: true, focus: true });
-			this._open(newLocation, preserveFocus);
-		}
-	}
-
-	next(preserveFocus: boolean): void {
-		if (!this._delegate) {
-			return;
-		}
-		const item = this._anchor();
-		if (!item) {
-			return;
-		}
-		const newItem = this._delegate.next(item);
-		const newLocation = this._delegate.location(newItem);
-		if (newLocation) {
-			this._view.reveal(newItem, { select: true, focus: true });
-			this._open(newLocation, preserveFocus);
-		}
-	}
+    private readonly _disposables: vscode.Disposable[] = [];
+    private readonly _ctxCanNavigate = new ContextKey('references-view.canNavigate');
+    private _delegate?: SymbolItemNavigation;
+    constructor(private readonly _view: vscode.TreeView) {
+        this._disposables.push(vscode.commands.registerCommand('references-view.next', () => this.next(false)), vscode.commands.registerCommand('references-view.prev', () => this.previous(false)));
+    }
+    dispose(): void {
+        vscode.Disposable.from(...this._disposables).dispose();
+    }
+    update(delegate: SymbolItemNavigation | undefined) {
+        this._delegate = delegate;
+        this._ctxCanNavigate.set(Boolean(this._delegate));
+    }
+    private _anchor(): undefined | unknown {
+        if (!this._delegate) {
+            return undefined;
+        }
+        const [sel] = this._view.selection;
+        if (sel) {
+            return sel;
+        }
+        if (!vscode.window.activeTextEditor) {
+            return undefined;
+        }
+        return this._delegate.nearest(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active);
+    }
+    private _open(loc: vscode.Location, preserveFocus: boolean) {
+        vscode.commands.executeCommand('vscode.open', loc.uri, {
+            selection: new vscode.Selection(loc.range.start, loc.range.start),
+            preserveFocus
+        });
+    }
+    previous(preserveFocus: boolean): void {
+        if (!this._delegate) {
+            return;
+        }
+        const item = this._anchor();
+        if (!item) {
+            return;
+        }
+        const newItem = this._delegate.previous(item);
+        const newLocation = this._delegate.location(newItem);
+        if (newLocation) {
+            this._view.reveal(newItem, { select: true, focus: true });
+            this._open(newLocation, preserveFocus);
+        }
+    }
+    next(preserveFocus: boolean): void {
+        if (!this._delegate) {
+            return;
+        }
+        const item = this._anchor();
+        if (!item) {
+            return;
+        }
+        const newItem = this._delegate.next(item);
+        const newLocation = this._delegate.location(newItem);
+        if (newLocation) {
+            this._view.reveal(newItem, { select: true, focus: true });
+            this._open(newLocation, preserveFocus);
+        }
+    }
 }
diff --git a/extensions/references-view/Source/references/index.ts b/extensions/references-view/Source/references/index.ts
index c4942f125b76e..76e505e4482c5 100644
--- a/extensions/references-view/Source/references/index.ts
+++ b/extensions/references-view/Source/references/index.ts
@@ -2,94 +2,78 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolsTree } from '../tree';
 import { FileItem, ReferenceItem, ReferencesModel, ReferencesTreeInput } from './model';
-
 export function register(tree: SymbolsTree, context: vscode.ExtensionContext): void {
-
-	function findLocations(title: string, command: string) {
-		if (vscode.window.activeTextEditor) {
-			const input = new ReferencesTreeInput(title, new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), command);
-			tree.setInput(input);
-		}
-	}
-
-	context.subscriptions.push(
-		vscode.commands.registerCommand('references-view.findReferences', () => findLocations('References', 'vscode.executeReferenceProvider')),
-		vscode.commands.registerCommand('references-view.findImplementations', () => findLocations('Implementations', 'vscode.executeImplementationProvider')),
-		// --- legacy name
-		vscode.commands.registerCommand('references-view.find', (...args: any[]) => vscode.commands.executeCommand('references-view.findReferences', ...args)),
-		vscode.commands.registerCommand('references-view.removeReferenceItem', removeReferenceItem),
-		vscode.commands.registerCommand('references-view.copy', copyCommand),
-		vscode.commands.registerCommand('references-view.copyAll', copyAllCommand),
-		vscode.commands.registerCommand('references-view.copyPath', copyPathCommand),
-	);
-
-
-	// --- references.preferredLocation setting
-
-	let showReferencesDisposable: vscode.Disposable | undefined;
-	const config = 'references.preferredLocation';
-	function updateShowReferences(event?: vscode.ConfigurationChangeEvent) {
-		if (event && !event.affectsConfiguration(config)) {
-			return;
-		}
-		const value = vscode.workspace.getConfiguration().get(config);
-
-		showReferencesDisposable?.dispose();
-		showReferencesDisposable = undefined;
-
-		if (value === 'view') {
-			showReferencesDisposable = vscode.commands.registerCommand('editor.action.showReferences', async (uri: vscode.Uri, position: vscode.Position, locations: vscode.Location[]) => {
-				const input = new ReferencesTreeInput(vscode.l10n.t('References'), new vscode.Location(uri, position), 'vscode.executeReferenceProvider', locations);
-				tree.setInput(input);
-			});
-		}
-	}
-	context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateShowReferences));
-	context.subscriptions.push({ dispose: () => showReferencesDisposable?.dispose() });
-	updateShowReferences();
+    function findLocations(title: string, command: string) {
+        if (vscode.window.activeTextEditor) {
+            const input = new ReferencesTreeInput(title, new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), command);
+            tree.setInput(input);
+        }
+    }
+    context.subscriptions.push(vscode.commands.registerCommand('references-view.findReferences', () => findLocations('References', 'vscode.executeReferenceProvider')), vscode.commands.registerCommand('references-view.findImplementations', () => findLocations('Implementations', 'vscode.executeImplementationProvider')), 
+    // --- legacy name
+    vscode.commands.registerCommand('references-view.find', (...args: any[]) => vscode.commands.executeCommand('references-view.findReferences', ...args)), vscode.commands.registerCommand('references-view.removeReferenceItem', removeReferenceItem), vscode.commands.registerCommand('references-view.copy', copyCommand), vscode.commands.registerCommand('references-view.copyAll', copyAllCommand), vscode.commands.registerCommand('references-view.copyPath', copyPathCommand));
+    // --- references.preferredLocation setting
+    let showReferencesDisposable: vscode.Disposable | undefined;
+    const config = 'references.preferredLocation';
+    function updateShowReferences(event?: vscode.ConfigurationChangeEvent) {
+        if (event && !event.affectsConfiguration(config)) {
+            return;
+        }
+        const value = vscode.workspace.getConfiguration().get(config);
+        showReferencesDisposable?.dispose();
+        showReferencesDisposable = undefined;
+        if (value === 'view') {
+            showReferencesDisposable = vscode.commands.registerCommand('editor.action.showReferences', async (uri: vscode.Uri, position: vscode.Position, locations: vscode.Location[]) => {
+                const input = new ReferencesTreeInput(vscode.l10n.t('References'), new vscode.Location(uri, position), 'vscode.executeReferenceProvider', locations);
+                tree.setInput(input);
+            });
+        }
+    }
+    context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateShowReferences));
+    context.subscriptions.push({ dispose: () => showReferencesDisposable?.dispose() });
+    updateShowReferences();
 }
-
 const copyAllCommand = async (item: ReferenceItem | FileItem | unknown) => {
-	if (item instanceof ReferenceItem) {
-		copyCommand(item.file.model);
-	} else if (item instanceof FileItem) {
-		copyCommand(item.model);
-	}
+    if (item instanceof ReferenceItem) {
+        copyCommand(item.file.model);
+    }
+    else if (item instanceof FileItem) {
+        copyCommand(item.model);
+    }
 };
-
 function removeReferenceItem(item: FileItem | ReferenceItem | unknown) {
-	if (item instanceof FileItem) {
-		item.remove();
-	} else if (item instanceof ReferenceItem) {
-		item.remove();
-	}
+    if (item instanceof FileItem) {
+        item.remove();
+    }
+    else if (item instanceof ReferenceItem) {
+        item.remove();
+    }
 }
-
-
 async function copyCommand(item: ReferencesModel | ReferenceItem | FileItem | unknown) {
-	let val: string | undefined;
-	if (item instanceof ReferencesModel) {
-		val = await item.asCopyText();
-	} else if (item instanceof ReferenceItem) {
-		val = await item.asCopyText();
-	} else if (item instanceof FileItem) {
-		val = await item.asCopyText();
-	}
-	if (val) {
-		await vscode.env.clipboard.writeText(val);
-	}
+    let val: string | undefined;
+    if (item instanceof ReferencesModel) {
+        val = await item.asCopyText();
+    }
+    else if (item instanceof ReferenceItem) {
+        val = await item.asCopyText();
+    }
+    else if (item instanceof FileItem) {
+        val = await item.asCopyText();
+    }
+    if (val) {
+        await vscode.env.clipboard.writeText(val);
+    }
 }
-
 async function copyPathCommand(item: FileItem | unknown) {
-	if (item instanceof FileItem) {
-		if (item.uri.scheme === 'file') {
-			vscode.env.clipboard.writeText(item.uri.fsPath);
-		} else {
-			vscode.env.clipboard.writeText(item.uri.toString(true));
-		}
-	}
+    if (item instanceof FileItem) {
+        if (item.uri.scheme === 'file') {
+            vscode.env.clipboard.writeText(item.uri.fsPath);
+        }
+        else {
+            vscode.env.clipboard.writeText(item.uri.toString(true));
+        }
+    }
 }
diff --git a/extensions/references-view/Source/references/model.ts b/extensions/references-view/Source/references/model.ts
index e6c4e23ab93fe..fa600f2464b90 100644
--- a/extensions/references-view/Source/references/model.ts
+++ b/extensions/references-view/Source/references/model.ts
@@ -2,384 +2,327 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput, SymbolTreeModel } from '../references-view';
 import { asResourceUrl, del, getPreviewChunks, tail } from '../utils';
-
 export class ReferencesTreeInput implements SymbolTreeInput {
-
-	readonly contextValue: string;
-
-	constructor(
-		readonly title: string,
-		readonly location: vscode.Location,
-		private readonly _command: string,
-		private readonly _result?: vscode.Location[] | vscode.LocationLink[]
-	) {
-		this.contextValue = _command;
-	}
-
-	async resolve(): Promise | undefined> {
-
-		let model: ReferencesModel;
-		if (this._result) {
-			model = new ReferencesModel(this._result);
-		} else {
-			const resut = await Promise.resolve(vscode.commands.executeCommand(this._command, this.location.uri, this.location.range.start));
-			model = new ReferencesModel(resut ?? []);
-		}
-
-		if (model.items.length === 0) {
-			return;
-		}
-
-		const provider = new ReferencesTreeDataProvider(model);
-		return {
-			provider,
-			get message() { return model.message; },
-			navigation: model,
-			highlights: model,
-			dnd: model,
-			dispose(): void {
-				provider.dispose();
-			}
-		};
-	}
-
-	with(location: vscode.Location): ReferencesTreeInput {
-		return new ReferencesTreeInput(this.title, location, this._command);
-	}
+    readonly contextValue: string;
+    constructor(readonly title: string, readonly location: vscode.Location, private readonly _command: string, private readonly _result?: vscode.Location[] | vscode.LocationLink[]) {
+        this.contextValue = _command;
+    }
+    async resolve(): Promise | undefined> {
+        let model: ReferencesModel;
+        if (this._result) {
+            model = new ReferencesModel(this._result);
+        }
+        else {
+            const resut = await Promise.resolve(vscode.commands.executeCommand(this._command, this.location.uri, this.location.range.start));
+            model = new ReferencesModel(resut ?? []);
+        }
+        if (model.items.length === 0) {
+            return;
+        }
+        const provider = new ReferencesTreeDataProvider(model);
+        return {
+            provider,
+            get message() { return model.message; },
+            navigation: model,
+            highlights: model,
+            dnd: model,
+            dispose(): void {
+                provider.dispose();
+            }
+        };
+    }
+    with(location: vscode.Location): ReferencesTreeInput {
+        return new ReferencesTreeInput(this.title, location, this._command);
+    }
 }
-
 export class ReferencesModel implements SymbolItemNavigation, SymbolItemEditorHighlights, SymbolItemDragAndDrop {
-
-	private _onDidChange = new vscode.EventEmitter();
-	readonly onDidChangeTreeData = this._onDidChange.event;
-
-	readonly items: FileItem[] = [];
-
-	constructor(locations: vscode.Location[] | vscode.LocationLink[]) {
-		let last: FileItem | undefined;
-		for (const item of locations.sort(ReferencesModel._compareLocations)) {
-			const loc = item instanceof vscode.Location
-				? item
-				: new vscode.Location(item.targetUri, item.targetRange);
-
-			if (!last || ReferencesModel._compareUriIgnoreFragment(last.uri, loc.uri) !== 0) {
-				last = new FileItem(loc.uri.with({ fragment: '' }), [], this);
-				this.items.push(last);
-			}
-			last.references.push(new ReferenceItem(loc, last));
-		}
-	}
-
-	private static _compareUriIgnoreFragment(a: vscode.Uri, b: vscode.Uri): number {
-		const aStr = a.with({ fragment: '' }).toString();
-		const bStr = b.with({ fragment: '' }).toString();
-		if (aStr < bStr) {
-			return -1;
-		} else if (aStr > bStr) {
-			return 1;
-		}
-		return 0;
-	}
-
-	private static _compareLocations(a: vscode.Location | vscode.LocationLink, b: vscode.Location | vscode.LocationLink): number {
-		const aUri = a instanceof vscode.Location ? a.uri : a.targetUri;
-		const bUri = b instanceof vscode.Location ? b.uri : b.targetUri;
-		if (aUri.toString() < bUri.toString()) {
-			return -1;
-		} else if (aUri.toString() > bUri.toString()) {
-			return 1;
-		}
-
-		const aRange = a instanceof vscode.Location ? a.range : a.targetRange;
-		const bRange = b instanceof vscode.Location ? b.range : b.targetRange;
-		if (aRange.start.isBefore(bRange.start)) {
-			return -1;
-		} else if (aRange.start.isAfter(bRange.start)) {
-			return 1;
-		} else {
-			return 0;
-		}
-	}
-
-	// --- adapter
-
-	get message() {
-		if (this.items.length === 0) {
-			return vscode.l10n.t('No results.');
-		}
-		const total = this.items.reduce((prev, cur) => prev + cur.references.length, 0);
-		const files = this.items.length;
-		if (total === 1 && files === 1) {
-			return vscode.l10n.t('{0} result in {1} file', total, files);
-		} else if (total === 1) {
-			return vscode.l10n.t('{0} result in {1} files', total, files);
-		} else if (files === 1) {
-			return vscode.l10n.t('{0} results in {1} file', total, files);
-		} else {
-			return vscode.l10n.t('{0} results in {1} files', total, files);
-		}
-	}
-
-	location(item: FileItem | ReferenceItem) {
-		return item instanceof ReferenceItem
-			? item.location
-			: new vscode.Location(item.uri, item.references[0]?.location.range ?? new vscode.Position(0, 0));
-	}
-
-	nearest(uri: vscode.Uri, position: vscode.Position): FileItem | ReferenceItem | undefined {
-
-		if (this.items.length === 0) {
-			return;
-		}
-		// NOTE: this.items is sorted by location (uri/range)
-		for (const item of this.items) {
-			if (item.uri.toString() === uri.toString()) {
-				// (1) pick the item at the request position
-				for (const ref of item.references) {
-					if (ref.location.range.contains(position)) {
-						return ref;
-					}
-				}
-				// (2) pick the first item after or last before the request position
-				let lastBefore: ReferenceItem | undefined;
-				for (const ref of item.references) {
-					if (ref.location.range.end.isAfter(position)) {
-						return ref;
-					}
-					lastBefore = ref;
-				}
-				if (lastBefore) {
-					return lastBefore;
-				}
-
-				break;
-			}
-		}
-
-		// (3) pick the file with the longest common prefix
-		let best = 0;
-		const bestValue = ReferencesModel._prefixLen(this.items[best].toString(), uri.toString());
-
-		for (let i = 1; i < this.items.length; i++) {
-			const value = ReferencesModel._prefixLen(this.items[i].uri.toString(), uri.toString());
-			if (value > bestValue) {
-				best = i;
-			}
-		}
-
-		return this.items[best].references[0];
-	}
-
-	private static _prefixLen(a: string, b: string): number {
-		let pos = 0;
-		while (pos < a.length && pos < b.length && a.charCodeAt(pos) === b.charCodeAt(pos)) {
-			pos += 1;
-		}
-		return pos;
-	}
-
-	next(item: FileItem | ReferenceItem): FileItem | ReferenceItem {
-		return this._move(item, true) ?? item;
-	}
-
-	previous(item: FileItem | ReferenceItem): FileItem | ReferenceItem {
-		return this._move(item, false) ?? item;
-	}
-
-	private _move(item: FileItem | ReferenceItem, fwd: boolean): ReferenceItem | void {
-
-		const delta = fwd ? +1 : -1;
-
-		const _move = (item: FileItem): FileItem => {
-			const idx = (this.items.indexOf(item) + delta + this.items.length) % this.items.length;
-			return this.items[idx];
-		};
-
-		if (item instanceof FileItem) {
-			if (fwd) {
-				return _move(item).references[0];
-			} else {
-				return tail(_move(item).references);
-			}
-		}
-
-		if (item instanceof ReferenceItem) {
-			const idx = item.file.references.indexOf(item) + delta;
-			if (idx < 0) {
-				return tail(_move(item.file).references);
-			} else if (idx >= item.file.references.length) {
-				return _move(item.file).references[0];
-			} else {
-				return item.file.references[idx];
-			}
-		}
-	}
-
-	getEditorHighlights(_item: FileItem | ReferenceItem, uri: vscode.Uri): vscode.Range[] | undefined {
-		const file = this.items.find(file => file.uri.toString() === uri.toString());
-		return file?.references.map(ref => ref.location.range);
-	}
-
-	remove(item: FileItem | ReferenceItem) {
-		if (item instanceof FileItem) {
-			del(this.items, item);
-			this._onDidChange.fire(undefined);
-		} else {
-			del(item.file.references, item);
-			if (item.file.references.length === 0) {
-				del(this.items, item.file);
-				this._onDidChange.fire(undefined);
-			} else {
-				this._onDidChange.fire(item.file);
-			}
-		}
-	}
-
-	async asCopyText() {
-		let result = '';
-		for (const item of this.items) {
-			result += `${await item.asCopyText()}\n`;
-		}
-		return result;
-	}
-
-	getDragUri(item: FileItem | ReferenceItem): vscode.Uri | undefined {
-		if (item instanceof FileItem) {
-			return item.uri;
-		} else {
-			return asResourceUrl(item.file.uri, item.location.range);
-		}
-	}
+    private _onDidChange = new vscode.EventEmitter();
+    readonly onDidChangeTreeData = this._onDidChange.event;
+    readonly items: FileItem[] = [];
+    constructor(locations: vscode.Location[] | vscode.LocationLink[]) {
+        let last: FileItem | undefined;
+        for (const item of locations.sort(ReferencesModel._compareLocations)) {
+            const loc = item instanceof vscode.Location
+                ? item
+                : new vscode.Location(item.targetUri, item.targetRange);
+            if (!last || ReferencesModel._compareUriIgnoreFragment(last.uri, loc.uri) !== 0) {
+                last = new FileItem(loc.uri.with({ fragment: '' }), [], this);
+                this.items.push(last);
+            }
+            last.references.push(new ReferenceItem(loc, last));
+        }
+    }
+    private static _compareUriIgnoreFragment(a: vscode.Uri, b: vscode.Uri): number {
+        const aStr = a.with({ fragment: '' }).toString();
+        const bStr = b.with({ fragment: '' }).toString();
+        if (aStr < bStr) {
+            return -1;
+        }
+        else if (aStr > bStr) {
+            return 1;
+        }
+        return 0;
+    }
+    private static _compareLocations(a: vscode.Location | vscode.LocationLink, b: vscode.Location | vscode.LocationLink): number {
+        const aUri = a instanceof vscode.Location ? a.uri : a.targetUri;
+        const bUri = b instanceof vscode.Location ? b.uri : b.targetUri;
+        if (aUri.toString() < bUri.toString()) {
+            return -1;
+        }
+        else if (aUri.toString() > bUri.toString()) {
+            return 1;
+        }
+        const aRange = a instanceof vscode.Location ? a.range : a.targetRange;
+        const bRange = b instanceof vscode.Location ? b.range : b.targetRange;
+        if (aRange.start.isBefore(bRange.start)) {
+            return -1;
+        }
+        else if (aRange.start.isAfter(bRange.start)) {
+            return 1;
+        }
+        else {
+            return 0;
+        }
+    }
+    // --- adapter
+    get message() {
+        if (this.items.length === 0) {
+            return vscode.l10n.t('No results.');
+        }
+        const total = this.items.reduce((prev, cur) => prev + cur.references.length, 0);
+        const files = this.items.length;
+        if (total === 1 && files === 1) {
+            return vscode.l10n.t('{0} result in {1} file', total, files);
+        }
+        else if (total === 1) {
+            return vscode.l10n.t('{0} result in {1} files', total, files);
+        }
+        else if (files === 1) {
+            return vscode.l10n.t('{0} results in {1} file', total, files);
+        }
+        else {
+            return vscode.l10n.t('{0} results in {1} files', total, files);
+        }
+    }
+    location(item: FileItem | ReferenceItem) {
+        return item instanceof ReferenceItem
+            ? item.location
+            : new vscode.Location(item.uri, item.references[0]?.location.range ?? new vscode.Position(0, 0));
+    }
+    nearest(uri: vscode.Uri, position: vscode.Position): FileItem | ReferenceItem | undefined {
+        if (this.items.length === 0) {
+            return;
+        }
+        // NOTE: this.items is sorted by location (uri/range)
+        for (const item of this.items) {
+            if (item.uri.toString() === uri.toString()) {
+                // (1) pick the item at the request position
+                for (const ref of item.references) {
+                    if (ref.location.range.contains(position)) {
+                        return ref;
+                    }
+                }
+                // (2) pick the first item after or last before the request position
+                let lastBefore: ReferenceItem | undefined;
+                for (const ref of item.references) {
+                    if (ref.location.range.end.isAfter(position)) {
+                        return ref;
+                    }
+                    lastBefore = ref;
+                }
+                if (lastBefore) {
+                    return lastBefore;
+                }
+                break;
+            }
+        }
+        // (3) pick the file with the longest common prefix
+        let best = 0;
+        const bestValue = ReferencesModel._prefixLen(this.items[best].toString(), uri.toString());
+        for (let i = 1; i < this.items.length; i++) {
+            const value = ReferencesModel._prefixLen(this.items[i].uri.toString(), uri.toString());
+            if (value > bestValue) {
+                best = i;
+            }
+        }
+        return this.items[best].references[0];
+    }
+    private static _prefixLen(a: string, b: string): number {
+        let pos = 0;
+        while (pos < a.length && pos < b.length && a.charCodeAt(pos) === b.charCodeAt(pos)) {
+            pos += 1;
+        }
+        return pos;
+    }
+    next(item: FileItem | ReferenceItem): FileItem | ReferenceItem {
+        return this._move(item, true) ?? item;
+    }
+    previous(item: FileItem | ReferenceItem): FileItem | ReferenceItem {
+        return this._move(item, false) ?? item;
+    }
+    private _move(item: FileItem | ReferenceItem, fwd: boolean): ReferenceItem | void {
+        const delta = fwd ? +1 : -1;
+        const _move = (item: FileItem): FileItem => {
+            const idx = (this.items.indexOf(item) + delta + this.items.length) % this.items.length;
+            return this.items[idx];
+        };
+        if (item instanceof FileItem) {
+            if (fwd) {
+                return _move(item).references[0];
+            }
+            else {
+                return tail(_move(item).references);
+            }
+        }
+        if (item instanceof ReferenceItem) {
+            const idx = item.file.references.indexOf(item) + delta;
+            if (idx < 0) {
+                return tail(_move(item.file).references);
+            }
+            else if (idx >= item.file.references.length) {
+                return _move(item.file).references[0];
+            }
+            else {
+                return item.file.references[idx];
+            }
+        }
+    }
+    getEditorHighlights(_item: FileItem | ReferenceItem, uri: vscode.Uri): vscode.Range[] | undefined {
+        const file = this.items.find(file => file.uri.toString() === uri.toString());
+        return file?.references.map(ref => ref.location.range);
+    }
+    remove(item: FileItem | ReferenceItem) {
+        if (item instanceof FileItem) {
+            del(this.items, item);
+            this._onDidChange.fire(undefined);
+        }
+        else {
+            del(item.file.references, item);
+            if (item.file.references.length === 0) {
+                del(this.items, item.file);
+                this._onDidChange.fire(undefined);
+            }
+            else {
+                this._onDidChange.fire(item.file);
+            }
+        }
+    }
+    async asCopyText() {
+        let result = '';
+        for (const item of this.items) {
+            result += `${await item.asCopyText()}\n`;
+        }
+        return result;
+    }
+    getDragUri(item: FileItem | ReferenceItem): vscode.Uri | undefined {
+        if (item instanceof FileItem) {
+            return item.uri;
+        }
+        else {
+            return asResourceUrl(item.file.uri, item.location.range);
+        }
+    }
 }
-
 class ReferencesTreeDataProvider implements vscode.TreeDataProvider {
-
-	private readonly _listener: vscode.Disposable;
-	private readonly _onDidChange = new vscode.EventEmitter();
-
-	readonly onDidChangeTreeData = this._onDidChange.event;
-
-	constructor(private readonly _model: ReferencesModel) {
-		this._listener = _model.onDidChangeTreeData(() => this._onDidChange.fire(undefined));
-	}
-
-	dispose(): void {
-		this._onDidChange.dispose();
-		this._listener.dispose();
-	}
-
-	async getTreeItem(element: FileItem | ReferenceItem) {
-		if (element instanceof FileItem) {
-			// files
-			const result = new vscode.TreeItem(element.uri);
-			result.contextValue = 'file-item';
-			result.description = true;
-			result.iconPath = vscode.ThemeIcon.File;
-			result.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
-			return result;
-
-		} else {
-			// references
-			const { range } = element.location;
-			const doc = await element.getDocument(true);
-			const { before, inside, after } = getPreviewChunks(doc, range);
-
-			const label: vscode.TreeItemLabel = {
-				label: before + inside + after,
-				highlights: [[before.length, before.length + inside.length]]
-			};
-
-			const result = new vscode.TreeItem(label);
-			result.collapsibleState = vscode.TreeItemCollapsibleState.None;
-			result.contextValue = 'reference-item';
-			result.command = {
-				command: 'vscode.open',
-				title: vscode.l10n.t('Open Reference'),
-				arguments: [
-					element.location.uri,
-					{ selection: range.with({ end: range.start }) } satisfies vscode.TextDocumentShowOptions
-				]
-			};
-			return result;
-		}
-	}
-
-	async getChildren(element?: FileItem | ReferenceItem) {
-		if (!element) {
-			return this._model.items;
-		}
-		if (element instanceof FileItem) {
-			return element.references;
-		}
-		return undefined;
-	}
-
-	getParent(element: FileItem | ReferenceItem) {
-		return element instanceof ReferenceItem ? element.file : undefined;
-	}
+    private readonly _listener: vscode.Disposable;
+    private readonly _onDidChange = new vscode.EventEmitter();
+    readonly onDidChangeTreeData = this._onDidChange.event;
+    constructor(private readonly _model: ReferencesModel) {
+        this._listener = _model.onDidChangeTreeData(() => this._onDidChange.fire(undefined));
+    }
+    dispose(): void {
+        this._onDidChange.dispose();
+        this._listener.dispose();
+    }
+    async getTreeItem(element: FileItem | ReferenceItem) {
+        if (element instanceof FileItem) {
+            // files
+            const result = new vscode.TreeItem(element.uri);
+            result.contextValue = 'file-item';
+            result.description = true;
+            result.iconPath = vscode.ThemeIcon.File;
+            result.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
+            return result;
+        }
+        else {
+            // references
+            const { range } = element.location;
+            const doc = await element.getDocument(true);
+            const { before, inside, after } = getPreviewChunks(doc, range);
+            const label: vscode.TreeItemLabel = {
+                label: before + inside + after,
+                highlights: [[before.length, before.length + inside.length]]
+            };
+            const result = new vscode.TreeItem(label);
+            result.collapsibleState = vscode.TreeItemCollapsibleState.None;
+            result.contextValue = 'reference-item';
+            result.command = {
+                command: 'vscode.open',
+                title: vscode.l10n.t('Open Reference'),
+                arguments: [
+                    element.location.uri,
+                    { selection: range.with({ end: range.start }) } satisfies vscode.TextDocumentShowOptions
+                ]
+            };
+            return result;
+        }
+    }
+    async getChildren(element?: FileItem | ReferenceItem) {
+        if (!element) {
+            return this._model.items;
+        }
+        if (element instanceof FileItem) {
+            return element.references;
+        }
+        return undefined;
+    }
+    getParent(element: FileItem | ReferenceItem) {
+        return element instanceof ReferenceItem ? element.file : undefined;
+    }
 }
-
 export class FileItem {
-
-	constructor(
-		readonly uri: vscode.Uri,
-		readonly references: Array,
-		readonly model: ReferencesModel
-	) { }
-
-	// --- adapter
-
-	remove(): void {
-		this.model.remove(this);
-	}
-
-	async asCopyText() {
-		let result = `${vscode.workspace.asRelativePath(this.uri)}\n`;
-		for (const ref of this.references) {
-			result += `  ${await ref.asCopyText()}\n`;
-		}
-		return result;
-	}
+    constructor(readonly uri: vscode.Uri, readonly references: Array, readonly model: ReferencesModel) { }
+    // --- adapter
+    remove(): void {
+        this.model.remove(this);
+    }
+    async asCopyText() {
+        let result = `${vscode.workspace.asRelativePath(this.uri)}\n`;
+        for (const ref of this.references) {
+            result += `  ${await ref.asCopyText()}\n`;
+        }
+        return result;
+    }
 }
-
 export class ReferenceItem {
-
-	private _document: Thenable | undefined;
-
-	constructor(
-		readonly location: vscode.Location,
-		readonly file: FileItem,
-	) { }
-
-	async getDocument(warmUpNext?: boolean) {
-		if (!this._document) {
-			this._document = vscode.workspace.openTextDocument(this.location.uri);
-		}
-		if (warmUpNext) {
-			// load next document once this document has been loaded
-			const next = this.file.model.next(this.file);
-			if (next instanceof FileItem && next !== this.file) {
-				vscode.workspace.openTextDocument(next.uri);
-			} else if (next instanceof ReferenceItem) {
-				vscode.workspace.openTextDocument(next.location.uri);
-			}
-		}
-		return this._document;
-	}
-
-	// --- adapter
-
-	remove(): void {
-		this.file.model.remove(this);
-	}
-
-	async asCopyText() {
-		const doc = await this.getDocument();
-		const chunks = getPreviewChunks(doc, this.location.range, 21, false);
-		return `${this.location.range.start.line + 1}, ${this.location.range.start.character + 1}: ${chunks.before + chunks.inside + chunks.after}`;
-	}
+    private _document: Thenable | undefined;
+    constructor(readonly location: vscode.Location, readonly file: FileItem) { }
+    async getDocument(warmUpNext?: boolean) {
+        if (!this._document) {
+            this._document = vscode.workspace.openTextDocument(this.location.uri);
+        }
+        if (warmUpNext) {
+            // load next document once this document has been loaded
+            const next = this.file.model.next(this.file);
+            if (next instanceof FileItem && next !== this.file) {
+                vscode.workspace.openTextDocument(next.uri);
+            }
+            else if (next instanceof ReferenceItem) {
+                vscode.workspace.openTextDocument(next.location.uri);
+            }
+        }
+        return this._document;
+    }
+    // --- adapter
+    remove(): void {
+        this.file.model.remove(this);
+    }
+    async asCopyText() {
+        const doc = await this.getDocument();
+        const chunks = getPreviewChunks(doc, this.location.range, 21, false);
+        return `${this.location.range.start.line + 1}, ${this.location.range.start.character + 1}: ${chunks.before + chunks.inside + chunks.after}`;
+    }
 }
diff --git a/extensions/references-view/Source/tree.ts b/extensions/references-view/Source/tree.ts
index 4a6bf189675a0..4a206ab24b31e 100644
--- a/extensions/references-view/Source/tree.ts
+++ b/extensions/references-view/Source/tree.ts
@@ -2,351 +2,269 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { EditorHighlights } from './highlights';
 import { Navigation } from './navigation';
 import { SymbolItemDragAndDrop, SymbolTreeInput } from './references-view';
 import { ContextKey, isValidRequestPosition, WordAnchor } from './utils';
-
-
 export class SymbolsTree {
-
-	readonly viewId = 'references-view.tree';
-
-	private readonly _ctxIsActive = new ContextKey('reference-list.isActive');
-	private readonly _ctxHasResult = new ContextKey('reference-list.hasResult');
-	private readonly _ctxInputSource = new ContextKey('reference-list.source');
-
-	private readonly _history = new TreeInputHistory(this);
-	private readonly _provider = new TreeDataProviderDelegate();
-	private readonly _dnd = new TreeDndDelegate();
-	private readonly _tree: vscode.TreeView;
-	private readonly _navigation: Navigation;
-
-	private _input?: SymbolTreeInput;
-	private _sessionDisposable?: vscode.Disposable;
-
-	constructor() {
-		this._tree = vscode.window.createTreeView(this.viewId, {
-			treeDataProvider: this._provider,
-			showCollapseAll: true,
-			dragAndDropController: this._dnd
-		});
-		this._navigation = new Navigation(this._tree);
-	}
-
-	dispose(): void {
-		this._history.dispose();
-		this._tree.dispose();
-		this._sessionDisposable?.dispose();
-	}
-
-	getInput(): SymbolTreeInput | undefined {
-		return this._input;
-	}
-
-	async setInput(input: SymbolTreeInput) {
-
-		if (!await isValidRequestPosition(input.location.uri, input.location.range.start)) {
-			this.clearInput();
-			return;
-		}
-
-		this._ctxInputSource.set(input.contextValue);
-		this._ctxIsActive.set(true);
-		this._ctxHasResult.set(true);
-		vscode.commands.executeCommand(`${this.viewId}.focus`);
-
-		const newInputKind = !this._input || Object.getPrototypeOf(this._input) !== Object.getPrototypeOf(input);
-		this._input = input;
-		this._sessionDisposable?.dispose();
-
-		this._tree.title = input.title;
-		this._tree.message = newInputKind ? undefined : this._tree.message;
-
-		const modelPromise = Promise.resolve(input.resolve());
-
-		// set promise to tree data provider to trigger tree loading UI
-		this._provider.update(modelPromise.then(model => model?.provider ?? this._history));
-		this._dnd.update(modelPromise.then(model => model?.dnd));
-
-		const model = await modelPromise;
-		if (this._input !== input) {
-			return;
-		}
-
-		if (!model) {
-			this.clearInput();
-			return;
-		}
-
-		this._history.add(input);
-		this._tree.message = model.message;
-
-		// navigation
-		this._navigation.update(model.navigation);
-
-		// reveal & select
-		const selection = model.navigation?.nearest(input.location.uri, input.location.range.start);
-		if (selection && this._tree.visible) {
-			await this._tree.reveal(selection, { select: true, focus: true, expand: true });
-		}
-
-		const disposables: vscode.Disposable[] = [];
-
-		// editor highlights
-		let highlights: EditorHighlights | undefined;
-		if (model.highlights) {
-			highlights = new EditorHighlights(this._tree, model.highlights);
-			disposables.push(highlights);
-		}
-
-		// listener
-		if (model.provider.onDidChangeTreeData) {
-			disposables.push(model.provider.onDidChangeTreeData(() => {
-				this._tree.title = input.title;
-				this._tree.message = model.message;
-				highlights?.update();
-			}));
-		}
-		if (typeof model.dispose === 'function') {
-			disposables.push(new vscode.Disposable(() => model.dispose!()));
-		}
-		this._sessionDisposable = vscode.Disposable.from(...disposables);
-	}
-
-	clearInput(): void {
-		this._sessionDisposable?.dispose();
-		this._input = undefined;
-		this._ctxHasResult.set(false);
-		this._ctxInputSource.reset();
-		this._tree.title = vscode.l10n.t('References');
-		this._tree.message = this._history.size === 0
-			? vscode.l10n.t('No results.')
-			: vscode.l10n.t('No results. Try running a previous search again:');
-		this._provider.update(Promise.resolve(this._history));
-	}
+    readonly viewId = 'references-view.tree';
+    private readonly _ctxIsActive = new ContextKey('reference-list.isActive');
+    private readonly _ctxHasResult = new ContextKey('reference-list.hasResult');
+    private readonly _ctxInputSource = new ContextKey('reference-list.source');
+    private readonly _history = new TreeInputHistory(this);
+    private readonly _provider = new TreeDataProviderDelegate();
+    private readonly _dnd = new TreeDndDelegate();
+    private readonly _tree: vscode.TreeView;
+    private readonly _navigation: Navigation;
+    private _input?: SymbolTreeInput;
+    private _sessionDisposable?: vscode.Disposable;
+    constructor() {
+        this._tree = vscode.window.createTreeView(this.viewId, {
+            treeDataProvider: this._provider,
+            showCollapseAll: true,
+            dragAndDropController: this._dnd
+        });
+        this._navigation = new Navigation(this._tree);
+    }
+    dispose(): void {
+        this._history.dispose();
+        this._tree.dispose();
+        this._sessionDisposable?.dispose();
+    }
+    getInput(): SymbolTreeInput | undefined {
+        return this._input;
+    }
+    async setInput(input: SymbolTreeInput) {
+        if (!await isValidRequestPosition(input.location.uri, input.location.range.start)) {
+            this.clearInput();
+            return;
+        }
+        this._ctxInputSource.set(input.contextValue);
+        this._ctxIsActive.set(true);
+        this._ctxHasResult.set(true);
+        vscode.commands.executeCommand(`${this.viewId}.focus`);
+        const newInputKind = !this._input || Object.getPrototypeOf(this._input) !== Object.getPrototypeOf(input);
+        this._input = input;
+        this._sessionDisposable?.dispose();
+        this._tree.title = input.title;
+        this._tree.message = newInputKind ? undefined : this._tree.message;
+        const modelPromise = Promise.resolve(input.resolve());
+        // set promise to tree data provider to trigger tree loading UI
+        this._provider.update(modelPromise.then(model => model?.provider ?? this._history));
+        this._dnd.update(modelPromise.then(model => model?.dnd));
+        const model = await modelPromise;
+        if (this._input !== input) {
+            return;
+        }
+        if (!model) {
+            this.clearInput();
+            return;
+        }
+        this._history.add(input);
+        this._tree.message = model.message;
+        // navigation
+        this._navigation.update(model.navigation);
+        // reveal & select
+        const selection = model.navigation?.nearest(input.location.uri, input.location.range.start);
+        if (selection && this._tree.visible) {
+            await this._tree.reveal(selection, { select: true, focus: true, expand: true });
+        }
+        const disposables: vscode.Disposable[] = [];
+        // editor highlights
+        let highlights: EditorHighlights | undefined;
+        if (model.highlights) {
+            highlights = new EditorHighlights(this._tree, model.highlights);
+            disposables.push(highlights);
+        }
+        // listener
+        if (model.provider.onDidChangeTreeData) {
+            disposables.push(model.provider.onDidChangeTreeData(() => {
+                this._tree.title = input.title;
+                this._tree.message = model.message;
+                highlights?.update();
+            }));
+        }
+        if (typeof model.dispose === 'function') {
+            disposables.push(new vscode.Disposable(() => model.dispose!()));
+        }
+        this._sessionDisposable = vscode.Disposable.from(...disposables);
+    }
+    clearInput(): void {
+        this._sessionDisposable?.dispose();
+        this._input = undefined;
+        this._ctxHasResult.set(false);
+        this._ctxInputSource.reset();
+        this._tree.title = vscode.l10n.t('References');
+        this._tree.message = this._history.size === 0
+            ? vscode.l10n.t('No results.')
+            : vscode.l10n.t('No results. Try running a previous search again:');
+        this._provider.update(Promise.resolve(this._history));
+    }
 }
-
 // --- tree data
-
 interface ActiveTreeDataProviderWrapper {
-	provider: Promise>;
+    provider: Promise>;
 }
-
 class TreeDataProviderDelegate implements vscode.TreeDataProvider {
-
-	provider?: Promise>;
-
-	private _sessionDispoables?: vscode.Disposable;
-	private _onDidChange = new vscode.EventEmitter();
-
-	readonly onDidChangeTreeData = this._onDidChange.event;
-
-	update(provider: Promise>) {
-
-		this._sessionDispoables?.dispose();
-		this._sessionDispoables = undefined;
-
-		this._onDidChange.fire(undefined);
-
-		this.provider = provider;
-
-		provider.then(value => {
-			if (this.provider === provider && value.onDidChangeTreeData) {
-				this._sessionDispoables = value.onDidChangeTreeData(this._onDidChange.fire, this._onDidChange);
-			}
-		}).catch(err => {
-			this.provider = undefined;
-			console.error(err);
-		});
-	}
-
-	async getTreeItem(element: unknown) {
-		this._assertProvider();
-		return (await this.provider).getTreeItem(element);
-	}
-
-	async getChildren(parent?: unknown | undefined) {
-		this._assertProvider();
-		return (await this.provider).getChildren(parent);
-	}
-
-	async getParent(element: unknown) {
-		this._assertProvider();
-		const provider = await this.provider;
-		return provider.getParent ? provider.getParent(element) : undefined;
-	}
-
-	private _assertProvider(): asserts this is ActiveTreeDataProviderWrapper {
-		if (!this.provider) {
-			throw new Error('MISSING provider');
-		}
-	}
+    provider?: Promise>;
+    private _sessionDispoables?: vscode.Disposable;
+    private _onDidChange = new vscode.EventEmitter();
+    readonly onDidChangeTreeData = this._onDidChange.event;
+    update(provider: Promise>) {
+        this._sessionDispoables?.dispose();
+        this._sessionDispoables = undefined;
+        this._onDidChange.fire(undefined);
+        this.provider = provider;
+        provider.then(value => {
+            if (this.provider === provider && value.onDidChangeTreeData) {
+                this._sessionDispoables = value.onDidChangeTreeData(this._onDidChange.fire, this._onDidChange);
+            }
+        }).catch(err => {
+            this.provider = undefined;
+            console.error(err);
+        });
+    }
+    async getTreeItem(element: unknown) {
+        this._assertProvider();
+        return (await this.provider).getTreeItem(element);
+    }
+    async getChildren(parent?: unknown | undefined) {
+        this._assertProvider();
+        return (await this.provider).getChildren(parent);
+    }
+    async getParent(element: unknown) {
+        this._assertProvider();
+        const provider = await this.provider;
+        return provider.getParent ? provider.getParent(element) : undefined;
+    }
+    private _assertProvider(): asserts this is ActiveTreeDataProviderWrapper {
+        if (!this.provider) {
+            throw new Error('MISSING provider');
+        }
+    }
 }
-
 // --- tree dnd
-
 class TreeDndDelegate implements vscode.TreeDragAndDropController {
-
-	private _delegate: SymbolItemDragAndDrop | undefined;
-
-	readonly dropMimeTypes: string[] = [];
-
-	readonly dragMimeTypes: string[] = ['text/uri-list'];
-
-	update(delegate: Promise | undefined>) {
-		this._delegate = undefined;
-		delegate.then(value => this._delegate = value);
-	}
-
-	handleDrag(source: undefined[], data: vscode.DataTransfer) {
-		if (this._delegate) {
-			const urls: string[] = [];
-			for (const item of source) {
-				const uri = this._delegate.getDragUri(item);
-				if (uri) {
-					urls.push(uri.toString());
-				}
-			}
-			if (urls.length > 0) {
-				data.set('text/uri-list', new vscode.DataTransferItem(urls.join('\r\n')));
-			}
-		}
-	}
-
-	handleDrop(): void | Thenable {
-		throw new Error('Method not implemented.');
-	}
+    private _delegate: SymbolItemDragAndDrop | undefined;
+    readonly dropMimeTypes: string[] = [];
+    readonly dragMimeTypes: string[] = ['text/uri-list'];
+    update(delegate: Promise | undefined>) {
+        this._delegate = undefined;
+        delegate.then(value => this._delegate = value);
+    }
+    handleDrag(source: undefined[], data: vscode.DataTransfer) {
+        if (this._delegate) {
+            const urls: string[] = [];
+            for (const item of source) {
+                const uri = this._delegate.getDragUri(item);
+                if (uri) {
+                    urls.push(uri.toString());
+                }
+            }
+            if (urls.length > 0) {
+                data.set('text/uri-list', new vscode.DataTransferItem(urls.join('\r\n')));
+            }
+        }
+    }
+    handleDrop(): void | Thenable {
+        throw new Error('Method not implemented.');
+    }
 }
-
 // --- history
-
 class HistoryItem {
-
-	readonly description: string;
-
-	constructor(
-		readonly key: string,
-		readonly word: string,
-		readonly anchor: WordAnchor,
-		readonly input: SymbolTreeInput,
-	) {
-		this.description = `${vscode.workspace.asRelativePath(input.location.uri)} • ${input.title.toLocaleLowerCase()}`;
-	}
+    readonly description: string;
+    constructor(readonly key: string, readonly word: string, readonly anchor: WordAnchor, readonly input: SymbolTreeInput) {
+        this.description = `${vscode.workspace.asRelativePath(input.location.uri)} • ${input.title.toLocaleLowerCase()}`;
+    }
 }
-
 class TreeInputHistory implements vscode.TreeDataProvider {
-
-	private readonly _onDidChangeTreeData = new vscode.EventEmitter();
-	readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
-
-	private readonly _disposables: vscode.Disposable[] = [];
-	private readonly _ctxHasHistory = new ContextKey('reference-list.hasHistory');
-	private readonly _inputs = new Map();
-
-	constructor(private readonly _tree: SymbolsTree) {
-
-		this._disposables.push(
-			vscode.commands.registerCommand('references-view.clear', () => _tree.clearInput()),
-			vscode.commands.registerCommand('references-view.clearHistory', () => {
-				this.clear();
-				_tree.clearInput();
-			}),
-			vscode.commands.registerCommand('references-view.refind', (item) => {
-				if (item instanceof HistoryItem) {
-					this._reRunHistoryItem(item);
-				}
-			}),
-			vscode.commands.registerCommand('references-view.refresh', () => {
-				const item = Array.from(this._inputs.values()).pop();
-				if (item) {
-					this._reRunHistoryItem(item);
-				}
-			}),
-			vscode.commands.registerCommand('_references-view.showHistoryItem', async (item) => {
-				if (item instanceof HistoryItem) {
-					const position = item.anchor.guessedTrackedPosition() ?? item.input.location.range.start;
-					await vscode.commands.executeCommand('vscode.open', item.input.location.uri, { selection: new vscode.Range(position, position) });
-				}
-			}),
-			vscode.commands.registerCommand('references-view.pickFromHistory', async () => {
-				interface HistoryPick extends vscode.QuickPickItem {
-					item: HistoryItem;
-				}
-				const entries = await this.getChildren();
-				const picks = entries.map((item): HistoryPick => ({
-					label: item.word,
-					description: item.description,
-					item
-				}));
-				const pick = await vscode.window.showQuickPick(picks, { placeHolder: vscode.l10n.t('Select previous reference search') });
-				if (pick) {
-					this._reRunHistoryItem(pick.item);
-				}
-			}),
-		);
-	}
-
-	dispose(): void {
-		vscode.Disposable.from(...this._disposables).dispose();
-		this._onDidChangeTreeData.dispose();
-	}
-
-	private _reRunHistoryItem(item: HistoryItem): void {
-		this._inputs.delete(item.key);
-		const newPosition = item.anchor.guessedTrackedPosition();
-		let newInput = item.input;
-		// create a new input when having a tracked position which is
-		// different than the original position.
-		if (newPosition && !item.input.location.range.start.isEqual(newPosition)) {
-			newInput = item.input.with(new vscode.Location(item.input.location.uri, newPosition));
-		}
-		this._tree.setInput(newInput);
-	}
-
-	async add(input: SymbolTreeInput) {
-
-		const doc = await vscode.workspace.openTextDocument(input.location.uri);
-
-		const anchor = new WordAnchor(doc, input.location.range.start);
-		const range = doc.getWordRangeAtPosition(input.location.range.start) ?? doc.getWordRangeAtPosition(input.location.range.start, /[^\s]+/);
-		const word = range ? doc.getText(range) : '???';
-
-		const item = new HistoryItem(JSON.stringify([range?.start ?? input.location.range.start, input.location.uri, input.title]), word, anchor, input);
-		// use filo-ordering of native maps
-		this._inputs.delete(item.key);
-		this._inputs.set(item.key, item);
-		this._ctxHasHistory.set(true);
-	}
-
-	clear(): void {
-		this._inputs.clear();
-		this._ctxHasHistory.set(false);
-		this._onDidChangeTreeData.fire(undefined);
-	}
-
-	get size() {
-		return this._inputs.size;
-	}
-
-	// --- tree data provider
-
-	getTreeItem(item: HistoryItem): vscode.TreeItem {
-		const result = new vscode.TreeItem(item.word);
-		result.description = item.description;
-		result.command = { command: '_references-view.showHistoryItem', arguments: [item], title: vscode.l10n.t('Rerun') };
-		result.collapsibleState = vscode.TreeItemCollapsibleState.None;
-		result.contextValue = 'history-item';
-		return result;
-	}
-
-	getChildren() {
-		return Promise.all([...this._inputs.values()].reverse());
-	}
-
-	getParent() {
-		return undefined;
-	}
+    private readonly _onDidChangeTreeData = new vscode.EventEmitter();
+    readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
+    private readonly _disposables: vscode.Disposable[] = [];
+    private readonly _ctxHasHistory = new ContextKey('reference-list.hasHistory');
+    private readonly _inputs = new Map();
+    constructor(private readonly _tree: SymbolsTree) {
+        this._disposables.push(vscode.commands.registerCommand('references-view.clear', () => _tree.clearInput()), vscode.commands.registerCommand('references-view.clearHistory', () => {
+            this.clear();
+            _tree.clearInput();
+        }), vscode.commands.registerCommand('references-view.refind', (item) => {
+            if (item instanceof HistoryItem) {
+                this._reRunHistoryItem(item);
+            }
+        }), vscode.commands.registerCommand('references-view.refresh', () => {
+            const item = Array.from(this._inputs.values()).pop();
+            if (item) {
+                this._reRunHistoryItem(item);
+            }
+        }), vscode.commands.registerCommand('_references-view.showHistoryItem', async (item) => {
+            if (item instanceof HistoryItem) {
+                const position = item.anchor.guessedTrackedPosition() ?? item.input.location.range.start;
+                await vscode.commands.executeCommand('vscode.open', item.input.location.uri, { selection: new vscode.Range(position, position) });
+            }
+        }), vscode.commands.registerCommand('references-view.pickFromHistory', async () => {
+            interface HistoryPick extends vscode.QuickPickItem {
+                item: HistoryItem;
+            }
+            const entries = await this.getChildren();
+            const picks = entries.map((item): HistoryPick => ({
+                label: item.word,
+                description: item.description,
+                item
+            }));
+            const pick = await vscode.window.showQuickPick(picks, { placeHolder: vscode.l10n.t('Select previous reference search') });
+            if (pick) {
+                this._reRunHistoryItem(pick.item);
+            }
+        }));
+    }
+    dispose(): void {
+        vscode.Disposable.from(...this._disposables).dispose();
+        this._onDidChangeTreeData.dispose();
+    }
+    private _reRunHistoryItem(item: HistoryItem): void {
+        this._inputs.delete(item.key);
+        const newPosition = item.anchor.guessedTrackedPosition();
+        let newInput = item.input;
+        // create a new input when having a tracked position which is
+        // different than the original position.
+        if (newPosition && !item.input.location.range.start.isEqual(newPosition)) {
+            newInput = item.input.with(new vscode.Location(item.input.location.uri, newPosition));
+        }
+        this._tree.setInput(newInput);
+    }
+    async add(input: SymbolTreeInput) {
+        const doc = await vscode.workspace.openTextDocument(input.location.uri);
+        const anchor = new WordAnchor(doc, input.location.range.start);
+        const range = doc.getWordRangeAtPosition(input.location.range.start) ?? doc.getWordRangeAtPosition(input.location.range.start, /[^\s]+/);
+        const word = range ? doc.getText(range) : '???';
+        const item = new HistoryItem(JSON.stringify([range?.start ?? input.location.range.start, input.location.uri, input.title]), word, anchor, input);
+        // use filo-ordering of native maps
+        this._inputs.delete(item.key);
+        this._inputs.set(item.key, item);
+        this._ctxHasHistory.set(true);
+    }
+    clear(): void {
+        this._inputs.clear();
+        this._ctxHasHistory.set(false);
+        this._onDidChangeTreeData.fire(undefined);
+    }
+    get size() {
+        return this._inputs.size;
+    }
+    // --- tree data provider
+    getTreeItem(item: HistoryItem): vscode.TreeItem {
+        const result = new vscode.TreeItem(item.word);
+        result.description = item.description;
+        result.command = { command: '_references-view.showHistoryItem', arguments: [item], title: vscode.l10n.t('Rerun') };
+        result.collapsibleState = vscode.TreeItemCollapsibleState.None;
+        result.contextValue = 'history-item';
+        return result;
+    }
+    getChildren() {
+        return Promise.all([...this._inputs.values()].reverse());
+    }
+    getParent() {
+        return undefined;
+    }
 }
diff --git a/extensions/references-view/Source/types/index.ts b/extensions/references-view/Source/types/index.ts
index 5d1fc2f33183f..97ec2ba1223bc 100644
--- a/extensions/references-view/Source/types/index.ts
+++ b/extensions/references-view/Source/types/index.ts
@@ -2,79 +2,60 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolsTree } from '../tree';
 import { ContextKey } from '../utils';
 import { TypeHierarchyDirection, TypeItem, TypesTreeInput } from './model';
-
 export function register(tree: SymbolsTree, context: vscode.ExtensionContext): void {
-
-	const direction = new RichTypesDirection(context.workspaceState, TypeHierarchyDirection.Subtypes);
-
-	function showTypeHierarchy() {
-		if (vscode.window.activeTextEditor) {
-			const input = new TypesTreeInput(new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), direction.value);
-			tree.setInput(input);
-		}
-	}
-
-	function setTypeHierarchyDirection(value: TypeHierarchyDirection, anchor: TypeItem | vscode.Location | unknown) {
-		direction.value = value;
-
-		let newInput: TypesTreeInput | undefined;
-		const oldInput = tree.getInput();
-		if (anchor instanceof TypeItem) {
-			newInput = new TypesTreeInput(new vscode.Location(anchor.item.uri, anchor.item.selectionRange.start), direction.value);
-		} else if (anchor instanceof vscode.Location) {
-			newInput = new TypesTreeInput(anchor, direction.value);
-		} else if (oldInput instanceof TypesTreeInput) {
-			newInput = new TypesTreeInput(oldInput.location, direction.value);
-		}
-		if (newInput) {
-			tree.setInput(newInput);
-		}
-	}
-
-	context.subscriptions.push(
-		vscode.commands.registerCommand('references-view.showTypeHierarchy', showTypeHierarchy),
-		vscode.commands.registerCommand('references-view.showSupertypes', (item: TypeItem | vscode.Location | unknown) => setTypeHierarchyDirection(TypeHierarchyDirection.Supertypes, item)),
-		vscode.commands.registerCommand('references-view.showSubtypes', (item: TypeItem | vscode.Location | unknown) => setTypeHierarchyDirection(TypeHierarchyDirection.Subtypes, item)),
-		vscode.commands.registerCommand('references-view.removeTypeItem', removeTypeItem)
-	);
+    const direction = new RichTypesDirection(context.workspaceState, TypeHierarchyDirection.Subtypes);
+    function showTypeHierarchy() {
+        if (vscode.window.activeTextEditor) {
+            const input = new TypesTreeInput(new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), direction.value);
+            tree.setInput(input);
+        }
+    }
+    function setTypeHierarchyDirection(value: TypeHierarchyDirection, anchor: TypeItem | vscode.Location | unknown) {
+        direction.value = value;
+        let newInput: TypesTreeInput | undefined;
+        const oldInput = tree.getInput();
+        if (anchor instanceof TypeItem) {
+            newInput = new TypesTreeInput(new vscode.Location(anchor.item.uri, anchor.item.selectionRange.start), direction.value);
+        }
+        else if (anchor instanceof vscode.Location) {
+            newInput = new TypesTreeInput(anchor, direction.value);
+        }
+        else if (oldInput instanceof TypesTreeInput) {
+            newInput = new TypesTreeInput(oldInput.location, direction.value);
+        }
+        if (newInput) {
+            tree.setInput(newInput);
+        }
+    }
+    context.subscriptions.push(vscode.commands.registerCommand('references-view.showTypeHierarchy', showTypeHierarchy), vscode.commands.registerCommand('references-view.showSupertypes', (item: TypeItem | vscode.Location | unknown) => setTypeHierarchyDirection(TypeHierarchyDirection.Supertypes, item)), vscode.commands.registerCommand('references-view.showSubtypes', (item: TypeItem | vscode.Location | unknown) => setTypeHierarchyDirection(TypeHierarchyDirection.Subtypes, item)), vscode.commands.registerCommand('references-view.removeTypeItem', removeTypeItem));
 }
-
 function removeTypeItem(item: TypeItem | unknown): void {
-	if (item instanceof TypeItem) {
-		item.remove();
-	}
+    if (item instanceof TypeItem) {
+        item.remove();
+    }
 }
-
 class RichTypesDirection {
-
-	private static _key = 'references-view.typeHierarchyMode';
-
-	private _ctxMode = new ContextKey('references-view.typeHierarchyMode');
-
-	constructor(
-		private _mem: vscode.Memento,
-		private _value: TypeHierarchyDirection = TypeHierarchyDirection.Subtypes,
-	) {
-		const raw = _mem.get(RichTypesDirection._key);
-		if (typeof raw === 'string') {
-			this.value = raw;
-		} else {
-			this.value = _value;
-		}
-	}
-
-	get value() {
-		return this._value;
-	}
-
-	set value(value: TypeHierarchyDirection) {
-		this._value = value;
-		this._ctxMode.set(value);
-		this._mem.update(RichTypesDirection._key, value);
-	}
+    private static _key = 'references-view.typeHierarchyMode';
+    private _ctxMode = new ContextKey('references-view.typeHierarchyMode');
+    constructor(private _mem: vscode.Memento, private _value: TypeHierarchyDirection = TypeHierarchyDirection.Subtypes) {
+        const raw = _mem.get(RichTypesDirection._key);
+        if (typeof raw === 'string') {
+            this.value = raw;
+        }
+        else {
+            this.value = _value;
+        }
+    }
+    get value() {
+        return this._value;
+    }
+    set value(value: TypeHierarchyDirection) {
+        this._value = value;
+        this._ctxMode.set(value);
+        this._mem.update(RichTypesDirection._key, value);
+    }
 }
diff --git a/extensions/references-view/Source/types/model.ts b/extensions/references-view/Source/types/model.ts
index 623c41c8d5f5a..5c14536e434da 100644
--- a/extensions/references-view/Source/types/model.ts
+++ b/extensions/references-view/Source/types/model.ts
@@ -2,195 +2,147 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput } from '../references-view';
 import { asResourceUrl, del, getThemeIcon, tail } from '../utils';
-
 export class TypesTreeInput implements SymbolTreeInput {
-
-	readonly title: string;
-	readonly contextValue: string = 'typeHierarchy';
-
-	constructor(
-		readonly location: vscode.Location,
-		readonly direction: TypeHierarchyDirection,
-	) {
-		this.title = direction === TypeHierarchyDirection.Supertypes
-			? vscode.l10n.t('Supertypes Of')
-			: vscode.l10n.t('Subtypes Of');
-	}
-
-	async resolve() {
-
-		const items = await Promise.resolve(vscode.commands.executeCommand('vscode.prepareTypeHierarchy', this.location.uri, this.location.range.start));
-		const model = new TypesModel(this.direction, items ?? []);
-		const provider = new TypeItemDataProvider(model);
-
-		if (model.roots.length === 0) {
-			return;
-		}
-
-		return {
-			provider,
-			get message() { return model.roots.length === 0 ? vscode.l10n.t('No results.') : undefined; },
-			navigation: model,
-			highlights: model,
-			dnd: model,
-			dispose() {
-				provider.dispose();
-			}
-		};
-	}
-
-	with(location: vscode.Location): TypesTreeInput {
-		return new TypesTreeInput(location, this.direction);
-	}
+    readonly title: string;
+    readonly contextValue: string = 'typeHierarchy';
+    constructor(readonly location: vscode.Location, readonly direction: TypeHierarchyDirection) {
+        this.title = direction === TypeHierarchyDirection.Supertypes
+            ? vscode.l10n.t('Supertypes Of')
+            : vscode.l10n.t('Subtypes Of');
+    }
+    async resolve() {
+        const items = await Promise.resolve(vscode.commands.executeCommand('vscode.prepareTypeHierarchy', this.location.uri, this.location.range.start));
+        const model = new TypesModel(this.direction, items ?? []);
+        const provider = new TypeItemDataProvider(model);
+        if (model.roots.length === 0) {
+            return;
+        }
+        return {
+            provider,
+            get message() { return model.roots.length === 0 ? vscode.l10n.t('No results.') : undefined; },
+            navigation: model,
+            highlights: model,
+            dnd: model,
+            dispose() {
+                provider.dispose();
+            }
+        };
+    }
+    with(location: vscode.Location): TypesTreeInput {
+        return new TypesTreeInput(location, this.direction);
+    }
 }
-
-
 export const enum TypeHierarchyDirection {
-	Subtypes = 'subtypes',
-	Supertypes = 'supertypes'
+    Subtypes = 'subtypes',
+    Supertypes = 'supertypes'
 }
-
-
 export class TypeItem {
-
-	children?: TypeItem[];
-
-	constructor(
-		readonly model: TypesModel,
-		readonly item: vscode.TypeHierarchyItem,
-		readonly parent: TypeItem | undefined,
-	) { }
-
-	remove(): void {
-		this.model.remove(this);
-	}
+    children?: TypeItem[];
+    constructor(readonly model: TypesModel, readonly item: vscode.TypeHierarchyItem, readonly parent: TypeItem | undefined) { }
+    remove(): void {
+        this.model.remove(this);
+    }
 }
-
 class TypesModel implements SymbolItemNavigation, SymbolItemEditorHighlights, SymbolItemDragAndDrop {
-
-	readonly roots: TypeItem[] = [];
-
-	private readonly _onDidChange = new vscode.EventEmitter();
-	readonly onDidChange = this._onDidChange.event;
-
-	constructor(readonly direction: TypeHierarchyDirection, items: vscode.TypeHierarchyItem[]) {
-		this.roots = items.map(item => new TypeItem(this, item, undefined));
-	}
-
-	private async _resolveTypes(currentType: TypeItem): Promise {
-		if (this.direction === TypeHierarchyDirection.Supertypes) {
-			const types = await vscode.commands.executeCommand('vscode.provideSupertypes', currentType.item);
-			return types ? types.map(item => new TypeItem(this, item, currentType)) : [];
-		} else {
-			const types = await vscode.commands.executeCommand('vscode.provideSubtypes', currentType.item);
-			return types ? types.map(item => new TypeItem(this, item, currentType)) : [];
-		}
-	}
-
-	async getTypeChildren(item: TypeItem): Promise {
-		if (!item.children) {
-			item.children = await this._resolveTypes(item);
-		}
-		return item.children;
-	}
-
-	// -- dnd
-
-	getDragUri(item: TypeItem): vscode.Uri | undefined {
-		return asResourceUrl(item.item.uri, item.item.range);
-	}
-
-	// -- navigation
-
-	location(currentType: TypeItem) {
-		return new vscode.Location(currentType.item.uri, currentType.item.range);
-	}
-
-	nearest(uri: vscode.Uri, _position: vscode.Position): TypeItem | undefined {
-		return this.roots.find(item => item.item.uri.toString() === uri.toString()) ?? this.roots[0];
-	}
-
-	next(from: TypeItem): TypeItem {
-		return this._move(from, true) ?? from;
-	}
-
-	previous(from: TypeItem): TypeItem {
-		return this._move(from, false) ?? from;
-	}
-
-	private _move(item: TypeItem, fwd: boolean): TypeItem | void {
-		if (item.children?.length) {
-			return fwd ? item.children[0] : tail(item.children);
-		}
-		const array = this.roots.includes(item) ? this.roots : item.parent?.children;
-		if (array?.length) {
-			const idx = array.indexOf(item);
-			const delta = fwd ? 1 : -1;
-			return array[idx + delta + array.length % array.length];
-		}
-	}
-
-	// --- highlights
-
-	getEditorHighlights(currentType: TypeItem, uri: vscode.Uri): vscode.Range[] | undefined {
-		return currentType.item.uri.toString() === uri.toString() ? [currentType.item.selectionRange] : undefined;
-	}
-
-	remove(item: TypeItem) {
-		const isInRoot = this.roots.includes(item);
-		const siblings = isInRoot ? this.roots : item.parent?.children;
-		if (siblings) {
-			del(siblings, item);
-			this._onDidChange.fire(this);
-		}
-	}
+    readonly roots: TypeItem[] = [];
+    private readonly _onDidChange = new vscode.EventEmitter();
+    readonly onDidChange = this._onDidChange.event;
+    constructor(readonly direction: TypeHierarchyDirection, items: vscode.TypeHierarchyItem[]) {
+        this.roots = items.map(item => new TypeItem(this, item, undefined));
+    }
+    private async _resolveTypes(currentType: TypeItem): Promise {
+        if (this.direction === TypeHierarchyDirection.Supertypes) {
+            const types = await vscode.commands.executeCommand('vscode.provideSupertypes', currentType.item);
+            return types ? types.map(item => new TypeItem(this, item, currentType)) : [];
+        }
+        else {
+            const types = await vscode.commands.executeCommand('vscode.provideSubtypes', currentType.item);
+            return types ? types.map(item => new TypeItem(this, item, currentType)) : [];
+        }
+    }
+    async getTypeChildren(item: TypeItem): Promise {
+        if (!item.children) {
+            item.children = await this._resolveTypes(item);
+        }
+        return item.children;
+    }
+    // -- dnd
+    getDragUri(item: TypeItem): vscode.Uri | undefined {
+        return asResourceUrl(item.item.uri, item.item.range);
+    }
+    // -- navigation
+    location(currentType: TypeItem) {
+        return new vscode.Location(currentType.item.uri, currentType.item.range);
+    }
+    nearest(uri: vscode.Uri, _position: vscode.Position): TypeItem | undefined {
+        return this.roots.find(item => item.item.uri.toString() === uri.toString()) ?? this.roots[0];
+    }
+    next(from: TypeItem): TypeItem {
+        return this._move(from, true) ?? from;
+    }
+    previous(from: TypeItem): TypeItem {
+        return this._move(from, false) ?? from;
+    }
+    private _move(item: TypeItem, fwd: boolean): TypeItem | void {
+        if (item.children?.length) {
+            return fwd ? item.children[0] : tail(item.children);
+        }
+        const array = this.roots.includes(item) ? this.roots : item.parent?.children;
+        if (array?.length) {
+            const idx = array.indexOf(item);
+            const delta = fwd ? 1 : -1;
+            return array[idx + delta + array.length % array.length];
+        }
+    }
+    // --- highlights
+    getEditorHighlights(currentType: TypeItem, uri: vscode.Uri): vscode.Range[] | undefined {
+        return currentType.item.uri.toString() === uri.toString() ? [currentType.item.selectionRange] : undefined;
+    }
+    remove(item: TypeItem) {
+        const isInRoot = this.roots.includes(item);
+        const siblings = isInRoot ? this.roots : item.parent?.children;
+        if (siblings) {
+            del(siblings, item);
+            this._onDidChange.fire(this);
+        }
+    }
 }
-
 class TypeItemDataProvider implements vscode.TreeDataProvider {
-
-	private readonly _emitter = new vscode.EventEmitter();
-	readonly onDidChangeTreeData = this._emitter.event;
-
-	private readonly _modelListener: vscode.Disposable;
-
-	constructor(private _model: TypesModel) {
-		this._modelListener = _model.onDidChange(e => this._emitter.fire(e instanceof TypeItem ? e : undefined));
-	}
-
-	dispose(): void {
-		this._emitter.dispose();
-		this._modelListener.dispose();
-	}
-
-	getTreeItem(element: TypeItem): vscode.TreeItem {
-
-		const item = new vscode.TreeItem(element.item.name);
-		item.description = element.item.detail;
-		item.contextValue = 'type-item';
-		item.iconPath = getThemeIcon(element.item.kind);
-		item.command = {
-			command: 'vscode.open',
-			title: vscode.l10n.t('Open Type'),
-			arguments: [
-				element.item.uri,
-				{ selection: element.item.selectionRange.with({ end: element.item.selectionRange.start }) } satisfies vscode.TextDocumentShowOptions
-			]
-		};
-		item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
-		return item;
-	}
-
-	getChildren(element?: TypeItem | undefined) {
-		return element
-			? this._model.getTypeChildren(element)
-			: this._model.roots;
-	}
-
-	getParent(element: TypeItem) {
-		return element.parent;
-	}
+    private readonly _emitter = new vscode.EventEmitter();
+    readonly onDidChangeTreeData = this._emitter.event;
+    private readonly _modelListener: vscode.Disposable;
+    constructor(private _model: TypesModel) {
+        this._modelListener = _model.onDidChange(e => this._emitter.fire(e instanceof TypeItem ? e : undefined));
+    }
+    dispose(): void {
+        this._emitter.dispose();
+        this._modelListener.dispose();
+    }
+    getTreeItem(element: TypeItem): vscode.TreeItem {
+        const item = new vscode.TreeItem(element.item.name);
+        item.description = element.item.detail;
+        item.contextValue = 'type-item';
+        item.iconPath = getThemeIcon(element.item.kind);
+        item.command = {
+            command: 'vscode.open',
+            title: vscode.l10n.t('Open Type'),
+            arguments: [
+                element.item.uri,
+                { selection: element.item.selectionRange.with({ end: element.item.selectionRange.start }) } satisfies vscode.TextDocumentShowOptions
+            ]
+        };
+        item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
+        return item;
+    }
+    getChildren(element?: TypeItem | undefined) {
+        return element
+            ? this._model.getTypeChildren(element)
+            : this._model.roots;
+    }
+    getParent(element: TypeItem) {
+        return element.parent;
+    }
 }
diff --git a/extensions/references-view/Source/utils.ts b/extensions/references-view/Source/utils.ts
index cd25e549e1716..7fcab4e5c270a 100644
--- a/extensions/references-view/Source/utils.ts
+++ b/extensions/references-view/Source/utils.ts
@@ -2,135 +2,114 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export function del(array: T[], e: T): void {
-	const idx = array.indexOf(e);
-	if (idx >= 0) {
-		array.splice(idx, 1);
-	}
+    const idx = array.indexOf(e);
+    if (idx >= 0) {
+        array.splice(idx, 1);
+    }
 }
-
 export function tail(array: T[]): T | undefined {
-	return array[array.length - 1];
+    return array[array.length - 1];
 }
-
 export function asResourceUrl(uri: vscode.Uri, range: vscode.Range): vscode.Uri {
-	return uri.with({ fragment: `L${1 + range.start.line},${1 + range.start.character}-${1 + range.end.line},${1 + range.end.character}` });
+    return uri.with({ fragment: `L${1 + range.start.line},${1 + range.start.character}-${1 + range.end.line},${1 + range.end.character}` });
 }
-
 export async function isValidRequestPosition(uri: vscode.Uri, position: vscode.Position) {
-	const doc = await vscode.workspace.openTextDocument(uri);
-	let range = doc.getWordRangeAtPosition(position);
-	if (!range) {
-		range = doc.getWordRangeAtPosition(position, /[^\s]+/);
-	}
-	return Boolean(range);
+    const doc = await vscode.workspace.openTextDocument(uri);
+    let range = doc.getWordRangeAtPosition(position);
+    if (!range) {
+        range = doc.getWordRangeAtPosition(position, /[^\s]+/);
+    }
+    return Boolean(range);
 }
-
 export function getPreviewChunks(doc: vscode.TextDocument, range: vscode.Range, beforeLen: number = 8, trim: boolean = true) {
-	const previewStart = range.start.with({ character: Math.max(0, range.start.character - beforeLen) });
-	const wordRange = doc.getWordRangeAtPosition(previewStart);
-	let before = doc.getText(new vscode.Range(wordRange ? wordRange.start : previewStart, range.start));
-	const inside = doc.getText(range);
-	const previewEnd = range.end.translate(0, 331);
-	let after = doc.getText(new vscode.Range(range.end, previewEnd));
-	if (trim) {
-		before = before.replace(/^\s*/g, '');
-		after = after.replace(/\s*$/g, '');
-	}
-	return { before, inside, after };
+    const previewStart = range.start.with({ character: Math.max(0, range.start.character - beforeLen) });
+    const wordRange = doc.getWordRangeAtPosition(previewStart);
+    let before = doc.getText(new vscode.Range(wordRange ? wordRange.start : previewStart, range.start));
+    const inside = doc.getText(range);
+    const previewEnd = range.end.translate(0, 331);
+    let after = doc.getText(new vscode.Range(range.end, previewEnd));
+    if (trim) {
+        before = before.replace(/^\s*/g, '');
+        after = after.replace(/\s*$/g, '');
+    }
+    return { before, inside, after };
 }
-
 export class ContextKey {
-
-	constructor(readonly name: string) { }
-
-	async set(value: V) {
-		await vscode.commands.executeCommand('setContext', this.name, value);
-	}
-
-	async reset() {
-		await vscode.commands.executeCommand('setContext', this.name, undefined);
-	}
+    constructor(readonly name: string) { }
+    async set(value: V) {
+        await vscode.commands.executeCommand('setContext', this.name, value);
+    }
+    async reset() {
+        await vscode.commands.executeCommand('setContext', this.name, undefined);
+    }
 }
-
 export class WordAnchor {
-
-	private readonly _version: number;
-	private readonly _word: string | undefined;
-
-	constructor(private readonly _doc: vscode.TextDocument, private readonly _position: vscode.Position) {
-		this._version = _doc.version;
-		this._word = this._getAnchorWord(_doc, _position);
-	}
-
-	private _getAnchorWord(doc: vscode.TextDocument, pos: vscode.Position): string | undefined {
-		const range = doc.getWordRangeAtPosition(pos) || doc.getWordRangeAtPosition(pos, /[^\s]+/);
-		return range && doc.getText(range);
-	}
-
-	guessedTrackedPosition(): vscode.Position | undefined {
-		// funky entry
-		if (!this._word) {
-			return this._position;
-		}
-
-		// no changes
-		if (this._version === this._doc.version) {
-			return this._position;
-		}
-
-		// no changes here...
-		const wordNow = this._getAnchorWord(this._doc, this._position);
-		if (this._word === wordNow) {
-			return this._position;
-		}
-
-		// changes: search _word downwards and upwards
-		const startLine = this._position.line;
-		let i = 0;
-		let line: number;
-		let checked: boolean;
-		do {
-			checked = false;
-			// nth line down
-			line = startLine + i;
-			if (line < this._doc.lineCount) {
-				checked = true;
-				const ch = this._doc.lineAt(line).text.indexOf(this._word);
-				if (ch >= 0) {
-					return new vscode.Position(line, ch);
-				}
-			}
-			i += 1;
-			// nth line up
-			line = startLine - i;
-			if (line >= 0) {
-				checked = true;
-				const ch = this._doc.lineAt(line).text.indexOf(this._word);
-				if (ch >= 0) {
-					return new vscode.Position(line, ch);
-				}
-			}
-		} while (i < 100 && checked);
-
-		// fallback
-		return this._position;
-	}
+    private readonly _version: number;
+    private readonly _word: string | undefined;
+    constructor(private readonly _doc: vscode.TextDocument, private readonly _position: vscode.Position) {
+        this._version = _doc.version;
+        this._word = this._getAnchorWord(_doc, _position);
+    }
+    private _getAnchorWord(doc: vscode.TextDocument, pos: vscode.Position): string | undefined {
+        const range = doc.getWordRangeAtPosition(pos) || doc.getWordRangeAtPosition(pos, /[^\s]+/);
+        return range && doc.getText(range);
+    }
+    guessedTrackedPosition(): vscode.Position | undefined {
+        // funky entry
+        if (!this._word) {
+            return this._position;
+        }
+        // no changes
+        if (this._version === this._doc.version) {
+            return this._position;
+        }
+        // no changes here...
+        const wordNow = this._getAnchorWord(this._doc, this._position);
+        if (this._word === wordNow) {
+            return this._position;
+        }
+        // changes: search _word downwards and upwards
+        const startLine = this._position.line;
+        let i = 0;
+        let line: number;
+        let checked: boolean;
+        do {
+            checked = false;
+            // nth line down
+            line = startLine + i;
+            if (line < this._doc.lineCount) {
+                checked = true;
+                const ch = this._doc.lineAt(line).text.indexOf(this._word);
+                if (ch >= 0) {
+                    return new vscode.Position(line, ch);
+                }
+            }
+            i += 1;
+            // nth line up
+            line = startLine - i;
+            if (line >= 0) {
+                checked = true;
+                const ch = this._doc.lineAt(line).text.indexOf(this._word);
+                if (ch >= 0) {
+                    return new vscode.Position(line, ch);
+                }
+            }
+        } while (i < 100 && checked);
+        // fallback
+        return this._position;
+    }
 }
-
 // vscode.SymbolKind.File === 0, Module === 1, etc...
 const _themeIconIds = [
-	'symbol-file', 'symbol-module', 'symbol-namespace', 'symbol-package', 'symbol-class', 'symbol-method',
-	'symbol-property', 'symbol-field', 'symbol-constructor', 'symbol-enum', 'symbol-interface',
-	'symbol-function', 'symbol-variable', 'symbol-constant', 'symbol-string', 'symbol-number', 'symbol-boolean',
-	'symbol-array', 'symbol-object', 'symbol-key', 'symbol-null', 'symbol-enum-member', 'symbol-struct',
-	'symbol-event', 'symbol-operator', 'symbol-type-parameter'
+    'symbol-file', 'symbol-module', 'symbol-namespace', 'symbol-package', 'symbol-class', 'symbol-method',
+    'symbol-property', 'symbol-field', 'symbol-constructor', 'symbol-enum', 'symbol-interface',
+    'symbol-function', 'symbol-variable', 'symbol-constant', 'symbol-string', 'symbol-number', 'symbol-boolean',
+    'symbol-array', 'symbol-object', 'symbol-key', 'symbol-null', 'symbol-enum-member', 'symbol-struct',
+    'symbol-event', 'symbol-operator', 'symbol-type-parameter'
 ];
-
 export function getThemeIcon(kind: vscode.SymbolKind): vscode.ThemeIcon | undefined {
-	const id = _themeIconIds[kind];
-	return id ? new vscode.ThemeIcon(id) : undefined;
+    const id = _themeIconIds[kind];
+    return id ? new vscode.ThemeIcon(id) : undefined;
 }
diff --git a/extensions/search-result/Source/extension.ts b/extensions/search-result/Source/extension.ts
index 815bd8c139166..37e990f4a761e 100644
--- a/extensions/search-result/Source/extension.ts
+++ b/extensions/search-result/Source/extension.ts
@@ -2,276 +2,233 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as pathUtils from 'path';
-
 const FILE_LINE_REGEX = /^(\S.*):$/;
 const RESULT_LINE_REGEX = /^(\s+)(\d+)(: |  )(\s*)(.*)$/;
 const ELISION_REGEX = /⟪ ([0-9]+) characters skipped ⟫/g;
 const SEARCH_RESULT_SELECTOR = { language: 'search-result', exclusive: true };
 const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:'];
 const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'];
-
-let cachedLastParse: { version: number; parse: ParsedSearchResults; uri: vscode.Uri } | undefined;
+let cachedLastParse: {
+    version: number;
+    parse: ParsedSearchResults;
+    uri: vscode.Uri;
+} | undefined;
 let documentChangeListener: vscode.Disposable | undefined;
-
-
 export function activate(context: vscode.ExtensionContext) {
-
-	const contextLineDecorations = vscode.window.createTextEditorDecorationType({ opacity: '0.7' });
-	const matchLineDecorations = vscode.window.createTextEditorDecorationType({ fontWeight: 'bold' });
-
-	const decorate = (editor: vscode.TextEditor) => {
-		const parsed = parseSearchResults(editor.document).filter(isResultLine);
-		const contextRanges = parsed.filter(line => line.isContext).map(line => line.prefixRange);
-		const matchRanges = parsed.filter(line => !line.isContext).map(line => line.prefixRange);
-		editor.setDecorations(contextLineDecorations, contextRanges);
-		editor.setDecorations(matchLineDecorations, matchRanges);
-	};
-
-	if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.languageId === 'search-result') {
-		decorate(vscode.window.activeTextEditor);
-	}
-
-	context.subscriptions.push(
-
-		vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, {
-			provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] {
-				const results = parseSearchResults(document, token)
-					.filter(isFileLine)
-					.map(line => new vscode.DocumentSymbol(
-						line.path,
-						'',
-						vscode.SymbolKind.File,
-						line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!),
-						line.location.originSelectionRange!,
-					));
-
-				return results;
-			}
-		}),
-
-		vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, {
-			provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
-
-				const line = document.lineAt(position.line);
-				if (position.line > 3) { return []; }
-				if (position.character === 0 || (position.character === 1 && line.text === '#')) {
-					const header = Array.from({ length: DIRECTIVES.length }).map((_, i) => document.lineAt(i).text);
-
-					return DIRECTIVES
-						.filter(suggestion => header.every(line => line.indexOf(suggestion) === -1))
-						.map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' }));
-				}
-
-				if (line.text.indexOf('# Flags:') === -1) { return []; }
-
-				return FLAGS
-					.filter(flag => line.text.indexOf(flag) === -1)
-					.map(flag => ({ label: flag, insertText: flag + ' ' }));
-			}
-		}, '#'),
-
-		vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, {
-			provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] {
-				const lineResult = parseSearchResults(document, token)[position.line];
-				if (!lineResult) { return []; }
-				if (lineResult.type === 'file') {
-					return lineResult.allLocations.map(l => ({ ...l, originSelectionRange: lineResult.location.originSelectionRange }));
-				}
-
-				const location = lineResult.locations.find(l => l.originSelectionRange.contains(position));
-				if (!location) {
-					return [];
-				}
-
-				const targetPos = new vscode.Position(
-					location.targetSelectionRange.start.line,
-					location.targetSelectionRange.start.character + (position.character - location.originSelectionRange.start.character)
-				);
-				return [{
-					...location,
-					targetSelectionRange: new vscode.Range(targetPos, targetPos),
-				}];
-			}
-		}),
-
-		vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, {
-			async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
-				return parseSearchResults(document, token)
-					.filter(isFileLine)
-					.map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri }));
-			}
-		}),
-
-		vscode.window.onDidChangeActiveTextEditor(editor => {
-			if (editor?.document.languageId === 'search-result') {
-				// Clear the parse whenever we open a new editor.
-				// Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast.
-				cachedLastParse = undefined;
-
-				documentChangeListener?.dispose();
-				documentChangeListener = vscode.workspace.onDidChangeTextDocument(doc => {
-					if (doc.document.uri === editor.document.uri) {
-						decorate(editor);
-					}
-				});
-
-				decorate(editor);
-			}
-		}),
-
-		{ dispose() { cachedLastParse = undefined; documentChangeListener?.dispose(); } }
-	);
+    const contextLineDecorations = vscode.window.createTextEditorDecorationType({ opacity: '0.7' });
+    const matchLineDecorations = vscode.window.createTextEditorDecorationType({ fontWeight: 'bold' });
+    const decorate = (editor: vscode.TextEditor) => {
+        const parsed = parseSearchResults(editor.document).filter(isResultLine);
+        const contextRanges = parsed.filter(line => line.isContext).map(line => line.prefixRange);
+        const matchRanges = parsed.filter(line => !line.isContext).map(line => line.prefixRange);
+        editor.setDecorations(contextLineDecorations, contextRanges);
+        editor.setDecorations(matchLineDecorations, matchRanges);
+    };
+    if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.languageId === 'search-result') {
+        decorate(vscode.window.activeTextEditor);
+    }
+    context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, {
+        provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] {
+            const results = parseSearchResults(document, token)
+                .filter(isFileLine)
+                .map(line => new vscode.DocumentSymbol(line.path, '', vscode.SymbolKind.File, line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!), line.location.originSelectionRange!));
+            return results;
+        }
+    }), vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, {
+        provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
+            const line = document.lineAt(position.line);
+            if (position.line > 3) {
+                return [];
+            }
+            if (position.character === 0 || (position.character === 1 && line.text === '#')) {
+                const header = Array.from({ length: DIRECTIVES.length }).map((_, i) => document.lineAt(i).text);
+                return DIRECTIVES
+                    .filter(suggestion => header.every(line => line.indexOf(suggestion) === -1))
+                    .map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' }));
+            }
+            if (line.text.indexOf('# Flags:') === -1) {
+                return [];
+            }
+            return FLAGS
+                .filter(flag => line.text.indexOf(flag) === -1)
+                .map(flag => ({ label: flag, insertText: flag + ' ' }));
+        }
+    }, '#'), vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, {
+        provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] {
+            const lineResult = parseSearchResults(document, token)[position.line];
+            if (!lineResult) {
+                return [];
+            }
+            if (lineResult.type === 'file') {
+                return lineResult.allLocations.map(l => ({ ...l, originSelectionRange: lineResult.location.originSelectionRange }));
+            }
+            const location = lineResult.locations.find(l => l.originSelectionRange.contains(position));
+            if (!location) {
+                return [];
+            }
+            const targetPos = new vscode.Position(location.targetSelectionRange.start.line, location.targetSelectionRange.start.character + (position.character - location.originSelectionRange.start.character));
+            return [{
+                    ...location,
+                    targetSelectionRange: new vscode.Range(targetPos, targetPos),
+                }];
+        }
+    }), vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, {
+        async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+            return parseSearchResults(document, token)
+                .filter(isFileLine)
+                .map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri }));
+        }
+    }), vscode.window.onDidChangeActiveTextEditor(editor => {
+        if (editor?.document.languageId === 'search-result') {
+            // Clear the parse whenever we open a new editor.
+            // Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast.
+            cachedLastParse = undefined;
+            documentChangeListener?.dispose();
+            documentChangeListener = vscode.workspace.onDidChangeTextDocument(doc => {
+                if (doc.document.uri === editor.document.uri) {
+                    decorate(editor);
+                }
+            });
+            decorate(editor);
+        }
+    }), { dispose() { cachedLastParse = undefined; documentChangeListener?.dispose(); } });
 }
-
-
 function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | undefined {
-
-	const userDataPrefix = '(Settings) ';
-	if (path.startsWith(userDataPrefix)) {
-		return vscode.Uri.file(path.slice(userDataPrefix.length)).with({ scheme: 'vscode-userdata' });
-	}
-
-	if (pathUtils.isAbsolute(path)) {
-		if (/^[\\\/]Untitled-\d*$/.test(path)) {
-			return vscode.Uri.file(path.slice(1)).with({ scheme: 'untitled', path: path.slice(1) });
-		}
-		return vscode.Uri.file(path);
-	}
-
-	if (path.indexOf('~/') === 0) {
-		const homePath = process.env.HOME || process.env.HOMEPATH || '';
-		return vscode.Uri.file(pathUtils.join(homePath, path.slice(2)));
-	}
-
-	const uriFromFolderWithPath = (folder: vscode.WorkspaceFolder, path: string): vscode.Uri =>
-		vscode.Uri.joinPath(folder.uri, path);
-
-	if (vscode.workspace.workspaceFolders) {
-		const multiRootFormattedPath = /^(.*) • (.*)$/.exec(path);
-		if (multiRootFormattedPath) {
-			const [, workspaceName, workspacePath] = multiRootFormattedPath;
-			const folder = vscode.workspace.workspaceFolders.filter(wf => wf.name === workspaceName)[0];
-			if (folder) {
-				return uriFromFolderWithPath(folder, workspacePath);
-			}
-		}
-		else if (vscode.workspace.workspaceFolders.length === 1) {
-			return uriFromFolderWithPath(vscode.workspace.workspaceFolders[0], path);
-		} else if (resultsUri.scheme !== 'untitled') {
-			// We're in a multi-root workspace, but the path is not multi-root formatted
-			// Possibly a saved search from a single root session. Try checking if the search result document's URI is in a current workspace folder.
-			const prefixMatch = vscode.workspace.workspaceFolders.filter(wf => resultsUri.toString().startsWith(wf.uri.toString()))[0];
-			if (prefixMatch) {
-				return uriFromFolderWithPath(prefixMatch, path);
-			}
-		}
-	}
-
-	console.error(`Unable to resolve path ${path}`);
-	return undefined;
+    const userDataPrefix = '(Settings) ';
+    if (path.startsWith(userDataPrefix)) {
+        return vscode.Uri.file(path.slice(userDataPrefix.length)).with({ scheme: 'vscode-userdata' });
+    }
+    if (pathUtils.isAbsolute(path)) {
+        if (/^[\\\/]Untitled-\d*$/.test(path)) {
+            return vscode.Uri.file(path.slice(1)).with({ scheme: 'untitled', path: path.slice(1) });
+        }
+        return vscode.Uri.file(path);
+    }
+    if (path.indexOf('~/') === 0) {
+        const homePath = process.env.HOME || process.env.HOMEPATH || '';
+        return vscode.Uri.file(pathUtils.join(homePath, path.slice(2)));
+    }
+    const uriFromFolderWithPath = (folder: vscode.WorkspaceFolder, path: string): vscode.Uri => vscode.Uri.joinPath(folder.uri, path);
+    if (vscode.workspace.workspaceFolders) {
+        const multiRootFormattedPath = /^(.*) • (.*)$/.exec(path);
+        if (multiRootFormattedPath) {
+            const [, workspaceName, workspacePath] = multiRootFormattedPath;
+            const folder = vscode.workspace.workspaceFolders.filter(wf => wf.name === workspaceName)[0];
+            if (folder) {
+                return uriFromFolderWithPath(folder, workspacePath);
+            }
+        }
+        else if (vscode.workspace.workspaceFolders.length === 1) {
+            return uriFromFolderWithPath(vscode.workspace.workspaceFolders[0], path);
+        }
+        else if (resultsUri.scheme !== 'untitled') {
+            // We're in a multi-root workspace, but the path is not multi-root formatted
+            // Possibly a saved search from a single root session. Try checking if the search result document's URI is in a current workspace folder.
+            const prefixMatch = vscode.workspace.workspaceFolders.filter(wf => resultsUri.toString().startsWith(wf.uri.toString()))[0];
+            if (prefixMatch) {
+                return uriFromFolderWithPath(prefixMatch, path);
+            }
+        }
+    }
+    console.error(`Unable to resolve path ${path}`);
+    return undefined;
 }
-
-type ParsedSearchFileLine = { type: 'file'; location: vscode.LocationLink; allLocations: vscode.LocationLink[]; path: string };
-type ParsedSearchResultLine = { type: 'result'; locations: Required[]; isContext: boolean; prefixRange: vscode.Range };
+type ParsedSearchFileLine = {
+    type: 'file';
+    location: vscode.LocationLink;
+    allLocations: vscode.LocationLink[];
+    path: string;
+};
+type ParsedSearchResultLine = {
+    type: 'result';
+    locations: Required[];
+    isContext: boolean;
+    prefixRange: vscode.Range;
+};
 type ParsedSearchResults = Array;
 const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file';
 const isResultLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchResultLine => line.type === 'result';
-
-
 function parseSearchResults(document: vscode.TextDocument, token?: vscode.CancellationToken): ParsedSearchResults {
-
-	if (cachedLastParse && cachedLastParse.uri === document.uri && cachedLastParse.version === document.version) {
-		return cachedLastParse.parse;
-	}
-
-	const lines = document.getText().split(/\r?\n/);
-	const links: ParsedSearchResults = [];
-
-	let currentTarget: vscode.Uri | undefined = undefined;
-	let currentTargetLocations: vscode.LocationLink[] | undefined = undefined;
-
-	for (let i = 0; i < lines.length; i++) {
-		// TODO: This is probably always false, given we're pegging the thread...
-		if (token?.isCancellationRequested) { return []; }
-		const line = lines[i];
-
-		const fileLine = FILE_LINE_REGEX.exec(line);
-		if (fileLine) {
-			const [, path] = fileLine;
-
-			currentTarget = relativePathToUri(path, document.uri);
-			if (!currentTarget) { continue; }
-			currentTargetLocations = [];
-
-			const location: vscode.LocationLink = {
-				targetRange: new vscode.Range(0, 0, 0, 1),
-				targetUri: currentTarget,
-				originSelectionRange: new vscode.Range(i, 0, i, line.length),
-			};
-
-
-			links[i] = { type: 'file', location, allLocations: currentTargetLocations, path };
-		}
-
-		if (!currentTarget) { continue; }
-
-		const resultLine = RESULT_LINE_REGEX.exec(line);
-		if (resultLine) {
-			const [, indentation, _lineNumber, separator] = resultLine;
-			const lineNumber = +_lineNumber - 1;
-			const metadataOffset = (indentation + _lineNumber + separator).length;
-			const targetRange = new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length);
-
-			const locations: Required[] = [];
-
-			let lastEnd = metadataOffset;
-			let offset = 0;
-			ELISION_REGEX.lastIndex = metadataOffset;
-			for (let match: RegExpExecArray | null; (match = ELISION_REGEX.exec(line));) {
-				locations.push({
-					targetRange,
-					targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset),
-					targetUri: currentTarget,
-					originSelectionRange: new vscode.Range(i, lastEnd, i, ELISION_REGEX.lastIndex - match[0].length),
-				});
-
-				offset += (ELISION_REGEX.lastIndex - lastEnd - match[0].length) + Number(match[1]);
-				lastEnd = ELISION_REGEX.lastIndex;
-			}
-
-			if (lastEnd < line.length) {
-				locations.push({
-					targetRange,
-					targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset),
-					targetUri: currentTarget,
-					originSelectionRange: new vscode.Range(i, lastEnd, i, line.length),
-				});
-			}
-			// only show result lines in file-level peek
-			if (separator.includes(':')) {
-				currentTargetLocations?.push(...locations);
-			}
-
-			// Allow line number, indentation, etc to take you to definition as well.
-			const convenienceLocation: Required = {
-				targetRange,
-				targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, 1),
-				targetUri: currentTarget,
-				originSelectionRange: new vscode.Range(i, 0, i, metadataOffset - 1),
-			};
-			locations.push(convenienceLocation);
-			links[i] = { type: 'result', locations, isContext: separator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
-		}
-	}
-
-	cachedLastParse = {
-		version: document.version,
-		parse: links,
-		uri: document.uri
-	};
-
-	return links;
+    if (cachedLastParse && cachedLastParse.uri === document.uri && cachedLastParse.version === document.version) {
+        return cachedLastParse.parse;
+    }
+    const lines = document.getText().split(/\r?\n/);
+    const links: ParsedSearchResults = [];
+    let currentTarget: vscode.Uri | undefined = undefined;
+    let currentTargetLocations: vscode.LocationLink[] | undefined = undefined;
+    for (let i = 0; i < lines.length; i++) {
+        // TODO: This is probably always false, given we're pegging the thread...
+        if (token?.isCancellationRequested) {
+            return [];
+        }
+        const line = lines[i];
+        const fileLine = FILE_LINE_REGEX.exec(line);
+        if (fileLine) {
+            const [, path] = fileLine;
+            currentTarget = relativePathToUri(path, document.uri);
+            if (!currentTarget) {
+                continue;
+            }
+            currentTargetLocations = [];
+            const location: vscode.LocationLink = {
+                targetRange: new vscode.Range(0, 0, 0, 1),
+                targetUri: currentTarget,
+                originSelectionRange: new vscode.Range(i, 0, i, line.length),
+            };
+            links[i] = { type: 'file', location, allLocations: currentTargetLocations, path };
+        }
+        if (!currentTarget) {
+            continue;
+        }
+        const resultLine = RESULT_LINE_REGEX.exec(line);
+        if (resultLine) {
+            const [, indentation, _lineNumber, separator] = resultLine;
+            const lineNumber = +_lineNumber - 1;
+            const metadataOffset = (indentation + _lineNumber + separator).length;
+            const targetRange = new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length);
+            const locations: Required[] = [];
+            let lastEnd = metadataOffset;
+            let offset = 0;
+            ELISION_REGEX.lastIndex = metadataOffset;
+            for (let match: RegExpExecArray | null; (match = ELISION_REGEX.exec(line));) {
+                locations.push({
+                    targetRange,
+                    targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset),
+                    targetUri: currentTarget,
+                    originSelectionRange: new vscode.Range(i, lastEnd, i, ELISION_REGEX.lastIndex - match[0].length),
+                });
+                offset += (ELISION_REGEX.lastIndex - lastEnd - match[0].length) + Number(match[1]);
+                lastEnd = ELISION_REGEX.lastIndex;
+            }
+            if (lastEnd < line.length) {
+                locations.push({
+                    targetRange,
+                    targetSelectionRange: new vscode.Range(lineNumber, offset, lineNumber, offset),
+                    targetUri: currentTarget,
+                    originSelectionRange: new vscode.Range(i, lastEnd, i, line.length),
+                });
+            }
+            // only show result lines in file-level peek
+            if (separator.includes(':')) {
+                currentTargetLocations?.push(...locations);
+            }
+            // Allow line number, indentation, etc to take you to definition as well.
+            const convenienceLocation: Required = {
+                targetRange,
+                targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, 1),
+                targetUri: currentTarget,
+                originSelectionRange: new vscode.Range(i, 0, i, metadataOffset - 1),
+            };
+            locations.push(convenienceLocation);
+            links[i] = { type: 'result', locations, isContext: separator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
+        }
+    }
+    cachedLastParse = {
+        version: document.version,
+        parse: links,
+        uri: document.uri
+    };
+    return links;
 }
diff --git a/extensions/simple-browser/Source/dispose.ts b/extensions/simple-browser/Source/dispose.ts
index 175acf7b36797..5f43a8edfc921 100644
--- a/extensions/simple-browser/Source/dispose.ts
+++ b/extensions/simple-browser/Source/dispose.ts
@@ -2,39 +2,33 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export function disposeAll(disposables: vscode.Disposable[]) {
-	while (disposables.length) {
-		const item = disposables.pop();
-		item?.dispose();
-	}
+    while (disposables.length) {
+        const item = disposables.pop();
+        item?.dispose();
+    }
 }
-
 export abstract class Disposable {
-	private _isDisposed = false;
-
-	protected _disposables: vscode.Disposable[] = [];
-
-	public dispose(): any {
-		if (this._isDisposed) {
-			return;
-		}
-		this._isDisposed = true;
-		disposeAll(this._disposables);
-	}
-
-	protected _register(value: T): T {
-		if (this._isDisposed) {
-			value.dispose();
-		} else {
-			this._disposables.push(value);
-		}
-		return value;
-	}
-
-	protected get isDisposed() {
-		return this._isDisposed;
-	}
+    private _isDisposed = false;
+    protected _disposables: vscode.Disposable[] = [];
+    public dispose(): any {
+        if (this._isDisposed) {
+            return;
+        }
+        this._isDisposed = true;
+        disposeAll(this._disposables);
+    }
+    protected _register(value: T): T {
+        if (this._isDisposed) {
+            value.dispose();
+        }
+        else {
+            this._disposables.push(value);
+        }
+        return value;
+    }
+    protected get isDisposed() {
+        return this._isDisposed;
+    }
 }
diff --git a/extensions/simple-browser/Source/extension.ts b/extensions/simple-browser/Source/extension.ts
index 576029578f256..0443a1b0c3ae3 100644
--- a/extensions/simple-browser/Source/extension.ts
+++ b/extensions/simple-browser/Source/extension.ts
@@ -2,90 +2,76 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SimpleBrowserManager } from './simpleBrowserManager';
 import { SimpleBrowserView } from './simpleBrowserView';
-
 declare class URL {
-	constructor(input: string, base?: string | URL);
-	hostname: string;
+    constructor(input: string, base?: string | URL);
+    hostname: string;
 }
-
 const openApiCommand = 'simpleBrowser.api.open';
 const showCommand = 'simpleBrowser.show';
-
 const enabledHosts = new Set([
-	'localhost',
-	// localhost IPv4
-	'127.0.0.1',
-	// localhost IPv6
-	'[0:0:0:0:0:0:0:1]',
-	'[::1]',
-	// all interfaces IPv4
-	'0.0.0.0',
-	// all interfaces IPv6
-	'[0:0:0:0:0:0:0:0]',
-	'[::]'
+    'localhost',
+    // localhost IPv4
+    '127.0.0.1',
+    // localhost IPv6
+    '[0:0:0:0:0:0:0:1]',
+    '[::1]',
+    // all interfaces IPv4
+    '0.0.0.0',
+    // all interfaces IPv6
+    '[0:0:0:0:0:0:0:0]',
+    '[::]'
 ]);
-
 const openerId = 'simpleBrowser.open';
-
 export function activate(context: vscode.ExtensionContext) {
-
-	const manager = new SimpleBrowserManager(context.extensionUri);
-	context.subscriptions.push(manager);
-
-	context.subscriptions.push(vscode.window.registerWebviewPanelSerializer(SimpleBrowserView.viewType, {
-		deserializeWebviewPanel: async (panel, state) => {
-			manager.restore(panel, state);
-		}
-	}));
-
-	context.subscriptions.push(vscode.commands.registerCommand(showCommand, async (url?: string) => {
-		if (!url) {
-			url = await vscode.window.showInputBox({
-				placeHolder: vscode.l10n.t("https://example.com"),
-				prompt: vscode.l10n.t("Enter url to visit")
-			});
-		}
-
-		if (url) {
-			manager.show(url);
-		}
-	}));
-
-	context.subscriptions.push(vscode.commands.registerCommand(openApiCommand, (url: vscode.Uri, showOptions?: {
-		preserveFocus?: boolean;
-		viewColumn: vscode.ViewColumn;
-	}) => {
-		manager.show(url, showOptions);
-	}));
-
-	context.subscriptions.push(vscode.window.registerExternalUriOpener(openerId, {
-		canOpenExternalUri(uri: vscode.Uri) {
-			// We have to replace the IPv6 hosts with IPv4 because URL can't handle IPv6.
-			const originalUri = new URL(uri.toString(true));
-			if (enabledHosts.has(originalUri.hostname)) {
-				return isWeb()
-					? vscode.ExternalUriOpenerPriority.Default
-					: vscode.ExternalUriOpenerPriority.Option;
-			}
-
-			return vscode.ExternalUriOpenerPriority.None;
-		},
-		openExternalUri(resolveUri: vscode.Uri) {
-			return manager.show(resolveUri, {
-				viewColumn: vscode.window.activeTextEditor ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active
-			});
-		}
-	}, {
-		schemes: ['http', 'https'],
-		label: vscode.l10n.t("Open in simple browser"),
-	}));
+    const manager = new SimpleBrowserManager(context.extensionUri);
+    context.subscriptions.push(manager);
+    context.subscriptions.push(vscode.window.registerWebviewPanelSerializer(SimpleBrowserView.viewType, {
+        deserializeWebviewPanel: async (panel, state) => {
+            manager.restore(panel, state);
+        }
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand(showCommand, async (url?: string) => {
+        if (!url) {
+            url = await vscode.window.showInputBox({
+                placeHolder: vscode.l10n.t("https://example.com"),
+                prompt: vscode.l10n.t("Enter url to visit")
+            });
+        }
+        if (url) {
+            manager.show(url);
+        }
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand(openApiCommand, (url: vscode.Uri, showOptions?: {
+        preserveFocus?: boolean;
+        viewColumn: vscode.ViewColumn;
+    }) => {
+        manager.show(url, showOptions);
+    }));
+    context.subscriptions.push(vscode.window.registerExternalUriOpener(openerId, {
+        canOpenExternalUri(uri: vscode.Uri) {
+            // We have to replace the IPv6 hosts with IPv4 because URL can't handle IPv6.
+            const originalUri = new URL(uri.toString(true));
+            if (enabledHosts.has(originalUri.hostname)) {
+                return isWeb()
+                    ? vscode.ExternalUriOpenerPriority.Default
+                    : vscode.ExternalUriOpenerPriority.Option;
+            }
+            return vscode.ExternalUriOpenerPriority.None;
+        },
+        openExternalUri(resolveUri: vscode.Uri) {
+            return manager.show(resolveUri, {
+                viewColumn: vscode.window.activeTextEditor ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active
+            });
+        }
+    }, {
+        schemes: ['http', 'https'],
+        label: vscode.l10n.t("Open in simple browser"),
+    }));
 }
-
 function isWeb(): boolean {
-	// @ts-expect-error
-	return typeof navigator !== 'undefined' && vscode.env.uiKind === vscode.UIKind.Web;
+    // @ts-expect-error
+    return typeof navigator !== 'undefined' && vscode.env.uiKind === vscode.UIKind.Web;
 }
diff --git a/extensions/simple-browser/Source/simpleBrowserManager.ts b/extensions/simple-browser/Source/simpleBrowserManager.ts
index ef485916411cd..0b544118b10ef 100644
--- a/extensions/simple-browser/Source/simpleBrowserManager.ts
+++ b/extensions/simple-browser/Source/simpleBrowserManager.ts
@@ -2,48 +2,37 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { ShowOptions, SimpleBrowserView } from './simpleBrowserView';
-
 export class SimpleBrowserManager {
-
-	private _activeView?: SimpleBrowserView;
-
-	constructor(
-		private readonly extensionUri: vscode.Uri,
-	) { }
-
-	dispose() {
-		this._activeView?.dispose();
-		this._activeView = undefined;
-	}
-
-	public show(inputUri: string | vscode.Uri, options?: ShowOptions): void {
-		const url = typeof inputUri === 'string' ? inputUri : inputUri.toString(true);
-		if (this._activeView) {
-			this._activeView.show(url, options);
-		} else {
-			const view = SimpleBrowserView.create(this.extensionUri, url, options);
-			this.registerWebviewListeners(view);
-
-			this._activeView = view;
-		}
-	}
-
-	public restore(panel: vscode.WebviewPanel, state: any): void {
-		const url = state?.url ?? '';
-		const view = SimpleBrowserView.restore(this.extensionUri, url, panel);
-		this.registerWebviewListeners(view);
-		this._activeView ??= view;
-	}
-
-	private registerWebviewListeners(view: SimpleBrowserView) {
-		view.onDispose(() => {
-			if (this._activeView === view) {
-				this._activeView = undefined;
-			}
-		});
-	}
-
+    private _activeView?: SimpleBrowserView;
+    constructor(private readonly extensionUri: vscode.Uri) { }
+    dispose() {
+        this._activeView?.dispose();
+        this._activeView = undefined;
+    }
+    public show(inputUri: string | vscode.Uri, options?: ShowOptions): void {
+        const url = typeof inputUri === 'string' ? inputUri : inputUri.toString(true);
+        if (this._activeView) {
+            this._activeView.show(url, options);
+        }
+        else {
+            const view = SimpleBrowserView.create(this.extensionUri, url, options);
+            this.registerWebviewListeners(view);
+            this._activeView = view;
+        }
+    }
+    public restore(panel: vscode.WebviewPanel, state: any): void {
+        const url = state?.url ?? '';
+        const view = SimpleBrowserView.restore(this.extensionUri, url, panel);
+        this.registerWebviewListeners(view);
+        this._activeView ??= view;
+    }
+    private registerWebviewListeners(view: SimpleBrowserView) {
+        view.onDispose(() => {
+            if (this._activeView === view) {
+                this._activeView = undefined;
+            }
+        });
+    }
 }
diff --git a/extensions/simple-browser/Source/simpleBrowserView.ts b/extensions/simple-browser/Source/simpleBrowserView.ts
index 5725dcf4f9ba6..ad5cf4dae07e9 100644
--- a/extensions/simple-browser/Source/simpleBrowserView.ts
+++ b/extensions/simple-browser/Source/simpleBrowserView.ts
@@ -2,123 +2,89 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Disposable } from './dispose';
-
-
 export interface ShowOptions {
-	readonly preserveFocus?: boolean;
-	readonly viewColumn?: vscode.ViewColumn;
+    readonly preserveFocus?: boolean;
+    readonly viewColumn?: vscode.ViewColumn;
 }
-
 export class SimpleBrowserView extends Disposable {
-
-	public static readonly viewType = 'simpleBrowser.view';
-	private static readonly title = vscode.l10n.t("Simple Browser");
-
-	private static getWebviewLocalResourceRoots(extensionUri: vscode.Uri): readonly vscode.Uri[] {
-		return [
-			vscode.Uri.joinPath(extensionUri, 'media')
-		];
-	}
-
-	private static getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
-		return {
-			enableScripts: true,
-			enableForms: true,
-			localResourceRoots: SimpleBrowserView.getWebviewLocalResourceRoots(extensionUri),
-		};
-	}
-
-	private readonly _webviewPanel: vscode.WebviewPanel;
-
-	private readonly _onDidDispose = this._register(new vscode.EventEmitter());
-	public readonly onDispose = this._onDidDispose.event;
-
-	public static create(
-		extensionUri: vscode.Uri,
-		url: string,
-		showOptions?: ShowOptions
-	): SimpleBrowserView {
-		const webview = vscode.window.createWebviewPanel(SimpleBrowserView.viewType, SimpleBrowserView.title, {
-			viewColumn: showOptions?.viewColumn ?? vscode.ViewColumn.Active,
-			preserveFocus: showOptions?.preserveFocus
-		}, {
-			retainContextWhenHidden: true,
-			...SimpleBrowserView.getWebviewOptions(extensionUri)
-		});
-		return new SimpleBrowserView(extensionUri, url, webview);
-	}
-
-	public static restore(
-		extensionUri: vscode.Uri,
-		url: string,
-		webviewPanel: vscode.WebviewPanel,
-	): SimpleBrowserView {
-		return new SimpleBrowserView(extensionUri, url, webviewPanel);
-	}
-
-	private constructor(
-		private readonly extensionUri: vscode.Uri,
-		url: string,
-		webviewPanel: vscode.WebviewPanel,
-	) {
-		super();
-
-		this._webviewPanel = this._register(webviewPanel);
-		this._webviewPanel.webview.options = SimpleBrowserView.getWebviewOptions(extensionUri);
-
-		this._register(this._webviewPanel.webview.onDidReceiveMessage(e => {
-			switch (e.type) {
-				case 'openExternal':
-					try {
-						const url = vscode.Uri.parse(e.url);
-						vscode.env.openExternal(url);
-					} catch {
-						// Noop
-					}
-					break;
-			}
-		}));
-
-		this._register(this._webviewPanel.onDidDispose(() => {
-			this.dispose();
-		}));
-
-		this._register(vscode.workspace.onDidChangeConfiguration(e => {
-			if (e.affectsConfiguration('simpleBrowser.focusLockIndicator.enabled')) {
-				const configuration = vscode.workspace.getConfiguration('simpleBrowser');
-				this._webviewPanel.webview.postMessage({
-					type: 'didChangeFocusLockIndicatorEnabled',
-					focusLockEnabled: configuration.get('focusLockIndicator.enabled', true)
-				});
-			}
-		}));
-
-		this.show(url);
-	}
-
-	public override dispose() {
-		this._onDidDispose.fire();
-		super.dispose();
-	}
-
-	public show(url: string, options?: ShowOptions) {
-		this._webviewPanel.webview.html = this.getHtml(url);
-		this._webviewPanel.reveal(options?.viewColumn, options?.preserveFocus);
-	}
-
-	private getHtml(url: string) {
-		const configuration = vscode.workspace.getConfiguration('simpleBrowser');
-
-		const nonce = getNonce();
-
-		const mainJs = this.extensionResourceUrl('media', 'index.js');
-		const mainCss = this.extensionResourceUrl('media', 'main.css');
-		const codiconsUri = this.extensionResourceUrl('media', 'codicon.css');
-
-		return /* html */ `
+    public static readonly viewType = 'simpleBrowser.view';
+    private static readonly title = vscode.l10n.t("Simple Browser");
+    private static getWebviewLocalResourceRoots(extensionUri: vscode.Uri): readonly vscode.Uri[] {
+        return [
+            vscode.Uri.joinPath(extensionUri, 'media')
+        ];
+    }
+    private static getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions {
+        return {
+            enableScripts: true,
+            enableForms: true,
+            localResourceRoots: SimpleBrowserView.getWebviewLocalResourceRoots(extensionUri),
+        };
+    }
+    private readonly _webviewPanel: vscode.WebviewPanel;
+    private readonly _onDidDispose = this._register(new vscode.EventEmitter());
+    public readonly onDispose = this._onDidDispose.event;
+    public static create(extensionUri: vscode.Uri, url: string, showOptions?: ShowOptions): SimpleBrowserView {
+        const webview = vscode.window.createWebviewPanel(SimpleBrowserView.viewType, SimpleBrowserView.title, {
+            viewColumn: showOptions?.viewColumn ?? vscode.ViewColumn.Active,
+            preserveFocus: showOptions?.preserveFocus
+        }, {
+            retainContextWhenHidden: true,
+            ...SimpleBrowserView.getWebviewOptions(extensionUri)
+        });
+        return new SimpleBrowserView(extensionUri, url, webview);
+    }
+    public static restore(extensionUri: vscode.Uri, url: string, webviewPanel: vscode.WebviewPanel): SimpleBrowserView {
+        return new SimpleBrowserView(extensionUri, url, webviewPanel);
+    }
+    private constructor(private readonly extensionUri: vscode.Uri, url: string, webviewPanel: vscode.WebviewPanel) {
+        super();
+        this._webviewPanel = this._register(webviewPanel);
+        this._webviewPanel.webview.options = SimpleBrowserView.getWebviewOptions(extensionUri);
+        this._register(this._webviewPanel.webview.onDidReceiveMessage(e => {
+            switch (e.type) {
+                case 'openExternal':
+                    try {
+                        const url = vscode.Uri.parse(e.url);
+                        vscode.env.openExternal(url);
+                    }
+                    catch {
+                        // Noop
+                    }
+                    break;
+            }
+        }));
+        this._register(this._webviewPanel.onDidDispose(() => {
+            this.dispose();
+        }));
+        this._register(vscode.workspace.onDidChangeConfiguration(e => {
+            if (e.affectsConfiguration('simpleBrowser.focusLockIndicator.enabled')) {
+                const configuration = vscode.workspace.getConfiguration('simpleBrowser');
+                this._webviewPanel.webview.postMessage({
+                    type: 'didChangeFocusLockIndicatorEnabled',
+                    focusLockEnabled: configuration.get('focusLockIndicator.enabled', true)
+                });
+            }
+        }));
+        this.show(url);
+    }
+    public override dispose() {
+        this._onDidDispose.fire();
+        super.dispose();
+    }
+    public show(url: string, options?: ShowOptions) {
+        this._webviewPanel.webview.html = this.getHtml(url);
+        this._webviewPanel.reveal(options?.viewColumn, options?.preserveFocus);
+    }
+    private getHtml(url: string) {
+        const configuration = vscode.workspace.getConfiguration('simpleBrowser');
+        const nonce = getNonce();
+        const mainJs = this.extensionResourceUrl('media', 'index.js');
+        const mainCss = this.extensionResourceUrl('media', 'main.css');
+        const codiconsUri = this.extensionResourceUrl('media', 'codicon.css');
+        return /* html */ `
 			
 			
 				
@@ -132,9 +98,9 @@ export class SimpleBrowserView extends Disposable {
 					">
 
 				
+            url: url,
+            focusLockEnabled: configuration.get('focusLockIndicator.enabled', true)
+        }))}">
 
 				
 				
@@ -171,22 +137,19 @@ export class SimpleBrowserView extends Disposable {
 				
 			
 			`;
-	}
-
-	private extensionResourceUrl(...parts: string[]): vscode.Uri {
-		return this._webviewPanel.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, ...parts));
-	}
+    }
+    private extensionResourceUrl(...parts: string[]): vscode.Uri {
+        return this._webviewPanel.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, ...parts));
+    }
 }
-
 function escapeAttribute(value: string | vscode.Uri): string {
-	return value.toString().replace(/"/g, '"');
+    return value.toString().replace(/"/g, '"');
 }
-
 function getNonce() {
-	let text = '';
-	const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-	for (let i = 0; i < 64; i++) {
-		text += possible.charAt(Math.floor(Math.random() * possible.length));
-	}
-	return text;
+    let text = '';
+    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+    for (let i = 0; i < 64; i++) {
+        text += possible.charAt(Math.floor(Math.random() * possible.length));
+    }
+    return text;
 }
diff --git a/extensions/simple-browser/preview-src/events.ts b/extensions/simple-browser/preview-src/events.ts
index 81ce5c616406f..304e91104d15a 100644
--- a/extensions/simple-browser/preview-src/events.ts
+++ b/extensions/simple-browser/preview-src/events.ts
@@ -2,11 +2,11 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function onceDocumentLoaded(f: () => void) {
-	if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') {
-		document.addEventListener('DOMContentLoaded', f);
-	} else {
-		f();
-	}
-}
\ No newline at end of file
+    if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') {
+        document.addEventListener('DOMContentLoaded', f);
+    }
+    else {
+        f();
+    }
+}
diff --git a/extensions/simple-browser/preview-src/index.ts b/extensions/simple-browser/preview-src/index.ts
index 3d804aa60fa41..54d76c920a2f4 100644
--- a/extensions/simple-browser/preview-src/index.ts
+++ b/extensions/simple-browser/preview-src/index.ts
@@ -2,25 +2,19 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { onceDocumentLoaded } from './events';
-
 const vscode = acquireVsCodeApi();
-
 function getSettings() {
-	const element = document.getElementById('simple-browser-settings');
-	if (element) {
-		const data = element.getAttribute('data-settings');
-		if (data) {
-			return JSON.parse(data);
-		}
-	}
-
-	throw new Error(`Could not load settings`);
+    const element = document.getElementById('simple-browser-settings');
+    if (element) {
+        const data = element.getAttribute('data-settings');
+        if (data) {
+            return JSON.parse(data);
+        }
+    }
+    throw new Error(`Could not load settings`);
 }
-
 const settings = getSettings();
-
 const iframe = document.querySelector('iframe')!;
 const header = document.querySelector('.header')!;
 const input = header.querySelector('.url-input')!;
@@ -28,85 +22,69 @@ const forwardButton = header.querySelector('.forward-button')
 const backButton = header.querySelector('.back-button')!;
 const reloadButton = header.querySelector('.reload-button')!;
 const openExternalButton = header.querySelector('.open-external-button')!;
-
 window.addEventListener('message', e => {
-	switch (e.data.type) {
-		case 'focus':
-			{
-				iframe.focus();
-				break;
-			}
-		case 'didChangeFocusLockIndicatorEnabled':
-			{
-				toggleFocusLockIndicatorEnabled(e.data.enabled);
-				break;
-			}
-	}
+    switch (e.data.type) {
+        case 'focus':
+            {
+                iframe.focus();
+                break;
+            }
+        case 'didChangeFocusLockIndicatorEnabled':
+            {
+                toggleFocusLockIndicatorEnabled(e.data.enabled);
+                break;
+            }
+    }
 });
-
 onceDocumentLoaded(() => {
-	setInterval(() => {
-		const iframeFocused = document.activeElement?.tagName === 'IFRAME';
-		document.body.classList.toggle('iframe-focused', iframeFocused);
-	}, 50);
-
-	iframe.addEventListener('load', () => {
-		// Noop
-	});
-
-	input.addEventListener('change', e => {
-		const url = (e.target as HTMLInputElement).value;
-		navigateTo(url);
-	});
-
-	forwardButton.addEventListener('click', () => {
-		history.forward();
-	});
-
-	backButton.addEventListener('click', () => {
-		history.back();
-	});
-
-	openExternalButton.addEventListener('click', () => {
-		vscode.postMessage({
-			type: 'openExternal',
-			url: input.value
-		});
-	});
-
-	reloadButton.addEventListener('click', () => {
-		// This does not seem to trigger what we want
-		// history.go(0);
-
-		// This incorrectly adds entries to the history but does reload
-		// It also always incorrectly always loads the value in the input bar,
-		// which may not match the current page if the user has navigated
-		navigateTo(input.value);
-	});
-
-	navigateTo(settings.url);
-	input.value = settings.url;
-
-	toggleFocusLockIndicatorEnabled(settings.focusLockIndicatorEnabled);
-
-	function navigateTo(rawUrl: string): void {
-		try {
-			const url = new URL(rawUrl);
-
-			// Try to bust the cache for the iframe
-			// There does not appear to be any way to reliably do this except modifying the url
-			url.searchParams.append('vscodeBrowserReqId', Date.now().toString());
-
-			iframe.src = url.toString();
-		} catch {
-			iframe.src = rawUrl;
-		}
-
-		vscode.setState({ url: rawUrl });
-	}
+    setInterval(() => {
+        const iframeFocused = document.activeElement?.tagName === 'IFRAME';
+        document.body.classList.toggle('iframe-focused', iframeFocused);
+    }, 50);
+    iframe.addEventListener('load', () => {
+        // Noop
+    });
+    input.addEventListener('change', e => {
+        const url = (e.target as HTMLInputElement).value;
+        navigateTo(url);
+    });
+    forwardButton.addEventListener('click', () => {
+        history.forward();
+    });
+    backButton.addEventListener('click', () => {
+        history.back();
+    });
+    openExternalButton.addEventListener('click', () => {
+        vscode.postMessage({
+            type: 'openExternal',
+            url: input.value
+        });
+    });
+    reloadButton.addEventListener('click', () => {
+        // This does not seem to trigger what we want
+        // history.go(0);
+        // This incorrectly adds entries to the history but does reload
+        // It also always incorrectly always loads the value in the input bar,
+        // which may not match the current page if the user has navigated
+        navigateTo(input.value);
+    });
+    navigateTo(settings.url);
+    input.value = settings.url;
+    toggleFocusLockIndicatorEnabled(settings.focusLockIndicatorEnabled);
+    function navigateTo(rawUrl: string): void {
+        try {
+            const url = new URL(rawUrl);
+            // Try to bust the cache for the iframe
+            // There does not appear to be any way to reliably do this except modifying the url
+            url.searchParams.append('vscodeBrowserReqId', Date.now().toString());
+            iframe.src = url.toString();
+        }
+        catch {
+            iframe.src = rawUrl;
+        }
+        vscode.setState({ url: rawUrl });
+    }
 });
-
 function toggleFocusLockIndicatorEnabled(enabled: boolean) {
-	document.body.classList.toggle('enable-focus-lock-indicator', enabled);
+    document.body.classList.toggle('enable-focus-lock-indicator', enabled);
 }
-
diff --git a/extensions/tunnel-forwarding/Source/deferredPromise.ts b/extensions/tunnel-forwarding/Source/deferredPromise.ts
index 54b0737f3c0d2..198dcf444cdcc 100644
--- a/extensions/tunnel-forwarding/Source/deferredPromise.ts
+++ b/extensions/tunnel-forwarding/Source/deferredPromise.ts
@@ -2,61 +2,55 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export type ValueCallback = (value: T | Promise) => void;
-
 const enum DeferredOutcome {
-	Resolved,
-	Rejected
+    Resolved,
+    Rejected
 }
-
 /**
  * Copied from src\vs\base\common\async.ts
  */
 export class DeferredPromise {
-
-	private completeCallback!: ValueCallback;
-	private errorCallback!: (err: unknown) => void;
-	private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };
-
-	public get isRejected() {
-		return this.outcome?.outcome === DeferredOutcome.Rejected;
-	}
-
-	public get isResolved() {
-		return this.outcome?.outcome === DeferredOutcome.Resolved;
-	}
-
-	public get isSettled() {
-		return !!this.outcome;
-	}
-
-	public get value() {
-		return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
-	}
-
-	public readonly p: Promise;
-
-	constructor() {
-		this.p = new Promise((c, e) => {
-			this.completeCallback = c;
-			this.errorCallback = e;
-		});
-	}
-
-	public complete(value: T) {
-		return new Promise(resolve => {
-			this.completeCallback(value);
-			this.outcome = { outcome: DeferredOutcome.Resolved, value };
-			resolve();
-		});
-	}
-
-	public error(err: unknown) {
-		return new Promise(resolve => {
-			this.errorCallback(err);
-			this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
-			resolve();
-		});
-	}
+    private completeCallback!: ValueCallback;
+    private errorCallback!: (err: unknown) => void;
+    private outcome?: {
+        outcome: DeferredOutcome.Rejected;
+        value: any;
+    } | {
+        outcome: DeferredOutcome.Resolved;
+        value: T;
+    };
+    public get isRejected() {
+        return this.outcome?.outcome === DeferredOutcome.Rejected;
+    }
+    public get isResolved() {
+        return this.outcome?.outcome === DeferredOutcome.Resolved;
+    }
+    public get isSettled() {
+        return !!this.outcome;
+    }
+    public get value() {
+        return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
+    }
+    public readonly p: Promise;
+    constructor() {
+        this.p = new Promise((c, e) => {
+            this.completeCallback = c;
+            this.errorCallback = e;
+        });
+    }
+    public complete(value: T) {
+        return new Promise(resolve => {
+            this.completeCallback(value);
+            this.outcome = { outcome: DeferredOutcome.Resolved, value };
+            resolve();
+        });
+    }
+    public error(err: unknown) {
+        return new Promise(resolve => {
+            this.errorCallback(err);
+            this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
+            resolve();
+        });
+    }
 }
diff --git a/extensions/tunnel-forwarding/Source/extension.ts b/extensions/tunnel-forwarding/Source/extension.ts
index 299c728719f78..996c4f205e553 100644
--- a/extensions/tunnel-forwarding/Source/extension.ts
+++ b/extensions/tunnel-forwarding/Source/extension.ts
@@ -2,330 +2,267 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
 import * as path from 'path';
 import * as vscode from 'vscode';
 import { DeferredPromise } from './deferredPromise';
 import { splitNewLines } from './split';
-
 export const enum TunnelPrivacyId {
-	Private = 'private',
-	Public = 'public',
+    Private = 'private',
+    Public = 'public'
 }
-
 /**
  * Timeout after the last port forwarding is disposed before we'll tear down
  * the CLI. This is primarily used since privacy changes to port will appear
  * as a dispose+re-create call, and we don't want to have to restart the CLI.
  */
-const CLEANUP_TIMEOUT = 10_000;
-
+const CLEANUP_TIMEOUT = 10000;
 const cliPath = process.env.VSCODE_FORWARDING_IS_DEV
-	? path.join(__dirname, '../../../cli/target/debug/code')
-	: path.join(
-		vscode.env.appRoot,
-		process.platform === 'darwin' ? 'bin' : '../../bin',
-		vscode.env.appQuality === 'stable' ? 'code-tunnel' : 'code-tunnel-insiders',
-	) + (process.platform === 'win32' ? '.exe' : '');
-
+    ? path.join(__dirname, '../../../cli/target/debug/code')
+    : path.join(vscode.env.appRoot, process.platform === 'darwin' ? 'bin' : '../../bin', vscode.env.appQuality === 'stable' ? 'code-tunnel' : 'code-tunnel-insiders') + (process.platform === 'win32' ? '.exe' : '');
 class Tunnel implements vscode.Tunnel {
-	private readonly disposeEmitter = new vscode.EventEmitter();
-	public readonly onDidDispose = this.disposeEmitter.event;
-	public localAddress!: string;
-
-	constructor(
-		public readonly remoteAddress: { port: number; host: string },
-		public readonly privacy: TunnelPrivacyId,
-		public readonly protocol: 'http' | 'https',
-	) { }
-
-	public setPortFormat(formatString: string) {
-		this.localAddress = formatString.replace('{port}', String(this.remoteAddress.port));
-	}
-
-	dispose() {
-		this.disposeEmitter.fire();
-	}
+    private readonly disposeEmitter = new vscode.EventEmitter();
+    public readonly onDidDispose = this.disposeEmitter.event;
+    public localAddress!: string;
+    constructor(public readonly remoteAddress: {
+        port: number;
+        host: string;
+    }, public readonly privacy: TunnelPrivacyId, public readonly protocol: 'http' | 'https') { }
+    public setPortFormat(formatString: string) {
+        this.localAddress = formatString.replace('{port}', String(this.remoteAddress.port));
+    }
+    dispose() {
+        this.disposeEmitter.fire();
+    }
 }
-
 const enum State {
-	Starting,
-	Active,
-	Inactive,
-	Error,
+    Starting,
+    Active,
+    Inactive,
+    Error
 }
-
-type StateT =
-	| { state: State.Inactive }
-	| { state: State.Starting; process: ChildProcessWithoutNullStreams; cleanupTimeout?: NodeJS.Timeout }
-	| { state: State.Active; portFormat: string; process: ChildProcessWithoutNullStreams; cleanupTimeout?: NodeJS.Timeout }
-	| { state: State.Error; error: string };
-
+type StateT = {
+    state: State.Inactive;
+} | {
+    state: State.Starting;
+    process: ChildProcessWithoutNullStreams;
+    cleanupTimeout?: NodeJS.Timeout;
+} | {
+    state: State.Active;
+    portFormat: string;
+    process: ChildProcessWithoutNullStreams;
+    cleanupTimeout?: NodeJS.Timeout;
+} | {
+    state: State.Error;
+    error: string;
+};
 export async function activate(context: vscode.ExtensionContext) {
-	if (vscode.env.remoteAuthority) {
-		return; // forwarding is local-only at the moment
-	}
-
-	const logger = new Logger(vscode.l10n.t('Port Forwarding'));
-	const provider = new TunnelProvider(logger, context);
-
-	context.subscriptions.push(
-		vscode.commands.registerCommand('tunnel-forwarding.showLog', () => logger.show()),
-		vscode.commands.registerCommand('tunnel-forwarding.restart', () => provider.restart()),
-
-		provider.onDidStateChange(s => {
-			vscode.commands.executeCommand('setContext', 'tunnelForwardingIsRunning', s.state !== State.Inactive);
-		}),
-
-		await vscode.workspace.registerTunnelProvider(
-			provider,
-			{
-				tunnelFeatures: {
-					elevation: false,
-					protocol: true,
-					privacyOptions: [
-						{ themeIcon: 'globe', id: TunnelPrivacyId.Public, label: vscode.l10n.t('Public') },
-						{ themeIcon: 'lock', id: TunnelPrivacyId.Private, label: vscode.l10n.t('Private') },
-					],
-				},
-			},
-		),
-	);
+    if (vscode.env.remoteAuthority) {
+        return; // forwarding is local-only at the moment
+    }
+    const logger = new Logger(vscode.l10n.t('Port Forwarding'));
+    const provider = new TunnelProvider(logger, context);
+    context.subscriptions.push(vscode.commands.registerCommand('tunnel-forwarding.showLog', () => logger.show()), vscode.commands.registerCommand('tunnel-forwarding.restart', () => provider.restart()), provider.onDidStateChange(s => {
+        vscode.commands.executeCommand('setContext', 'tunnelForwardingIsRunning', s.state !== State.Inactive);
+    }), await vscode.workspace.registerTunnelProvider(provider, {
+        tunnelFeatures: {
+            elevation: false,
+            protocol: true,
+            privacyOptions: [
+                { themeIcon: 'globe', id: TunnelPrivacyId.Public, label: vscode.l10n.t('Public') },
+                { themeIcon: 'lock', id: TunnelPrivacyId.Private, label: vscode.l10n.t('Private') },
+            ],
+        },
+    }));
 }
-
 export function deactivate() { }
-
 class Logger {
-	private outputChannel?: vscode.LogOutputChannel;
-
-	constructor(private readonly label: string) { }
-
-	public show(): void {
-		return this.outputChannel?.show();
-	}
-
-	public clear() {
-		this.outputChannel?.clear();
-	}
-
-	public log(
-		logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error',
-		message: string,
-		...args: unknown[]
-	) {
-		if (!this.outputChannel) {
-			this.outputChannel = vscode.window.createOutputChannel(this.label, { log: true });
-			vscode.commands.executeCommand('setContext', 'tunnelForwardingHasLog', true);
-		}
-		this.outputChannel[logLevel](message, ...args);
-	}
+    private outputChannel?: vscode.LogOutputChannel;
+    constructor(private readonly label: string) { }
+    public show(): void {
+        return this.outputChannel?.show();
+    }
+    public clear() {
+        this.outputChannel?.clear();
+    }
+    public log(logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error', message: string, ...args: unknown[]) {
+        if (!this.outputChannel) {
+            this.outputChannel = vscode.window.createOutputChannel(this.label, { log: true });
+            vscode.commands.executeCommand('setContext', 'tunnelForwardingHasLog', true);
+        }
+        this.outputChannel[logLevel](message, ...args);
+    }
 }
-
 const didWarnPublicKey = 'didWarnPublic';
-
 class TunnelProvider implements vscode.TunnelProvider {
-	private readonly tunnels = new Set();
-	private readonly stateChange = new vscode.EventEmitter();
-	private _state: StateT = { state: State.Inactive };
-
-	private get state(): StateT {
-		return this._state;
-	}
-
-	private set state(state: StateT) {
-		this._state = state;
-		this.stateChange.fire(state);
-	}
-
-	public readonly onDidStateChange = this.stateChange.event;
-
-	constructor(private readonly logger: Logger, private readonly context: vscode.ExtensionContext) { }
-
-	/** @inheritdoc */
-	public async provideTunnel(tunnelOptions: vscode.TunnelOptions): Promise {
-		if (tunnelOptions.privacy === TunnelPrivacyId.Public) {
-			if (!(await this.consentPublicPort(tunnelOptions.remoteAddress.port))) {
-				return;
-			}
-		}
-
-		const tunnel = new Tunnel(
-			tunnelOptions.remoteAddress,
-			(tunnelOptions.privacy as TunnelPrivacyId) || TunnelPrivacyId.Private,
-			tunnelOptions.protocol === 'https' ? 'https' : 'http',
-		);
-
-		this.tunnels.add(tunnel);
-		tunnel.onDidDispose(() => {
-			this.tunnels.delete(tunnel);
-			this.updateActivePortsIfRunning();
-		});
-
-		switch (this.state.state) {
-			case State.Error:
-			case State.Inactive:
-				await this.setupPortForwardingProcess();
-			// fall through since state is now starting
-			case State.Starting:
-				this.updateActivePortsIfRunning();
-				return new Promise((resolve, reject) => {
-					const l = this.stateChange.event(state => {
-						if (state.state === State.Active) {
-							tunnel.setPortFormat(state.portFormat);
-							l.dispose();
-							resolve(tunnel);
-						} else if (state.state === State.Error) {
-							l.dispose();
-							reject(new Error(state.error));
-						}
-					});
-				});
-			case State.Active:
-				tunnel.setPortFormat(this.state.portFormat);
-				this.updateActivePortsIfRunning();
-				return tunnel;
-		}
-	}
-
-	/** Re/starts the port forwarding system. */
-	public async restart() {
-		this.killRunningProcess();
-		await this.setupPortForwardingProcess(); // will show progress
-		this.updateActivePortsIfRunning();
-	}
-
-	private async consentPublicPort(portNumber: number) {
-		const didWarn = this.context.globalState.get(didWarnPublicKey, false);
-		if (didWarn) {
-			return true;
-		}
-
-		const continueOpt = vscode.l10n.t('Continue');
-		const dontShowAgain = vscode.l10n.t("Don't show again");
-		const r = await vscode.window.showWarningMessage(
-			vscode.l10n.t("You're about to create a publicly forwarded port. Anyone on the internet will be able to connect to the service listening on port {0}. You should only proceed if this service is secure and non-sensitive.", portNumber),
-			{ modal: true },
-			continueOpt,
-			dontShowAgain,
-		);
-		if (r === continueOpt) {
-			// continue
-		} else if (r === dontShowAgain) {
-			await this.context.globalState.update(didWarnPublicKey, true);
-		} else {
-			return false;
-		}
-
-		return true;
-	}
-
-	private isInStateWithProcess(process: ChildProcessWithoutNullStreams) {
-		return (
-			(this.state.state === State.Starting || this.state.state === State.Active) &&
-			this.state.process === process
-		);
-	}
-
-	private killRunningProcess() {
-		if (this.state.state === State.Starting || this.state.state === State.Active) {
-			this.logger.log('info', '[forwarding] no more ports, stopping forwarding CLI');
-			this.state.process.kill();
-			this.state = { state: State.Inactive };
-		}
-	}
-
-	private updateActivePortsIfRunning() {
-		if (this.state.state !== State.Starting && this.state.state !== State.Active) {
-			return;
-		}
-
-		const ports = [...this.tunnels].map(t => ({ number: t.remoteAddress.port, privacy: t.privacy, protocol: t.protocol }));
-		this.state.process.stdin.write(`${JSON.stringify(ports)}\n`);
-
-		if (ports.length === 0 && !this.state.cleanupTimeout) {
-			this.state.cleanupTimeout = setTimeout(() => this.killRunningProcess(), CLEANUP_TIMEOUT);
-		} else if (ports.length > 0 && this.state.cleanupTimeout) {
-			clearTimeout(this.state.cleanupTimeout);
-			this.state.cleanupTimeout = undefined;
-		}
-	}
-
-	private async setupPortForwardingProcess() {
-		const session = await vscode.authentication.getSession('github', ['user:email', 'read:org'], {
-			createIfNone: true,
-		});
-
-		const args = [
-			'--verbose',
-			'tunnel',
-			'forward-internal',
-			'--provider',
-			'github',
-		];
-
-		this.logger.log('info', '[forwarding] starting CLI');
-		const child = spawn(cliPath, args, { stdio: 'pipe', env: { ...process.env, NO_COLOR: '1', VSCODE_CLI_ACCESS_TOKEN: session.accessToken } });
-		this.state = { state: State.Starting, process: child };
-
-		const progressP = new DeferredPromise();
-		vscode.window.withProgress(
-			{
-				location: vscode.ProgressLocation.Notification,
-				title: vscode.l10n.t({
-					comment: ['do not change link format [Show Log](command), only change the text "Show Log"'],
-					message: 'Starting port forwarding system ([Show Log]({0}))',
-					args: ['command:tunnel-forwarding.showLog']
-				}),
-			},
-			() => progressP.p,
-		);
-
-		let lastPortFormat: string | undefined;
-		child.on('exit', status => {
-			const msg = `[forwarding] exited with code ${status}`;
-			this.logger.log('info', msg);
-			progressP.complete(); // make sure to clear progress on unexpected exit
-			if (this.isInStateWithProcess(child)) {
-				this.state = { state: State.Error, error: msg };
-			}
-		});
-
-		child.on('error', err => {
-			this.logger.log('error', `[forwarding] ${err}`);
-			progressP.complete(); // make sure to clear progress on unexpected exit
-			if (this.isInStateWithProcess(child)) {
-				this.state = { state: State.Error, error: String(err) };
-			}
-		});
-
-		child.stdout
-			.pipe(splitNewLines())
-			.on('data', line => this.logger.log('info', `[forwarding] ${line}`))
-			.resume();
-
-		child.stderr
-			.pipe(splitNewLines())
-			.on('data', line => {
-				try {
-					const l: { port_format: string } = JSON.parse(line);
-					if (l.port_format && l.port_format !== lastPortFormat) {
-						this.state = {
-							state: State.Active,
-							portFormat: l.port_format, process: child,
-							cleanupTimeout: 'cleanupTimeout' in this.state ? this.state.cleanupTimeout : undefined,
-						};
-						progressP.complete();
-					}
-				} catch (e) {
-					this.logger.log('error', `[forwarding] ${line}`);
-				}
-			})
-			.resume();
-
-		await new Promise((resolve, reject) => {
-			child.on('spawn', resolve);
-			child.on('error', reject);
-		});
-	}
+    private readonly tunnels = new Set();
+    private readonly stateChange = new vscode.EventEmitter();
+    private _state: StateT = { state: State.Inactive };
+    private get state(): StateT {
+        return this._state;
+    }
+    private set state(state: StateT) {
+        this._state = state;
+        this.stateChange.fire(state);
+    }
+    public readonly onDidStateChange = this.stateChange.event;
+    constructor(private readonly logger: Logger, private readonly context: vscode.ExtensionContext) { }
+    /** @inheritdoc */
+    public async provideTunnel(tunnelOptions: vscode.TunnelOptions): Promise {
+        if (tunnelOptions.privacy === TunnelPrivacyId.Public) {
+            if (!(await this.consentPublicPort(tunnelOptions.remoteAddress.port))) {
+                return;
+            }
+        }
+        const tunnel = new Tunnel(tunnelOptions.remoteAddress, (tunnelOptions.privacy as TunnelPrivacyId) || TunnelPrivacyId.Private, tunnelOptions.protocol === 'https' ? 'https' : 'http');
+        this.tunnels.add(tunnel);
+        tunnel.onDidDispose(() => {
+            this.tunnels.delete(tunnel);
+            this.updateActivePortsIfRunning();
+        });
+        switch (this.state.state) {
+            case State.Error:
+            case State.Inactive:
+                await this.setupPortForwardingProcess();
+            // fall through since state is now starting
+            case State.Starting:
+                this.updateActivePortsIfRunning();
+                return new Promise((resolve, reject) => {
+                    const l = this.stateChange.event(state => {
+                        if (state.state === State.Active) {
+                            tunnel.setPortFormat(state.portFormat);
+                            l.dispose();
+                            resolve(tunnel);
+                        }
+                        else if (state.state === State.Error) {
+                            l.dispose();
+                            reject(new Error(state.error));
+                        }
+                    });
+                });
+            case State.Active:
+                tunnel.setPortFormat(this.state.portFormat);
+                this.updateActivePortsIfRunning();
+                return tunnel;
+        }
+    }
+    /** Re/starts the port forwarding system. */
+    public async restart() {
+        this.killRunningProcess();
+        await this.setupPortForwardingProcess(); // will show progress
+        this.updateActivePortsIfRunning();
+    }
+    private async consentPublicPort(portNumber: number) {
+        const didWarn = this.context.globalState.get(didWarnPublicKey, false);
+        if (didWarn) {
+            return true;
+        }
+        const continueOpt = vscode.l10n.t('Continue');
+        const dontShowAgain = vscode.l10n.t("Don't show again");
+        const r = await vscode.window.showWarningMessage(vscode.l10n.t("You're about to create a publicly forwarded port. Anyone on the internet will be able to connect to the service listening on port {0}. You should only proceed if this service is secure and non-sensitive.", portNumber), { modal: true }, continueOpt, dontShowAgain);
+        if (r === continueOpt) {
+            // continue
+        }
+        else if (r === dontShowAgain) {
+            await this.context.globalState.update(didWarnPublicKey, true);
+        }
+        else {
+            return false;
+        }
+        return true;
+    }
+    private isInStateWithProcess(process: ChildProcessWithoutNullStreams) {
+        return ((this.state.state === State.Starting || this.state.state === State.Active) &&
+            this.state.process === process);
+    }
+    private killRunningProcess() {
+        if (this.state.state === State.Starting || this.state.state === State.Active) {
+            this.logger.log('info', '[forwarding] no more ports, stopping forwarding CLI');
+            this.state.process.kill();
+            this.state = { state: State.Inactive };
+        }
+    }
+    private updateActivePortsIfRunning() {
+        if (this.state.state !== State.Starting && this.state.state !== State.Active) {
+            return;
+        }
+        const ports = [...this.tunnels].map(t => ({ number: t.remoteAddress.port, privacy: t.privacy, protocol: t.protocol }));
+        this.state.process.stdin.write(`${JSON.stringify(ports)}\n`);
+        if (ports.length === 0 && !this.state.cleanupTimeout) {
+            this.state.cleanupTimeout = setTimeout(() => this.killRunningProcess(), CLEANUP_TIMEOUT);
+        }
+        else if (ports.length > 0 && this.state.cleanupTimeout) {
+            clearTimeout(this.state.cleanupTimeout);
+            this.state.cleanupTimeout = undefined;
+        }
+    }
+    private async setupPortForwardingProcess() {
+        const session = await vscode.authentication.getSession('github', ['user:email', 'read:org'], {
+            createIfNone: true,
+        });
+        const args = [
+            '--verbose',
+            'tunnel',
+            'forward-internal',
+            '--provider',
+            'github',
+        ];
+        this.logger.log('info', '[forwarding] starting CLI');
+        const child = spawn(cliPath, args, { stdio: 'pipe', env: { ...process.env, NO_COLOR: '1', VSCODE_CLI_ACCESS_TOKEN: session.accessToken } });
+        this.state = { state: State.Starting, process: child };
+        const progressP = new DeferredPromise();
+        vscode.window.withProgress({
+            location: vscode.ProgressLocation.Notification,
+            title: vscode.l10n.t({
+                comment: ['do not change link format [Show Log](command), only change the text "Show Log"'],
+                message: 'Starting port forwarding system ([Show Log]({0}))',
+                args: ['command:tunnel-forwarding.showLog']
+            }),
+        }, () => progressP.p);
+        let lastPortFormat: string | undefined;
+        child.on('exit', status => {
+            const msg = `[forwarding] exited with code ${status}`;
+            this.logger.log('info', msg);
+            progressP.complete(); // make sure to clear progress on unexpected exit
+            if (this.isInStateWithProcess(child)) {
+                this.state = { state: State.Error, error: msg };
+            }
+        });
+        child.on('error', err => {
+            this.logger.log('error', `[forwarding] ${err}`);
+            progressP.complete(); // make sure to clear progress on unexpected exit
+            if (this.isInStateWithProcess(child)) {
+                this.state = { state: State.Error, error: String(err) };
+            }
+        });
+        child.stdout
+            .pipe(splitNewLines())
+            .on('data', line => this.logger.log('info', `[forwarding] ${line}`))
+            .resume();
+        child.stderr
+            .pipe(splitNewLines())
+            .on('data', line => {
+            try {
+                const l: {
+                    port_format: string;
+                } = JSON.parse(line);
+                if (l.port_format && l.port_format !== lastPortFormat) {
+                    this.state = {
+                        state: State.Active,
+                        portFormat: l.port_format, process: child,
+                        cleanupTimeout: 'cleanupTimeout' in this.state ? this.state.cleanupTimeout : undefined,
+                    };
+                    progressP.complete();
+                }
+            }
+            catch (e) {
+                this.logger.log('error', `[forwarding] ${line}`);
+            }
+        })
+            .resume();
+        await new Promise((resolve, reject) => {
+            child.on('spawn', resolve);
+            child.on('error', reject);
+        });
+    }
 }
diff --git a/extensions/tunnel-forwarding/Source/split.ts b/extensions/tunnel-forwarding/Source/split.ts
index 33ad055ac67f1..6144efc83379e 100644
--- a/extensions/tunnel-forwarding/Source/split.ts
+++ b/extensions/tunnel-forwarding/Source/split.ts
@@ -2,50 +2,41 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { Transform } from 'stream';
-
 export const splitNewLines = () => new StreamSplitter('\n'.charCodeAt(0));
-
 /**
  * Copied and simplified from src\vs\base\node\nodeStreams.ts
  *
  * Exception: does not include the split character in the output.
  */
 export class StreamSplitter extends Transform {
-	private buffer: Buffer | undefined;
-
-	constructor(private readonly splitter: number) {
-		super();
-	}
-
-	override _transform(chunk: Buffer, _encoding: string, callback: (error?: Error | null, data?: any) => void): void {
-		if (!this.buffer) {
-			this.buffer = chunk;
-		} else {
-			this.buffer = Buffer.concat([this.buffer, chunk]);
-		}
-
-		let offset = 0;
-		while (offset < this.buffer.length) {
-			const index = this.buffer.indexOf(this.splitter, offset);
-			if (index === -1) {
-				break;
-			}
-
-			this.push(this.buffer.subarray(offset, index));
-			offset = index + 1;
-		}
-
-		this.buffer = offset === this.buffer.length ? undefined : this.buffer.subarray(offset);
-		callback();
-	}
-
-	override _flush(callback: (error?: Error | null, data?: any) => void): void {
-		if (this.buffer) {
-			this.push(this.buffer);
-		}
-
-		callback();
-	}
+    private buffer: Buffer | undefined;
+    constructor(private readonly splitter: number) {
+        super();
+    }
+    override _transform(chunk: Buffer, _encoding: string, callback: (error?: Error | null, data?: any) => void): void {
+        if (!this.buffer) {
+            this.buffer = chunk;
+        }
+        else {
+            this.buffer = Buffer.concat([this.buffer, chunk]);
+        }
+        let offset = 0;
+        while (offset < this.buffer.length) {
+            const index = this.buffer.indexOf(this.splitter, offset);
+            if (index === -1) {
+                break;
+            }
+            this.push(this.buffer.subarray(offset, index));
+            offset = index + 1;
+        }
+        this.buffer = offset === this.buffer.length ? undefined : this.buffer.subarray(offset);
+        callback();
+    }
+    override _flush(callback: (error?: Error | null, data?: any) => void): void {
+        if (this.buffer) {
+            this.push(this.buffer);
+        }
+        callback();
+    }
 }
diff --git a/extensions/typescript-language-features/Source/api.ts b/extensions/typescript-language-features/Source/api.ts
index 5c408f6f29bc7..b64f4ea213d50 100644
--- a/extensions/typescript-language-features/Source/api.ts
+++ b/extensions/typescript-language-features/Source/api.ts
@@ -2,35 +2,26 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { PluginManager } from './tsServer/plugins';
-
 class ApiV0 {
-	public constructor(
-		public readonly onCompletionAccepted: vscode.Event,
-		private readonly _pluginManager: PluginManager,
-	) { }
-
-	configurePlugin(pluginId: string, configuration: {}): void {
-		this._pluginManager.setConfiguration(pluginId, configuration);
-	}
+    public constructor(public readonly onCompletionAccepted: vscode.Event, private readonly _pluginManager: PluginManager) { }
+    configurePlugin(pluginId: string, configuration: {}): void {
+        this._pluginManager.setConfiguration(pluginId, configuration);
+    }
 }
-
 export interface Api {
-	getAPI(version: 0): ApiV0 | undefined;
+    getAPI(version: 0): ApiV0 | undefined;
 }
-
-export function getExtensionApi(
-	onCompletionAccepted: vscode.Event,
-	pluginManager: PluginManager,
-): Api {
-	return {
-		getAPI(version) {
-			if (version === 0) {
-				return new ApiV0(onCompletionAccepted, pluginManager);
-			}
-			return undefined;
-		}
-	};
+export function getExtensionApi(onCompletionAccepted: vscode.Event, pluginManager: PluginManager): Api {
+    return {
+        getAPI(version) {
+            if (version === 0) {
+                return new ApiV0(onCompletionAccepted, pluginManager);
+            }
+            return undefined;
+        }
+    };
 }
diff --git a/extensions/typescript-language-features/Source/commands/commandManager.ts b/extensions/typescript-language-features/Source/commands/commandManager.ts
index 92b5158560c33..33d6fd2969ba2 100644
--- a/extensions/typescript-language-features/Source/commands/commandManager.ts
+++ b/extensions/typescript-language-features/Source/commands/commandManager.ts
@@ -2,40 +2,37 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export interface Command {
-	readonly id: string;
-
-	execute(...args: any[]): void | any;
+    readonly id: string;
+    execute(...args: any[]): void | any;
 }
-
 export class CommandManager {
-	private readonly commands = new Map();
-
-	public dispose() {
-		for (const registration of this.commands.values()) {
-			registration.registration.dispose();
-		}
-		this.commands.clear();
-	}
-
-	public register(command: T): vscode.Disposable {
-		let entry = this.commands.get(command.id);
-		if (!entry) {
-			entry = { refCount: 1, registration: vscode.commands.registerCommand(command.id, command.execute, command) };
-			this.commands.set(command.id, entry);
-		} else {
-			entry.refCount += 1;
-		}
-
-		return new vscode.Disposable(() => {
-			entry.refCount -= 1;
-			if (entry.refCount <= 0) {
-				entry.registration.dispose();
-				this.commands.delete(command.id);
-			}
-		});
-	}
+    private readonly commands = new Map();
+    public dispose() {
+        for (const registration of this.commands.values()) {
+            registration.registration.dispose();
+        }
+        this.commands.clear();
+    }
+    public register(command: T): vscode.Disposable {
+        let entry = this.commands.get(command.id);
+        if (!entry) {
+            entry = { refCount: 1, registration: vscode.commands.registerCommand(command.id, command.execute, command) };
+            this.commands.set(command.id, entry);
+        }
+        else {
+            entry.refCount += 1;
+        }
+        return new vscode.Disposable(() => {
+            entry.refCount -= 1;
+            if (entry.refCount <= 0) {
+                entry.registration.dispose();
+                this.commands.delete(command.id);
+            }
+        });
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/configurePlugin.ts b/extensions/typescript-language-features/Source/commands/configurePlugin.ts
index 356738294adef..351cb16e03d40 100644
--- a/extensions/typescript-language-features/Source/commands/configurePlugin.ts
+++ b/extensions/typescript-language-features/Source/commands/configurePlugin.ts
@@ -2,18 +2,12 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { PluginManager } from '../tsServer/plugins';
 import { Command } from './commandManager';
-
 export class ConfigurePluginCommand implements Command {
-	public readonly id = '_typescript.configurePlugin';
-
-	public constructor(
-		private readonly pluginManager: PluginManager,
-	) { }
-
-	public execute(pluginId: string, configuration: any) {
-		this.pluginManager.setConfiguration(pluginId, configuration);
-	}
+    public readonly id = '_typescript.configurePlugin';
+    public constructor(private readonly pluginManager: PluginManager) { }
+    public execute(pluginId: string, configuration: any) {
+        this.pluginManager.setConfiguration(pluginId, configuration);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/goToProjectConfiguration.ts b/extensions/typescript-language-features/Source/commands/goToProjectConfiguration.ts
index 0222f32aae8ba..0ad6e270a888a 100644
--- a/extensions/typescript-language-features/Source/commands/goToProjectConfiguration.ts
+++ b/extensions/typescript-language-features/Source/commands/goToProjectConfiguration.ts
@@ -2,41 +2,28 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { ActiveJsTsEditorTracker } from '../ui/activeJsTsEditorTracker';
 import { Lazy } from '../utils/lazy';
 import { openProjectConfigForFile, ProjectType } from '../tsconfig';
 import { Command } from './commandManager';
-
 export class TypeScriptGoToProjectConfigCommand implements Command {
-	public readonly id = 'typescript.goToProjectConfig';
-
-	public constructor(
-		private readonly activeJsTsEditorTracker: ActiveJsTsEditorTracker,
-		private readonly lazyClientHost: Lazy,
-	) { }
-
-	public execute() {
-		const editor = this.activeJsTsEditorTracker.activeJsTsEditor;
-		if (editor) {
-			openProjectConfigForFile(ProjectType.TypeScript, this.lazyClientHost.value.serviceClient, editor.document.uri);
-		}
-	}
+    public readonly id = 'typescript.goToProjectConfig';
+    public constructor(private readonly activeJsTsEditorTracker: ActiveJsTsEditorTracker, private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        const editor = this.activeJsTsEditorTracker.activeJsTsEditor;
+        if (editor) {
+            openProjectConfigForFile(ProjectType.TypeScript, this.lazyClientHost.value.serviceClient, editor.document.uri);
+        }
+    }
 }
-
 export class JavaScriptGoToProjectConfigCommand implements Command {
-	public readonly id = 'javascript.goToProjectConfig';
-
-	public constructor(
-		private readonly activeJsTsEditorTracker: ActiveJsTsEditorTracker,
-		private readonly lazyClientHost: Lazy,
-	) { }
-
-	public execute() {
-		const editor = this.activeJsTsEditorTracker.activeJsTsEditor;
-		if (editor) {
-			openProjectConfigForFile(ProjectType.JavaScript, this.lazyClientHost.value.serviceClient, editor.document.uri);
-		}
-	}
+    public readonly id = 'javascript.goToProjectConfig';
+    public constructor(private readonly activeJsTsEditorTracker: ActiveJsTsEditorTracker, private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        const editor = this.activeJsTsEditorTracker.activeJsTsEditor;
+        if (editor) {
+            openProjectConfigForFile(ProjectType.JavaScript, this.lazyClientHost.value.serviceClient, editor.document.uri);
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/index.ts b/extensions/typescript-language-features/Source/commands/index.ts
index 7130f2019759d..1f0a6508223a2 100644
--- a/extensions/typescript-language-features/Source/commands/index.ts
+++ b/extensions/typescript-language-features/Source/commands/index.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { PluginManager } from '../tsServer/plugins';
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { ActiveJsTsEditorTracker } from '../ui/activeJsTsEditorTracker';
@@ -17,22 +16,16 @@ import { ReloadJavaScriptProjectsCommand, ReloadTypeScriptProjectsCommand } from
 import { RestartTsServerCommand } from './restartTsServer';
 import { SelectTypeScriptVersionCommand } from './selectTypeScriptVersion';
 import { TSServerRequestCommand } from './tsserverRequests';
-
-export function registerBaseCommands(
-	commandManager: CommandManager,
-	lazyClientHost: Lazy,
-	pluginManager: PluginManager,
-	activeJsTsEditorTracker: ActiveJsTsEditorTracker,
-): void {
-	commandManager.register(new ReloadTypeScriptProjectsCommand(lazyClientHost));
-	commandManager.register(new ReloadJavaScriptProjectsCommand(lazyClientHost));
-	commandManager.register(new SelectTypeScriptVersionCommand(lazyClientHost));
-	commandManager.register(new OpenTsServerLogCommand(lazyClientHost));
-	commandManager.register(new RestartTsServerCommand(lazyClientHost));
-	commandManager.register(new TypeScriptGoToProjectConfigCommand(activeJsTsEditorTracker, lazyClientHost));
-	commandManager.register(new JavaScriptGoToProjectConfigCommand(activeJsTsEditorTracker, lazyClientHost));
-	commandManager.register(new ConfigurePluginCommand(pluginManager));
-	commandManager.register(new LearnMoreAboutRefactoringsCommand());
-	commandManager.register(new TSServerRequestCommand(lazyClientHost));
-	commandManager.register(new OpenJsDocLinkCommand());
+export function registerBaseCommands(commandManager: CommandManager, lazyClientHost: Lazy, pluginManager: PluginManager, activeJsTsEditorTracker: ActiveJsTsEditorTracker): void {
+    commandManager.register(new ReloadTypeScriptProjectsCommand(lazyClientHost));
+    commandManager.register(new ReloadJavaScriptProjectsCommand(lazyClientHost));
+    commandManager.register(new SelectTypeScriptVersionCommand(lazyClientHost));
+    commandManager.register(new OpenTsServerLogCommand(lazyClientHost));
+    commandManager.register(new RestartTsServerCommand(lazyClientHost));
+    commandManager.register(new TypeScriptGoToProjectConfigCommand(activeJsTsEditorTracker, lazyClientHost));
+    commandManager.register(new JavaScriptGoToProjectConfigCommand(activeJsTsEditorTracker, lazyClientHost));
+    commandManager.register(new ConfigurePluginCommand(pluginManager));
+    commandManager.register(new LearnMoreAboutRefactoringsCommand());
+    commandManager.register(new TSServerRequestCommand(lazyClientHost));
+    commandManager.register(new OpenJsDocLinkCommand());
 }
diff --git a/extensions/typescript-language-features/Source/commands/learnMoreAboutRefactorings.ts b/extensions/typescript-language-features/Source/commands/learnMoreAboutRefactorings.ts
index 212307f2cd8cd..ced90f4be6e35 100644
--- a/extensions/typescript-language-features/Source/commands/learnMoreAboutRefactorings.ts
+++ b/extensions/typescript-language-features/Source/commands/learnMoreAboutRefactorings.ts
@@ -2,20 +2,16 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { isTypeScriptDocument } from '../configuration/languageIds';
 import { Command } from './commandManager';
-
 export class LearnMoreAboutRefactoringsCommand implements Command {
-	public static readonly id = '_typescript.learnMoreAboutRefactorings';
-	public readonly id = LearnMoreAboutRefactoringsCommand.id;
-
-	public execute() {
-		const docUrl = vscode.window.activeTextEditor && isTypeScriptDocument(vscode.window.activeTextEditor.document)
-			? 'https://go.microsoft.com/fwlink/?linkid=2114477'
-			: 'https://go.microsoft.com/fwlink/?linkid=2116761';
-
-		vscode.env.openExternal(vscode.Uri.parse(docUrl));
-	}
+    public static readonly id = '_typescript.learnMoreAboutRefactorings';
+    public readonly id = LearnMoreAboutRefactoringsCommand.id;
+    public execute() {
+        const docUrl = vscode.window.activeTextEditor && isTypeScriptDocument(vscode.window.activeTextEditor.document)
+            ? 'https://go.microsoft.com/fwlink/?linkid=2114477'
+            : 'https://go.microsoft.com/fwlink/?linkid=2116761';
+        vscode.env.openExternal(vscode.Uri.parse(docUrl));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/openJsDocLink.ts b/extensions/typescript-language-features/Source/commands/openJsDocLink.ts
index 8535fb1c239a9..127f33957ee30 100644
--- a/extensions/typescript-language-features/Source/commands/openJsDocLink.ts
+++ b/extensions/typescript-language-features/Source/commands/openJsDocLink.ts
@@ -2,38 +2,34 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command } from './commandManager';
-
 export interface OpenJsDocLinkCommand_Args {
-	readonly file: {
-		readonly scheme: string;
-		readonly authority?: string;
-		readonly path?: string;
-		readonly query?: string;
-		readonly fragment?: string;
-	};
-	readonly position: {
-		readonly line: number;
-		readonly character: number;
-	};
+    readonly file: {
+        readonly scheme: string;
+        readonly authority?: string;
+        readonly path?: string;
+        readonly query?: string;
+        readonly fragment?: string;
+    };
+    readonly position: {
+        readonly line: number;
+        readonly character: number;
+    };
 }
-
 /**
  * Proxy command for opening links in jsdoc comments.
  *
  * This is needed to avoid incorrectly rewriting uris.
  */
 export class OpenJsDocLinkCommand implements Command {
-	public static readonly id = '_typescript.openJsDocLink';
-	public readonly id = OpenJsDocLinkCommand.id;
-
-	public async execute(args: OpenJsDocLinkCommand_Args): Promise {
-		const { line, character } = args.position;
-		const position = new vscode.Position(line, character);
-		await vscode.commands.executeCommand('vscode.open', vscode.Uri.from(args.file), {
-			selection: new vscode.Range(position, position),
-		} satisfies vscode.TextDocumentShowOptions);
-	}
+    public static readonly id = '_typescript.openJsDocLink';
+    public readonly id = OpenJsDocLinkCommand.id;
+    public async execute(args: OpenJsDocLinkCommand_Args): Promise {
+        const { line, character } = args.position;
+        const position = new vscode.Position(line, character);
+        await vscode.commands.executeCommand('vscode.open', vscode.Uri.from(args.file), {
+            selection: new vscode.Range(position, position),
+        } satisfies vscode.TextDocumentShowOptions);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/openTsServerLog.ts b/extensions/typescript-language-features/Source/commands/openTsServerLog.ts
index cd41445fef201..65b9ed8455a9b 100644
--- a/extensions/typescript-language-features/Source/commands/openTsServerLog.ts
+++ b/extensions/typescript-language-features/Source/commands/openTsServerLog.ts
@@ -2,19 +2,13 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { Lazy } from '../utils/lazy';
 import { Command } from './commandManager';
-
 export class OpenTsServerLogCommand implements Command {
-	public readonly id = 'typescript.openTsServerLog';
-
-	public constructor(
-		private readonly lazyClientHost: Lazy
-	) { }
-
-	public execute() {
-		this.lazyClientHost.value.serviceClient.openTsServerLogFile();
-	}
+    public readonly id = 'typescript.openTsServerLog';
+    public constructor(private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        this.lazyClientHost.value.serviceClient.openTsServerLogFile();
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/reloadProject.ts b/extensions/typescript-language-features/Source/commands/reloadProject.ts
index 4da59685f673c..0819074114cc5 100644
--- a/extensions/typescript-language-features/Source/commands/reloadProject.ts
+++ b/extensions/typescript-language-features/Source/commands/reloadProject.ts
@@ -2,31 +2,20 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { Lazy } from '../utils/lazy';
 import { Command } from './commandManager';
-
 export class ReloadTypeScriptProjectsCommand implements Command {
-	public readonly id = 'typescript.reloadProjects';
-
-	public constructor(
-		private readonly lazyClientHost: Lazy
-	) { }
-
-	public execute() {
-		this.lazyClientHost.value.reloadProjects();
-	}
+    public readonly id = 'typescript.reloadProjects';
+    public constructor(private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        this.lazyClientHost.value.reloadProjects();
+    }
 }
-
 export class ReloadJavaScriptProjectsCommand implements Command {
-	public readonly id = 'javascript.reloadProjects';
-
-	public constructor(
-		private readonly lazyClientHost: Lazy
-	) { }
-
-	public execute() {
-		this.lazyClientHost.value.reloadProjects();
-	}
+    public readonly id = 'javascript.reloadProjects';
+    public constructor(private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        this.lazyClientHost.value.reloadProjects();
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/restartTsServer.ts b/extensions/typescript-language-features/Source/commands/restartTsServer.ts
index a6cc47758a8e3..0951a3735a2cf 100644
--- a/extensions/typescript-language-features/Source/commands/restartTsServer.ts
+++ b/extensions/typescript-language-features/Source/commands/restartTsServer.ts
@@ -2,19 +2,13 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { Lazy } from '../utils/lazy';
 import { Command } from './commandManager';
-
 export class RestartTsServerCommand implements Command {
-	public readonly id = 'typescript.restartTsServer';
-
-	public constructor(
-		private readonly lazyClientHost: Lazy
-	) { }
-
-	public execute() {
-		this.lazyClientHost.value.serviceClient.restartTsServer(true);
-	}
+    public readonly id = 'typescript.restartTsServer';
+    public constructor(private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        this.lazyClientHost.value.serviceClient.restartTsServer(true);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/selectTypeScriptVersion.ts b/extensions/typescript-language-features/Source/commands/selectTypeScriptVersion.ts
index 24925524387da..08d69295f0b8e 100644
--- a/extensions/typescript-language-features/Source/commands/selectTypeScriptVersion.ts
+++ b/extensions/typescript-language-features/Source/commands/selectTypeScriptVersion.ts
@@ -2,20 +2,14 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { Lazy } from '../utils/lazy';
 import { Command } from './commandManager';
-
 export class SelectTypeScriptVersionCommand implements Command {
-	public static readonly id = 'typescript.selectTypeScriptVersion';
-	public readonly id = SelectTypeScriptVersionCommand.id;
-
-	public constructor(
-		private readonly lazyClientHost: Lazy
-	) { }
-
-	public execute() {
-		this.lazyClientHost.value.serviceClient.showVersionPicker();
-	}
+    public static readonly id = 'typescript.selectTypeScriptVersion';
+    public readonly id = SelectTypeScriptVersionCommand.id;
+    public constructor(private readonly lazyClientHost: Lazy) { }
+    public execute() {
+        this.lazyClientHost.value.serviceClient.showVersionPicker();
+    }
 }
diff --git a/extensions/typescript-language-features/Source/commands/tsserverRequests.ts b/extensions/typescript-language-features/Source/commands/tsserverRequests.ts
index ba545dfbea9fe..6c8f0d17b7dd0 100644
--- a/extensions/typescript-language-features/Source/commands/tsserverRequests.ts
+++ b/extensions/typescript-language-features/Source/commands/tsserverRequests.ts
@@ -2,42 +2,35 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { TypeScriptRequests } from '../typescriptService';
 import TypeScriptServiceClientHost from '../typeScriptServiceClientHost';
 import { nulToken } from '../utils/cancellation';
 import { Lazy } from '../utils/lazy';
 import { Command } from './commandManager';
-
 export class TSServerRequestCommand implements Command {
-	public readonly id = 'typescript.tsserverRequest';
-
-	public constructor(
-		private readonly lazyClientHost: Lazy
-	) { }
-
-	public execute(requestID: keyof TypeScriptRequests, args?: any, config?: any) {
-		// A cancellation token cannot be passed through the command infrastructure
-		const token = nulToken;
-
-		// The list can be found in the TypeScript compiler as `const enum CommandTypes`,
-		// to avoid extensions making calls which could affect the internal tsserver state
-		// these are only read-y sorts of commands
-		const allowList = [
-			// Seeing the JS/DTS output for a file
-			'emit-output',
-			// Grabbing a file's diagnostics
-			'semanticDiagnosticsSync',
-			'syntacticDiagnosticsSync',
-			'suggestionDiagnosticsSync',
-			// Introspecting code at a position
-			'quickinfo',
-			'quickinfo-full',
-			'completionInfo'
-		];
-
-		if (!allowList.includes(requestID)) { return; }
-		return this.lazyClientHost.value.serviceClient.execute(requestID, args, token, config);
-	}
+    public readonly id = 'typescript.tsserverRequest';
+    public constructor(private readonly lazyClientHost: Lazy) { }
+    public execute(requestID: keyof TypeScriptRequests, args?: any, config?: any) {
+        // A cancellation token cannot be passed through the command infrastructure
+        const token = nulToken;
+        // The list can be found in the TypeScript compiler as `const enum CommandTypes`,
+        // to avoid extensions making calls which could affect the internal tsserver state
+        // these are only read-y sorts of commands
+        const allowList = [
+            // Seeing the JS/DTS output for a file
+            'emit-output',
+            // Grabbing a file's diagnostics
+            'semanticDiagnosticsSync',
+            'syntacticDiagnosticsSync',
+            'suggestionDiagnosticsSync',
+            // Introspecting code at a position
+            'quickinfo',
+            'quickinfo-full',
+            'completionInfo'
+        ];
+        if (!allowList.includes(requestID)) {
+            return;
+        }
+        return this.lazyClientHost.value.serviceClient.execute(requestID, args, token, config);
+    }
 }
-
diff --git a/extensions/typescript-language-features/Source/configuration/configuration.browser.ts b/extensions/typescript-language-features/Source/configuration/configuration.browser.ts
index 15d4705de0ec9..3b533fc097891 100644
--- a/extensions/typescript-language-features/Source/configuration/configuration.browser.ts
+++ b/extensions/typescript-language-features/Source/configuration/configuration.browser.ts
@@ -2,27 +2,21 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { BaseServiceConfigurationProvider } from './configuration';
-
 export class BrowserServiceConfigurationProvider extends BaseServiceConfigurationProvider {
-
-	// On browsers, we only support using the built-in TS version
-	protected readGlobalTsdk(_configuration: vscode.WorkspaceConfiguration): string | null {
-		return null;
-	}
-
-	protected readLocalTsdk(_configuration: vscode.WorkspaceConfiguration): string | null {
-		return null;
-	}
-
-	// On browsers, we don't run TSServer on Node
-	protected readLocalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null {
-		return null;
-	}
-
-	protected override readGlobalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null {
-		return null;
-	}
+    // On browsers, we only support using the built-in TS version
+    protected readGlobalTsdk(_configuration: vscode.WorkspaceConfiguration): string | null {
+        return null;
+    }
+    protected readLocalTsdk(_configuration: vscode.WorkspaceConfiguration): string | null {
+        return null;
+    }
+    // On browsers, we don't run TSServer on Node
+    protected readLocalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null {
+        return null;
+    }
+    protected override readGlobalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null {
+        return null;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/configuration/configuration.electron.ts b/extensions/typescript-language-features/Source/configuration/configuration.electron.ts
index 0c2a7ab12f780..de54fffa5432b 100644
--- a/extensions/typescript-language-features/Source/configuration/configuration.electron.ts
+++ b/extensions/typescript-language-features/Source/configuration/configuration.electron.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as os from 'os';
 import * as path from 'path';
 import * as vscode from 'vscode';
@@ -10,93 +9,84 @@ import * as child_process from 'child_process';
 import * as fs from 'fs';
 import { BaseServiceConfigurationProvider } from './configuration';
 import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver';
-
 export class ElectronServiceConfigurationProvider extends BaseServiceConfigurationProvider {
-
-	private fixPathPrefixes(inspectValue: string): string {
-		const pathPrefixes = ['~' + path.sep];
-		for (const pathPrefix of pathPrefixes) {
-			if (inspectValue.startsWith(pathPrefix)) {
-				return path.join(os.homedir(), inspectValue.slice(pathPrefix.length));
-			}
-		}
-		return inspectValue;
-	}
-
-	protected readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null {
-		const inspect = configuration.inspect('typescript.tsdk');
-		if (inspect && typeof inspect.globalValue === 'string') {
-			return this.fixPathPrefixes(inspect.globalValue);
-		}
-		return null;
-	}
-
-	protected readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null {
-		const inspect = configuration.inspect('typescript.tsdk');
-		if (inspect && typeof inspect.workspaceValue === 'string') {
-			return this.fixPathPrefixes(inspect.workspaceValue);
-		}
-		return null;
-	}
-
-	protected readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null {
-		return this.validatePath(this.readLocalNodePathWorker(configuration));
-	}
-
-	private readLocalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null {
-		const inspect = configuration.inspect('typescript.tsserver.nodePath');
-		if (inspect?.workspaceValue && typeof inspect.workspaceValue === 'string') {
-			if (inspect.workspaceValue === 'node') {
-				return this.findNodePath();
-			}
-			const fixedPath = this.fixPathPrefixes(inspect.workspaceValue);
-			if (!path.isAbsolute(fixedPath)) {
-				const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(fixedPath);
-				return workspacePath || null;
-			}
-			return fixedPath;
-		}
-		return null;
-	}
-
-	protected readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null {
-		return this.validatePath(this.readGlobalNodePathWorker(configuration));
-	}
-
-	private readGlobalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null {
-		const inspect = configuration.inspect('typescript.tsserver.nodePath');
-		if (inspect?.globalValue && typeof inspect.globalValue === 'string') {
-			if (inspect.globalValue === 'node') {
-				return this.findNodePath();
-			}
-			const fixedPath = this.fixPathPrefixes(inspect.globalValue);
-			if (path.isAbsolute(fixedPath)) {
-				return fixedPath;
-			}
-		}
-		return null;
-	}
-
-	private findNodePath(): string | null {
-		try {
-			const out = child_process.execFileSync('node', ['-e', 'console.log(process.execPath)'], {
-				windowsHide: true,
-				timeout: 2000,
-				cwd: vscode.workspace.workspaceFolders?.[0].uri.fsPath,
-				encoding: 'utf-8',
-			});
-			return out.trim();
-		} catch (error) {
-			vscode.window.showWarningMessage(vscode.l10n.t("Could not detect a Node installation to run TS Server."));
-			return null;
-		}
-	}
-
-	private validatePath(nodePath: string | null): string | null {
-		if (nodePath && (!fs.existsSync(nodePath) || fs.lstatSync(nodePath).isDirectory())) {
-			vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node installation to run TS Server. Falling back to bundled Node.", nodePath));
-			return null;
-		}
-		return nodePath;
-	}
+    private fixPathPrefixes(inspectValue: string): string {
+        const pathPrefixes = ['~' + path.sep];
+        for (const pathPrefix of pathPrefixes) {
+            if (inspectValue.startsWith(pathPrefix)) {
+                return path.join(os.homedir(), inspectValue.slice(pathPrefix.length));
+            }
+        }
+        return inspectValue;
+    }
+    protected readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null {
+        const inspect = configuration.inspect('typescript.tsdk');
+        if (inspect && typeof inspect.globalValue === 'string') {
+            return this.fixPathPrefixes(inspect.globalValue);
+        }
+        return null;
+    }
+    protected readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null {
+        const inspect = configuration.inspect('typescript.tsdk');
+        if (inspect && typeof inspect.workspaceValue === 'string') {
+            return this.fixPathPrefixes(inspect.workspaceValue);
+        }
+        return null;
+    }
+    protected readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null {
+        return this.validatePath(this.readLocalNodePathWorker(configuration));
+    }
+    private readLocalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null {
+        const inspect = configuration.inspect('typescript.tsserver.nodePath');
+        if (inspect?.workspaceValue && typeof inspect.workspaceValue === 'string') {
+            if (inspect.workspaceValue === 'node') {
+                return this.findNodePath();
+            }
+            const fixedPath = this.fixPathPrefixes(inspect.workspaceValue);
+            if (!path.isAbsolute(fixedPath)) {
+                const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(fixedPath);
+                return workspacePath || null;
+            }
+            return fixedPath;
+        }
+        return null;
+    }
+    protected readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null {
+        return this.validatePath(this.readGlobalNodePathWorker(configuration));
+    }
+    private readGlobalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null {
+        const inspect = configuration.inspect('typescript.tsserver.nodePath');
+        if (inspect?.globalValue && typeof inspect.globalValue === 'string') {
+            if (inspect.globalValue === 'node') {
+                return this.findNodePath();
+            }
+            const fixedPath = this.fixPathPrefixes(inspect.globalValue);
+            if (path.isAbsolute(fixedPath)) {
+                return fixedPath;
+            }
+        }
+        return null;
+    }
+    private findNodePath(): string | null {
+        try {
+            const out = child_process.execFileSync('node', ['-e', 'console.log(process.execPath)'], {
+                windowsHide: true,
+                timeout: 2000,
+                cwd: vscode.workspace.workspaceFolders?.[0].uri.fsPath,
+                encoding: 'utf-8',
+            });
+            return out.trim();
+        }
+        catch (error) {
+            vscode.window.showWarningMessage(vscode.l10n.t("Could not detect a Node installation to run TS Server."));
+            return null;
+        }
+    }
+    private validatePath(nodePath: string | null): string | null {
+        if (nodePath && (!fs.existsSync(nodePath) || fs.lstatSync(nodePath).isDirectory())) {
+            vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node installation to run TS Server. Falling back to bundled Node.", nodePath));
+            return null;
+        }
+        return nodePath;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/configuration/configuration.ts b/extensions/typescript-language-features/Source/configuration/configuration.ts
index 693e7ad17e472..7961253d2ef1b 100644
--- a/extensions/typescript-language-features/Source/configuration/configuration.ts
+++ b/extensions/typescript-language-features/Source/configuration/configuration.ts
@@ -2,304 +2,257 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as Proto from '../tsServer/protocol/protocol';
 import * as objects from '../utils/objects';
-
 export enum TsServerLogLevel {
-	Off,
-	Normal,
-	Terse,
-	Verbose,
+    Off,
+    Normal,
+    Terse,
+    Verbose
 }
-
 export namespace TsServerLogLevel {
-	export function fromString(value: string): TsServerLogLevel {
-		switch (value?.toLowerCase()) {
-			case 'normal':
-				return TsServerLogLevel.Normal;
-			case 'terse':
-				return TsServerLogLevel.Terse;
-			case 'verbose':
-				return TsServerLogLevel.Verbose;
-			case 'off':
-			default:
-				return TsServerLogLevel.Off;
-		}
-	}
-
-	export function toString(value: TsServerLogLevel): string {
-		switch (value) {
-			case TsServerLogLevel.Normal:
-				return 'normal';
-			case TsServerLogLevel.Terse:
-				return 'terse';
-			case TsServerLogLevel.Verbose:
-				return 'verbose';
-			case TsServerLogLevel.Off:
-			default:
-				return 'off';
-		}
-	}
+    export function fromString(value: string): TsServerLogLevel {
+        switch (value?.toLowerCase()) {
+            case 'normal':
+                return TsServerLogLevel.Normal;
+            case 'terse':
+                return TsServerLogLevel.Terse;
+            case 'verbose':
+                return TsServerLogLevel.Verbose;
+            case 'off':
+            default:
+                return TsServerLogLevel.Off;
+        }
+    }
+    export function toString(value: TsServerLogLevel): string {
+        switch (value) {
+            case TsServerLogLevel.Normal:
+                return 'normal';
+            case TsServerLogLevel.Terse:
+                return 'terse';
+            case TsServerLogLevel.Verbose:
+                return 'verbose';
+            case TsServerLogLevel.Off:
+            default:
+                return 'off';
+        }
+    }
 }
-
 export const enum SyntaxServerConfiguration {
-	Never,
-	Always,
-	/** Use a single syntax server for every request, even on desktop */
-	Auto,
+    Never,
+    Always,
+    /** Use a single syntax server for every request, even on desktop */
+    Auto
 }
-
 export class ImplicitProjectConfiguration {
-
-	public readonly target: string | undefined;
-	public readonly module: string | undefined;
-	public readonly checkJs: boolean;
-	public readonly experimentalDecorators: boolean;
-	public readonly strictNullChecks: boolean;
-	public readonly strictFunctionTypes: boolean;
-
-	constructor(configuration: vscode.WorkspaceConfiguration) {
-		this.target = ImplicitProjectConfiguration.readTarget(configuration);
-		this.module = ImplicitProjectConfiguration.readModule(configuration);
-		this.checkJs = ImplicitProjectConfiguration.readCheckJs(configuration);
-		this.experimentalDecorators = ImplicitProjectConfiguration.readExperimentalDecorators(configuration);
-		this.strictNullChecks = ImplicitProjectConfiguration.readImplicitStrictNullChecks(configuration);
-		this.strictFunctionTypes = ImplicitProjectConfiguration.readImplicitStrictFunctionTypes(configuration);
-	}
-
-	public isEqualTo(other: ImplicitProjectConfiguration): boolean {
-		return objects.equals(this, other);
-	}
-
-	private static readTarget(configuration: vscode.WorkspaceConfiguration): string | undefined {
-		return configuration.get('js/ts.implicitProjectConfig.target');
-	}
-
-	private static readModule(configuration: vscode.WorkspaceConfiguration): string | undefined {
-		return configuration.get('js/ts.implicitProjectConfig.module');
-	}
-
-	private static readCheckJs(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('js/ts.implicitProjectConfig.checkJs')
-			?? configuration.get('javascript.implicitProjectConfig.checkJs', false);
-	}
-
-	private static readExperimentalDecorators(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('js/ts.implicitProjectConfig.experimentalDecorators')
-			?? configuration.get('javascript.implicitProjectConfig.experimentalDecorators', false);
-	}
-
-	private static readImplicitStrictNullChecks(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('js/ts.implicitProjectConfig.strictNullChecks', true);
-	}
-
-	private static readImplicitStrictFunctionTypes(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('js/ts.implicitProjectConfig.strictFunctionTypes', true);
-	}
+    public readonly target: string | undefined;
+    public readonly module: string | undefined;
+    public readonly checkJs: boolean;
+    public readonly experimentalDecorators: boolean;
+    public readonly strictNullChecks: boolean;
+    public readonly strictFunctionTypes: boolean;
+    constructor(configuration: vscode.WorkspaceConfiguration) {
+        this.target = ImplicitProjectConfiguration.readTarget(configuration);
+        this.module = ImplicitProjectConfiguration.readModule(configuration);
+        this.checkJs = ImplicitProjectConfiguration.readCheckJs(configuration);
+        this.experimentalDecorators = ImplicitProjectConfiguration.readExperimentalDecorators(configuration);
+        this.strictNullChecks = ImplicitProjectConfiguration.readImplicitStrictNullChecks(configuration);
+        this.strictFunctionTypes = ImplicitProjectConfiguration.readImplicitStrictFunctionTypes(configuration);
+    }
+    public isEqualTo(other: ImplicitProjectConfiguration): boolean {
+        return objects.equals(this, other);
+    }
+    private static readTarget(configuration: vscode.WorkspaceConfiguration): string | undefined {
+        return configuration.get('js/ts.implicitProjectConfig.target');
+    }
+    private static readModule(configuration: vscode.WorkspaceConfiguration): string | undefined {
+        return configuration.get('js/ts.implicitProjectConfig.module');
+    }
+    private static readCheckJs(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('js/ts.implicitProjectConfig.checkJs')
+            ?? configuration.get('javascript.implicitProjectConfig.checkJs', false);
+    }
+    private static readExperimentalDecorators(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('js/ts.implicitProjectConfig.experimentalDecorators')
+            ?? configuration.get('javascript.implicitProjectConfig.experimentalDecorators', false);
+    }
+    private static readImplicitStrictNullChecks(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('js/ts.implicitProjectConfig.strictNullChecks', true);
+    }
+    private static readImplicitStrictFunctionTypes(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('js/ts.implicitProjectConfig.strictFunctionTypes', true);
+    }
 }
-
 export interface TypeScriptServiceConfiguration {
-	readonly locale: string | null;
-	readonly globalTsdk: string | null;
-	readonly localTsdk: string | null;
-	readonly npmLocation: string | null;
-	readonly tsServerLogLevel: TsServerLogLevel;
-	readonly tsServerPluginPaths: readonly string[];
-	readonly implicitProjectConfiguration: ImplicitProjectConfiguration;
-	readonly disableAutomaticTypeAcquisition: boolean;
-	readonly useSyntaxServer: SyntaxServerConfiguration;
-	readonly webProjectWideIntellisenseEnabled: boolean;
-	readonly webProjectWideIntellisenseSuppressSemanticErrors: boolean;
-	readonly webTypeAcquisitionEnabled: boolean;
-	readonly enableDiagnosticsTelemetry: boolean;
-	readonly enableProjectDiagnostics: boolean;
-	readonly maxTsServerMemory: number;
-	readonly enablePromptUseWorkspaceTsdk: boolean;
-	readonly useVsCodeWatcher: boolean;
-	readonly watchOptions: Proto.WatchOptions | undefined;
-	readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined;
-	readonly enableTsServerTracing: boolean;
-	readonly localNodePath: string | null;
-	readonly globalNodePath: string | null;
-	readonly workspaceSymbolsExcludeLibrarySymbols: boolean;
-	readonly enableRegionDiagnostics: boolean;
+    readonly locale: string | null;
+    readonly globalTsdk: string | null;
+    readonly localTsdk: string | null;
+    readonly npmLocation: string | null;
+    readonly tsServerLogLevel: TsServerLogLevel;
+    readonly tsServerPluginPaths: readonly string[];
+    readonly implicitProjectConfiguration: ImplicitProjectConfiguration;
+    readonly disableAutomaticTypeAcquisition: boolean;
+    readonly useSyntaxServer: SyntaxServerConfiguration;
+    readonly webProjectWideIntellisenseEnabled: boolean;
+    readonly webProjectWideIntellisenseSuppressSemanticErrors: boolean;
+    readonly webTypeAcquisitionEnabled: boolean;
+    readonly enableDiagnosticsTelemetry: boolean;
+    readonly enableProjectDiagnostics: boolean;
+    readonly maxTsServerMemory: number;
+    readonly enablePromptUseWorkspaceTsdk: boolean;
+    readonly useVsCodeWatcher: boolean;
+    readonly watchOptions: Proto.WatchOptions | undefined;
+    readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined;
+    readonly enableTsServerTracing: boolean;
+    readonly localNodePath: string | null;
+    readonly globalNodePath: string | null;
+    readonly workspaceSymbolsExcludeLibrarySymbols: boolean;
+    readonly enableRegionDiagnostics: boolean;
 }
-
 export function areServiceConfigurationsEqual(a: TypeScriptServiceConfiguration, b: TypeScriptServiceConfiguration): boolean {
-	return objects.equals(a, b);
+    return objects.equals(a, b);
 }
-
 export interface ServiceConfigurationProvider {
-	loadFromWorkspace(): TypeScriptServiceConfiguration;
+    loadFromWorkspace(): TypeScriptServiceConfiguration;
 }
-
 const vscodeWatcherName = 'vscode';
 type vscodeWatcherName = typeof vscodeWatcherName;
-
-
 export abstract class BaseServiceConfigurationProvider implements ServiceConfigurationProvider {
-
-	public loadFromWorkspace(): TypeScriptServiceConfiguration {
-		const configuration = vscode.workspace.getConfiguration();
-		return {
-			locale: this.readLocale(configuration),
-			globalTsdk: this.readGlobalTsdk(configuration),
-			localTsdk: this.readLocalTsdk(configuration),
-			npmLocation: this.readNpmLocation(configuration),
-			tsServerLogLevel: this.readTsServerLogLevel(configuration),
-			tsServerPluginPaths: this.readTsServerPluginPaths(configuration),
-			implicitProjectConfiguration: new ImplicitProjectConfiguration(configuration),
-			disableAutomaticTypeAcquisition: this.readDisableAutomaticTypeAcquisition(configuration),
-			useSyntaxServer: this.readUseSyntaxServer(configuration),
-			webProjectWideIntellisenseEnabled: this.readWebProjectWideIntellisenseEnable(configuration),
-			webProjectWideIntellisenseSuppressSemanticErrors: this.readWebProjectWideIntellisenseSuppressSemanticErrors(configuration),
-			webTypeAcquisitionEnabled: this.readWebTypeAcquisition(configuration),
-			enableDiagnosticsTelemetry: this.readEnableDiagnosticsTelemetry(configuration),
-			enableProjectDiagnostics: this.readEnableProjectDiagnostics(configuration),
-			maxTsServerMemory: this.readMaxTsServerMemory(configuration),
-			enablePromptUseWorkspaceTsdk: this.readEnablePromptUseWorkspaceTsdk(configuration),
-			useVsCodeWatcher: this.readUseVsCodeWatcher(configuration),
-			watchOptions: this.readWatchOptions(configuration),
-			includePackageJsonAutoImports: this.readIncludePackageJsonAutoImports(configuration),
-			enableTsServerTracing: this.readEnableTsServerTracing(configuration),
-			localNodePath: this.readLocalNodePath(configuration),
-			globalNodePath: this.readGlobalNodePath(configuration),
-			workspaceSymbolsExcludeLibrarySymbols: this.readWorkspaceSymbolsExcludeLibrarySymbols(configuration),
-			enableRegionDiagnostics: this.readEnableRegionDiagnostics(configuration),
-		};
-	}
-
-	protected abstract readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null;
-	protected abstract readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null;
-	protected abstract readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null;
-	protected abstract readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null;
-
-	protected readTsServerLogLevel(configuration: vscode.WorkspaceConfiguration): TsServerLogLevel {
-		const setting = configuration.get('typescript.tsserver.log', 'off');
-		return TsServerLogLevel.fromString(setting);
-	}
-
-	protected readTsServerPluginPaths(configuration: vscode.WorkspaceConfiguration): string[] {
-		return configuration.get('typescript.tsserver.pluginPaths', []);
-	}
-
-	protected readNpmLocation(configuration: vscode.WorkspaceConfiguration): string | null {
-		return configuration.get('typescript.npm', null);
-	}
-
-	protected readDisableAutomaticTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.disableAutomaticTypeAcquisition', false);
-	}
-
-	protected readLocale(configuration: vscode.WorkspaceConfiguration): string | null {
-		const value = configuration.get('typescript.locale', 'auto');
-		return !value || value === 'auto' ? null : value;
-	}
-
-	protected readUseSyntaxServer(configuration: vscode.WorkspaceConfiguration): SyntaxServerConfiguration {
-		const value = configuration.get('typescript.tsserver.useSyntaxServer');
-		switch (value) {
-			case 'never': return SyntaxServerConfiguration.Never;
-			case 'always': return SyntaxServerConfiguration.Always;
-			case 'auto': return SyntaxServerConfiguration.Auto;
-		}
-
-		// Fallback to deprecated setting
-		const deprecatedValue = configuration.get('typescript.tsserver.useSeparateSyntaxServer', true);
-		if (deprecatedValue === 'forAllRequests') { // Undocumented setting
-			return SyntaxServerConfiguration.Always;
-		}
-		if (deprecatedValue === true) {
-			return SyntaxServerConfiguration.Auto;
-		}
-		return SyntaxServerConfiguration.Never;
-	}
-
-	protected readEnableDiagnosticsTelemetry(configuration: vscode.WorkspaceConfiguration): boolean {
-		// This setting does not appear in the settings view, as it is not to be enabled by users outside the team
-		return configuration.get('typescript.enableDiagnosticsTelemetry', false);
-	}
-
-	protected readEnableProjectDiagnostics(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.tsserver.experimental.enableProjectDiagnostics', false);
-	}
-
-	private readUseVsCodeWatcher(configuration: vscode.WorkspaceConfiguration): boolean {
-		const watcherExcludes = configuration.get>('files.watcherExclude') ?? {};
-		if (
-			watcherExcludes['**/node_modules/*/**'] === true || // VS Code default prior to 1.94.x
-			watcherExcludes['**/node_modules/**'] === true ||
-			watcherExcludes['**/node_modules'] === true ||
-			watcherExcludes['**'] === true	 					// VS Code Watching is entirely disabled
-		) {
-			return false;
-		}
-
-		const experimentalConfig = configuration.inspect('typescript.tsserver.experimental.useVsCodeWatcher');
-		if (typeof experimentalConfig?.globalValue === 'boolean') {
-			return experimentalConfig.globalValue;
-		}
-		if (typeof experimentalConfig?.workspaceValue === 'boolean') {
-			return experimentalConfig.workspaceValue;
-		}
-		if (typeof experimentalConfig?.workspaceFolderValue === 'boolean') {
-			return experimentalConfig.workspaceFolderValue;
-		}
-
-		return configuration.get('typescript.tsserver.watchOptions', vscodeWatcherName) === vscodeWatcherName;
-	}
-
-	private readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined {
-		const watchOptions = configuration.get('typescript.tsserver.watchOptions');
-		if (watchOptions === vscodeWatcherName) {
-			return undefined;
-		}
-
-		// Returned value may be a proxy. Clone it into a normal object
-		return { ...(watchOptions ?? {}) };
-	}
-
-	protected readIncludePackageJsonAutoImports(configuration: vscode.WorkspaceConfiguration): 'auto' | 'on' | 'off' | undefined {
-		return configuration.get<'auto' | 'on' | 'off'>('typescript.preferences.includePackageJsonAutoImports');
-	}
-
-	protected readMaxTsServerMemory(configuration: vscode.WorkspaceConfiguration): number {
-		const defaultMaxMemory = 3072;
-		const minimumMaxMemory = 128;
-		const memoryInMB = configuration.get('typescript.tsserver.maxTsServerMemory', defaultMaxMemory);
-		if (!Number.isSafeInteger(memoryInMB)) {
-			return defaultMaxMemory;
-		}
-		return Math.max(memoryInMB, minimumMaxMemory);
-	}
-
-	protected readEnablePromptUseWorkspaceTsdk(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.enablePromptUseWorkspaceTsdk', false);
-	}
-
-	protected readEnableTsServerTracing(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.tsserver.enableTracing', false);
-	}
-
-	private readWorkspaceSymbolsExcludeLibrarySymbols(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.workspaceSymbols.excludeLibrarySymbols', true);
-	}
-
-	private readWebProjectWideIntellisenseEnable(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.tsserver.web.projectWideIntellisense.enabled', true);
-	}
-
-	private readWebProjectWideIntellisenseSuppressSemanticErrors(configuration: vscode.WorkspaceConfiguration): boolean {
-		return this.readWebTypeAcquisition(configuration) && configuration.get('typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors', false);
-	}
-
-	private readWebTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.tsserver.web.typeAcquisition.enabled', true);
-	}
-
-	private readEnableRegionDiagnostics(configuration: vscode.WorkspaceConfiguration): boolean {
-		return configuration.get('typescript.tsserver.enableRegionDiagnostics', true);
-	}
+    public loadFromWorkspace(): TypeScriptServiceConfiguration {
+        const configuration = vscode.workspace.getConfiguration();
+        return {
+            locale: this.readLocale(configuration),
+            globalTsdk: this.readGlobalTsdk(configuration),
+            localTsdk: this.readLocalTsdk(configuration),
+            npmLocation: this.readNpmLocation(configuration),
+            tsServerLogLevel: this.readTsServerLogLevel(configuration),
+            tsServerPluginPaths: this.readTsServerPluginPaths(configuration),
+            implicitProjectConfiguration: new ImplicitProjectConfiguration(configuration),
+            disableAutomaticTypeAcquisition: this.readDisableAutomaticTypeAcquisition(configuration),
+            useSyntaxServer: this.readUseSyntaxServer(configuration),
+            webProjectWideIntellisenseEnabled: this.readWebProjectWideIntellisenseEnable(configuration),
+            webProjectWideIntellisenseSuppressSemanticErrors: this.readWebProjectWideIntellisenseSuppressSemanticErrors(configuration),
+            webTypeAcquisitionEnabled: this.readWebTypeAcquisition(configuration),
+            enableDiagnosticsTelemetry: this.readEnableDiagnosticsTelemetry(configuration),
+            enableProjectDiagnostics: this.readEnableProjectDiagnostics(configuration),
+            maxTsServerMemory: this.readMaxTsServerMemory(configuration),
+            enablePromptUseWorkspaceTsdk: this.readEnablePromptUseWorkspaceTsdk(configuration),
+            useVsCodeWatcher: this.readUseVsCodeWatcher(configuration),
+            watchOptions: this.readWatchOptions(configuration),
+            includePackageJsonAutoImports: this.readIncludePackageJsonAutoImports(configuration),
+            enableTsServerTracing: this.readEnableTsServerTracing(configuration),
+            localNodePath: this.readLocalNodePath(configuration),
+            globalNodePath: this.readGlobalNodePath(configuration),
+            workspaceSymbolsExcludeLibrarySymbols: this.readWorkspaceSymbolsExcludeLibrarySymbols(configuration),
+            enableRegionDiagnostics: this.readEnableRegionDiagnostics(configuration),
+        };
+    }
+    protected abstract readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null;
+    protected abstract readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null;
+    protected abstract readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null;
+    protected abstract readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null;
+    protected readTsServerLogLevel(configuration: vscode.WorkspaceConfiguration): TsServerLogLevel {
+        const setting = configuration.get('typescript.tsserver.log', 'off');
+        return TsServerLogLevel.fromString(setting);
+    }
+    protected readTsServerPluginPaths(configuration: vscode.WorkspaceConfiguration): string[] {
+        return configuration.get('typescript.tsserver.pluginPaths', []);
+    }
+    protected readNpmLocation(configuration: vscode.WorkspaceConfiguration): string | null {
+        return configuration.get('typescript.npm', null);
+    }
+    protected readDisableAutomaticTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.disableAutomaticTypeAcquisition', false);
+    }
+    protected readLocale(configuration: vscode.WorkspaceConfiguration): string | null {
+        const value = configuration.get('typescript.locale', 'auto');
+        return !value || value === 'auto' ? null : value;
+    }
+    protected readUseSyntaxServer(configuration: vscode.WorkspaceConfiguration): SyntaxServerConfiguration {
+        const value = configuration.get('typescript.tsserver.useSyntaxServer');
+        switch (value) {
+            case 'never': return SyntaxServerConfiguration.Never;
+            case 'always': return SyntaxServerConfiguration.Always;
+            case 'auto': return SyntaxServerConfiguration.Auto;
+        }
+        // Fallback to deprecated setting
+        const deprecatedValue = configuration.get('typescript.tsserver.useSeparateSyntaxServer', true);
+        if (deprecatedValue === 'forAllRequests') { // Undocumented setting
+            return SyntaxServerConfiguration.Always;
+        }
+        if (deprecatedValue === true) {
+            return SyntaxServerConfiguration.Auto;
+        }
+        return SyntaxServerConfiguration.Never;
+    }
+    protected readEnableDiagnosticsTelemetry(configuration: vscode.WorkspaceConfiguration): boolean {
+        // This setting does not appear in the settings view, as it is not to be enabled by users outside the team
+        return configuration.get('typescript.enableDiagnosticsTelemetry', false);
+    }
+    protected readEnableProjectDiagnostics(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.tsserver.experimental.enableProjectDiagnostics', false);
+    }
+    private readUseVsCodeWatcher(configuration: vscode.WorkspaceConfiguration): boolean {
+        const watcherExcludes = configuration.get>('files.watcherExclude') ?? {};
+        if (watcherExcludes['**/node_modules/*/**'] === true || // VS Code default prior to 1.94.x
+            watcherExcludes['**/node_modules/**'] === true ||
+            watcherExcludes['**/node_modules'] === true ||
+            watcherExcludes['**'] === true // VS Code Watching is entirely disabled
+        ) {
+            return false;
+        }
+        const experimentalConfig = configuration.inspect('typescript.tsserver.experimental.useVsCodeWatcher');
+        if (typeof experimentalConfig?.globalValue === 'boolean') {
+            return experimentalConfig.globalValue;
+        }
+        if (typeof experimentalConfig?.workspaceValue === 'boolean') {
+            return experimentalConfig.workspaceValue;
+        }
+        if (typeof experimentalConfig?.workspaceFolderValue === 'boolean') {
+            return experimentalConfig.workspaceFolderValue;
+        }
+        return configuration.get('typescript.tsserver.watchOptions', vscodeWatcherName) === vscodeWatcherName;
+    }
+    private readWatchOptions(configuration: vscode.WorkspaceConfiguration): Proto.WatchOptions | undefined {
+        const watchOptions = configuration.get('typescript.tsserver.watchOptions');
+        if (watchOptions === vscodeWatcherName) {
+            return undefined;
+        }
+        // Returned value may be a proxy. Clone it into a normal object
+        return { ...(watchOptions ?? {}) };
+    }
+    protected readIncludePackageJsonAutoImports(configuration: vscode.WorkspaceConfiguration): 'auto' | 'on' | 'off' | undefined {
+        return configuration.get<'auto' | 'on' | 'off'>('typescript.preferences.includePackageJsonAutoImports');
+    }
+    protected readMaxTsServerMemory(configuration: vscode.WorkspaceConfiguration): number {
+        const defaultMaxMemory = 3072;
+        const minimumMaxMemory = 128;
+        const memoryInMB = configuration.get('typescript.tsserver.maxTsServerMemory', defaultMaxMemory);
+        if (!Number.isSafeInteger(memoryInMB)) {
+            return defaultMaxMemory;
+        }
+        return Math.max(memoryInMB, minimumMaxMemory);
+    }
+    protected readEnablePromptUseWorkspaceTsdk(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.enablePromptUseWorkspaceTsdk', false);
+    }
+    protected readEnableTsServerTracing(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.tsserver.enableTracing', false);
+    }
+    private readWorkspaceSymbolsExcludeLibrarySymbols(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.workspaceSymbols.excludeLibrarySymbols', true);
+    }
+    private readWebProjectWideIntellisenseEnable(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.tsserver.web.projectWideIntellisense.enabled', true);
+    }
+    private readWebProjectWideIntellisenseSuppressSemanticErrors(configuration: vscode.WorkspaceConfiguration): boolean {
+        return this.readWebTypeAcquisition(configuration) && configuration.get('typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors', false);
+    }
+    private readWebTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.tsserver.web.typeAcquisition.enabled', true);
+    }
+    private readEnableRegionDiagnostics(configuration: vscode.WorkspaceConfiguration): boolean {
+        return configuration.get('typescript.tsserver.enableRegionDiagnostics', true);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/configuration/documentSelector.ts b/extensions/typescript-language-features/Source/configuration/documentSelector.ts
index 780389320b829..a3c8d499da7dc 100644
--- a/extensions/typescript-language-features/Source/configuration/documentSelector.ts
+++ b/extensions/typescript-language-features/Source/configuration/documentSelector.ts
@@ -2,17 +2,14 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export interface DocumentSelector {
-	/**
-	 * Selector for files which only require a basic syntax server.
-	 */
-	readonly syntax: readonly vscode.DocumentFilter[];
-
-	/**
-	 * Selector for files which require semantic server support.
-	 */
-	readonly semantic: readonly vscode.DocumentFilter[];
+    /**
+     * Selector for files which only require a basic syntax server.
+     */
+    readonly syntax: readonly vscode.DocumentFilter[];
+    /**
+     * Selector for files which require semantic server support.
+     */
+    readonly semantic: readonly vscode.DocumentFilter[];
 }
diff --git a/extensions/typescript-language-features/Source/configuration/fileSchemes.ts b/extensions/typescript-language-features/Source/configuration/fileSchemes.ts
index 9d54f0b67a74d..494e65e0025ed 100644
--- a/extensions/typescript-language-features/Source/configuration/fileSchemes.ts
+++ b/extensions/typescript-language-features/Source/configuration/fileSchemes.ts
@@ -2,57 +2,48 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { isWeb } from '../utils/platform';
-
 export const file = 'file';
 export const untitled = 'untitled';
 export const git = 'git';
 export const github = 'github';
 export const azurerepos = 'azurerepos';
-
 /** Live share scheme */
 export const vsls = 'vsls';
 export const walkThroughSnippet = 'walkThroughSnippet';
 export const vscodeNotebookCell = 'vscode-notebook-cell';
 export const officeScript = 'office-script';
-
 /** Used for code blocks in chat by vs code core */
 export const chatCodeBlock = 'vscode-chat-code-block';
-
 export function getSemanticSupportedSchemes() {
-	const alwaysSupportedSchemes = [
-		untitled,
-		walkThroughSnippet,
-		vscodeNotebookCell,
-		chatCodeBlock,
-	];
-
-	if (isWeb()) {
-		return [
-			...(vscode.workspace.workspaceFolders ?? []).map(folder => folder.uri.scheme),
-			...alwaysSupportedSchemes,
-		];
-	}
-
-	return [
-		file,
-		...alwaysSupportedSchemes,
-	];
+    const alwaysSupportedSchemes = [
+        untitled,
+        walkThroughSnippet,
+        vscodeNotebookCell,
+        chatCodeBlock,
+    ];
+    if (isWeb()) {
+        return [
+            ...(vscode.workspace.workspaceFolders ?? []).map(folder => folder.uri.scheme),
+            ...alwaysSupportedSchemes,
+        ];
+    }
+    return [
+        file,
+        ...alwaysSupportedSchemes,
+    ];
 }
-
 /**
  * File scheme for which JS/TS language feature should be disabled
  */
 export const disabledSchemes = new Set([
-	git,
-	vsls,
-	github,
-	azurerepos,
+    git,
+    vsls,
+    github,
+    azurerepos,
 ]);
-
 export function isOfScheme(uri: vscode.Uri, ...schemes: string[]): boolean {
-	const normalizedUriScheme = uri.scheme.toLowerCase();
-	return schemes.some(scheme => normalizedUriScheme === scheme);
+    const normalizedUriScheme = uri.scheme.toLowerCase();
+    return schemes.some(scheme => normalizedUriScheme === scheme);
 }
diff --git a/extensions/typescript-language-features/Source/configuration/languageDescription.ts b/extensions/typescript-language-features/Source/configuration/languageDescription.ts
index a97530e5ac478..6b94e66fb7603 100644
--- a/extensions/typescript-language-features/Source/configuration/languageDescription.ts
+++ b/extensions/typescript-language-features/Source/configuration/languageDescription.ts
@@ -2,73 +2,64 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { basename } from 'path';
 import * as vscode from 'vscode';
 import * as languageIds from './languageIds';
-
 export const enum DiagnosticLanguage {
-	JavaScript,
-	TypeScript
+    JavaScript,
+    TypeScript
 }
-
 export const allDiagnosticLanguages = [DiagnosticLanguage.JavaScript, DiagnosticLanguage.TypeScript];
-
 export interface LanguageDescription {
-	readonly id: string;
-	readonly diagnosticOwner: string;
-	readonly diagnosticSource: string;
-	readonly diagnosticLanguage: DiagnosticLanguage;
-	readonly languageIds: readonly string[];
-	readonly configFilePattern?: RegExp;
-	readonly isExternal?: boolean;
-	readonly standardFileExtensions: readonly string[];
+    readonly id: string;
+    readonly diagnosticOwner: string;
+    readonly diagnosticSource: string;
+    readonly diagnosticLanguage: DiagnosticLanguage;
+    readonly languageIds: readonly string[];
+    readonly configFilePattern?: RegExp;
+    readonly isExternal?: boolean;
+    readonly standardFileExtensions: readonly string[];
 }
-
 export const standardLanguageDescriptions: LanguageDescription[] = [
-	{
-		id: 'typescript',
-		diagnosticOwner: 'typescript',
-		diagnosticSource: 'ts',
-		diagnosticLanguage: DiagnosticLanguage.TypeScript,
-		languageIds: [languageIds.typescript, languageIds.typescriptreact],
-		configFilePattern: /^tsconfig(\..*)?\.json$/i,
-		standardFileExtensions: [
-			'ts',
-			'tsx',
-			'cts',
-			'mts'
-		],
-	}, {
-		id: 'javascript',
-		diagnosticOwner: 'typescript',
-		diagnosticSource: 'ts',
-		diagnosticLanguage: DiagnosticLanguage.JavaScript,
-		languageIds: [languageIds.javascript, languageIds.javascriptreact],
-		configFilePattern: /^jsconfig(\..*)?\.json$/i,
-		standardFileExtensions: [
-			'js',
-			'jsx',
-			'cjs',
-			'mjs',
-			'es6',
-			'pac',
-		],
-	}
+    {
+        id: 'typescript',
+        diagnosticOwner: 'typescript',
+        diagnosticSource: 'ts',
+        diagnosticLanguage: DiagnosticLanguage.TypeScript,
+        languageIds: [languageIds.typescript, languageIds.typescriptreact],
+        configFilePattern: /^tsconfig(\..*)?\.json$/i,
+        standardFileExtensions: [
+            'ts',
+            'tsx',
+            'cts',
+            'mts'
+        ],
+    }, {
+        id: 'javascript',
+        diagnosticOwner: 'typescript',
+        diagnosticSource: 'ts',
+        diagnosticLanguage: DiagnosticLanguage.JavaScript,
+        languageIds: [languageIds.javascript, languageIds.javascriptreact],
+        configFilePattern: /^jsconfig(\..*)?\.json$/i,
+        standardFileExtensions: [
+            'js',
+            'jsx',
+            'cjs',
+            'mjs',
+            'es6',
+            'pac',
+        ],
+    }
 ];
-
 export function isTsConfigFileName(fileName: string): boolean {
-	return /^tsconfig\.(.+\.)?json$/i.test(basename(fileName));
+    return /^tsconfig\.(.+\.)?json$/i.test(basename(fileName));
 }
-
 export function isJsConfigOrTsConfigFileName(fileName: string): boolean {
-	return /^[jt]sconfig\.(.+\.)?json$/i.test(basename(fileName));
+    return /^[jt]sconfig\.(.+\.)?json$/i.test(basename(fileName));
 }
-
 export function doesResourceLookLikeATypeScriptFile(resource: vscode.Uri): boolean {
-	return /\.(tsx?|mts|cts)$/i.test(resource.fsPath);
+    return /\.(tsx?|mts|cts)$/i.test(resource.fsPath);
 }
-
 export function doesResourceLookLikeAJavaScriptFile(resource: vscode.Uri): boolean {
-	return /\.(jsx?|mjs|cjs)$/i.test(resource.fsPath);
+    return /\.(jsx?|mjs|cjs)$/i.test(resource.fsPath);
 }
diff --git a/extensions/typescript-language-features/Source/configuration/languageIds.ts b/extensions/typescript-language-features/Source/configuration/languageIds.ts
index 5decbc602d6a8..5d134a64f40d6 100644
--- a/extensions/typescript-language-features/Source/configuration/languageIds.ts
+++ b/extensions/typescript-language-features/Source/configuration/languageIds.ts
@@ -2,26 +2,21 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export const typescript = 'typescript';
 export const typescriptreact = 'typescriptreact';
 export const javascript = 'javascript';
 export const javascriptreact = 'javascriptreact';
 export const jsxTags = 'jsx-tags';
-
 export const jsTsLanguageModes = [
-	javascript,
-	javascriptreact,
-	typescript,
-	typescriptreact,
+    javascript,
+    javascriptreact,
+    typescript,
+    typescriptreact,
 ];
-
 export function isSupportedLanguageMode(doc: vscode.TextDocument) {
-	return vscode.languages.match([typescript, typescriptreact, javascript, javascriptreact], doc) > 0;
+    return vscode.languages.match([typescript, typescriptreact, javascript, javascriptreact], doc) > 0;
 }
-
 export function isTypeScriptDocument(doc: vscode.TextDocument) {
-	return vscode.languages.match([typescript, typescriptreact], doc) > 0;
+    return vscode.languages.match([typescript, typescriptreact], doc) > 0;
 }
diff --git a/extensions/typescript-language-features/Source/configuration/schemes.ts b/extensions/typescript-language-features/Source/configuration/schemes.ts
index 3eae0754ad299..9dafdcd4c8622 100644
--- a/extensions/typescript-language-features/Source/configuration/schemes.ts
+++ b/extensions/typescript-language-features/Source/configuration/schemes.ts
@@ -2,16 +2,14 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export const Schemes = Object.freeze({
-	file: 'file',
-	untitled: 'untitled',
-	mailto: 'mailto',
-	vscode: 'vscode',
-	'vscode-insiders': 'vscode-insiders',
-	notebookCell: 'vscode-notebook-cell',
+    file: 'file',
+    untitled: 'untitled',
+    mailto: 'mailto',
+    vscode: 'vscode',
+    'vscode-insiders': 'vscode-insiders',
+    notebookCell: 'vscode-notebook-cell',
 });
-
 export function isOfScheme(scheme: string, link: string): boolean {
-	return link.toLowerCase().startsWith(scheme + ':');
+    return link.toLowerCase().startsWith(scheme + ':');
 }
diff --git a/extensions/typescript-language-features/Source/experimentTelemetryReporter.ts b/extensions/typescript-language-features/Source/experimentTelemetryReporter.ts
index 8fd7ce4aa6655..07524bd920a26 100644
--- a/extensions/typescript-language-features/Source/experimentTelemetryReporter.ts
+++ b/extensions/typescript-language-features/Source/experimentTelemetryReporter.ts
@@ -2,50 +2,43 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
 import * as vscode from 'vscode';
 import * as tas from 'vscode-tas-client';
-
 export interface IExperimentationTelemetryReporter extends tas.IExperimentationTelemetry, vscode.Disposable {
-	postEventObj(eventName: string, props: { [prop: string]: string }): void;
+    postEventObj(eventName: string, props: {
+        [prop: string]: string;
+    }): void;
 }
-
 /**
  * This reporter *supports* experimentation telemetry,
  * but will only do so when passed to an {@link ExperimentationService}.
  */
-
 export class ExperimentationTelemetryReporter implements IExperimentationTelemetryReporter {
-
-	private _sharedProperties: Record = {};
-	private readonly _reporter: VsCodeTelemetryReporter;
-
-	constructor(reporter: VsCodeTelemetryReporter) {
-		this._reporter = reporter;
-	}
-
-	setSharedProperty(name: string, value: string): void {
-		this._sharedProperties[name] = value;
-	}
-
-	postEvent(eventName: string, props: Map): void {
-		const propsObject = {
-			...this._sharedProperties,
-			...Object.fromEntries(props),
-		};
-		this._reporter.sendTelemetryEvent(eventName, propsObject);
-	}
-
-	postEventObj(eventName: string, props: { [prop: string]: string }) {
-		this._reporter.sendTelemetryEvent(eventName, {
-			...this._sharedProperties,
-			...props,
-		});
-	}
-
-	dispose() {
-		this._reporter.dispose();
-	}
+    private _sharedProperties: Record = {};
+    private readonly _reporter: VsCodeTelemetryReporter;
+    constructor(reporter: VsCodeTelemetryReporter) {
+        this._reporter = reporter;
+    }
+    setSharedProperty(name: string, value: string): void {
+        this._sharedProperties[name] = value;
+    }
+    postEvent(eventName: string, props: Map): void {
+        const propsObject = {
+            ...this._sharedProperties,
+            ...Object.fromEntries(props),
+        };
+        this._reporter.sendTelemetryEvent(eventName, propsObject);
+    }
+    postEventObj(eventName: string, props: {
+        [prop: string]: string;
+    }) {
+        this._reporter.sendTelemetryEvent(eventName, {
+            ...this._sharedProperties,
+            ...props,
+        });
+    }
+    dispose() {
+        this._reporter.dispose();
+    }
 }
-
diff --git a/extensions/typescript-language-features/Source/experimentationService.ts b/extensions/typescript-language-features/Source/experimentationService.ts
index 5dc458277d46d..8de79614ca1a3 100644
--- a/extensions/typescript-language-features/Source/experimentationService.ts
+++ b/extensions/typescript-language-features/Source/experimentationService.ts
@@ -2,61 +2,49 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as tas from 'vscode-tas-client';
-
 import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
-
 interface ExperimentTypes {
-	// None for now.
 }
-
 export class ExperimentationService {
-	private readonly _experimentationServicePromise: Promise;
-	private readonly _telemetryReporter: IExperimentationTelemetryReporter;
-
-	constructor(telemetryReporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento) {
-		this._telemetryReporter = telemetryReporter;
-		this._experimentationServicePromise = createTasExperimentationService(this._telemetryReporter, id, version, globalState);
-	}
-
-	public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise {
-		const experimentationService = await this._experimentationServicePromise;
-		try {
-			const treatmentVariable = experimentationService.getTreatmentVariableAsync('vscode', name, /*checkCache*/ true) as Promise;
-			return treatmentVariable;
-		} catch {
-			return defaultValue;
-		}
-	}
+    private readonly _experimentationServicePromise: Promise;
+    private readonly _telemetryReporter: IExperimentationTelemetryReporter;
+    constructor(telemetryReporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento) {
+        this._telemetryReporter = telemetryReporter;
+        this._experimentationServicePromise = createTasExperimentationService(this._telemetryReporter, id, version, globalState);
+    }
+    public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise {
+        const experimentationService = await this._experimentationServicePromise;
+        try {
+            const treatmentVariable = experimentationService.getTreatmentVariableAsync('vscode', name, /*checkCache*/ true) as Promise;
+            return treatmentVariable;
+        }
+        catch {
+            return defaultValue;
+        }
+    }
 }
-
-export async function createTasExperimentationService(
-	reporter: IExperimentationTelemetryReporter,
-	id: string,
-	version: string,
-	globalState: vscode.Memento): Promise {
-	let targetPopulation: tas.TargetPopulation;
-	switch (vscode.env.uriScheme) {
-		case 'vscode':
-			targetPopulation = tas.TargetPopulation.Public;
-			break;
-		case 'vscode-insiders':
-			targetPopulation = tas.TargetPopulation.Insiders;
-			break;
-		case 'vscode-exploration':
-			targetPopulation = tas.TargetPopulation.Internal;
-			break;
-		case 'code-oss':
-			targetPopulation = tas.TargetPopulation.Team;
-			break;
-		default:
-			targetPopulation = tas.TargetPopulation.Public;
-			break;
-	}
-
-	const experimentationService = tas.getExperimentationService(id, version, targetPopulation, reporter, globalState);
-	await experimentationService.initialFetch;
-	return experimentationService;
+export async function createTasExperimentationService(reporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento): Promise {
+    let targetPopulation: tas.TargetPopulation;
+    switch (vscode.env.uriScheme) {
+        case 'vscode':
+            targetPopulation = tas.TargetPopulation.Public;
+            break;
+        case 'vscode-insiders':
+            targetPopulation = tas.TargetPopulation.Insiders;
+            break;
+        case 'vscode-exploration':
+            targetPopulation = tas.TargetPopulation.Internal;
+            break;
+        case 'code-oss':
+            targetPopulation = tas.TargetPopulation.Team;
+            break;
+        default:
+            targetPopulation = tas.TargetPopulation.Public;
+            break;
+    }
+    const experimentationService = tas.getExperimentationService(id, version, targetPopulation, reporter, globalState);
+    await experimentationService.initialFetch;
+    return experimentationService;
 }
diff --git a/extensions/typescript-language-features/Source/extension.browser.ts b/extensions/typescript-language-features/Source/extension.browser.ts
index b87a41901bb7b..b4b1a4a1743a4 100644
--- a/extensions/typescript-language-features/Source/extension.browser.ts
+++ b/extensions/typescript-language-features/Source/extension.browser.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
 import * as vscode from 'vscode';
 import { Api, getExtensionApi } from './api';
@@ -25,148 +24,113 @@ import { ActiveJsTsEditorTracker } from './ui/activeJsTsEditorTracker';
 import { Disposable } from './utils/dispose';
 import { getPackageInfo } from './utils/packageInfo';
 import { isWebAndHasSharedArrayBuffers } from './utils/platform';
-
 class StaticVersionProvider implements ITypeScriptVersionProvider {
-
-	constructor(
-		private readonly _version: TypeScriptVersion
-	) { }
-
-	updateConfiguration(_configuration: TypeScriptServiceConfiguration): void {
-		// noop
-	}
-
-	get defaultVersion() { return this._version; }
-	get bundledVersion() { return this._version; }
-
-	readonly globalVersion = undefined;
-	readonly localVersion = undefined;
-	readonly localVersions = [];
+    constructor(private readonly _version: TypeScriptVersion) { }
+    updateConfiguration(_configuration: TypeScriptServiceConfiguration): void {
+        // noop
+    }
+    get defaultVersion() { return this._version; }
+    get bundledVersion() { return this._version; }
+    readonly globalVersion = undefined;
+    readonly localVersion = undefined;
+    readonly localVersions = [];
 }
-
 export async function activate(context: vscode.ExtensionContext): Promise {
-	const pluginManager = new PluginManager();
-	context.subscriptions.push(pluginManager);
-
-	const commandManager = new CommandManager();
-	context.subscriptions.push(commandManager);
-
-	const onCompletionAccepted = new vscode.EventEmitter();
-	context.subscriptions.push(onCompletionAccepted);
-
-	const activeJsTsEditorTracker = new ActiveJsTsEditorTracker();
-	context.subscriptions.push(activeJsTsEditorTracker);
-
-	const versionProvider = new StaticVersionProvider(
-		new TypeScriptVersion(
-			TypeScriptVersionSource.Bundled,
-			vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(),
-			API.fromSimpleString('5.6.2')));
-
-	let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
-	const packageInfo = getPackageInfo(context);
-	if (packageInfo) {
-		const { aiKey } = packageInfo;
-		const vscTelemetryReporter = new VsCodeTelemetryReporter(aiKey);
-		experimentTelemetryReporter = new ExperimentationTelemetryReporter(vscTelemetryReporter);
-		context.subscriptions.push(experimentTelemetryReporter);
-	}
-
-	const logger = new Logger();
-
-	const lazyClientHost = createLazyClientHost(context, false, {
-		pluginManager,
-		commandManager,
-		logDirectoryProvider: noopLogDirectoryProvider,
-		cancellerFactory: noopRequestCancellerFactory,
-		versionProvider,
-		processFactory: new WorkerServerProcessFactory(context.extensionUri, logger),
-		activeJsTsEditorTracker,
-		serviceConfigurationProvider: new BrowserServiceConfigurationProvider(),
-		experimentTelemetryReporter,
-		logger,
-	}, item => {
-		onCompletionAccepted.fire(item);
-	});
-
-	registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
-
-	// context.subscriptions.push(task.register(lazyClientHost.map(x => x.serviceClient)));
-
-	import('./languageFeatures/tsconfig').then(module => {
-		context.subscriptions.push(module.register());
-	});
-
-	context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker, async () => {
-		await startPreloadWorkspaceContentsIfNeeded(context, logger);
-	}));
-
-	context.subscriptions.push(registerAtaSupport(logger));
-
-	return getExtensionApi(onCompletionAccepted.event, pluginManager);
+    const pluginManager = new PluginManager();
+    context.subscriptions.push(pluginManager);
+    const commandManager = new CommandManager();
+    context.subscriptions.push(commandManager);
+    const onCompletionAccepted = new vscode.EventEmitter();
+    context.subscriptions.push(onCompletionAccepted);
+    const activeJsTsEditorTracker = new ActiveJsTsEditorTracker();
+    context.subscriptions.push(activeJsTsEditorTracker);
+    const versionProvider = new StaticVersionProvider(new TypeScriptVersion(TypeScriptVersionSource.Bundled, vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(), API.fromSimpleString('5.6.2')));
+    let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
+    const packageInfo = getPackageInfo(context);
+    if (packageInfo) {
+        const { aiKey } = packageInfo;
+        const vscTelemetryReporter = new VsCodeTelemetryReporter(aiKey);
+        experimentTelemetryReporter = new ExperimentationTelemetryReporter(vscTelemetryReporter);
+        context.subscriptions.push(experimentTelemetryReporter);
+    }
+    const logger = new Logger();
+    const lazyClientHost = createLazyClientHost(context, false, {
+        pluginManager,
+        commandManager,
+        logDirectoryProvider: noopLogDirectoryProvider,
+        cancellerFactory: noopRequestCancellerFactory,
+        versionProvider,
+        processFactory: new WorkerServerProcessFactory(context.extensionUri, logger),
+        activeJsTsEditorTracker,
+        serviceConfigurationProvider: new BrowserServiceConfigurationProvider(),
+        experimentTelemetryReporter,
+        logger,
+    }, item => {
+        onCompletionAccepted.fire(item);
+    });
+    registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
+    // context.subscriptions.push(task.register(lazyClientHost.map(x => x.serviceClient)));
+    import('./languageFeatures/tsconfig').then(module => {
+        context.subscriptions.push(module.register());
+    });
+    context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker, async () => {
+        await startPreloadWorkspaceContentsIfNeeded(context, logger);
+    }));
+    context.subscriptions.push(registerAtaSupport(logger));
+    return getExtensionApi(onCompletionAccepted.event, pluginManager);
 }
-
 async function startPreloadWorkspaceContentsIfNeeded(context: vscode.ExtensionContext, logger: Logger): Promise {
-	if (!isWebAndHasSharedArrayBuffers()) {
-		return;
-	}
-
-	if (!vscode.workspace.workspaceFolders) {
-		return;
-	}
-
-	await Promise.all(vscode.workspace.workspaceFolders.map(async folder => {
-		const workspaceUri = folder.uri;
-		if (workspaceUri.scheme !== 'vscode-vfs' || !workspaceUri.authority.startsWith('github')) {
-			logger.info(`Skipped pre loading workspace contents for repository ${workspaceUri?.toString()}`);
-			return;
-		}
-
-		const loader = new RemoteWorkspaceContentsPreloader(workspaceUri, logger);
-		context.subscriptions.push(loader);
-		try {
-			await loader.triggerPreload();
-		} catch (error) {
-			console.error(error);
-		}
-	}));
+    if (!isWebAndHasSharedArrayBuffers()) {
+        return;
+    }
+    if (!vscode.workspace.workspaceFolders) {
+        return;
+    }
+    await Promise.all(vscode.workspace.workspaceFolders.map(async (folder) => {
+        const workspaceUri = folder.uri;
+        if (workspaceUri.scheme !== 'vscode-vfs' || !workspaceUri.authority.startsWith('github')) {
+            logger.info(`Skipped pre loading workspace contents for repository ${workspaceUri?.toString()}`);
+            return;
+        }
+        const loader = new RemoteWorkspaceContentsPreloader(workspaceUri, logger);
+        context.subscriptions.push(loader);
+        try {
+            await loader.triggerPreload();
+        }
+        catch (error) {
+            console.error(error);
+        }
+    }));
 }
-
 class RemoteWorkspaceContentsPreloader extends Disposable {
-
-	private _preload: Promise | undefined;
-
-	constructor(
-		private readonly workspaceUri: vscode.Uri,
-		private readonly logger: Logger,
-	) {
-		super();
-
-		const fsWatcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(workspaceUri, '*')));
-		this._register(fsWatcher.onDidChange(uri => {
-			if (uri.toString() === workspaceUri.toString()) {
-				this._preload = undefined;
-				this.triggerPreload();
-			}
-		}));
-	}
-
-	async triggerPreload() {
-		this._preload ??= this.doPreload();
-		return this._preload;
-	}
-
-	private async doPreload(): Promise {
-		try {
-			const remoteHubApi = await RemoteRepositories.getApi();
-			if (await remoteHubApi.loadWorkspaceContents?.(this.workspaceUri)) {
-				this.logger.info(`Successfully loaded workspace content for repository ${this.workspaceUri.toString()}`);
-			} else {
-				this.logger.info(`Failed to load workspace content for repository ${this.workspaceUri.toString()}`);
-			}
-		} catch (error) {
-			this.logger.info(`Loading workspace content for repository ${this.workspaceUri.toString()} failed: ${error instanceof Error ? error.toString() : 'Unknown reason'}`);
-			console.error(error);
-		}
-	}
+    private _preload: Promise | undefined;
+    constructor(private readonly workspaceUri: vscode.Uri, private readonly logger: Logger) {
+        super();
+        const fsWatcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(workspaceUri, '*')));
+        this._register(fsWatcher.onDidChange(uri => {
+            if (uri.toString() === workspaceUri.toString()) {
+                this._preload = undefined;
+                this.triggerPreload();
+            }
+        }));
+    }
+    async triggerPreload() {
+        this._preload ??= this.doPreload();
+        return this._preload;
+    }
+    private async doPreload(): Promise {
+        try {
+            const remoteHubApi = await RemoteRepositories.getApi();
+            if (await remoteHubApi.loadWorkspaceContents?.(this.workspaceUri)) {
+                this.logger.info(`Successfully loaded workspace content for repository ${this.workspaceUri.toString()}`);
+            }
+            else {
+                this.logger.info(`Failed to load workspace content for repository ${this.workspaceUri.toString()}`);
+            }
+        }
+        catch (error) {
+            this.logger.info(`Loading workspace content for repository ${this.workspaceUri.toString()} failed: ${error instanceof Error ? error.toString() : 'Unknown reason'}`);
+            console.error(error);
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/extension.ts b/extensions/typescript-language-features/Source/extension.ts
index dee3929baba47..40240118b1cdd 100644
--- a/extensions/typescript-language-features/Source/extension.ts
+++ b/extensions/typescript-language-features/Source/extension.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import VsCodeTelemetryReporter from '@vscode/extension-telemetry';
 import * as fs from 'fs';
 import * as vscode from 'vscode';
@@ -23,70 +22,53 @@ import { Logger } from './logging/logger';
 import { getPackageInfo } from './utils/packageInfo';
 import { PluginManager } from './tsServer/plugins';
 import * as temp from './utils/temp.electron';
-
-export function activate(
-	context: vscode.ExtensionContext
-): Api {
-	const pluginManager = new PluginManager();
-	context.subscriptions.push(pluginManager);
-
-	const commandManager = new CommandManager();
-	context.subscriptions.push(commandManager);
-
-	const onCompletionAccepted = new vscode.EventEmitter();
-	context.subscriptions.push(onCompletionAccepted);
-
-	const logDirectoryProvider = new NodeLogDirectoryProvider(context);
-	const versionProvider = new DiskTypeScriptVersionProvider();
-
-	const activeJsTsEditorTracker = new ActiveJsTsEditorTracker();
-	context.subscriptions.push(activeJsTsEditorTracker);
-
-	let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
-	const packageInfo = getPackageInfo(context);
-	if (packageInfo) {
-		const { name: id, version, aiKey } = packageInfo;
-		const vscTelemetryReporter = new VsCodeTelemetryReporter(aiKey);
-		experimentTelemetryReporter = new ExperimentationTelemetryReporter(vscTelemetryReporter);
-		context.subscriptions.push(experimentTelemetryReporter);
-
-		// Currently we have no experiments, but creating the service adds the appropriate
-		// shared properties to the ExperimentationTelemetryReporter we just created.
-		new ExperimentationService(experimentTelemetryReporter, id, version, context.globalState);
-	}
-
-	const logger = new Logger();
-
-	const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), {
-		pluginManager,
-		commandManager,
-		logDirectoryProvider,
-		cancellerFactory: nodeRequestCancellerFactory,
-		versionProvider,
-		processFactory: new ElectronServiceProcessFactory(),
-		activeJsTsEditorTracker,
-		serviceConfigurationProvider: new ElectronServiceConfigurationProvider(),
-		experimentTelemetryReporter,
-		logger,
-	}, item => {
-		onCompletionAccepted.fire(item);
-	});
-
-	registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
-
-	import('./task/taskProvider').then(module => {
-		context.subscriptions.push(module.register(lazyClientHost.map(x => x.serviceClient)));
-	});
-
-	import('./languageFeatures/tsconfig').then(module => {
-		context.subscriptions.push(module.register());
-	});
-
-	context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker));
-
-	return getExtensionApi(onCompletionAccepted.event, pluginManager);
+export function activate(context: vscode.ExtensionContext): Api {
+    const pluginManager = new PluginManager();
+    context.subscriptions.push(pluginManager);
+    const commandManager = new CommandManager();
+    context.subscriptions.push(commandManager);
+    const onCompletionAccepted = new vscode.EventEmitter();
+    context.subscriptions.push(onCompletionAccepted);
+    const logDirectoryProvider = new NodeLogDirectoryProvider(context);
+    const versionProvider = new DiskTypeScriptVersionProvider();
+    const activeJsTsEditorTracker = new ActiveJsTsEditorTracker();
+    context.subscriptions.push(activeJsTsEditorTracker);
+    let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
+    const packageInfo = getPackageInfo(context);
+    if (packageInfo) {
+        const { name: id, version, aiKey } = packageInfo;
+        const vscTelemetryReporter = new VsCodeTelemetryReporter(aiKey);
+        experimentTelemetryReporter = new ExperimentationTelemetryReporter(vscTelemetryReporter);
+        context.subscriptions.push(experimentTelemetryReporter);
+        // Currently we have no experiments, but creating the service adds the appropriate
+        // shared properties to the ExperimentationTelemetryReporter we just created.
+        new ExperimentationService(experimentTelemetryReporter, id, version, context.globalState);
+    }
+    const logger = new Logger();
+    const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), {
+        pluginManager,
+        commandManager,
+        logDirectoryProvider,
+        cancellerFactory: nodeRequestCancellerFactory,
+        versionProvider,
+        processFactory: new ElectronServiceProcessFactory(),
+        activeJsTsEditorTracker,
+        serviceConfigurationProvider: new ElectronServiceConfigurationProvider(),
+        experimentTelemetryReporter,
+        logger,
+    }, item => {
+        onCompletionAccepted.fire(item);
+    });
+    registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker);
+    import('./task/taskProvider').then(module => {
+        context.subscriptions.push(module.register(lazyClientHost.map(x => x.serviceClient)));
+    });
+    import('./languageFeatures/tsconfig').then(module => {
+        context.subscriptions.push(module.register());
+    });
+    context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker));
+    return getExtensionApi(onCompletionAccepted.event, pluginManager);
 }
-
 export function deactivate() {
-	fs.rmSync(temp.instanceTempDir.value, { recursive: true, force: true });
+    fs.rmSync(temp.instanceTempDir.value, { recursive: true, force: true });
 }
diff --git a/extensions/typescript-language-features/Source/filesystems/ata.ts b/extensions/typescript-language-features/Source/filesystems/ata.ts
index b5e43244e1bfe..42d33d5f396fc 100644
--- a/extensions/typescript-language-features/Source/filesystems/ata.ts
+++ b/extensions/typescript-language-features/Source/filesystems/ata.ts
@@ -2,33 +2,29 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { conditionalRegistration, requireGlobalConfiguration } from '../languageFeatures/util/dependentRegistration';
 import { supportsReadableByteStreams } from '../utils/platform';
 import { AutoInstallerFs } from './autoInstallerFs';
 import { MemFs } from './memFs';
 import { Logger } from '../logging/logger';
-
 export function registerAtaSupport(logger: Logger): vscode.Disposable {
-	if (!supportsReadableByteStreams()) {
-		return vscode.Disposable.from();
-	}
-
-	return conditionalRegistration([
-		requireGlobalConfiguration('typescript', 'tsserver.web.typeAcquisition.enabled'),
-	], () => {
-		return vscode.Disposable.from(
-			// Ata
-			vscode.workspace.registerFileSystemProvider('vscode-global-typings', new MemFs('global-typings', logger), {
-				isCaseSensitive: true,
-				isReadonly: false,
-			}),
-
-			// Read accesses to node_modules
-			vscode.workspace.registerFileSystemProvider('vscode-node-modules', new AutoInstallerFs(logger), {
-				isCaseSensitive: true,
-				isReadonly: false
-			}));
-	});
+    if (!supportsReadableByteStreams()) {
+        return vscode.Disposable.from();
+    }
+    return conditionalRegistration([
+        requireGlobalConfiguration('typescript', 'tsserver.web.typeAcquisition.enabled'),
+    ], () => {
+        return vscode.Disposable.from(
+        // Ata
+        vscode.workspace.registerFileSystemProvider('vscode-global-typings', new MemFs('global-typings', logger), {
+            isCaseSensitive: true,
+            isReadonly: false,
+        }), 
+        // Read accesses to node_modules
+        vscode.workspace.registerFileSystemProvider('vscode-node-modules', new AutoInstallerFs(logger), {
+            isCaseSensitive: true,
+            isReadonly: false
+        }));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/filesystems/autoInstallerFs.ts b/extensions/typescript-language-features/Source/filesystems/autoInstallerFs.ts
index d639b7fe99903..edc7a1b928d82 100644
--- a/extensions/typescript-language-features/Source/filesystems/autoInstallerFs.ts
+++ b/extensions/typescript-language-features/Source/filesystems/autoInstallerFs.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { PackageManager } from '@vscode/ts-package-manager';
 import { basename, join } from 'path';
 import * as vscode from 'vscode';
@@ -10,237 +9,204 @@ import { URI } from 'vscode-uri';
 import { Disposable } from '../utils/dispose';
 import { MemFs } from './memFs';
 import { Logger } from '../logging/logger';
-
 const TEXT_DECODER = new TextDecoder('utf-8');
 const TEXT_ENCODER = new TextEncoder();
-
 export class AutoInstallerFs extends Disposable implements vscode.FileSystemProvider {
-
-	private readonly memfs: MemFs;
-	private readonly packageManager: PackageManager;
-	private readonly _projectCache = new Map | undefined>();
-
-	private readonly _emitter = this._register(new vscode.EventEmitter());
-	readonly onDidChangeFile = this._emitter.event;
-
-	constructor(
-		private readonly logger: Logger
-	) {
-		super();
-
-		const memfs = new MemFs('auto-installer', logger);
-		this.memfs = memfs;
-		memfs.onDidChangeFile((e) => {
-			this._emitter.fire(e.map(ev => ({
-				type: ev.type,
-				// TODO: we're gonna need a MappedUri dance...
-				uri: ev.uri.with({ scheme: 'memfs' })
-			})));
-		});
-
-		this.packageManager = new PackageManager({
-			readDirectory(path: string, _extensions?: readonly string[], _exclude?: readonly string[], _include?: readonly string[], _depth?: number): string[] {
-				return memfs.readDirectory(URI.file(path)).map(([name, _]) => name);
-			},
-
-			deleteFile(path: string): void {
-				memfs.delete(URI.file(path));
-			},
-
-			createDirectory(path: string): void {
-				memfs.createDirectory(URI.file(path));
-			},
-
-			writeFile(path: string, data: string, _writeByteOrderMark?: boolean): void {
-				memfs.writeFile(URI.file(path), TEXT_ENCODER.encode(data), { overwrite: true, create: true });
-			},
-
-			directoryExists(path: string): boolean {
-				try {
-					const stat = memfs.stat(URI.file(path));
-					return stat.type === vscode.FileType.Directory;
-				} catch (e) {
-					return false;
-				}
-			},
-
-			readFile(path: string, _encoding?: string): string | undefined {
-				try {
-					return TEXT_DECODER.decode(memfs.readFile(URI.file(path)));
-				} catch (e) {
-					return undefined;
-				}
-			}
-		});
-	}
-
-	watch(resource: vscode.Uri): vscode.Disposable {
-		this.logger.trace(`AutoInstallerFs.watch. Resource: ${resource.toString()}}`);
-		return this.memfs.watch(resource);
-	}
-
-	async stat(uri: vscode.Uri): Promise {
-		this.logger.trace(`AutoInstallerFs.stat: ${uri}`);
-
-		const mapped = new MappedUri(uri);
-
-		// TODO: case sensitivity configuration
-
-		// We pretend every single node_modules or @types directory ever actually
-		// exists.
-		if (basename(mapped.path) === 'node_modules' || basename(mapped.path) === '@types') {
-			return {
-				mtime: 0,
-				ctime: 0,
-				type: vscode.FileType.Directory,
-				size: 0
-			};
-		}
-
-		await this.ensurePackageContents(mapped);
-
-		return this.memfs.stat(URI.file(mapped.path));
-	}
-
-	async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
-		this.logger.trace(`AutoInstallerFs.readDirectory: ${uri}`);
-
-		const mapped = new MappedUri(uri);
-		await this.ensurePackageContents(mapped);
-
-		return this.memfs.readDirectory(URI.file(mapped.path));
-	}
-
-	async readFile(uri: vscode.Uri): Promise {
-		this.logger.trace(`AutoInstallerFs.readFile: ${uri}`);
-
-		const mapped = new MappedUri(uri);
-		await this.ensurePackageContents(mapped);
-
-		return this.memfs.readFile(URI.file(mapped.path));
-	}
-
-	writeFile(_uri: vscode.Uri, _content: Uint8Array, _options: { create: boolean; overwrite: boolean }): void {
-		throw new Error('not implemented');
-	}
-
-	rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: { overwrite: boolean }): void {
-		throw new Error('not implemented');
-	}
-
-	delete(_uri: vscode.Uri): void {
-		throw new Error('not implemented');
-	}
-
-	createDirectory(_uri: vscode.Uri): void {
-		throw new Error('not implemented');
-	}
-
-	private async ensurePackageContents(incomingUri: MappedUri): Promise {
-		// If we're not looking for something inside node_modules, bail early.
-		if (!incomingUri.path.includes('node_modules')) {
-			throw vscode.FileSystemError.FileNotFound();
-		}
-
-		// standard lib files aren't handled through here
-		if (incomingUri.path.includes('node_modules/@typescript') || incomingUri.path.includes('node_modules/@types/typescript__')) {
-			throw vscode.FileSystemError.FileNotFound();
-		}
-
-		const root = await this.getProjectRoot(incomingUri.original);
-		if (!root) {
-			return;
-		}
-
-		this.logger.trace(`AutoInstallerFs.ensurePackageContents. Path: ${incomingUri.path}, Root: ${root}`);
-
-		const existingInstall = this._projectCache.get(root);
-		if (existingInstall) {
-			this.logger.trace(`AutoInstallerFs.ensurePackageContents. Found ongoing install for: ${root}/node_modules`);
-			return existingInstall;
-		}
-
-		const installing = (async () => {
-			const proj = await this.packageManager.resolveProject(root, await this.getInstallOpts(incomingUri.original, root));
-			try {
-				await proj.restore();
-			} catch (e) {
-				console.error(`failed to restore package at ${incomingUri.path}: `, e);
-				throw e;
-			}
-		})();
-		this._projectCache.set(root, installing);
-		await installing;
-	}
-
-	private async getInstallOpts(originalUri: URI, root: string) {
-		const vsfs = vscode.workspace.fs;
-
-		// We definitely need a package.json to be there.
-		const pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') })));
-
-		let kdlLock;
-		try {
-			kdlLock = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package-lock.kdl') })));
-		} catch (e) { }
-
-		let npmLock;
-		try {
-			npmLock = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package-lock.json') })));
-		} catch (e) { }
-
-		return {
-			pkgJson,
-			kdlLock,
-			npmLock
-		};
-	}
-
-	private async getProjectRoot(incomingUri: URI): Promise {
-		const vsfs = vscode.workspace.fs;
-		const pkgPath = incomingUri.path.match(/^(.*?)\/node_modules/);
-		const ret = pkgPath?.[1];
-		if (!ret) {
-			return;
-		}
-		try {
-			await vsfs.stat(incomingUri.with({ path: join(ret, 'package.json') }));
-			return ret;
-		} catch (e) {
-			return;
-		}
-	}
+    private readonly memfs: MemFs;
+    private readonly packageManager: PackageManager;
+    private readonly _projectCache = new Map | undefined>();
+    private readonly _emitter = this._register(new vscode.EventEmitter());
+    readonly onDidChangeFile = this._emitter.event;
+    constructor(private readonly logger: Logger) {
+        super();
+        const memfs = new MemFs('auto-installer', logger);
+        this.memfs = memfs;
+        memfs.onDidChangeFile((e) => {
+            this._emitter.fire(e.map(ev => ({
+                type: ev.type,
+                // TODO: we're gonna need a MappedUri dance...
+                uri: ev.uri.with({ scheme: 'memfs' })
+            })));
+        });
+        this.packageManager = new PackageManager({
+            readDirectory(path: string, _extensions?: readonly string[], _exclude?: readonly string[], _include?: readonly string[], _depth?: number): string[] {
+                return memfs.readDirectory(URI.file(path)).map(([name, _]) => name);
+            },
+            deleteFile(path: string): void {
+                memfs.delete(URI.file(path));
+            },
+            createDirectory(path: string): void {
+                memfs.createDirectory(URI.file(path));
+            },
+            writeFile(path: string, data: string, _writeByteOrderMark?: boolean): void {
+                memfs.writeFile(URI.file(path), TEXT_ENCODER.encode(data), { overwrite: true, create: true });
+            },
+            directoryExists(path: string): boolean {
+                try {
+                    const stat = memfs.stat(URI.file(path));
+                    return stat.type === vscode.FileType.Directory;
+                }
+                catch (e) {
+                    return false;
+                }
+            },
+            readFile(path: string, _encoding?: string): string | undefined {
+                try {
+                    return TEXT_DECODER.decode(memfs.readFile(URI.file(path)));
+                }
+                catch (e) {
+                    return undefined;
+                }
+            }
+        });
+    }
+    watch(resource: vscode.Uri): vscode.Disposable {
+        this.logger.trace(`AutoInstallerFs.watch. Resource: ${resource.toString()}}`);
+        return this.memfs.watch(resource);
+    }
+    async stat(uri: vscode.Uri): Promise {
+        this.logger.trace(`AutoInstallerFs.stat: ${uri}`);
+        const mapped = new MappedUri(uri);
+        // TODO: case sensitivity configuration
+        // We pretend every single node_modules or @types directory ever actually
+        // exists.
+        if (basename(mapped.path) === 'node_modules' || basename(mapped.path) === '@types') {
+            return {
+                mtime: 0,
+                ctime: 0,
+                type: vscode.FileType.Directory,
+                size: 0
+            };
+        }
+        await this.ensurePackageContents(mapped);
+        return this.memfs.stat(URI.file(mapped.path));
+    }
+    async readDirectory(uri: vscode.Uri): Promise<[
+        string,
+        vscode.FileType
+    ][]> {
+        this.logger.trace(`AutoInstallerFs.readDirectory: ${uri}`);
+        const mapped = new MappedUri(uri);
+        await this.ensurePackageContents(mapped);
+        return this.memfs.readDirectory(URI.file(mapped.path));
+    }
+    async readFile(uri: vscode.Uri): Promise {
+        this.logger.trace(`AutoInstallerFs.readFile: ${uri}`);
+        const mapped = new MappedUri(uri);
+        await this.ensurePackageContents(mapped);
+        return this.memfs.readFile(URI.file(mapped.path));
+    }
+    writeFile(_uri: vscode.Uri, _content: Uint8Array, _options: {
+        create: boolean;
+        overwrite: boolean;
+    }): void {
+        throw new Error('not implemented');
+    }
+    rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: {
+        overwrite: boolean;
+    }): void {
+        throw new Error('not implemented');
+    }
+    delete(_uri: vscode.Uri): void {
+        throw new Error('not implemented');
+    }
+    createDirectory(_uri: vscode.Uri): void {
+        throw new Error('not implemented');
+    }
+    private async ensurePackageContents(incomingUri: MappedUri): Promise {
+        // If we're not looking for something inside node_modules, bail early.
+        if (!incomingUri.path.includes('node_modules')) {
+            throw vscode.FileSystemError.FileNotFound();
+        }
+        // standard lib files aren't handled through here
+        if (incomingUri.path.includes('node_modules/@typescript') || incomingUri.path.includes('node_modules/@types/typescript__')) {
+            throw vscode.FileSystemError.FileNotFound();
+        }
+        const root = await this.getProjectRoot(incomingUri.original);
+        if (!root) {
+            return;
+        }
+        this.logger.trace(`AutoInstallerFs.ensurePackageContents. Path: ${incomingUri.path}, Root: ${root}`);
+        const existingInstall = this._projectCache.get(root);
+        if (existingInstall) {
+            this.logger.trace(`AutoInstallerFs.ensurePackageContents. Found ongoing install for: ${root}/node_modules`);
+            return existingInstall;
+        }
+        const installing = (async () => {
+            const proj = await this.packageManager.resolveProject(root, await this.getInstallOpts(incomingUri.original, root));
+            try {
+                await proj.restore();
+            }
+            catch (e) {
+                console.error(`failed to restore package at ${incomingUri.path}: `, e);
+                throw e;
+            }
+        })();
+        this._projectCache.set(root, installing);
+        await installing;
+    }
+    private async getInstallOpts(originalUri: URI, root: string) {
+        const vsfs = vscode.workspace.fs;
+        // We definitely need a package.json to be there.
+        const pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') })));
+        let kdlLock;
+        try {
+            kdlLock = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package-lock.kdl') })));
+        }
+        catch (e) { }
+        let npmLock;
+        try {
+            npmLock = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package-lock.json') })));
+        }
+        catch (e) { }
+        return {
+            pkgJson,
+            kdlLock,
+            npmLock
+        };
+    }
+    private async getProjectRoot(incomingUri: URI): Promise {
+        const vsfs = vscode.workspace.fs;
+        const pkgPath = incomingUri.path.match(/^(.*?)\/node_modules/);
+        const ret = pkgPath?.[1];
+        if (!ret) {
+            return;
+        }
+        try {
+            await vsfs.stat(incomingUri.with({ path: join(ret, 'package.json') }));
+            return ret;
+        }
+        catch (e) {
+            return;
+        }
+    }
 }
-
 class MappedUri {
-	readonly raw: vscode.Uri;
-	readonly original: vscode.Uri;
-	readonly mapped: vscode.Uri;
-	constructor(uri: vscode.Uri) {
-		this.raw = uri;
-
-		const parts = uri.path.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/);
-		if (!parts) {
-			throw new Error(`Invalid uri: ${uri.toString()}, ${uri.path}`);
-		}
-
-		const scheme = parts[1];
-		const authority = parts[2] === 'ts-nul-authority' ? '' : parts[2];
-		const path = parts[3];
-		this.original = URI.from({ scheme, authority, path: (path ? '/' + path : path) });
-		this.mapped = this.original.with({ scheme: this.raw.scheme, authority: this.raw.authority });
-	}
-
-	get path() {
-		return this.mapped.path;
-	}
-	get scheme() {
-		return this.mapped.scheme;
-	}
-	get authority() {
-		return this.mapped.authority;
-	}
-	get flatPath() {
-		return join('/', this.scheme, this.authority, this.path);
-	}
+    readonly raw: vscode.Uri;
+    readonly original: vscode.Uri;
+    readonly mapped: vscode.Uri;
+    constructor(uri: vscode.Uri) {
+        this.raw = uri;
+        const parts = uri.path.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/);
+        if (!parts) {
+            throw new Error(`Invalid uri: ${uri.toString()}, ${uri.path}`);
+        }
+        const scheme = parts[1];
+        const authority = parts[2] === 'ts-nul-authority' ? '' : parts[2];
+        const path = parts[3];
+        this.original = URI.from({ scheme, authority, path: (path ? '/' + path : path) });
+        this.mapped = this.original.with({ scheme: this.raw.scheme, authority: this.raw.authority });
+    }
+    get path() {
+        return this.mapped.path;
+    }
+    get scheme() {
+        return this.mapped.scheme;
+    }
+    get authority() {
+        return this.mapped.authority;
+    }
+    get flatPath() {
+        return join('/', this.scheme, this.authority, this.path);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/filesystems/memFs.ts b/extensions/typescript-language-features/Source/filesystems/memFs.ts
index 05c4e7c3db747..072acd35cdc70 100644
--- a/extensions/typescript-language-features/Source/filesystems/memFs.ts
+++ b/extensions/typescript-language-features/Source/filesystems/memFs.ts
@@ -2,203 +2,162 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { basename, dirname } from 'path';
 import * as vscode from 'vscode';
 import { Logger } from '../logging/logger';
-
 export class MemFs implements vscode.FileSystemProvider {
-
-	private readonly root = new FsDirectoryEntry(
-		new Map(),
-		0,
-		0,
-	);
-
-	constructor(
-		private readonly id: string,
-		private readonly logger: Logger,
-	) { }
-
-	stat(uri: vscode.Uri): vscode.FileStat {
-		this.logger.trace(`MemFs.stat ${this.id}. uri: ${uri}`);
-		const entry = this.getEntry(uri);
-		if (!entry) {
-			throw vscode.FileSystemError.FileNotFound();
-		}
-
-		return entry;
-	}
-
-	readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
-		this.logger.trace(`MemFs.readDirectory ${this.id}. uri: ${uri}`);
-
-		const entry = this.getEntry(uri);
-		if (!entry) {
-			throw vscode.FileSystemError.FileNotFound();
-		}
-		if (!(entry instanceof FsDirectoryEntry)) {
-			throw vscode.FileSystemError.FileNotADirectory();
-		}
-
-		return Array.from(entry.contents.entries(), ([name, entry]) => [name, entry.type]);
-	}
-
-	readFile(uri: vscode.Uri): Uint8Array {
-		this.logger.trace(`MemFs.readFile ${this.id}. uri: ${uri}`);
-
-		const entry = this.getEntry(uri);
-		if (!entry) {
-			throw vscode.FileSystemError.FileNotFound();
-		}
-
-		if (!(entry instanceof FsFileEntry)) {
-			throw vscode.FileSystemError.FileIsADirectory(uri);
-		}
-
-		return entry.data;
-	}
-
-	writeFile(uri: vscode.Uri, content: Uint8Array, { create, overwrite }: { create: boolean; overwrite: boolean }): void {
-		this.logger.trace(`MemFs.writeFile ${this.id}. uri: ${uri}`);
-
-		const dir = this.getParent(uri);
-
-		const fileName = basename(uri.path);
-		const dirContents = dir.contents;
-
-		const time = Date.now() / 1000;
-		const entry = dirContents.get(basename(uri.path));
-		if (!entry) {
-			if (create) {
-				dirContents.set(fileName, new FsFileEntry(content, time, time));
-				this._emitter.fire([{ type: vscode.FileChangeType.Created, uri }]);
-			} else {
-				throw vscode.FileSystemError.FileNotFound();
-			}
-		} else {
-			if (entry instanceof FsDirectoryEntry) {
-				throw vscode.FileSystemError.FileIsADirectory(uri);
-			}
-
-			if (overwrite) {
-				entry.mtime = time;
-				entry.data = content;
-				this._emitter.fire([{ type: vscode.FileChangeType.Changed, uri }]);
-			} else {
-				throw vscode.FileSystemError.NoPermissions('overwrite option was not passed in');
-			}
-		}
-	}
-
-	rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: { overwrite: boolean }): void {
-		throw new Error('not implemented');
-	}
-
-	delete(uri: vscode.Uri): void {
-		try {
-			const dir = this.getParent(uri);
-			dir.contents.delete(basename(uri.path));
-			this._emitter.fire([{ type: vscode.FileChangeType.Deleted, uri }]);
-		} catch (e) { }
-	}
-
-	createDirectory(uri: vscode.Uri): void {
-		this.logger.trace(`MemFs.createDirectory ${this.id}. uri: ${uri}`);
-
-		const dir = this.getParent(uri);
-		const now = Date.now() / 1000;
-		dir.contents.set(basename(uri.path), new FsDirectoryEntry(new Map(), now, now));
-	}
-
-	private getEntry(uri: vscode.Uri): FsEntry | undefined {
-		// TODO: have this throw FileNotFound itself?
-		// TODO: support configuring case sensitivity
-		let node: FsEntry = this.root;
-		for (const component of uri.path.split('/')) {
-			if (!component) {
-				// Skip empty components (root, stuff between double slashes,
-				// trailing slashes)
-				continue;
-			}
-
-			if (!(node instanceof FsDirectoryEntry)) {
-				// We're looking at a File or such, so bail.
-				return;
-			}
-
-			const next = node.contents.get(component);
-			if (!next) {
-				// not found!
-				return;
-			}
-
-			node = next;
-		}
-		return node;
-	}
-
-	private getParent(uri: vscode.Uri): FsDirectoryEntry {
-		const dir = this.getEntry(uri.with({ path: dirname(uri.path) }));
-		if (!dir) {
-			throw vscode.FileSystemError.FileNotFound();
-		}
-		if (!(dir instanceof FsDirectoryEntry)) {
-			throw vscode.FileSystemError.FileNotADirectory();
-		}
-		return dir;
-	}
-
-	// --- manage file events
-
-	private readonly _emitter = new vscode.EventEmitter();
-
-	readonly onDidChangeFile: vscode.Event = this._emitter.event;
-	private readonly watchers = new Map>;
-
-	watch(resource: vscode.Uri): vscode.Disposable {
-		if (!this.watchers.has(resource.path)) {
-			this.watchers.set(resource.path, new Set());
-		}
-		const sy = Symbol(resource.path);
-		return new vscode.Disposable(() => {
-			const watcher = this.watchers.get(resource.path);
-			if (watcher) {
-				watcher.delete(sy);
-				if (!watcher.size) {
-					this.watchers.delete(resource.path);
-				}
-			}
-		});
-	}
+    private readonly root = new FsDirectoryEntry(new Map(), 0, 0);
+    constructor(private readonly id: string, private readonly logger: Logger) { }
+    stat(uri: vscode.Uri): vscode.FileStat {
+        this.logger.trace(`MemFs.stat ${this.id}. uri: ${uri}`);
+        const entry = this.getEntry(uri);
+        if (!entry) {
+            throw vscode.FileSystemError.FileNotFound();
+        }
+        return entry;
+    }
+    readDirectory(uri: vscode.Uri): [
+        string,
+        vscode.FileType
+    ][] {
+        this.logger.trace(`MemFs.readDirectory ${this.id}. uri: ${uri}`);
+        const entry = this.getEntry(uri);
+        if (!entry) {
+            throw vscode.FileSystemError.FileNotFound();
+        }
+        if (!(entry instanceof FsDirectoryEntry)) {
+            throw vscode.FileSystemError.FileNotADirectory();
+        }
+        return Array.from(entry.contents.entries(), ([name, entry]) => [name, entry.type]);
+    }
+    readFile(uri: vscode.Uri): Uint8Array {
+        this.logger.trace(`MemFs.readFile ${this.id}. uri: ${uri}`);
+        const entry = this.getEntry(uri);
+        if (!entry) {
+            throw vscode.FileSystemError.FileNotFound();
+        }
+        if (!(entry instanceof FsFileEntry)) {
+            throw vscode.FileSystemError.FileIsADirectory(uri);
+        }
+        return entry.data;
+    }
+    writeFile(uri: vscode.Uri, content: Uint8Array, { create, overwrite }: {
+        create: boolean;
+        overwrite: boolean;
+    }): void {
+        this.logger.trace(`MemFs.writeFile ${this.id}. uri: ${uri}`);
+        const dir = this.getParent(uri);
+        const fileName = basename(uri.path);
+        const dirContents = dir.contents;
+        const time = Date.now() / 1000;
+        const entry = dirContents.get(basename(uri.path));
+        if (!entry) {
+            if (create) {
+                dirContents.set(fileName, new FsFileEntry(content, time, time));
+                this._emitter.fire([{ type: vscode.FileChangeType.Created, uri }]);
+            }
+            else {
+                throw vscode.FileSystemError.FileNotFound();
+            }
+        }
+        else {
+            if (entry instanceof FsDirectoryEntry) {
+                throw vscode.FileSystemError.FileIsADirectory(uri);
+            }
+            if (overwrite) {
+                entry.mtime = time;
+                entry.data = content;
+                this._emitter.fire([{ type: vscode.FileChangeType.Changed, uri }]);
+            }
+            else {
+                throw vscode.FileSystemError.NoPermissions('overwrite option was not passed in');
+            }
+        }
+    }
+    rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: {
+        overwrite: boolean;
+    }): void {
+        throw new Error('not implemented');
+    }
+    delete(uri: vscode.Uri): void {
+        try {
+            const dir = this.getParent(uri);
+            dir.contents.delete(basename(uri.path));
+            this._emitter.fire([{ type: vscode.FileChangeType.Deleted, uri }]);
+        }
+        catch (e) { }
+    }
+    createDirectory(uri: vscode.Uri): void {
+        this.logger.trace(`MemFs.createDirectory ${this.id}. uri: ${uri}`);
+        const dir = this.getParent(uri);
+        const now = Date.now() / 1000;
+        dir.contents.set(basename(uri.path), new FsDirectoryEntry(new Map(), now, now));
+    }
+    private getEntry(uri: vscode.Uri): FsEntry | undefined {
+        // TODO: have this throw FileNotFound itself?
+        // TODO: support configuring case sensitivity
+        let node: FsEntry = this.root;
+        for (const component of uri.path.split('/')) {
+            if (!component) {
+                // Skip empty components (root, stuff between double slashes,
+                // trailing slashes)
+                continue;
+            }
+            if (!(node instanceof FsDirectoryEntry)) {
+                // We're looking at a File or such, so bail.
+                return;
+            }
+            const next = node.contents.get(component);
+            if (!next) {
+                // not found!
+                return;
+            }
+            node = next;
+        }
+        return node;
+    }
+    private getParent(uri: vscode.Uri): FsDirectoryEntry {
+        const dir = this.getEntry(uri.with({ path: dirname(uri.path) }));
+        if (!dir) {
+            throw vscode.FileSystemError.FileNotFound();
+        }
+        if (!(dir instanceof FsDirectoryEntry)) {
+            throw vscode.FileSystemError.FileNotADirectory();
+        }
+        return dir;
+    }
+    // --- manage file events
+    private readonly _emitter = new vscode.EventEmitter();
+    readonly onDidChangeFile: vscode.Event = this._emitter.event;
+    private readonly watchers = new Map>;
+    watch(resource: vscode.Uri): vscode.Disposable {
+        if (!this.watchers.has(resource.path)) {
+            this.watchers.set(resource.path, new Set());
+        }
+        const sy = Symbol(resource.path);
+        return new vscode.Disposable(() => {
+            const watcher = this.watchers.get(resource.path);
+            if (watcher) {
+                watcher.delete(sy);
+                if (!watcher.size) {
+                    this.watchers.delete(resource.path);
+                }
+            }
+        });
+    }
 }
-
 class FsFileEntry {
-	readonly type = vscode.FileType.File;
-
-	get size(): number {
-		return this.data.length;
-	}
-
-	constructor(
-		public data: Uint8Array,
-		public readonly ctime: number,
-		public mtime: number,
-	) { }
+    readonly type = vscode.FileType.File;
+    get size(): number {
+        return this.data.length;
+    }
+    constructor(public data: Uint8Array, public readonly ctime: number, public mtime: number) { }
 }
-
 class FsDirectoryEntry {
-	readonly type = vscode.FileType.Directory;
-
-	get size(): number {
-		return [...this.contents.values()].reduce((acc: number, entry: FsEntry) => acc + entry.size, 0);
-	}
-
-	constructor(
-		public readonly contents: Map,
-		public readonly ctime: number,
-		public readonly mtime: number,
-	) { }
+    readonly type = vscode.FileType.Directory;
+    get size(): number {
+        return [...this.contents.values()].reduce((acc: number, entry: FsEntry) => acc + entry.size, 0);
+    }
+    constructor(public readonly contents: Map, public readonly ctime: number, public readonly mtime: number) { }
 }
-
 type FsEntry = FsFileEntry | FsDirectoryEntry;
diff --git a/extensions/typescript-language-features/Source/languageFeatures/callHierarchy.ts b/extensions/typescript-language-features/Source/languageFeatures/callHierarchy.ts
index 00adb9407c834..dc8e0ded6ee1b 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/callHierarchy.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/callHierarchy.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
@@ -13,113 +12,73 @@ import * as PConst from '../tsServer/protocol/protocol.const';
 import * as typeConverters from '../typeConverters';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration';
-
 class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider {
-	public static readonly minVersion = API.v380;
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async prepareCallHierarchy(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
-		const response = await this.client.execute('prepareCallHierarchy', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		return Array.isArray(response.body)
-			? response.body.map(fromProtocolCallHierarchyItem)
-			: fromProtocolCallHierarchyItem(response.body);
-	}
-
-	public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise {
-		const filepath = this.client.toTsFilePath(item.uri);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start);
-		const response = await this.client.execute('provideCallHierarchyIncomingCalls', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		return response.body.map(fromProtocolCallHierarchyIncomingCall);
-	}
-
-	public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise {
-		const filepath = this.client.toTsFilePath(item.uri);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start);
-		const response = await this.client.execute('provideCallHierarchyOutgoingCalls', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		return response.body.map(fromProtocolCallHierarchyOutgoingCall);
-	}
+    public static readonly minVersion = API.v380;
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return undefined;
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
+        const response = await this.client.execute('prepareCallHierarchy', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        return Array.isArray(response.body)
+            ? response.body.map(fromProtocolCallHierarchyItem)
+            : fromProtocolCallHierarchyItem(response.body);
+    }
+    public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toTsFilePath(item.uri);
+        if (!filepath) {
+            return undefined;
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start);
+        const response = await this.client.execute('provideCallHierarchyIncomingCalls', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        return response.body.map(fromProtocolCallHierarchyIncomingCall);
+    }
+    public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toTsFilePath(item.uri);
+        if (!filepath) {
+            return undefined;
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start);
+        const response = await this.client.execute('provideCallHierarchyOutgoingCalls', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        return response.body.map(fromProtocolCallHierarchyOutgoingCall);
+    }
 }
-
 function isSourceFileItem(item: Proto.CallHierarchyItem) {
-	return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 1 && item.selectionSpan.start.offset === 1;
+    return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 1 && item.selectionSpan.start.offset === 1;
 }
-
 function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem {
-	const useFileName = isSourceFileItem(item);
-	const name = useFileName ? path.basename(item.file) : item.name;
-	const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : item.containerName ?? '';
-	const result = new vscode.CallHierarchyItem(
-		typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind),
-		name,
-		detail,
-		vscode.Uri.file(item.file),
-		typeConverters.Range.fromTextSpan(item.span),
-		typeConverters.Range.fromTextSpan(item.selectionSpan)
-	);
-
-	const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined;
-	if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
-		result.tags = [vscode.SymbolTag.Deprecated];
-	}
-	return result;
+    const useFileName = isSourceFileItem(item);
+    const name = useFileName ? path.basename(item.file) : item.name;
+    const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : item.containerName ?? '';
+    const result = new vscode.CallHierarchyItem(typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind), name, detail, vscode.Uri.file(item.file), typeConverters.Range.fromTextSpan(item.span), typeConverters.Range.fromTextSpan(item.selectionSpan));
+    const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined;
+    if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
+        result.tags = [vscode.SymbolTag.Deprecated];
+    }
+    return result;
 }
-
 function fromProtocolCallHierarchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall {
-	return new vscode.CallHierarchyIncomingCall(
-		fromProtocolCallHierarchyItem(item.from),
-		item.fromSpans.map(typeConverters.Range.fromTextSpan)
-	);
+    return new vscode.CallHierarchyIncomingCall(fromProtocolCallHierarchyItem(item.from), item.fromSpans.map(typeConverters.Range.fromTextSpan));
 }
-
 function fromProtocolCallHierarchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall {
-	return new vscode.CallHierarchyOutgoingCall(
-		fromProtocolCallHierarchyItem(item.to),
-		item.fromSpans.map(typeConverters.Range.fromTextSpan)
-	);
+    return new vscode.CallHierarchyOutgoingCall(fromProtocolCallHierarchyItem(item.to), item.fromSpans.map(typeConverters.Range.fromTextSpan));
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient
-) {
-	return conditionalRegistration([
-		requireMinVersion(client, TypeScriptCallHierarchySupport.minVersion),
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerCallHierarchyProvider(selector.semantic,
-			new TypeScriptCallHierarchySupport(client));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireMinVersion(client, TypeScriptCallHierarchySupport.minVersion),
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerCallHierarchyProvider(selector.semantic, new TypeScriptCallHierarchySupport(client));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/codeLens/baseCodeLensProvider.ts b/extensions/typescript-language-features/Source/languageFeatures/codeLens/baseCodeLensProvider.ts
index 4907fe4c282c2..8a6a619878868 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/codeLens/baseCodeLensProvider.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/codeLens/baseCodeLensProvider.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { CachedResponse } from '../../tsServer/cachedResponse';
 import type * as Proto from '../../tsServer/protocol/protocol';
@@ -10,99 +9,62 @@ import * as typeConverters from '../../typeConverters';
 import { ITypeScriptServiceClient } from '../../typescriptService';
 import { escapeRegExp } from '../../utils/regexp';
 import { Disposable } from '../../utils/dispose';
-
-
 export class ReferencesCodeLens extends vscode.CodeLens {
-	constructor(
-		public document: vscode.Uri,
-		public file: string,
-		range: vscode.Range
-	) {
-		super(range);
-	}
+    constructor(public document: vscode.Uri, public file: string, range: vscode.Range) {
+        super(range);
+    }
 }
-
 export abstract class TypeScriptBaseCodeLensProvider extends Disposable implements vscode.CodeLensProvider {
-	protected changeEmitter = this._register(new vscode.EventEmitter());
-	public onDidChangeCodeLenses = this.changeEmitter.event;
-
-	public static readonly cancelledCommand: vscode.Command = {
-		// Cancellation is not an error. Just show nothing until we can properly re-compute the code lens
-		title: '',
-		command: ''
-	};
-
-	public static readonly errorCommand: vscode.Command = {
-		title: vscode.l10n.t("Could not determine references"),
-		command: ''
-	};
-
-	public constructor(
-		protected client: ITypeScriptServiceClient,
-		private readonly cachedResponse: CachedResponse
-	) {
-		super();
-	}
-
-	async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return [];
-		}
-
-		const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', { file: filepath }, token));
-		if (response.type !== 'response') {
-			return [];
-		}
-
-		const referenceableSpans: vscode.Range[] = [];
-		response.body?.childItems?.forEach(item => this.walkNavTree(document, item, undefined, referenceableSpans));
-		return referenceableSpans.map(span => new ReferencesCodeLens(document.uri, filepath, span));
-	}
-
-	protected abstract extractSymbol(
-		document: vscode.TextDocument,
-		item: Proto.NavigationTree,
-		parent: Proto.NavigationTree | undefined
-	): vscode.Range | undefined;
-
-	private walkNavTree(
-		document: vscode.TextDocument,
-		item: Proto.NavigationTree,
-		parent: Proto.NavigationTree | undefined,
-		results: vscode.Range[]
-	): void {
-		const range = this.extractSymbol(document, item, parent);
-		if (range) {
-			results.push(range);
-		}
-
-		item.childItems?.forEach(child => this.walkNavTree(document, child, item, results));
-	}
+    protected changeEmitter = this._register(new vscode.EventEmitter());
+    public onDidChangeCodeLenses = this.changeEmitter.event;
+    public static readonly cancelledCommand: vscode.Command = {
+        // Cancellation is not an error. Just show nothing until we can properly re-compute the code lens
+        title: '',
+        command: ''
+    };
+    public static readonly errorCommand: vscode.Command = {
+        title: vscode.l10n.t("Could not determine references"),
+        command: ''
+    };
+    public constructor(protected client: ITypeScriptServiceClient, private readonly cachedResponse: CachedResponse) {
+        super();
+    }
+    async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return [];
+        }
+        const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', { file: filepath }, token));
+        if (response.type !== 'response') {
+            return [];
+        }
+        const referenceableSpans: vscode.Range[] = [];
+        response.body?.childItems?.forEach(item => this.walkNavTree(document, item, undefined, referenceableSpans));
+        return referenceableSpans.map(span => new ReferencesCodeLens(document.uri, filepath, span));
+    }
+    protected abstract extractSymbol(document: vscode.TextDocument, item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined): vscode.Range | undefined;
+    private walkNavTree(document: vscode.TextDocument, item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined, results: vscode.Range[]): void {
+        const range = this.extractSymbol(document, item, parent);
+        if (range) {
+            results.push(range);
+        }
+        item.childItems?.forEach(child => this.walkNavTree(document, child, item, results));
+    }
 }
-
-export function getSymbolRange(
-	document: vscode.TextDocument,
-	item: Proto.NavigationTree
-): vscode.Range | undefined {
-	if (item.nameSpan) {
-		return typeConverters.Range.fromTextSpan(item.nameSpan);
-	}
-
-	// In older versions, we have to calculate this manually. See #23924
-	const span = item.spans?.[0];
-	if (!span) {
-		return undefined;
-	}
-
-	const range = typeConverters.Range.fromTextSpan(span);
-	const text = document.getText(range);
-
-	const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm');
-	const match = identifierMatch.exec(text);
-	const prefixLength = match ? match.index + match[1].length : 0;
-	const startOffset = document.offsetAt(new vscode.Position(range.start.line, range.start.character)) + prefixLength;
-	return new vscode.Range(
-		document.positionAt(startOffset),
-		document.positionAt(startOffset + item.text.length));
+export function getSymbolRange(document: vscode.TextDocument, item: Proto.NavigationTree): vscode.Range | undefined {
+    if (item.nameSpan) {
+        return typeConverters.Range.fromTextSpan(item.nameSpan);
+    }
+    // In older versions, we have to calculate this manually. See #23924
+    const span = item.spans?.[0];
+    if (!span) {
+        return undefined;
+    }
+    const range = typeConverters.Range.fromTextSpan(span);
+    const text = document.getText(range);
+    const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm');
+    const match = identifierMatch.exec(text);
+    const prefixLength = match ? match.index + match[1].length : 0;
+    const startOffset = document.offsetAt(new vscode.Position(range.start.line, range.start.character)) + prefixLength;
+    return new vscode.Range(document.positionAt(startOffset), document.positionAt(startOffset + item.text.length));
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/Source/languageFeatures/codeLens/implementationsCodeLens.ts
index 6088292f8d0a3..9868071eba0cd 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/codeLens/implementationsCodeLens.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/codeLens/implementationsCodeLens.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../../configuration/documentSelector';
 import { LanguageDescription } from '../../configuration/languageDescription';
@@ -14,111 +13,78 @@ import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptServ
 import { conditionalRegistration, requireGlobalConfiguration, requireSomeCapability } from '../util/dependentRegistration';
 import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider';
 import { ExecutionTarget } from '../../tsServer/server';
-
-
 export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider {
-	public constructor(
-		client: ITypeScriptServiceClient,
-		protected _cachedResponse: CachedResponse,
-		private readonly language: LanguageDescription
-	) {
-		super(client, _cachedResponse);
-		this._register(
-			vscode.workspace.onDidChangeConfiguration(evt => {
-				if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`)) {
-					this.changeEmitter.fire();
-				}
-			})
-		);
-	}
-
-	public async resolveCodeLens(
-		codeLens: ReferencesCodeLens,
-		token: vscode.CancellationToken,
-	): Promise {
-		const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
-		const response = await this.client.execute('implementation', args, token, {
-			lowPriority: true,
-			executionTarget: ExecutionTarget.Semantic,
-			cancelOnResourceChange: codeLens.document,
-		});
-		if (response.type !== 'response' || !response.body) {
-			codeLens.command = response.type === 'cancelled'
-				? TypeScriptBaseCodeLensProvider.cancelledCommand
-				: TypeScriptBaseCodeLensProvider.errorCommand;
-			return codeLens;
-		}
-
-		const locations = response.body
-			.map(reference =>
-				// Only take first line on implementation: https://github.com/microsoft/vscode/issues/23924
-				new vscode.Location(this.client.toResource(reference.file),
-					reference.start.line === reference.end.line
-						? typeConverters.Range.fromTextSpan(reference)
-						: new vscode.Range(
-							typeConverters.Position.fromLocation(reference.start),
-							new vscode.Position(reference.start.line, 0))))
-			// Exclude original from implementations
-			.filter(location =>
-				!(location.uri.toString() === codeLens.document.toString() &&
-					location.range.start.line === codeLens.range.start.line &&
-					location.range.start.character === codeLens.range.start.character));
-
-		codeLens.command = this.getCommand(locations, codeLens);
-		return codeLens;
-	}
-
-	private getCommand(locations: vscode.Location[], codeLens: ReferencesCodeLens): vscode.Command | undefined {
-		return {
-			title: this.getTitle(locations),
-			command: locations.length ? 'editor.action.showReferences' : '',
-			arguments: [codeLens.document, codeLens.range.start, locations]
-		};
-	}
-
-	private getTitle(locations: vscode.Location[]): string {
-		return locations.length === 1
-			? vscode.l10n.t("1 implementation")
-			: vscode.l10n.t("{0} implementations", locations.length);
-	}
-
-	protected extractSymbol(
-		document: vscode.TextDocument,
-		item: Proto.NavigationTree,
-		parent: Proto.NavigationTree | undefined
-	): vscode.Range | undefined {
-		if (item.kind === PConst.Kind.method && parent && parent.kind === PConst.Kind.interface && vscode.workspace.getConfiguration(this.language.id).get('implementationsCodeLens.showOnInterfaceMethods')) {
-			return getSymbolRange(document, item);
-		}
-		switch (item.kind) {
-			case PConst.Kind.interface:
-				return getSymbolRange(document, item);
-
-			case PConst.Kind.class:
-			case PConst.Kind.method:
-			case PConst.Kind.memberVariable:
-			case PConst.Kind.memberGetAccessor:
-			case PConst.Kind.memberSetAccessor:
-				if (item.kindModifiers.match(/\babstract\b/g)) {
-					return getSymbolRange(document, item);
-				}
-				break;
-		}
-		return undefined;
-	}
+    public constructor(client: ITypeScriptServiceClient, protected _cachedResponse: CachedResponse, private readonly language: LanguageDescription) {
+        super(client, _cachedResponse);
+        this._register(vscode.workspace.onDidChangeConfiguration(evt => {
+            if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`)) {
+                this.changeEmitter.fire();
+            }
+        }));
+    }
+    public async resolveCodeLens(codeLens: ReferencesCodeLens, token: vscode.CancellationToken): Promise {
+        const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
+        const response = await this.client.execute('implementation', args, token, {
+            lowPriority: true,
+            executionTarget: ExecutionTarget.Semantic,
+            cancelOnResourceChange: codeLens.document,
+        });
+        if (response.type !== 'response' || !response.body) {
+            codeLens.command = response.type === 'cancelled'
+                ? TypeScriptBaseCodeLensProvider.cancelledCommand
+                : TypeScriptBaseCodeLensProvider.errorCommand;
+            return codeLens;
+        }
+        const locations = response.body
+            .map(reference => 
+        // Only take first line on implementation: https://github.com/microsoft/vscode/issues/23924
+        new vscode.Location(this.client.toResource(reference.file), reference.start.line === reference.end.line
+            ? typeConverters.Range.fromTextSpan(reference)
+            : new vscode.Range(typeConverters.Position.fromLocation(reference.start), new vscode.Position(reference.start.line, 0))))
+            // Exclude original from implementations
+            .filter(location => !(location.uri.toString() === codeLens.document.toString() &&
+            location.range.start.line === codeLens.range.start.line &&
+            location.range.start.character === codeLens.range.start.character));
+        codeLens.command = this.getCommand(locations, codeLens);
+        return codeLens;
+    }
+    private getCommand(locations: vscode.Location[], codeLens: ReferencesCodeLens): vscode.Command | undefined {
+        return {
+            title: this.getTitle(locations),
+            command: locations.length ? 'editor.action.showReferences' : '',
+            arguments: [codeLens.document, codeLens.range.start, locations]
+        };
+    }
+    private getTitle(locations: vscode.Location[]): string {
+        return locations.length === 1
+            ? vscode.l10n.t("1 implementation")
+            : vscode.l10n.t("{0} implementations", locations.length);
+    }
+    protected extractSymbol(document: vscode.TextDocument, item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined): vscode.Range | undefined {
+        if (item.kind === PConst.Kind.method && parent && parent.kind === PConst.Kind.interface && vscode.workspace.getConfiguration(this.language.id).get('implementationsCodeLens.showOnInterfaceMethods')) {
+            return getSymbolRange(document, item);
+        }
+        switch (item.kind) {
+            case PConst.Kind.interface:
+                return getSymbolRange(document, item);
+            case PConst.Kind.class:
+            case PConst.Kind.method:
+            case PConst.Kind.memberVariable:
+            case PConst.Kind.memberGetAccessor:
+            case PConst.Kind.memberSetAccessor:
+                if (item.kindModifiers.match(/\babstract\b/g)) {
+                    return getSymbolRange(document, item);
+                }
+                break;
+        }
+        return undefined;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	cachedResponse: CachedResponse,
-) {
-	return conditionalRegistration([
-		requireGlobalConfiguration(language.id, 'implementationsCodeLens.enabled'),
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerCodeLensProvider(selector.semantic,
-			new TypeScriptImplementationsCodeLensProvider(client, cachedResponse, language));
-	});
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, cachedResponse: CachedResponse) {
+    return conditionalRegistration([
+        requireGlobalConfiguration(language.id, 'implementationsCodeLens.enabled'),
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerCodeLensProvider(selector.semantic, new TypeScriptImplementationsCodeLensProvider(client, cachedResponse, language));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/codeLens/referencesCodeLens.ts b/extensions/typescript-language-features/Source/languageFeatures/codeLens/referencesCodeLens.ts
index de7d1f6d900c6..bb54079905fd5 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/codeLens/referencesCodeLens.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/codeLens/referencesCodeLens.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../../configuration/documentSelector';
 import { LanguageDescription } from '../../configuration/languageDescription';
@@ -14,133 +13,100 @@ import * as typeConverters from '../../typeConverters';
 import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService';
 import { conditionalRegistration, requireGlobalConfiguration, requireSomeCapability } from '../util/dependentRegistration';
 import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider';
-
-
 export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
-	public constructor(
-		client: ITypeScriptServiceClient,
-		protected _cachedResponse: CachedResponse,
-		private readonly language: LanguageDescription
-	) {
-		super(client, _cachedResponse);
-		this._register(
-			vscode.workspace.onDidChangeConfiguration(evt => {
-				if (evt.affectsConfiguration(`${language.id}.referencesCodeLens.showOnAllFunctions`)) {
-					this.changeEmitter.fire();
-				}
-			})
-		);
-	}
-
-	public async resolveCodeLens(codeLens: ReferencesCodeLens, token: vscode.CancellationToken): Promise {
-		const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
-		const response = await this.client.execute('references', args, token, {
-			lowPriority: true,
-			executionTarget: ExecutionTarget.Semantic,
-			cancelOnResourceChange: codeLens.document,
-		});
-		if (response.type !== 'response' || !response.body) {
-			codeLens.command = response.type === 'cancelled'
-				? TypeScriptBaseCodeLensProvider.cancelledCommand
-				: TypeScriptBaseCodeLensProvider.errorCommand;
-			return codeLens;
-		}
-
-		const locations = response.body.refs
-			.filter(reference => !reference.isDefinition)
-			.map(reference =>
-				typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference));
-
-		codeLens.command = {
-			title: this.getCodeLensLabel(locations),
-			command: locations.length ? 'editor.action.showReferences' : '',
-			arguments: [codeLens.document, codeLens.range.start, locations]
-		};
-		return codeLens;
-	}
-
-	private getCodeLensLabel(locations: ReadonlyArray): string {
-		return locations.length === 1
-			? vscode.l10n.t("1 reference")
-			: vscode.l10n.t("{0} references", locations.length);
-	}
-
-	protected extractSymbol(
-		document: vscode.TextDocument,
-		item: Proto.NavigationTree,
-		parent: Proto.NavigationTree | undefined
-	): vscode.Range | undefined {
-		if (parent && parent.kind === PConst.Kind.enum) {
-			return getSymbolRange(document, item);
-		}
-
-		switch (item.kind) {
-			case PConst.Kind.function: {
-				const showOnAllFunctions = vscode.workspace.getConfiguration(this.language.id).get('referencesCodeLens.showOnAllFunctions');
-				if (showOnAllFunctions && item.nameSpan) {
-					return getSymbolRange(document, item);
-				}
-			}
-			// fallthrough
-
-			case PConst.Kind.const:
-			case PConst.Kind.let:
-			case PConst.Kind.variable:
-				// Only show references for exported variables
-				if (/\bexport\b/.test(item.kindModifiers)) {
-					return getSymbolRange(document, item);
-				}
-				break;
-
-			case PConst.Kind.class:
-				if (item.text === '') {
-					break;
-				}
-				return getSymbolRange(document, item);
-
-			case PConst.Kind.interface:
-			case PConst.Kind.type:
-			case PConst.Kind.enum:
-				return getSymbolRange(document, item);
-
-			case PConst.Kind.method:
-			case PConst.Kind.memberGetAccessor:
-			case PConst.Kind.memberSetAccessor:
-			case PConst.Kind.constructorImplementation:
-			case PConst.Kind.memberVariable:
-				// Don't show if child and parent have same start
-				// For https://github.com/microsoft/vscode/issues/90396
-				if (parent &&
-					typeConverters.Position.fromLocation(parent.spans[0].start).isEqual(typeConverters.Position.fromLocation(item.spans[0].start))
-				) {
-					return undefined;
-				}
-
-				// Only show if parent is a class type object (not a literal)
-				switch (parent?.kind) {
-					case PConst.Kind.class:
-					case PConst.Kind.interface:
-					case PConst.Kind.type:
-						return getSymbolRange(document, item);
-				}
-				break;
-		}
-
-		return undefined;
-	}
+    public constructor(client: ITypeScriptServiceClient, protected _cachedResponse: CachedResponse, private readonly language: LanguageDescription) {
+        super(client, _cachedResponse);
+        this._register(vscode.workspace.onDidChangeConfiguration(evt => {
+            if (evt.affectsConfiguration(`${language.id}.referencesCodeLens.showOnAllFunctions`)) {
+                this.changeEmitter.fire();
+            }
+        }));
+    }
+    public async resolveCodeLens(codeLens: ReferencesCodeLens, token: vscode.CancellationToken): Promise {
+        const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start);
+        const response = await this.client.execute('references', args, token, {
+            lowPriority: true,
+            executionTarget: ExecutionTarget.Semantic,
+            cancelOnResourceChange: codeLens.document,
+        });
+        if (response.type !== 'response' || !response.body) {
+            codeLens.command = response.type === 'cancelled'
+                ? TypeScriptBaseCodeLensProvider.cancelledCommand
+                : TypeScriptBaseCodeLensProvider.errorCommand;
+            return codeLens;
+        }
+        const locations = response.body.refs
+            .filter(reference => !reference.isDefinition)
+            .map(reference => typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference));
+        codeLens.command = {
+            title: this.getCodeLensLabel(locations),
+            command: locations.length ? 'editor.action.showReferences' : '',
+            arguments: [codeLens.document, codeLens.range.start, locations]
+        };
+        return codeLens;
+    }
+    private getCodeLensLabel(locations: ReadonlyArray): string {
+        return locations.length === 1
+            ? vscode.l10n.t("1 reference")
+            : vscode.l10n.t("{0} references", locations.length);
+    }
+    protected extractSymbol(document: vscode.TextDocument, item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined): vscode.Range | undefined {
+        if (parent && parent.kind === PConst.Kind.enum) {
+            return getSymbolRange(document, item);
+        }
+        switch (item.kind) {
+            case PConst.Kind.function: {
+                const showOnAllFunctions = vscode.workspace.getConfiguration(this.language.id).get('referencesCodeLens.showOnAllFunctions');
+                if (showOnAllFunctions && item.nameSpan) {
+                    return getSymbolRange(document, item);
+                }
+            }
+            // fallthrough
+            case PConst.Kind.const:
+            case PConst.Kind.let:
+            case PConst.Kind.variable:
+                // Only show references for exported variables
+                if (/\bexport\b/.test(item.kindModifiers)) {
+                    return getSymbolRange(document, item);
+                }
+                break;
+            case PConst.Kind.class:
+                if (item.text === '') {
+                    break;
+                }
+                return getSymbolRange(document, item);
+            case PConst.Kind.interface:
+            case PConst.Kind.type:
+            case PConst.Kind.enum:
+                return getSymbolRange(document, item);
+            case PConst.Kind.method:
+            case PConst.Kind.memberGetAccessor:
+            case PConst.Kind.memberSetAccessor:
+            case PConst.Kind.constructorImplementation:
+            case PConst.Kind.memberVariable:
+                // Don't show if child and parent have same start
+                // For https://github.com/microsoft/vscode/issues/90396
+                if (parent &&
+                    typeConverters.Position.fromLocation(parent.spans[0].start).isEqual(typeConverters.Position.fromLocation(item.spans[0].start))) {
+                    return undefined;
+                }
+                // Only show if parent is a class type object (not a literal)
+                switch (parent?.kind) {
+                    case PConst.Kind.class:
+                    case PConst.Kind.interface:
+                    case PConst.Kind.type:
+                        return getSymbolRange(document, item);
+                }
+                break;
+        }
+        return undefined;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	cachedResponse: CachedResponse,
-) {
-	return conditionalRegistration([
-		requireGlobalConfiguration(language.id, 'referencesCodeLens.enabled'),
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerCodeLensProvider(selector.semantic,
-			new TypeScriptReferencesCodeLensProvider(client, cachedResponse, language));
-	});
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, cachedResponse: CachedResponse) {
+    return conditionalRegistration([
+        requireGlobalConfiguration(language.id, 'referencesCodeLens.enabled'),
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerCodeLensProvider(selector.semantic, new TypeScriptReferencesCodeLensProvider(client, cachedResponse, language));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/completions.ts b/extensions/typescript-language-features/Source/languageFeatures/completions.ts
index 749e74b404885..b0d760b895dfb 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/completions.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/completions.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command, CommandManager } from '../commands/commandManager';
 import { DocumentSelector } from '../configuration/documentSelector';
@@ -21,926 +20,720 @@ import { applyCodeAction } from './util/codeAction';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
 import { snippetForFunctionCall } from './util/snippetForFunctionCall';
 import * as Previewer from './util/textRendering';
-
-
 interface DotAccessorContext {
-	readonly range: vscode.Range;
-	readonly text: string;
+    readonly range: vscode.Range;
+    readonly text: string;
 }
-
 interface CompletionContext {
-	readonly isNewIdentifierLocation: boolean;
-	readonly isMemberCompletion: boolean;
-
-	readonly dotAccessorContext?: DotAccessorContext;
-
-	readonly enableCallCompletions: boolean;
-	readonly completeFunctionCalls: boolean;
-
-	readonly wordRange: vscode.Range | undefined;
-	readonly line: string;
-	readonly optionalReplacementRange: vscode.Range | undefined;
+    readonly isNewIdentifierLocation: boolean;
+    readonly isMemberCompletion: boolean;
+    readonly dotAccessorContext?: DotAccessorContext;
+    readonly enableCallCompletions: boolean;
+    readonly completeFunctionCalls: boolean;
+    readonly wordRange: vscode.Range | undefined;
+    readonly line: string;
+    readonly optionalReplacementRange: vscode.Range | undefined;
 }
-
 type ResolvedCompletionItem = {
-	readonly edits?: readonly vscode.TextEdit[];
-	readonly commands: readonly vscode.Command[];
+    readonly edits?: readonly vscode.TextEdit[];
+    readonly commands: readonly vscode.Command[];
 };
-
 class MyCompletionItem extends vscode.CompletionItem {
-
-	public readonly useCodeSnippet: boolean;
-
-	constructor(
-		public readonly position: vscode.Position,
-		public readonly document: vscode.TextDocument,
-		public readonly tsEntry: Proto.CompletionEntry,
-		private readonly completionContext: CompletionContext,
-		public readonly metadata: any | undefined,
-		client: ITypeScriptServiceClient,
-		defaultCommitCharacters: readonly string[] | undefined,
-	) {
-		const label = tsEntry.name || (tsEntry.insertText ?? '');
-		super(label, MyCompletionItem.convertKind(tsEntry.kind));
-
-		if (tsEntry.source && tsEntry.hasAction && client.apiVersion.lt(API.v490)) {
-			// De-prioritze auto-imports
-			// https://github.com/microsoft/vscode/issues/40311
-			this.sortText = '\uffff' + tsEntry.sortText;
-		} else {
-			this.sortText = tsEntry.sortText;
-		}
-
-		if (tsEntry.source && tsEntry.hasAction) {
-			// Render "fancy" when source is a workspace path
-			const qualifierCandidate = vscode.workspace.asRelativePath(tsEntry.source);
-			if (qualifierCandidate !== tsEntry.source) {
-				this.label = { label, description: qualifierCandidate };
-			}
-
-		}
-
-		const { sourceDisplay, isSnippet } = tsEntry;
-		if (sourceDisplay) {
-			this.label = { label, description: Previewer.asPlainTextWithLinks(sourceDisplay, client) };
-		}
-
-		if (tsEntry.labelDetails) {
-			this.label = { label, ...tsEntry.labelDetails };
-		}
-
-		this.preselect = tsEntry.isRecommended;
-		this.position = position;
-		this.useCodeSnippet = completionContext.completeFunctionCalls && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method);
-
-		this.range = this.getRangeFromReplacementSpan(tsEntry, completionContext);
-		this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry, defaultCommitCharacters);
-		this.insertText = isSnippet && tsEntry.insertText ? new vscode.SnippetString(tsEntry.insertText) : tsEntry.insertText;
-		this.filterText = tsEntry.filterText || this.getFilterText(completionContext.line, tsEntry.insertText);
-
-		if (completionContext.isMemberCompletion && completionContext.dotAccessorContext && !(this.insertText instanceof vscode.SnippetString)) {
-			this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.textLabel);
-			if (!this.range) {
-				const replacementRange = this.completionContext.wordRange;
-				if (replacementRange) {
-					this.range = {
-						inserting: completionContext.dotAccessorContext.range,
-						replacing: completionContext.dotAccessorContext.range.union(replacementRange),
-					};
-				} else {
-					this.range = completionContext.dotAccessorContext.range;
-				}
-				this.insertText = this.filterText;
-			}
-		}
-
-		if (tsEntry.kindModifiers) {
-			const kindModifiers = parseKindModifier(tsEntry.kindModifiers);
-			if (kindModifiers.has(PConst.KindModifiers.optional)) {
-				this.insertText ??= this.textLabel;
-				this.filterText ??= this.textLabel;
-
-				if (typeof this.label === 'string') {
-					this.label += '?';
-				} else {
-					this.label.label += '?';
-				}
-			}
-			if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
-				this.tags = [vscode.CompletionItemTag.Deprecated];
-			}
-
-			if (kindModifiers.has(PConst.KindModifiers.color)) {
-				this.kind = vscode.CompletionItemKind.Color;
-			}
-
-			this.detail = getScriptKindDetails(tsEntry);
-		}
-
-		this.resolveRange();
-	}
-
-	private get textLabel() {
-		return typeof this.label === 'string' ? this.label : this.label.label;
-	}
-
-	private _resolvedPromise?: {
-		readonly requestToken: vscode.CancellationTokenSource;
-		readonly promise: Promise;
-		waiting: number;
-	};
-
-	public async resolveCompletionItem(
-		client: ITypeScriptServiceClient,
-		token: vscode.CancellationToken,
-	): Promise {
-		token.onCancellationRequested(() => {
-			if (this._resolvedPromise && --this._resolvedPromise.waiting <= 0) {
-				// Give a little extra time for another caller to come in
-				setTimeout(() => {
-					if (this._resolvedPromise && this._resolvedPromise.waiting <= 0) {
-						this._resolvedPromise.requestToken.cancel();
-					}
-				}, 300);
-			}
-		});
-
-		if (this._resolvedPromise) {
-			++this._resolvedPromise.waiting;
-			return this._resolvedPromise.promise;
-		}
-
-		const requestToken = new vscode.CancellationTokenSource();
-
-		const promise = (async (): Promise => {
-			const filepath = client.toOpenTsFilePath(this.document);
-			if (!filepath) {
-				return undefined;
-			}
-
-			const args: Proto.CompletionDetailsRequestArgs = {
-				...typeConverters.Position.toFileLocationRequestArgs(filepath, this.position),
-				entryNames: [
-					this.tsEntry.source || this.tsEntry.data ? {
-						name: this.tsEntry.name,
-						source: this.tsEntry.source,
-						data: this.tsEntry.data,
-					} : this.tsEntry.name
-				]
-			};
-			const response = await client.interruptGetErr(() => client.execute('completionEntryDetails', args, requestToken.token));
-			if (response.type !== 'response' || !response.body?.length) {
-				return undefined;
-			}
-
-			const detail = response.body[0];
-
-			const newItemDetails = this.getDetails(client, detail);
-			if (newItemDetails) {
-				this.detail = newItemDetails;
-			}
-
-			this.documentation = this.getDocumentation(client, detail, this.document.uri);
-
-			const codeAction = this.getCodeActions(detail, filepath);
-			const commands: vscode.Command[] = [{
-				command: CompletionAcceptedCommand.ID,
-				title: '',
-				arguments: [this]
-			}];
-			if (codeAction.command) {
-				commands.push(codeAction.command);
-			}
-			const additionalTextEdits = codeAction.additionalTextEdits;
-
-			if (this.useCodeSnippet) {
-				const shouldCompleteFunction = await this.isValidFunctionCompletionContext(client, filepath, this.position, this.document, token);
-				if (shouldCompleteFunction) {
-					const { snippet, parameterCount } = snippetForFunctionCall({ ...this, label: this.textLabel }, detail.displayParts);
-					this.insertText = snippet;
-					if (parameterCount > 0) {
-						//Fix for https://github.com/microsoft/vscode/issues/104059
-						//Don't show parameter hints if "editor.parameterHints.enabled": false
-						if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled')) {
-							commands.push({ title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' });
-						}
-					}
-				}
-			}
-
-			return { commands, edits: additionalTextEdits };
-		})();
-
-		this._resolvedPromise = {
-			promise,
-			requestToken,
-			waiting: 1,
-		};
-
-		return this._resolvedPromise.promise;
-	}
-
-	private getDetails(
-		client: ITypeScriptServiceClient,
-		detail: Proto.CompletionEntryDetails,
-	): string | undefined {
-		const parts: string[] = [];
-
-		if (detail.kind === PConst.Kind.script) {
-			// details were already added
-			return undefined;
-		}
-
-		for (const action of detail.codeActions ?? []) {
-			parts.push(action.description);
-		}
-
-		parts.push(Previewer.asPlainTextWithLinks(detail.displayParts, client));
-		return parts.join('\n\n');
-	}
-
-	private getDocumentation(
-		client: ITypeScriptServiceClient,
-		detail: Proto.CompletionEntryDetails,
-		baseUri: vscode.Uri,
-	): vscode.MarkdownString | undefined {
-		const documentation = new vscode.MarkdownString();
-		Previewer.appendDocumentationAsMarkdown(documentation, detail.documentation, detail.tags, client);
-		documentation.baseUri = baseUri;
-		return documentation.value.length ? documentation : undefined;
-	}
-
-	private async isValidFunctionCompletionContext(
-		client: ITypeScriptServiceClient,
-		filepath: string,
-		position: vscode.Position,
-		document: vscode.TextDocument,
-		token: vscode.CancellationToken
-	): Promise {
-		// Workaround for https://github.com/microsoft/TypeScript/issues/12677
-		// Don't complete function calls inside of destructive assignments or imports
-		try {
-			const args: Proto.FileLocationRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
-			const response = await client.execute('quickinfo', args, token);
-			if (response.type === 'response' && response.body) {
-				switch (response.body.kind) {
-					case 'var':
-					case 'let':
-					case 'const':
-					case 'alias':
-						return false;
-				}
-			}
-		} catch {
-			// Noop
-		}
-
-		const line = document.lineAt(position.line);
-		// Don't complete function call if there is already something that looks like a function call
-		// https://github.com/microsoft/vscode/issues/18131
-
-		const after = line.text.slice(position.character);
-		if (after.match(/^[a-z_$0-9]*\s*\(/gi)) {
-			return false;
-		}
-
-		// Don't complete function call if it looks like a jsx tag.
-		const before = line.text.slice(0, position.character);
-		if (before.match(/<\s*[\w]*$/gi)) {
-			return false;
-		}
-
-		return true;
-	}
-
-	private getCodeActions(
-		detail: Proto.CompletionEntryDetails,
-		filepath: string
-	): { command?: vscode.Command; additionalTextEdits?: vscode.TextEdit[] } {
-		if (!detail.codeActions?.length) {
-			return {};
-		}
-
-		// Try to extract out the additionalTextEdits for the current file.
-		// Also check if we still have to apply other workspace edits and commands
-		// using a vscode command
-		const additionalTextEdits: vscode.TextEdit[] = [];
-		let hasRemainingCommandsOrEdits = false;
-		for (const tsAction of detail.codeActions) {
-			if (tsAction.commands) {
-				hasRemainingCommandsOrEdits = true;
-			}
-
-			// Apply all edits in the current file using `additionalTextEdits`
-			if (tsAction.changes) {
-				for (const change of tsAction.changes) {
-					if (change.fileName === filepath) {
-						additionalTextEdits.push(...change.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
-					} else {
-						hasRemainingCommandsOrEdits = true;
-					}
-				}
-			}
-		}
-
-		let command: vscode.Command | undefined = undefined;
-		if (hasRemainingCommandsOrEdits) {
-			// Create command that applies all edits not in the current file.
-			command = {
-				title: '',
-				command: ApplyCompletionCodeActionCommand.ID,
-				arguments: [filepath, detail.codeActions.map((x): Proto.CodeAction => ({
-					commands: x.commands,
-					description: x.description,
-					changes: x.changes.filter(x => x.fileName !== filepath)
-				}))]
-			};
-		}
-
-		return {
-			command,
-			additionalTextEdits: additionalTextEdits.length ? additionalTextEdits : undefined
-		};
-	}
-
-	private getRangeFromReplacementSpan(tsEntry: Proto.CompletionEntry, completionContext: CompletionContext) {
-		if (!tsEntry.replacementSpan) {
-			if (completionContext.optionalReplacementRange) {
-				return {
-					inserting: new vscode.Range(completionContext.optionalReplacementRange.start, this.position),
-					replacing: completionContext.optionalReplacementRange,
-				};
-			}
-
-			return undefined;
-		}
-
-		// If TS returns an explicit replacement range on this item, we should use it for both types of completion
-
-		// Make sure we only replace a single line at most
-		let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan);
-		if (!replaceRange.isSingleLine) {
-			replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, completionContext.line.length);
-		}
-		return {
-			inserting: replaceRange,
-			replacing: replaceRange,
-		};
-	}
-
-	private getFilterText(line: string, insertText: string | undefined): string | undefined {
-		// Handle private field completions
-		if (this.tsEntry.name.startsWith('#')) {
-			const wordRange = this.completionContext.wordRange;
-			const wordStart = wordRange ? line.charAt(wordRange.start.character) : undefined;
-			if (insertText) {
-				if (insertText.startsWith('this.#')) {
-					return wordStart === '#' ? insertText : insertText.replace(/^this\.#/, '');
-				} else {
-					return insertText;
-				}
-			} else {
-				return wordStart === '#' ? undefined : this.tsEntry.name.replace(/^#/, '');
-			}
-		}
-
-		// For `this.` completions, generally don't set the filter text since we don't want them to be overly prioritized. #74164
-		if (insertText?.startsWith('this.')) {
-			return undefined;
-		}
-
-		// Handle the case:
-		// ```
-		// const xyz = { 'ab c': 1 };
-		// xyz.ab|
-		// ```
-		// In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of
-		// the bracketed insert text.
-		else if (insertText?.startsWith('[')) {
-			return insertText.replace(/^\[['"](.+)[['"]\]$/, '.$1');
-		}
-
-		// In all other cases, fallback to using the insertText
-		return insertText;
-	}
-
-	private resolveRange(): void {
-		if (this.range) {
-			return;
-		}
-
-		const replaceRange = this.completionContext.wordRange;
-		if (replaceRange) {
-			this.range = {
-				inserting: new vscode.Range(replaceRange.start, this.position),
-				replacing: replaceRange
-			};
-		}
-	}
-
-	private static convertKind(kind: string): vscode.CompletionItemKind {
-		switch (kind) {
-			case PConst.Kind.primitiveType:
-			case PConst.Kind.keyword:
-				return vscode.CompletionItemKind.Keyword;
-
-			case PConst.Kind.const:
-			case PConst.Kind.let:
-			case PConst.Kind.variable:
-			case PConst.Kind.localVariable:
-			case PConst.Kind.alias:
-			case PConst.Kind.parameter:
-				return vscode.CompletionItemKind.Variable;
-
-			case PConst.Kind.memberVariable:
-			case PConst.Kind.memberGetAccessor:
-			case PConst.Kind.memberSetAccessor:
-				return vscode.CompletionItemKind.Field;
-
-			case PConst.Kind.function:
-			case PConst.Kind.localFunction:
-				return vscode.CompletionItemKind.Function;
-
-			case PConst.Kind.method:
-			case PConst.Kind.constructSignature:
-			case PConst.Kind.callSignature:
-			case PConst.Kind.indexSignature:
-				return vscode.CompletionItemKind.Method;
-
-			case PConst.Kind.enum:
-				return vscode.CompletionItemKind.Enum;
-
-			case PConst.Kind.enumMember:
-				return vscode.CompletionItemKind.EnumMember;
-
-			case PConst.Kind.module:
-			case PConst.Kind.externalModuleName:
-				return vscode.CompletionItemKind.Module;
-
-			case PConst.Kind.class:
-			case PConst.Kind.type:
-				return vscode.CompletionItemKind.Class;
-
-			case PConst.Kind.interface:
-				return vscode.CompletionItemKind.Interface;
-
-			case PConst.Kind.warning:
-				return vscode.CompletionItemKind.Text;
-
-			case PConst.Kind.script:
-				return vscode.CompletionItemKind.File;
-
-			case PConst.Kind.directory:
-				return vscode.CompletionItemKind.Folder;
-
-			case PConst.Kind.string:
-				return vscode.CompletionItemKind.Constant;
-
-			default:
-				return vscode.CompletionItemKind.Property;
-		}
-	}
-
-	private static getCommitCharacters(
-		context: CompletionContext,
-		entry: Proto.CompletionEntry,
-		defaultCommitCharacters: readonly string[] | undefined,
-	): string[] | undefined {
-		let commitCharacters = entry.commitCharacters ?? (defaultCommitCharacters ? Array.from(defaultCommitCharacters) : undefined);
-		if (commitCharacters) {
-			if (context.enableCallCompletions
-				&& !context.isNewIdentifierLocation
-				&& entry.kind !== PConst.Kind.warning
-				&& entry.kind !== PConst.Kind.string) {
-				commitCharacters.push('(');
-			}
-			return commitCharacters;
-		}
-
-		if (entry.kind === PConst.Kind.warning || entry.kind === PConst.Kind.string) { // Ambient JS word based suggestion, strings
-			return undefined;
-		}
-
-		if (context.isNewIdentifierLocation) {
-			return undefined;
-		}
-
-		commitCharacters = ['.', ',', ';'];
-		if (context.enableCallCompletions) {
-			commitCharacters.push('(');
-		}
-
-		return commitCharacters;
-	}
+    public readonly useCodeSnippet: boolean;
+    constructor(public readonly position: vscode.Position, public readonly document: vscode.TextDocument, public readonly tsEntry: Proto.CompletionEntry, private readonly completionContext: CompletionContext, public readonly metadata: any | undefined, client: ITypeScriptServiceClient, defaultCommitCharacters: readonly string[] | undefined) {
+        const label = tsEntry.name || (tsEntry.insertText ?? '');
+        super(label, MyCompletionItem.convertKind(tsEntry.kind));
+        if (tsEntry.source && tsEntry.hasAction && client.apiVersion.lt(API.v490)) {
+            // De-prioritze auto-imports
+            // https://github.com/microsoft/vscode/issues/40311
+            this.sortText = '\uffff' + tsEntry.sortText;
+        }
+        else {
+            this.sortText = tsEntry.sortText;
+        }
+        if (tsEntry.source && tsEntry.hasAction) {
+            // Render "fancy" when source is a workspace path
+            const qualifierCandidate = vscode.workspace.asRelativePath(tsEntry.source);
+            if (qualifierCandidate !== tsEntry.source) {
+                this.label = { label, description: qualifierCandidate };
+            }
+        }
+        const { sourceDisplay, isSnippet } = tsEntry;
+        if (sourceDisplay) {
+            this.label = { label, description: Previewer.asPlainTextWithLinks(sourceDisplay, client) };
+        }
+        if (tsEntry.labelDetails) {
+            this.label = { label, ...tsEntry.labelDetails };
+        }
+        this.preselect = tsEntry.isRecommended;
+        this.position = position;
+        this.useCodeSnippet = completionContext.completeFunctionCalls && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method);
+        this.range = this.getRangeFromReplacementSpan(tsEntry, completionContext);
+        this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry, defaultCommitCharacters);
+        this.insertText = isSnippet && tsEntry.insertText ? new vscode.SnippetString(tsEntry.insertText) : tsEntry.insertText;
+        this.filterText = tsEntry.filterText || this.getFilterText(completionContext.line, tsEntry.insertText);
+        if (completionContext.isMemberCompletion && completionContext.dotAccessorContext && !(this.insertText instanceof vscode.SnippetString)) {
+            this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.textLabel);
+            if (!this.range) {
+                const replacementRange = this.completionContext.wordRange;
+                if (replacementRange) {
+                    this.range = {
+                        inserting: completionContext.dotAccessorContext.range,
+                        replacing: completionContext.dotAccessorContext.range.union(replacementRange),
+                    };
+                }
+                else {
+                    this.range = completionContext.dotAccessorContext.range;
+                }
+                this.insertText = this.filterText;
+            }
+        }
+        if (tsEntry.kindModifiers) {
+            const kindModifiers = parseKindModifier(tsEntry.kindModifiers);
+            if (kindModifiers.has(PConst.KindModifiers.optional)) {
+                this.insertText ??= this.textLabel;
+                this.filterText ??= this.textLabel;
+                if (typeof this.label === 'string') {
+                    this.label += '?';
+                }
+                else {
+                    this.label.label += '?';
+                }
+            }
+            if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
+                this.tags = [vscode.CompletionItemTag.Deprecated];
+            }
+            if (kindModifiers.has(PConst.KindModifiers.color)) {
+                this.kind = vscode.CompletionItemKind.Color;
+            }
+            this.detail = getScriptKindDetails(tsEntry);
+        }
+        this.resolveRange();
+    }
+    private get textLabel() {
+        return typeof this.label === 'string' ? this.label : this.label.label;
+    }
+    private _resolvedPromise?: {
+        readonly requestToken: vscode.CancellationTokenSource;
+        readonly promise: Promise;
+        waiting: number;
+    };
+    public async resolveCompletionItem(client: ITypeScriptServiceClient, token: vscode.CancellationToken): Promise {
+        token.onCancellationRequested(() => {
+            if (this._resolvedPromise && --this._resolvedPromise.waiting <= 0) {
+                // Give a little extra time for another caller to come in
+                setTimeout(() => {
+                    if (this._resolvedPromise && this._resolvedPromise.waiting <= 0) {
+                        this._resolvedPromise.requestToken.cancel();
+                    }
+                }, 300);
+            }
+        });
+        if (this._resolvedPromise) {
+            ++this._resolvedPromise.waiting;
+            return this._resolvedPromise.promise;
+        }
+        const requestToken = new vscode.CancellationTokenSource();
+        const promise = (async (): Promise => {
+            const filepath = client.toOpenTsFilePath(this.document);
+            if (!filepath) {
+                return undefined;
+            }
+            const args: Proto.CompletionDetailsRequestArgs = {
+                ...typeConverters.Position.toFileLocationRequestArgs(filepath, this.position),
+                entryNames: [
+                    this.tsEntry.source || this.tsEntry.data ? {
+                        name: this.tsEntry.name,
+                        source: this.tsEntry.source,
+                        data: this.tsEntry.data,
+                    } : this.tsEntry.name
+                ]
+            };
+            const response = await client.interruptGetErr(() => client.execute('completionEntryDetails', args, requestToken.token));
+            if (response.type !== 'response' || !response.body?.length) {
+                return undefined;
+            }
+            const detail = response.body[0];
+            const newItemDetails = this.getDetails(client, detail);
+            if (newItemDetails) {
+                this.detail = newItemDetails;
+            }
+            this.documentation = this.getDocumentation(client, detail, this.document.uri);
+            const codeAction = this.getCodeActions(detail, filepath);
+            const commands: vscode.Command[] = [{
+                    command: CompletionAcceptedCommand.ID,
+                    title: '',
+                    arguments: [this]
+                }];
+            if (codeAction.command) {
+                commands.push(codeAction.command);
+            }
+            const additionalTextEdits = codeAction.additionalTextEdits;
+            if (this.useCodeSnippet) {
+                const shouldCompleteFunction = await this.isValidFunctionCompletionContext(client, filepath, this.position, this.document, token);
+                if (shouldCompleteFunction) {
+                    const { snippet, parameterCount } = snippetForFunctionCall({ ...this, label: this.textLabel }, detail.displayParts);
+                    this.insertText = snippet;
+                    if (parameterCount > 0) {
+                        //Fix for https://github.com/microsoft/vscode/issues/104059
+                        //Don't show parameter hints if "editor.parameterHints.enabled": false
+                        if (vscode.workspace.getConfiguration('editor.parameterHints').get('enabled')) {
+                            commands.push({ title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' });
+                        }
+                    }
+                }
+            }
+            return { commands, edits: additionalTextEdits };
+        })();
+        this._resolvedPromise = {
+            promise,
+            requestToken,
+            waiting: 1,
+        };
+        return this._resolvedPromise.promise;
+    }
+    private getDetails(client: ITypeScriptServiceClient, detail: Proto.CompletionEntryDetails): string | undefined {
+        const parts: string[] = [];
+        if (detail.kind === PConst.Kind.script) {
+            // details were already added
+            return undefined;
+        }
+        for (const action of detail.codeActions ?? []) {
+            parts.push(action.description);
+        }
+        parts.push(Previewer.asPlainTextWithLinks(detail.displayParts, client));
+        return parts.join('\n\n');
+    }
+    private getDocumentation(client: ITypeScriptServiceClient, detail: Proto.CompletionEntryDetails, baseUri: vscode.Uri): vscode.MarkdownString | undefined {
+        const documentation = new vscode.MarkdownString();
+        Previewer.appendDocumentationAsMarkdown(documentation, detail.documentation, detail.tags, client);
+        documentation.baseUri = baseUri;
+        return documentation.value.length ? documentation : undefined;
+    }
+    private async isValidFunctionCompletionContext(client: ITypeScriptServiceClient, filepath: string, position: vscode.Position, document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+        // Workaround for https://github.com/microsoft/TypeScript/issues/12677
+        // Don't complete function calls inside of destructive assignments or imports
+        try {
+            const args: Proto.FileLocationRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
+            const response = await client.execute('quickinfo', args, token);
+            if (response.type === 'response' && response.body) {
+                switch (response.body.kind) {
+                    case 'var':
+                    case 'let':
+                    case 'const':
+                    case 'alias':
+                        return false;
+                }
+            }
+        }
+        catch {
+            // Noop
+        }
+        const line = document.lineAt(position.line);
+        // Don't complete function call if there is already something that looks like a function call
+        // https://github.com/microsoft/vscode/issues/18131
+        const after = line.text.slice(position.character);
+        if (after.match(/^[a-z_$0-9]*\s*\(/gi)) {
+            return false;
+        }
+        // Don't complete function call if it looks like a jsx tag.
+        const before = line.text.slice(0, position.character);
+        if (before.match(/<\s*[\w]*$/gi)) {
+            return false;
+        }
+        return true;
+    }
+    private getCodeActions(detail: Proto.CompletionEntryDetails, filepath: string): {
+        command?: vscode.Command;
+        additionalTextEdits?: vscode.TextEdit[];
+    } {
+        if (!detail.codeActions?.length) {
+            return {};
+        }
+        // Try to extract out the additionalTextEdits for the current file.
+        // Also check if we still have to apply other workspace edits and commands
+        // using a vscode command
+        const additionalTextEdits: vscode.TextEdit[] = [];
+        let hasRemainingCommandsOrEdits = false;
+        for (const tsAction of detail.codeActions) {
+            if (tsAction.commands) {
+                hasRemainingCommandsOrEdits = true;
+            }
+            // Apply all edits in the current file using `additionalTextEdits`
+            if (tsAction.changes) {
+                for (const change of tsAction.changes) {
+                    if (change.fileName === filepath) {
+                        additionalTextEdits.push(...change.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
+                    }
+                    else {
+                        hasRemainingCommandsOrEdits = true;
+                    }
+                }
+            }
+        }
+        let command: vscode.Command | undefined = undefined;
+        if (hasRemainingCommandsOrEdits) {
+            // Create command that applies all edits not in the current file.
+            command = {
+                title: '',
+                command: ApplyCompletionCodeActionCommand.ID,
+                arguments: [filepath, detail.codeActions.map((x): Proto.CodeAction => ({
+                        commands: x.commands,
+                        description: x.description,
+                        changes: x.changes.filter(x => x.fileName !== filepath)
+                    }))]
+            };
+        }
+        return {
+            command,
+            additionalTextEdits: additionalTextEdits.length ? additionalTextEdits : undefined
+        };
+    }
+    private getRangeFromReplacementSpan(tsEntry: Proto.CompletionEntry, completionContext: CompletionContext) {
+        if (!tsEntry.replacementSpan) {
+            if (completionContext.optionalReplacementRange) {
+                return {
+                    inserting: new vscode.Range(completionContext.optionalReplacementRange.start, this.position),
+                    replacing: completionContext.optionalReplacementRange,
+                };
+            }
+            return undefined;
+        }
+        // If TS returns an explicit replacement range on this item, we should use it for both types of completion
+        // Make sure we only replace a single line at most
+        let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan);
+        if (!replaceRange.isSingleLine) {
+            replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, completionContext.line.length);
+        }
+        return {
+            inserting: replaceRange,
+            replacing: replaceRange,
+        };
+    }
+    private getFilterText(line: string, insertText: string | undefined): string | undefined {
+        // Handle private field completions
+        if (this.tsEntry.name.startsWith('#')) {
+            const wordRange = this.completionContext.wordRange;
+            const wordStart = wordRange ? line.charAt(wordRange.start.character) : undefined;
+            if (insertText) {
+                if (insertText.startsWith('this.#')) {
+                    return wordStart === '#' ? insertText : insertText.replace(/^this\.#/, '');
+                }
+                else {
+                    return insertText;
+                }
+            }
+            else {
+                return wordStart === '#' ? undefined : this.tsEntry.name.replace(/^#/, '');
+            }
+        }
+        // For `this.` completions, generally don't set the filter text since we don't want them to be overly prioritized. #74164
+        if (insertText?.startsWith('this.')) {
+            return undefined;
+        }
+        // Handle the case:
+        // ```
+        // const xyz = { 'ab c': 1 };
+        // xyz.ab|
+        // ```
+        // In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of
+        // the bracketed insert text.
+        else if (insertText?.startsWith('[')) {
+            return insertText.replace(/^\[['"](.+)[['"]\]$/, '.$1');
+        }
+        // In all other cases, fallback to using the insertText
+        return insertText;
+    }
+    private resolveRange(): void {
+        if (this.range) {
+            return;
+        }
+        const replaceRange = this.completionContext.wordRange;
+        if (replaceRange) {
+            this.range = {
+                inserting: new vscode.Range(replaceRange.start, this.position),
+                replacing: replaceRange
+            };
+        }
+    }
+    private static convertKind(kind: string): vscode.CompletionItemKind {
+        switch (kind) {
+            case PConst.Kind.primitiveType:
+            case PConst.Kind.keyword:
+                return vscode.CompletionItemKind.Keyword;
+            case PConst.Kind.const:
+            case PConst.Kind.let:
+            case PConst.Kind.variable:
+            case PConst.Kind.localVariable:
+            case PConst.Kind.alias:
+            case PConst.Kind.parameter:
+                return vscode.CompletionItemKind.Variable;
+            case PConst.Kind.memberVariable:
+            case PConst.Kind.memberGetAccessor:
+            case PConst.Kind.memberSetAccessor:
+                return vscode.CompletionItemKind.Field;
+            case PConst.Kind.function:
+            case PConst.Kind.localFunction:
+                return vscode.CompletionItemKind.Function;
+            case PConst.Kind.method:
+            case PConst.Kind.constructSignature:
+            case PConst.Kind.callSignature:
+            case PConst.Kind.indexSignature:
+                return vscode.CompletionItemKind.Method;
+            case PConst.Kind.enum:
+                return vscode.CompletionItemKind.Enum;
+            case PConst.Kind.enumMember:
+                return vscode.CompletionItemKind.EnumMember;
+            case PConst.Kind.module:
+            case PConst.Kind.externalModuleName:
+                return vscode.CompletionItemKind.Module;
+            case PConst.Kind.class:
+            case PConst.Kind.type:
+                return vscode.CompletionItemKind.Class;
+            case PConst.Kind.interface:
+                return vscode.CompletionItemKind.Interface;
+            case PConst.Kind.warning:
+                return vscode.CompletionItemKind.Text;
+            case PConst.Kind.script:
+                return vscode.CompletionItemKind.File;
+            case PConst.Kind.directory:
+                return vscode.CompletionItemKind.Folder;
+            case PConst.Kind.string:
+                return vscode.CompletionItemKind.Constant;
+            default:
+                return vscode.CompletionItemKind.Property;
+        }
+    }
+    private static getCommitCharacters(context: CompletionContext, entry: Proto.CompletionEntry, defaultCommitCharacters: readonly string[] | undefined): string[] | undefined {
+        let commitCharacters = entry.commitCharacters ?? (defaultCommitCharacters ? Array.from(defaultCommitCharacters) : undefined);
+        if (commitCharacters) {
+            if (context.enableCallCompletions
+                && !context.isNewIdentifierLocation
+                && entry.kind !== PConst.Kind.warning
+                && entry.kind !== PConst.Kind.string) {
+                commitCharacters.push('(');
+            }
+            return commitCharacters;
+        }
+        if (entry.kind === PConst.Kind.warning || entry.kind === PConst.Kind.string) { // Ambient JS word based suggestion, strings
+            return undefined;
+        }
+        if (context.isNewIdentifierLocation) {
+            return undefined;
+        }
+        commitCharacters = ['.', ',', ';'];
+        if (context.enableCallCompletions) {
+            commitCharacters.push('(');
+        }
+        return commitCharacters;
+    }
 }
-
-function getScriptKindDetails(tsEntry: Proto.CompletionEntry,): string | undefined {
-	if (!tsEntry.kindModifiers || tsEntry.kind !== PConst.Kind.script) {
-		return;
-	}
-
-	const kindModifiers = parseKindModifier(tsEntry.kindModifiers);
-	for (const extModifier of PConst.KindModifiers.fileExtensionKindModifiers) {
-		if (kindModifiers.has(extModifier)) {
-			if (tsEntry.name.toLowerCase().endsWith(extModifier)) {
-				return tsEntry.name;
-			} else {
-				return tsEntry.name + extModifier;
-			}
-		}
-	}
-	return undefined;
+function getScriptKindDetails(tsEntry: Proto.CompletionEntry): string | undefined {
+    if (!tsEntry.kindModifiers || tsEntry.kind !== PConst.Kind.script) {
+        return;
+    }
+    const kindModifiers = parseKindModifier(tsEntry.kindModifiers);
+    for (const extModifier of PConst.KindModifiers.fileExtensionKindModifiers) {
+        if (kindModifiers.has(extModifier)) {
+            if (tsEntry.name.toLowerCase().endsWith(extModifier)) {
+                return tsEntry.name;
+            }
+            else {
+                return tsEntry.name + extModifier;
+            }
+        }
+    }
+    return undefined;
 }
-
-
 class CompletionAcceptedCommand implements Command {
-	public static readonly ID = '_typescript.onCompletionAccepted';
-	public readonly id = CompletionAcceptedCommand.ID;
-
-	public constructor(
-		private readonly onCompletionAccepted: (item: vscode.CompletionItem) => void,
-		private readonly telemetryReporter: TelemetryReporter,
-	) { }
-
-	public execute(item: vscode.CompletionItem) {
-		this.onCompletionAccepted(item);
-		if (item instanceof MyCompletionItem) {
-			/* __GDPR__
-				"completions.accept" : {
-					"owner": "mjbvz",
-					"isPackageJsonImport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-					"isImportStatementCompletion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-					"${include}": [
-						"${TypeScriptCommonProperties}"
-					]
-				}
-			*/
-			this.telemetryReporter.logTelemetry('completions.accept', {
-				isPackageJsonImport: item.tsEntry.isPackageJsonImport ? 'true' : undefined,
-				isImportStatementCompletion: item.tsEntry.isImportStatementCompletion ? 'true' : undefined,
-			});
-		}
-	}
+    public static readonly ID = '_typescript.onCompletionAccepted';
+    public readonly id = CompletionAcceptedCommand.ID;
+    public constructor(private readonly onCompletionAccepted: (item: vscode.CompletionItem) => void, private readonly telemetryReporter: TelemetryReporter) { }
+    public execute(item: vscode.CompletionItem) {
+        this.onCompletionAccepted(item);
+        if (item instanceof MyCompletionItem) {
+            /* __GDPR__
+                "completions.accept" : {
+                    "owner": "mjbvz",
+                    "isPackageJsonImport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                    "isImportStatementCompletion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                    "${include}": [
+                        "${TypeScriptCommonProperties}"
+                    ]
+                }
+            */
+            this.telemetryReporter.logTelemetry('completions.accept', {
+                isPackageJsonImport: item.tsEntry.isPackageJsonImport ? 'true' : undefined,
+                isImportStatementCompletion: item.tsEntry.isImportStatementCompletion ? 'true' : undefined,
+            });
+        }
+    }
 }
-
 /**
  * Command fired when an completion item needs to be applied
  */
 class ApplyCompletionCommand implements Command {
-	public static readonly ID = '_typescript.applyCompletionCommand';
-	public readonly id = ApplyCompletionCommand.ID;
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-	) { }
-
-	public async execute(item: MyCompletionItem) {
-		const resolved = await item.resolveCompletionItem(this.client, nulToken);
-		if (!resolved) {
-			return;
-		}
-
-		const { edits, commands } = resolved;
-
-		if (edits) {
-			const workspaceEdit = new vscode.WorkspaceEdit();
-			for (const edit of edits) {
-				workspaceEdit.replace(item.document.uri, edit.range, edit.newText);
-			}
-			await vscode.workspace.applyEdit(workspaceEdit);
-		}
-
-		for (const command of commands) {
-			await vscode.commands.executeCommand(command.command, ...(command.arguments ?? []));
-		}
-	}
+    public static readonly ID = '_typescript.applyCompletionCommand';
+    public readonly id = ApplyCompletionCommand.ID;
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async execute(item: MyCompletionItem) {
+        const resolved = await item.resolveCompletionItem(this.client, nulToken);
+        if (!resolved) {
+            return;
+        }
+        const { edits, commands } = resolved;
+        if (edits) {
+            const workspaceEdit = new vscode.WorkspaceEdit();
+            for (const edit of edits) {
+                workspaceEdit.replace(item.document.uri, edit.range, edit.newText);
+            }
+            await vscode.workspace.applyEdit(workspaceEdit);
+        }
+        for (const command of commands) {
+            await vscode.commands.executeCommand(command.command, ...(command.arguments ?? []));
+        }
+    }
 }
-
 class ApplyCompletionCodeActionCommand implements Command {
-	public static readonly ID = '_typescript.applyCompletionCodeAction';
-	public readonly id = ApplyCompletionCodeActionCommand.ID;
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async execute(_file: string, codeActions: Proto.CodeAction[]): Promise {
-		if (codeActions.length === 0) {
-			return true;
-		}
-
-		if (codeActions.length === 1) {
-			return applyCodeAction(this.client, codeActions[0], nulToken);
-		}
-
-		const selection = await vscode.window.showQuickPick(
-			codeActions.map(action => ({
-				label: action.description,
-				description: '',
-				action,
-			})), {
-			placeHolder: vscode.l10n.t("Select code action to apply")
-		});
-
-		if (selection) {
-			return applyCodeAction(this.client, selection.action, nulToken);
-		}
-		return false;
-	}
+    public static readonly ID = '_typescript.applyCompletionCodeAction';
+    public readonly id = ApplyCompletionCodeActionCommand.ID;
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async execute(_file: string, codeActions: Proto.CodeAction[]): Promise {
+        if (codeActions.length === 0) {
+            return true;
+        }
+        if (codeActions.length === 1) {
+            return applyCodeAction(this.client, codeActions[0], nulToken);
+        }
+        const selection = await vscode.window.showQuickPick(codeActions.map(action => ({
+            label: action.description,
+            description: '',
+            action,
+        })), {
+            placeHolder: vscode.l10n.t("Select code action to apply")
+        });
+        if (selection) {
+            return applyCodeAction(this.client, selection.action, nulToken);
+        }
+        return false;
+    }
 }
-
 interface CompletionConfiguration {
-	readonly completeFunctionCalls: boolean;
-	readonly nameSuggestions: boolean;
-	readonly pathSuggestions: boolean;
-	readonly autoImportSuggestions: boolean;
-	readonly importStatementSuggestions: boolean;
+    readonly completeFunctionCalls: boolean;
+    readonly nameSuggestions: boolean;
+    readonly pathSuggestions: boolean;
+    readonly autoImportSuggestions: boolean;
+    readonly importStatementSuggestions: boolean;
 }
-
 namespace CompletionConfiguration {
-	export const completeFunctionCalls = 'suggest.completeFunctionCalls';
-	export const nameSuggestions = 'suggest.names';
-	export const pathSuggestions = 'suggest.paths';
-	export const autoImportSuggestions = 'suggest.autoImports';
-	export const importStatementSuggestions = 'suggest.importStatements';
-
-	export function getConfigurationForResource(
-		modeId: string,
-		resource: vscode.Uri
-	): CompletionConfiguration {
-		const config = vscode.workspace.getConfiguration(modeId, resource);
-		return {
-			completeFunctionCalls: config.get(CompletionConfiguration.completeFunctionCalls, false),
-			pathSuggestions: config.get(CompletionConfiguration.pathSuggestions, true),
-			autoImportSuggestions: config.get(CompletionConfiguration.autoImportSuggestions, true),
-			nameSuggestions: config.get(CompletionConfiguration.nameSuggestions, true),
-			importStatementSuggestions: config.get(CompletionConfiguration.importStatementSuggestions, true),
-		};
-	}
+    export const completeFunctionCalls = 'suggest.completeFunctionCalls';
+    export const nameSuggestions = 'suggest.names';
+    export const pathSuggestions = 'suggest.paths';
+    export const autoImportSuggestions = 'suggest.autoImports';
+    export const importStatementSuggestions = 'suggest.importStatements';
+    export function getConfigurationForResource(modeId: string, resource: vscode.Uri): CompletionConfiguration {
+        const config = vscode.workspace.getConfiguration(modeId, resource);
+        return {
+            completeFunctionCalls: config.get(CompletionConfiguration.completeFunctionCalls, false),
+            pathSuggestions: config.get(CompletionConfiguration.pathSuggestions, true),
+            autoImportSuggestions: config.get(CompletionConfiguration.autoImportSuggestions, true),
+            nameSuggestions: config.get(CompletionConfiguration.nameSuggestions, true),
+            importStatementSuggestions: config.get(CompletionConfiguration.importStatementSuggestions, true),
+        };
+    }
 }
-
 class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider {
-
-	public static readonly triggerCharacters = ['.', '"', '\'', '`', '/', '@', '<', '#', ' '];
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly language: LanguageDescription,
-		private readonly typingsStatus: TypingsStatus,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-		commandManager: CommandManager,
-		private readonly telemetryReporter: TelemetryReporter,
-		onCompletionAccepted: (item: vscode.CompletionItem) => void
-	) {
-		commandManager.register(new ApplyCompletionCodeActionCommand(this.client));
-		commandManager.register(new CompletionAcceptedCommand(onCompletionAccepted, this.telemetryReporter));
-		commandManager.register(new ApplyCompletionCommand(this.client));
-	}
-
-	public async provideCompletionItems(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken,
-		context: vscode.CompletionContext
-	): Promise | undefined> {
-		if (!vscode.workspace.getConfiguration(this.language.id, document).get('suggest.enabled')) {
-			return undefined;
-		}
-
-		if (this.typingsStatus.isAcquiringTypings) {
-			return Promise.reject>({
-				label: vscode.l10n.t({
-					message: "Acquiring typings...",
-					comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'],
-				}),
-				detail: vscode.l10n.t({
-					message: "Acquiring typings definitions for IntelliSense.",
-					comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'],
-				})
-			});
-		}
-
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		const line = document.lineAt(position.line);
-		const completionConfiguration = CompletionConfiguration.getConfigurationForResource(this.language.id, document.uri);
-
-		if (!this.shouldTrigger(context, line, position, completionConfiguration)) {
-			return undefined;
-		}
-
-		let wordRange = document.getWordRangeAtPosition(position);
-		if (wordRange && !wordRange.isEmpty) {
-			const secondCharPosition = wordRange.start.translate(0, 1);
-			const firstChar = document.getText(new vscode.Range(wordRange.start, secondCharPosition));
-			if (firstChar === '@') {
-				wordRange = wordRange.with(secondCharPosition);
-			}
-		}
-
-		await this.client.interruptGetErr(() => this.fileConfigurationManager.ensureConfigurationForDocument(document, token));
-
-		const args: Proto.CompletionsRequestArgs = {
-			...typeConverters.Position.toFileLocationRequestArgs(file, position),
-			includeExternalModuleExports: completionConfiguration.autoImportSuggestions,
-			includeInsertTextCompletions: true,
-			triggerCharacter: this.getTsTriggerCharacter(context),
-			triggerKind: typeConverters.CompletionTriggerKind.toProtocolCompletionTriggerKind(context.triggerKind),
-		};
-
-		let dotAccessorContext: DotAccessorContext | undefined;
-		let response: ServerResponse.Response | undefined;
-		let duration: number | undefined;
-		let optionalReplacementRange: vscode.Range | undefined;
-
-		const startTime = Date.now();
-		try {
-			response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token));
-		} finally {
-			duration = Date.now() - startTime;
-		}
-
-		if (response.type !== 'response' || !response.body) {
-			this.logCompletionsTelemetry(duration, response);
-			return undefined;
-		}
-		const isNewIdentifierLocation = response.body.isNewIdentifierLocation;
-		const isMemberCompletion = response.body.isMemberCompletion;
-		if (isMemberCompletion) {
-			const dotMatch = line.text.slice(0, position.character).match(/\??\.\s*$/) || undefined;
-			if (dotMatch) {
-				const range = new vscode.Range(position.translate({ characterDelta: -dotMatch[0].length }), position);
-				const text = document.getText(range);
-				dotAccessorContext = { range, text };
-			}
-		}
-		const isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete;
-		const entries = response.body.entries;
-		const metadata = response.metadata;
-		const defaultCommitCharacters = Object.freeze(response.body.defaultCommitCharacters);
-
-		if (response.body.optionalReplacementSpan) {
-			optionalReplacementRange = typeConverters.Range.fromTextSpan(response.body.optionalReplacementSpan);
-		}
-
-		const completionContext: CompletionContext = {
-			isNewIdentifierLocation,
-			isMemberCompletion,
-			dotAccessorContext,
-			enableCallCompletions: !completionConfiguration.completeFunctionCalls,
-			wordRange,
-			line: line.text,
-			completeFunctionCalls: completionConfiguration.completeFunctionCalls,
-			optionalReplacementRange,
-		};
-
-		let includesPackageJsonImport = false;
-		let includesImportStatementCompletion = false;
-		const items: MyCompletionItem[] = [];
-		for (const entry of entries) {
-			if (!shouldExcludeCompletionEntry(entry, completionConfiguration)) {
-				const item = new MyCompletionItem(
-					position,
-					document,
-					entry,
-					completionContext,
-					metadata,
-					this.client,
-					defaultCommitCharacters);
-				item.command = {
-					command: ApplyCompletionCommand.ID,
-					title: '',
-					arguments: [item]
-				};
-				items.push(item);
-				includesPackageJsonImport = includesPackageJsonImport || !!entry.isPackageJsonImport;
-				includesImportStatementCompletion = includesImportStatementCompletion || !!entry.isImportStatementCompletion;
-			}
-		}
-		if (duration !== undefined) {
-			this.logCompletionsTelemetry(duration, response, includesPackageJsonImport, includesImportStatementCompletion);
-		}
-		return new vscode.CompletionList(items, isIncomplete);
-	}
-
-	private logCompletionsTelemetry(
-		duration: number,
-		response: ServerResponse.Response | undefined,
-		includesPackageJsonImport?: boolean,
-		includesImportStatementCompletion?: boolean,
-	) {
-		/* __GDPR__
-			"completions.execute" : {
-				"owner": "mjbvz",
-				"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"flags": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"updateGraphDurationMs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"createAutoImportProviderProgramDurationMs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"includesPackageJsonImport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"includesImportStatementCompletion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		this.telemetryReporter.logTelemetry('completions.execute', {
-			duration: String(duration),
-			type: response?.type ?? 'unknown',
-			flags: response?.type === 'response' && typeof response.body?.flags === 'number' ? String(response.body.flags) : undefined,
-			count: String(response?.type === 'response' && response.body ? response.body.entries.length : 0),
-			updateGraphDurationMs: response?.type === 'response' && typeof response.performanceData?.updateGraphDurationMs === 'number'
-				? String(response.performanceData.updateGraphDurationMs)
-				: undefined,
-			createAutoImportProviderProgramDurationMs: response?.type === 'response' && typeof response.performanceData?.createAutoImportProviderProgramDurationMs === 'number'
-				? String(response.performanceData.createAutoImportProviderProgramDurationMs)
-				: undefined,
-			includesPackageJsonImport: includesPackageJsonImport ? 'true' : undefined,
-			includesImportStatementCompletion: includesImportStatementCompletion ? 'true' : undefined,
-		});
-	}
-
-	private getTsTriggerCharacter(context: vscode.CompletionContext): Proto.CompletionsTriggerCharacter | undefined {
-		switch (context.triggerCharacter) {
-			case '@': {
-				return '@';
-			}
-			case '#': {
-				return '#';
-			}
-			case ' ': {
-				return this.client.apiVersion.gte(API.v430) ? ' ' : undefined;
-			}
-			case '.':
-			case '"':
-			case '\'':
-			case '`':
-			case '/':
-			case '<': {
-				return context.triggerCharacter;
-			}
-			default: {
-				return undefined;
-			}
-		}
-	}
-
-	public async resolveCompletionItem(
-		item: MyCompletionItem,
-		token: vscode.CancellationToken
-	): Promise {
-		await item.resolveCompletionItem(this.client, token);
-		return item;
-	}
-
-	private shouldTrigger(
-		context: vscode.CompletionContext,
-		line: vscode.TextLine,
-		position: vscode.Position,
-		configuration: CompletionConfiguration,
-	): boolean {
-		if (context.triggerCharacter === ' ') {
-			if (!configuration.importStatementSuggestions || this.client.apiVersion.lt(API.v430)) {
-				return false;
-			}
-			const pre = line.text.slice(0, position.character);
-			return pre === 'import';
-		}
-		return true;
-	}
+    public static readonly triggerCharacters = ['.', '"', '\'', '`', '/', '@', '<', '#', ' '];
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly language: LanguageDescription, private readonly typingsStatus: TypingsStatus, private readonly fileConfigurationManager: FileConfigurationManager, commandManager: CommandManager, private readonly telemetryReporter: TelemetryReporter, onCompletionAccepted: (item: vscode.CompletionItem) => void) {
+        commandManager.register(new ApplyCompletionCodeActionCommand(this.client));
+        commandManager.register(new CompletionAcceptedCommand(onCompletionAccepted, this.telemetryReporter));
+        commandManager.register(new ApplyCompletionCommand(this.client));
+    }
+    public async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): Promise | undefined> {
+        if (!vscode.workspace.getConfiguration(this.language.id, document).get('suggest.enabled')) {
+            return undefined;
+        }
+        if (this.typingsStatus.isAcquiringTypings) {
+            return Promise.reject>({
+                label: vscode.l10n.t({
+                    message: "Acquiring typings...",
+                    comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'],
+                }),
+                detail: vscode.l10n.t({
+                    message: "Acquiring typings definitions for IntelliSense.",
+                    comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'],
+                })
+            });
+        }
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        const line = document.lineAt(position.line);
+        const completionConfiguration = CompletionConfiguration.getConfigurationForResource(this.language.id, document.uri);
+        if (!this.shouldTrigger(context, line, position, completionConfiguration)) {
+            return undefined;
+        }
+        let wordRange = document.getWordRangeAtPosition(position);
+        if (wordRange && !wordRange.isEmpty) {
+            const secondCharPosition = wordRange.start.translate(0, 1);
+            const firstChar = document.getText(new vscode.Range(wordRange.start, secondCharPosition));
+            if (firstChar === '@') {
+                wordRange = wordRange.with(secondCharPosition);
+            }
+        }
+        await this.client.interruptGetErr(() => this.fileConfigurationManager.ensureConfigurationForDocument(document, token));
+        const args: Proto.CompletionsRequestArgs = {
+            ...typeConverters.Position.toFileLocationRequestArgs(file, position),
+            includeExternalModuleExports: completionConfiguration.autoImportSuggestions,
+            includeInsertTextCompletions: true,
+            triggerCharacter: this.getTsTriggerCharacter(context),
+            triggerKind: typeConverters.CompletionTriggerKind.toProtocolCompletionTriggerKind(context.triggerKind),
+        };
+        let dotAccessorContext: DotAccessorContext | undefined;
+        let response: ServerResponse.Response | undefined;
+        let duration: number | undefined;
+        let optionalReplacementRange: vscode.Range | undefined;
+        const startTime = Date.now();
+        try {
+            response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token));
+        }
+        finally {
+            duration = Date.now() - startTime;
+        }
+        if (response.type !== 'response' || !response.body) {
+            this.logCompletionsTelemetry(duration, response);
+            return undefined;
+        }
+        const isNewIdentifierLocation = response.body.isNewIdentifierLocation;
+        const isMemberCompletion = response.body.isMemberCompletion;
+        if (isMemberCompletion) {
+            const dotMatch = line.text.slice(0, position.character).match(/\??\.\s*$/) || undefined;
+            if (dotMatch) {
+                const range = new vscode.Range(position.translate({ characterDelta: -dotMatch[0].length }), position);
+                const text = document.getText(range);
+                dotAccessorContext = { range, text };
+            }
+        }
+        const isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete;
+        const entries = response.body.entries;
+        const metadata = response.metadata;
+        const defaultCommitCharacters = Object.freeze(response.body.defaultCommitCharacters);
+        if (response.body.optionalReplacementSpan) {
+            optionalReplacementRange = typeConverters.Range.fromTextSpan(response.body.optionalReplacementSpan);
+        }
+        const completionContext: CompletionContext = {
+            isNewIdentifierLocation,
+            isMemberCompletion,
+            dotAccessorContext,
+            enableCallCompletions: !completionConfiguration.completeFunctionCalls,
+            wordRange,
+            line: line.text,
+            completeFunctionCalls: completionConfiguration.completeFunctionCalls,
+            optionalReplacementRange,
+        };
+        let includesPackageJsonImport = false;
+        let includesImportStatementCompletion = false;
+        const items: MyCompletionItem[] = [];
+        for (const entry of entries) {
+            if (!shouldExcludeCompletionEntry(entry, completionConfiguration)) {
+                const item = new MyCompletionItem(position, document, entry, completionContext, metadata, this.client, defaultCommitCharacters);
+                item.command = {
+                    command: ApplyCompletionCommand.ID,
+                    title: '',
+                    arguments: [item]
+                };
+                items.push(item);
+                includesPackageJsonImport = includesPackageJsonImport || !!entry.isPackageJsonImport;
+                includesImportStatementCompletion = includesImportStatementCompletion || !!entry.isImportStatementCompletion;
+            }
+        }
+        if (duration !== undefined) {
+            this.logCompletionsTelemetry(duration, response, includesPackageJsonImport, includesImportStatementCompletion);
+        }
+        return new vscode.CompletionList(items, isIncomplete);
+    }
+    private logCompletionsTelemetry(duration: number, response: ServerResponse.Response | undefined, includesPackageJsonImport?: boolean, includesImportStatementCompletion?: boolean) {
+        /* __GDPR__
+            "completions.execute" : {
+                "owner": "mjbvz",
+                "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "flags": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "updateGraphDurationMs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "createAutoImportProviderProgramDurationMs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "includesPackageJsonImport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "includesImportStatementCompletion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        this.telemetryReporter.logTelemetry('completions.execute', {
+            duration: String(duration),
+            type: response?.type ?? 'unknown',
+            flags: response?.type === 'response' && typeof response.body?.flags === 'number' ? String(response.body.flags) : undefined,
+            count: String(response?.type === 'response' && response.body ? response.body.entries.length : 0),
+            updateGraphDurationMs: response?.type === 'response' && typeof response.performanceData?.updateGraphDurationMs === 'number'
+                ? String(response.performanceData.updateGraphDurationMs)
+                : undefined,
+            createAutoImportProviderProgramDurationMs: response?.type === 'response' && typeof response.performanceData?.createAutoImportProviderProgramDurationMs === 'number'
+                ? String(response.performanceData.createAutoImportProviderProgramDurationMs)
+                : undefined,
+            includesPackageJsonImport: includesPackageJsonImport ? 'true' : undefined,
+            includesImportStatementCompletion: includesImportStatementCompletion ? 'true' : undefined,
+        });
+    }
+    private getTsTriggerCharacter(context: vscode.CompletionContext): Proto.CompletionsTriggerCharacter | undefined {
+        switch (context.triggerCharacter) {
+            case '@': {
+                return '@';
+            }
+            case '#': {
+                return '#';
+            }
+            case ' ': {
+                return this.client.apiVersion.gte(API.v430) ? ' ' : undefined;
+            }
+            case '.':
+            case '"':
+            case '\'':
+            case '`':
+            case '/':
+            case '<': {
+                return context.triggerCharacter;
+            }
+            default: {
+                return undefined;
+            }
+        }
+    }
+    public async resolveCompletionItem(item: MyCompletionItem, token: vscode.CancellationToken): Promise {
+        await item.resolveCompletionItem(this.client, token);
+        return item;
+    }
+    private shouldTrigger(context: vscode.CompletionContext, line: vscode.TextLine, position: vscode.Position, configuration: CompletionConfiguration): boolean {
+        if (context.triggerCharacter === ' ') {
+            if (!configuration.importStatementSuggestions || this.client.apiVersion.lt(API.v430)) {
+                return false;
+            }
+            const pre = line.text.slice(0, position.character);
+            return pre === 'import';
+        }
+        return true;
+    }
 }
-
-function shouldExcludeCompletionEntry(
-	element: Proto.CompletionEntry,
-	completionConfiguration: CompletionConfiguration
-) {
-	return (
-		(!completionConfiguration.nameSuggestions && element.kind === PConst.Kind.warning)
-		|| (!completionConfiguration.pathSuggestions &&
-			(element.kind === PConst.Kind.directory || element.kind === PConst.Kind.script || element.kind === PConst.Kind.externalModuleName))
-		|| (!completionConfiguration.autoImportSuggestions && element.hasAction)
-	);
+function shouldExcludeCompletionEntry(element: Proto.CompletionEntry, completionConfiguration: CompletionConfiguration) {
+    return ((!completionConfiguration.nameSuggestions && element.kind === PConst.Kind.warning)
+        || (!completionConfiguration.pathSuggestions &&
+            (element.kind === PConst.Kind.directory || element.kind === PConst.Kind.script || element.kind === PConst.Kind.externalModuleName))
+        || (!completionConfiguration.autoImportSuggestions && element.hasAction));
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	typingsStatus: TypingsStatus,
-	fileConfigurationManager: FileConfigurationManager,
-	commandManager: CommandManager,
-	telemetryReporter: TelemetryReporter,
-	onCompletionAccepted: (item: vscode.CompletionItem) => void
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerCompletionItemProvider(selector.syntax,
-			new TypeScriptCompletionItemProvider(client, language, typingsStatus, fileConfigurationManager, commandManager, telemetryReporter, onCompletionAccepted),
-			...TypeScriptCompletionItemProvider.triggerCharacters);
-	});
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, typingsStatus: TypingsStatus, fileConfigurationManager: FileConfigurationManager, commandManager: CommandManager, telemetryReporter: TelemetryReporter, onCompletionAccepted: (item: vscode.CompletionItem) => void) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerCompletionItemProvider(selector.syntax, new TypeScriptCompletionItemProvider(client, language, typingsStatus, fileConfigurationManager, commandManager, telemetryReporter, onCompletionAccepted), ...TypeScriptCompletionItemProvider.triggerCharacters);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/copilotRelated.ts b/extensions/typescript-language-features/Source/languageFeatures/copilotRelated.ts
index f1ceac1a584a7..f994ebe712732 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/copilotRelated.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/copilotRelated.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { isSupportedLanguageMode } from '../configuration/languageIds';
 import { DocumentSelector } from '../configuration/documentSelector';
@@ -10,77 +9,74 @@ import { API } from '../tsServer/api';
 import type * as Proto from '../tsServer/protocol/protocol';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireMinVersion } from './util/dependentRegistration';
-
 const minVersion = API.v570;
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-): vscode.Disposable {
-	return conditionalRegistration([
-		requireMinVersion(client, minVersion),
-	], () => {
-		const ext = vscode.extensions.getExtension('github.copilot');
-		if (!ext) {
-			return new vscode.Disposable(() => { });
-		}
-		const disposers: vscode.Disposable[] = [];
-		ext.activate().then(() => {
-			const relatedAPI = ext.exports as {
-				registerRelatedFilesProvider(
-					providerId: { extensionId: string; languageId: string },
-					callback: (
-						uri: vscode.Uri,
-						context: { flags: Record },
-						cancellationToken: vscode.CancellationToken
-					) => Promise<{
-						entries: vscode.Uri[];
-						traits?: Array<{ name: string; value: string; includeInPrompt?: boolean; promptTextOverride?: string }>;
-					}>
-				): vscode.Disposable;
-			} | undefined;
-			if (relatedAPI?.registerRelatedFilesProvider) {
-				for (const syntax of selector.syntax) {
-					if (!syntax.language) {
-						continue;
-					}
-					const id = {
-						extensionId: 'vscode.typescript-language-features',
-						languageId: syntax.language
-					};
-					disposers.push(relatedAPI.registerRelatedFilesProvider(id, async (uri, _context, token) => {
-						let document;
-						try {
-							document = await vscode.workspace.openTextDocument(uri);
-						} catch {
-							if (!vscode.window.activeTextEditor) {
-								vscode.window.showErrorMessage(vscode.l10n.t("Related files provider failed. No active text editor."));
-								return { entries: [] };
-							}
-							// something is REALLY wrong if you can't open the active text editor's document, so don't catch that
-							document = await vscode.workspace.openTextDocument(vscode.window.activeTextEditor.document.uri);
-						}
-
-						if (!isSupportedLanguageMode(document)) {
-							vscode.window.showErrorMessage(vscode.l10n.t("Related files provider failed. Copilot requested file with unsupported language mode."));
-							return { entries: [] };
-						}
-
-						const file = client.toOpenTsFilePath(document);
-						if (!file) {
-							return { entries: [] };
-						}
-						// @ts-expect-error until ts5.7
-						const response = await client.execute('copilotRelated', { file, }, token) as Proto.CopilotRelatedResponse;
-						if (response.type !== 'response' || !response.body) {
-							return { entries: [] };
-						}
-						// @ts-expect-error until ts5.7
-						return { entries: response.body.relatedFiles.map(f => client.toResource(f)), traits: [] };
-					}));
-				}
-			}
-		});
-		return vscode.Disposable.from(...disposers);
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient): vscode.Disposable {
+    return conditionalRegistration([
+        requireMinVersion(client, minVersion),
+    ], () => {
+        const ext = vscode.extensions.getExtension('github.copilot');
+        if (!ext) {
+            return new vscode.Disposable(() => { });
+        }
+        const disposers: vscode.Disposable[] = [];
+        ext.activate().then(() => {
+            const relatedAPI = ext.exports as {
+                registerRelatedFilesProvider(providerId: {
+                    extensionId: string;
+                    languageId: string;
+                }, callback: (uri: vscode.Uri, context: {
+                    flags: Record;
+                }, cancellationToken: vscode.CancellationToken) => Promise<{
+                    entries: vscode.Uri[];
+                    traits?: Array<{
+                        name: string;
+                        value: string;
+                        includeInPrompt?: boolean;
+                        promptTextOverride?: string;
+                    }>;
+                }>): vscode.Disposable;
+            } | undefined;
+            if (relatedAPI?.registerRelatedFilesProvider) {
+                for (const syntax of selector.syntax) {
+                    if (!syntax.language) {
+                        continue;
+                    }
+                    const id = {
+                        extensionId: 'vscode.typescript-language-features',
+                        languageId: syntax.language
+                    };
+                    disposers.push(relatedAPI.registerRelatedFilesProvider(id, async (uri, _context, token) => {
+                        let document;
+                        try {
+                            document = await vscode.workspace.openTextDocument(uri);
+                        }
+                        catch {
+                            if (!vscode.window.activeTextEditor) {
+                                vscode.window.showErrorMessage(vscode.l10n.t("Related files provider failed. No active text editor."));
+                                return { entries: [] };
+                            }
+                            // something is REALLY wrong if you can't open the active text editor's document, so don't catch that
+                            document = await vscode.workspace.openTextDocument(vscode.window.activeTextEditor.document.uri);
+                        }
+                        if (!isSupportedLanguageMode(document)) {
+                            vscode.window.showErrorMessage(vscode.l10n.t("Related files provider failed. Copilot requested file with unsupported language mode."));
+                            return { entries: [] };
+                        }
+                        const file = client.toOpenTsFilePath(document);
+                        if (!file) {
+                            return { entries: [] };
+                        }
+                        // @ts-expect-error until ts5.7
+                        const response = await client.execute('copilotRelated', { file, }, token) as Proto.CopilotRelatedResponse;
+                        if (response.type !== 'response' || !response.body) {
+                            return { entries: [] };
+                        }
+                        // @ts-expect-error until ts5.7
+                        return { entries: response.body.relatedFiles.map(f => client.toResource(f)), traits: [] };
+                    }));
+                }
+            }
+        });
+        return vscode.Disposable.from(...disposers);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/copyPaste.ts b/extensions/typescript-language-features/Source/languageFeatures/copyPaste.ts
index ca3f7708397d9..3b92de7f0f293 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/copyPaste.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/copyPaste.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import * as typeConverters from '../typeConverters';
@@ -11,155 +10,118 @@ import { conditionalRegistration, requireGlobalConfiguration, requireMinVersion,
 import protocol from '../tsServer/protocol/protocol';
 import { API } from '../tsServer/api';
 import { LanguageDescription } from '../configuration/languageDescription';
-
 class CopyMetadata {
-	constructor(
-		readonly resource: vscode.Uri,
-		readonly ranges: readonly vscode.Range[],
-	) { }
-
-	toJSON() {
-		return JSON.stringify({
-			resource: this.resource.toJSON(),
-			ranges: this.ranges,
-		});
-	}
-
-	static fromJSON(str: string): CopyMetadata | undefined {
-		try {
-			const parsed = JSON.parse(str);
-			return new CopyMetadata(
-				vscode.Uri.from(parsed.resource),
-				parsed.ranges.map((r: any) => new vscode.Range(r[0].line, r[0].character, r[1].line, r[1].character)));
-		} catch {
-			// ignore
-		}
-		return undefined;
-	}
+    constructor(readonly resource: vscode.Uri, readonly ranges: readonly vscode.Range[]) { }
+    toJSON() {
+        return JSON.stringify({
+            resource: this.resource.toJSON(),
+            ranges: this.ranges,
+        });
+    }
+    static fromJSON(str: string): CopyMetadata | undefined {
+        try {
+            const parsed = JSON.parse(str);
+            return new CopyMetadata(vscode.Uri.from(parsed.resource), parsed.ranges.map((r: any) => new vscode.Range(r[0].line, r[0].character, r[1].line, r[1].character)));
+        }
+        catch {
+            // ignore
+        }
+        return undefined;
+    }
 }
-
 const settingId = 'experimental.updateImportsOnPaste';
-
 class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
-
-	static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'jsts', 'pasteWithImports');
-	static readonly metadataMimeType = 'application/vnd.code.jsts.metadata';
-
-	constructor(
-		private readonly _modeId: string,
-		private readonly _client: ITypeScriptServiceClient,
-	) { }
-
-	async prepareDocumentPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken) {
-		if (!this.isEnabled(document)) {
-			return;
-		}
-
-		const file = this._client.toOpenTsFilePath(document);
-		if (!file) {
-			return;
-		}
-
-		const response = await this._client.execute('preparePasteEdits', {
-			file,
-			copiedTextSpan: ranges.map(typeConverters.Range.toTextSpan),
-		}, token);
-		if (token.isCancellationRequested || response.type !== 'response' || !response.body) {
-			return;
-		}
-
-		dataTransfer.set(DocumentPasteProvider.metadataMimeType,
-			new vscode.DataTransferItem(new CopyMetadata(document.uri, ranges).toJSON()));
-	}
-
-	async provideDocumentPasteEdits(
-		document: vscode.TextDocument,
-		ranges: readonly vscode.Range[],
-		dataTransfer: vscode.DataTransfer,
-		_context: vscode.DocumentPasteEditContext,
-		token: vscode.CancellationToken,
-	): Promise {
-		if (!this.isEnabled(document)) {
-			return;
-		}
-
-		const file = this._client.toOpenTsFilePath(document);
-		if (!file) {
-			return;
-		}
-
-		const text = await dataTransfer.get('text/plain')?.asString();
-		if (!text || token.isCancellationRequested) {
-			return;
-		}
-
-		// Get optional metadata
-		const metadata = await this.extractMetadata(dataTransfer, token);
-		if (token.isCancellationRequested) {
-			return;
-		}
-
-		let copiedFrom: {
-			file: string;
-			spans: protocol.TextSpan[];
-		} | undefined;
-		if (metadata) {
-			const spans = metadata.ranges.map(typeConverters.Range.toTextSpan);
-			const copyFile = this._client.toTsFilePath(metadata.resource);
-			if (copyFile) {
-				copiedFrom = { file: copyFile, spans };
-			}
-		}
-
-		if (copiedFrom?.file === file) {
-			return;
-		}
-
-		const response = await this._client.interruptGetErr(() => this._client.execute('getPasteEdits', {
-			file,
-			// TODO: only supports a single paste for now
-			pastedText: [text],
-			pasteLocations: ranges.map(typeConverters.Range.toTextSpan),
-			copiedFrom
-		}, token));
-		if (response.type !== 'response' || !response.body?.edits.length || token.isCancellationRequested) {
-			return;
-		}
-
-		const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"), DocumentPasteProvider.kind);
-		const additionalEdit = new vscode.WorkspaceEdit();
-		for (const edit of response.body.edits) {
-			additionalEdit.set(this._client.toResource(edit.fileName), edit.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
-		}
-		edit.additionalEdit = additionalEdit;
-		return [edit];
-	}
-
-	private async extractMetadata(dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
-		const metadata = await dataTransfer.get(DocumentPasteProvider.metadataMimeType)?.asString();
-		if (token.isCancellationRequested) {
-			return undefined;
-		}
-
-		return metadata ? CopyMetadata.fromJSON(metadata) : undefined;
-	}
-
-	private isEnabled(document: vscode.TextDocument) {
-		const config = vscode.workspace.getConfiguration(this._modeId, document.uri);
-		return config.get(settingId, false);
-	}
+    static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'jsts', 'pasteWithImports');
+    static readonly metadataMimeType = 'application/vnd.code.jsts.metadata';
+    constructor(private readonly _modeId: string, private readonly _client: ITypeScriptServiceClient) { }
+    async prepareDocumentPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken) {
+        if (!this.isEnabled(document)) {
+            return;
+        }
+        const file = this._client.toOpenTsFilePath(document);
+        if (!file) {
+            return;
+        }
+        const response = await this._client.execute('preparePasteEdits', {
+            file,
+            copiedTextSpan: ranges.map(typeConverters.Range.toTextSpan),
+        }, token);
+        if (token.isCancellationRequested || response.type !== 'response' || !response.body) {
+            return;
+        }
+        dataTransfer.set(DocumentPasteProvider.metadataMimeType, new vscode.DataTransferItem(new CopyMetadata(document.uri, ranges).toJSON()));
+    }
+    async provideDocumentPasteEdits(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, _context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken): Promise {
+        if (!this.isEnabled(document)) {
+            return;
+        }
+        const file = this._client.toOpenTsFilePath(document);
+        if (!file) {
+            return;
+        }
+        const text = await dataTransfer.get('text/plain')?.asString();
+        if (!text || token.isCancellationRequested) {
+            return;
+        }
+        // Get optional metadata
+        const metadata = await this.extractMetadata(dataTransfer, token);
+        if (token.isCancellationRequested) {
+            return;
+        }
+        let copiedFrom: {
+            file: string;
+            spans: protocol.TextSpan[];
+        } | undefined;
+        if (metadata) {
+            const spans = metadata.ranges.map(typeConverters.Range.toTextSpan);
+            const copyFile = this._client.toTsFilePath(metadata.resource);
+            if (copyFile) {
+                copiedFrom = { file: copyFile, spans };
+            }
+        }
+        if (copiedFrom?.file === file) {
+            return;
+        }
+        const response = await this._client.interruptGetErr(() => this._client.execute('getPasteEdits', {
+            file,
+            // TODO: only supports a single paste for now
+            pastedText: [text],
+            pasteLocations: ranges.map(typeConverters.Range.toTextSpan),
+            copiedFrom
+        }, token));
+        if (response.type !== 'response' || !response.body?.edits.length || token.isCancellationRequested) {
+            return;
+        }
+        const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"), DocumentPasteProvider.kind);
+        const additionalEdit = new vscode.WorkspaceEdit();
+        for (const edit of response.body.edits) {
+            additionalEdit.set(this._client.toResource(edit.fileName), edit.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
+        }
+        edit.additionalEdit = additionalEdit;
+        return [edit];
+    }
+    private async extractMetadata(dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
+        const metadata = await dataTransfer.get(DocumentPasteProvider.metadataMimeType)?.asString();
+        if (token.isCancellationRequested) {
+            return undefined;
+        }
+        return metadata ? CopyMetadata.fromJSON(metadata) : undefined;
+    }
+    private isEnabled(document: vscode.TextDocument) {
+        const config = vscode.workspace.getConfiguration(this._modeId, document.uri);
+        return config.get(settingId, false);
+    }
 }
-
 export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-		requireMinVersion(client, API.v570),
-		requireGlobalConfiguration(language.id, settingId),
-	], () => {
-		return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(language.id, client), {
-			providedPasteEditKinds: [DocumentPasteProvider.kind],
-			copyMimeTypes: [DocumentPasteProvider.metadataMimeType],
-			pasteMimeTypes: [DocumentPasteProvider.metadataMimeType],
-		});
-	});
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+        requireMinVersion(client, API.v570),
+        requireGlobalConfiguration(language.id, settingId),
+    ], () => {
+        return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(language.id, client), {
+            providedPasteEditKinds: [DocumentPasteProvider.kind],
+            copyMimeTypes: [DocumentPasteProvider.metadataMimeType],
+            pasteMimeTypes: [DocumentPasteProvider.metadataMimeType],
+        });
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/definitionProviderBase.ts b/extensions/typescript-language-features/Source/languageFeatures/definitionProviderBase.ts
index 7ebf7c892883f..e448a5d3eec0e 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/definitionProviderBase.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/definitionProviderBase.ts
@@ -2,35 +2,21 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
-
 export default class TypeScriptDefinitionProviderBase {
-	constructor(
-		protected readonly client: ITypeScriptServiceClient
-	) { }
-
-	protected async getSymbolLocations(
-		definitionType: 'definition' | 'implementation' | 'typeDefinition',
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
-		const response = await this.client.execute(definitionType, args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		return response.body.map(location =>
-			typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location));
-	}
+    constructor(protected readonly client: ITypeScriptServiceClient) { }
+    protected async getSymbolLocations(definitionType: 'definition' | 'implementation' | 'typeDefinition', document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
+        const response = await this.client.execute(definitionType, args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        return response.body.map(location => typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/definitions.ts b/extensions/typescript-language-features/Source/languageFeatures/definitions.ts
index 76c5818be6cc0..0d571a604e144 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/definitions.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/definitions.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { API } from '../tsServer/api';
@@ -10,63 +9,48 @@ import * as typeConverters from '../typeConverters';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import DefinitionProviderBase from './definitionProviderBase';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
 export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements vscode.DefinitionProvider {
-
-	public async provideDefinition(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
-		const response = await this.client.execute('definitionAndBoundSpan', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined;
-		let definitions = response.body.definitions;
-
-		if (vscode.workspace.getConfiguration(document.languageId).get('preferGoToSourceDefinition', false) && this.client.apiVersion.gte(API.v470)) {
-			const sourceDefinitionsResponse = await this.client.execute('findSourceDefinition', args, token);
-			if (sourceDefinitionsResponse.type === 'response' && sourceDefinitionsResponse.body?.length) {
-				definitions = sourceDefinitionsResponse.body;
-			}
-		}
-
-		return definitions
-			.map((location): vscode.DefinitionLink => {
-				const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location);
-				if (location.contextStart && location.contextEnd) {
-					return {
-						originSelectionRange: span,
-						targetRange: typeConverters.Range.fromLocations(location.contextStart, location.contextEnd),
-						targetUri: target.uri,
-						targetSelectionRange: target.range,
-					};
-				}
-				return {
-					originSelectionRange: span,
-					targetRange: target.range,
-					targetUri: target.uri
-				};
-			});
-	}
+    public async provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return undefined;
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
+        const response = await this.client.execute('definitionAndBoundSpan', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined;
+        let definitions = response.body.definitions;
+        if (vscode.workspace.getConfiguration(document.languageId).get('preferGoToSourceDefinition', false) && this.client.apiVersion.gte(API.v470)) {
+            const sourceDefinitionsResponse = await this.client.execute('findSourceDefinition', args, token);
+            if (sourceDefinitionsResponse.type === 'response' && sourceDefinitionsResponse.body?.length) {
+                definitions = sourceDefinitionsResponse.body;
+            }
+        }
+        return definitions
+            .map((location): vscode.DefinitionLink => {
+            const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location);
+            if (location.contextStart && location.contextEnd) {
+                return {
+                    originSelectionRange: span,
+                    targetRange: typeConverters.Range.fromLocations(location.contextStart, location.contextEnd),
+                    targetUri: target.uri,
+                    targetSelectionRange: target.range,
+                };
+            }
+            return {
+                originSelectionRange: span,
+                targetRange: target.range,
+                targetUri: target.uri
+            };
+        });
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerDefinitionProvider(selector.syntax,
-			new TypeScriptDefinitionProvider(client));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerDefinitionProvider(selector.syntax, new TypeScriptDefinitionProvider(client));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/diagnostics.ts b/extensions/typescript-language-features/Source/languageFeatures/diagnostics.ts
index d86f64637c63c..2720a5e9979fb 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/diagnostics.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/diagnostics.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TypeScriptServiceConfiguration } from '../configuration/configuration';
 import { DiagnosticLanguage } from '../configuration/languageDescription';
@@ -12,405 +11,320 @@ import * as arrays from '../utils/arrays';
 import { Disposable } from '../utils/dispose';
 import { equals } from '../utils/objects';
 import { ResourceMap } from '../utils/resourceMap';
-
 function diagnosticsEquals(a: vscode.Diagnostic, b: vscode.Diagnostic): boolean {
-	if (a === b) {
-		return true;
-	}
-
-	return a.code === b.code
-		&& a.message === b.message
-		&& a.severity === b.severity
-		&& a.source === b.source
-		&& a.range.isEqual(b.range)
-		&& arrays.equals(a.relatedInformation || arrays.empty, b.relatedInformation || arrays.empty, (a, b) => {
-			return a.message === b.message
-				&& a.location.range.isEqual(b.location.range)
-				&& a.location.uri.fsPath === b.location.uri.fsPath;
-		})
-		&& arrays.equals(a.tags || arrays.empty, b.tags || arrays.empty);
+    if (a === b) {
+        return true;
+    }
+    return a.code === b.code
+        && a.message === b.message
+        && a.severity === b.severity
+        && a.source === b.source
+        && a.range.isEqual(b.range)
+        && arrays.equals(a.relatedInformation || arrays.empty, b.relatedInformation || arrays.empty, (a, b) => {
+            return a.message === b.message
+                && a.location.range.isEqual(b.location.range)
+                && a.location.uri.fsPath === b.location.uri.fsPath;
+        })
+        && arrays.equals(a.tags || arrays.empty, b.tags || arrays.empty);
 }
-
 export const enum DiagnosticKind {
-	Syntax,
-	Semantic,
-	Suggestion,
-	RegionSemantic,
+    Syntax,
+    Semantic,
+    Suggestion,
+    RegionSemantic
 }
-
 class FileDiagnostics {
-
-	private readonly _diagnostics = new Map>();
-
-	constructor(
-		public readonly file: vscode.Uri,
-		public language: DiagnosticLanguage
-	) { }
-
-	public updateDiagnostics(
-		language: DiagnosticLanguage,
-		kind: DiagnosticKind,
-		diagnostics: ReadonlyArray,
-		ranges: ReadonlyArray | undefined
-	): boolean {
-		if (language !== this.language) {
-			this._diagnostics.clear();
-			this.language = language;
-		}
-
-		const existing = this._diagnostics.get(kind);
-		if (existing?.length === 0 && diagnostics.length === 0) {
-			// No need to update
-			return false;
-		}
-
-		if (kind === DiagnosticKind.RegionSemantic) {
-			return this.updateRegionDiagnostics(diagnostics, ranges!);
-		}
-		this._diagnostics.set(kind, diagnostics);
-		return true;
-	}
-
-	public getAllDiagnostics(settings: DiagnosticSettings): vscode.Diagnostic[] {
-		if (!settings.getValidate(this.language)) {
-			return [];
-		}
-
-		return [
-			...this.get(DiagnosticKind.Syntax),
-			...this.get(DiagnosticKind.Semantic),
-			...this.getSuggestionDiagnostics(settings),
-		];
-	}
-
-	public delete(toDelete: vscode.Diagnostic): void {
-		for (const [type, diags] of this._diagnostics) {
-			this._diagnostics.set(type, diags.filter(diag => !diagnosticsEquals(diag, toDelete)));
-		}
-	}
-
-	/**
-	 * @param ranges The ranges whose diagnostics were updated.
-	 */
-	private updateRegionDiagnostics(
-		diagnostics: ReadonlyArray,
-		ranges: ReadonlyArray): boolean {
-		if (!this._diagnostics.get(DiagnosticKind.Semantic)) {
-			this._diagnostics.set(DiagnosticKind.Semantic, diagnostics);
-			return true;
-		}
-		const oldDiagnostics = this._diagnostics.get(DiagnosticKind.Semantic)!;
-		const newDiagnostics = oldDiagnostics.filter(diag => !ranges.some(range => diag.range.intersection(range)));
-		newDiagnostics.push(...diagnostics);
-		this._diagnostics.set(DiagnosticKind.Semantic, newDiagnostics);
-		return true;
-	}
-
-	private getSuggestionDiagnostics(settings: DiagnosticSettings) {
-		const enableSuggestions = settings.getEnableSuggestions(this.language);
-		return this.get(DiagnosticKind.Suggestion).filter(x => {
-			if (!enableSuggestions) {
-				// Still show unused
-				return x.tags && (x.tags.includes(vscode.DiagnosticTag.Unnecessary) || x.tags.includes(vscode.DiagnosticTag.Deprecated));
-			}
-			return true;
-		});
-	}
-
-	private get(kind: DiagnosticKind): ReadonlyArray {
-		return this._diagnostics.get(kind) || [];
-	}
+    private readonly _diagnostics = new Map>();
+    constructor(public readonly file: vscode.Uri, public language: DiagnosticLanguage) { }
+    public updateDiagnostics(language: DiagnosticLanguage, kind: DiagnosticKind, diagnostics: ReadonlyArray, ranges: ReadonlyArray | undefined): boolean {
+        if (language !== this.language) {
+            this._diagnostics.clear();
+            this.language = language;
+        }
+        const existing = this._diagnostics.get(kind);
+        if (existing?.length === 0 && diagnostics.length === 0) {
+            // No need to update
+            return false;
+        }
+        if (kind === DiagnosticKind.RegionSemantic) {
+            return this.updateRegionDiagnostics(diagnostics, ranges!);
+        }
+        this._diagnostics.set(kind, diagnostics);
+        return true;
+    }
+    public getAllDiagnostics(settings: DiagnosticSettings): vscode.Diagnostic[] {
+        if (!settings.getValidate(this.language)) {
+            return [];
+        }
+        return [
+            ...this.get(DiagnosticKind.Syntax),
+            ...this.get(DiagnosticKind.Semantic),
+            ...this.getSuggestionDiagnostics(settings),
+        ];
+    }
+    public delete(toDelete: vscode.Diagnostic): void {
+        for (const [type, diags] of this._diagnostics) {
+            this._diagnostics.set(type, diags.filter(diag => !diagnosticsEquals(diag, toDelete)));
+        }
+    }
+    /**
+     * @param ranges The ranges whose diagnostics were updated.
+     */
+    private updateRegionDiagnostics(diagnostics: ReadonlyArray, ranges: ReadonlyArray): boolean {
+        if (!this._diagnostics.get(DiagnosticKind.Semantic)) {
+            this._diagnostics.set(DiagnosticKind.Semantic, diagnostics);
+            return true;
+        }
+        const oldDiagnostics = this._diagnostics.get(DiagnosticKind.Semantic)!;
+        const newDiagnostics = oldDiagnostics.filter(diag => !ranges.some(range => diag.range.intersection(range)));
+        newDiagnostics.push(...diagnostics);
+        this._diagnostics.set(DiagnosticKind.Semantic, newDiagnostics);
+        return true;
+    }
+    private getSuggestionDiagnostics(settings: DiagnosticSettings) {
+        const enableSuggestions = settings.getEnableSuggestions(this.language);
+        return this.get(DiagnosticKind.Suggestion).filter(x => {
+            if (!enableSuggestions) {
+                // Still show unused
+                return x.tags && (x.tags.includes(vscode.DiagnosticTag.Unnecessary) || x.tags.includes(vscode.DiagnosticTag.Deprecated));
+            }
+            return true;
+        });
+    }
+    private get(kind: DiagnosticKind): ReadonlyArray {
+        return this._diagnostics.get(kind) || [];
+    }
 }
-
 interface LanguageDiagnosticSettings {
-	readonly validate: boolean;
-	readonly enableSuggestions: boolean;
+    readonly validate: boolean;
+    readonly enableSuggestions: boolean;
 }
-
 function areLanguageDiagnosticSettingsEqual(currentSettings: LanguageDiagnosticSettings, newSettings: LanguageDiagnosticSettings): boolean {
-	return currentSettings.validate === newSettings.validate
-		&& currentSettings.enableSuggestions === newSettings.enableSuggestions;
+    return currentSettings.validate === newSettings.validate
+        && currentSettings.enableSuggestions === newSettings.enableSuggestions;
 }
-
 class DiagnosticSettings {
-	private static readonly defaultSettings: LanguageDiagnosticSettings = {
-		validate: true,
-		enableSuggestions: true
-	};
-
-	private readonly _languageSettings = new Map();
-
-	public getValidate(language: DiagnosticLanguage): boolean {
-		return this.get(language).validate;
-	}
-
-	public setValidate(language: DiagnosticLanguage, value: boolean): boolean {
-		return this.update(language, settings => ({
-			validate: value,
-			enableSuggestions: settings.enableSuggestions,
-		}));
-	}
-
-	public getEnableSuggestions(language: DiagnosticLanguage): boolean {
-		return this.get(language).enableSuggestions;
-	}
-
-	public setEnableSuggestions(language: DiagnosticLanguage, value: boolean): boolean {
-		return this.update(language, settings => ({
-			validate: settings.validate,
-			enableSuggestions: value
-		}));
-	}
-
-	private get(language: DiagnosticLanguage): LanguageDiagnosticSettings {
-		return this._languageSettings.get(language) || DiagnosticSettings.defaultSettings;
-	}
-
-	private update(language: DiagnosticLanguage, f: (x: LanguageDiagnosticSettings) => LanguageDiagnosticSettings): boolean {
-		const currentSettings = this.get(language);
-		const newSettings = f(currentSettings);
-		this._languageSettings.set(language, newSettings);
-		return !areLanguageDiagnosticSettingsEqual(currentSettings, newSettings);
-	}
+    private static readonly defaultSettings: LanguageDiagnosticSettings = {
+        validate: true,
+        enableSuggestions: true
+    };
+    private readonly _languageSettings = new Map();
+    public getValidate(language: DiagnosticLanguage): boolean {
+        return this.get(language).validate;
+    }
+    public setValidate(language: DiagnosticLanguage, value: boolean): boolean {
+        return this.update(language, settings => ({
+            validate: value,
+            enableSuggestions: settings.enableSuggestions,
+        }));
+    }
+    public getEnableSuggestions(language: DiagnosticLanguage): boolean {
+        return this.get(language).enableSuggestions;
+    }
+    public setEnableSuggestions(language: DiagnosticLanguage, value: boolean): boolean {
+        return this.update(language, settings => ({
+            validate: settings.validate,
+            enableSuggestions: value
+        }));
+    }
+    private get(language: DiagnosticLanguage): LanguageDiagnosticSettings {
+        return this._languageSettings.get(language) || DiagnosticSettings.defaultSettings;
+    }
+    private update(language: DiagnosticLanguage, f: (x: LanguageDiagnosticSettings) => LanguageDiagnosticSettings): boolean {
+        const currentSettings = this.get(language);
+        const newSettings = f(currentSettings);
+        this._languageSettings.set(language, newSettings);
+        return !areLanguageDiagnosticSettingsEqual(currentSettings, newSettings);
+    }
 }
-
 interface DiagnosticPerformanceData extends TsDiagnosticPerformanceData {
-	fileLineCount?: number;
+    fileLineCount?: number;
 }
-
 class DiagnosticsTelemetryManager extends Disposable {
-
-	private readonly _diagnosticCodesMap = new Map();
-	private readonly _diagnosticSnapshotsMap = new ResourceMap(uri => uri.toString(), { onCaseInsensitiveFileSystem: false });
-	private _timeout: NodeJS.Timeout | undefined;
-	private _telemetryEmitter: NodeJS.Timeout | undefined;
-
-	constructor(
-		private readonly _telemetryReporter: TelemetryReporter,
-		private readonly _diagnosticsCollection: vscode.DiagnosticCollection,
-	) {
-		super();
-		this._register(vscode.workspace.onDidChangeTextDocument(e => {
-			if (e.document.languageId === 'typescript' || e.document.languageId === 'typescriptreact') {
-				this._updateAllDiagnosticCodesAfterTimeout();
-			}
-		}));
-		this._updateAllDiagnosticCodesAfterTimeout();
-		this._registerTelemetryEventEmitter();
-	}
-
-	public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void {
-		for (const data of performanceData) {
-			/* __GDPR__
-				"diagnostics.performance" : {
-					"owner": "mjbvz",
-					"syntaxDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
-					"semanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
-					"suggestionDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
-					"regionSemanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
-					"fileLineCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
-					"${include}": [
-						"${TypeScriptCommonProperties}"
-					]
-				}
-			*/
-			this._telemetryReporter.logTelemetry('diagnostics.performance',
-				{
-					syntaxDiagDuration: data.syntaxDiag,
-					semanticDiagDuration: data.semanticDiag,
-					suggestionDiagDuration: data.suggestionDiag,
-					regionSemanticDiagDuration: data.regionSemanticDiag,
-					fileLineCount: data.fileLineCount,
-				},
-			);
-		}
-	}
-
-	private _updateAllDiagnosticCodesAfterTimeout() {
-		clearTimeout(this._timeout);
-		this._timeout = setTimeout(() => this._updateDiagnosticCodes(), 5000);
-	}
-
-	private _increaseDiagnosticCodeCount(code: string | number | undefined) {
-		if (code === undefined) {
-			return;
-		}
-		this._diagnosticCodesMap.set(Number(code), (this._diagnosticCodesMap.get(Number(code)) || 0) + 1);
-	}
-
-	private _updateDiagnosticCodes() {
-		this._diagnosticsCollection.forEach((uri, diagnostics) => {
-			const previousDiagnostics = this._diagnosticSnapshotsMap.get(uri);
-			this._diagnosticSnapshotsMap.set(uri, diagnostics);
-			const diagnosticsDiff = diagnostics.filter((diagnostic) => !previousDiagnostics?.some((previousDiagnostic) => equals(diagnostic, previousDiagnostic)));
-			diagnosticsDiff.forEach((diagnostic) => {
-				const code = diagnostic.code;
-				this._increaseDiagnosticCodeCount(typeof code === 'string' || typeof code === 'number' ? code : code?.value);
-			});
-		});
-	}
-
-	private _registerTelemetryEventEmitter() {
-		this._telemetryEmitter = setInterval(() => {
-			if (this._diagnosticCodesMap.size > 0) {
-				let diagnosticCodes = '';
-				this._diagnosticCodesMap.forEach((value, key) => {
-					diagnosticCodes += `${key}:${value},`;
-				});
-				this._diagnosticCodesMap.clear();
-				/* __GDPR__
-					"typescript.diagnostics" : {
-						"owner": "aiday-mar",
-						"diagnosticCodes" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-						"${include}": [
-							"${TypeScriptCommonProperties}"
-						]
-					}
-				*/
-				this._telemetryReporter.logTelemetry('typescript.diagnostics', {
-					diagnosticCodes: diagnosticCodes
-				});
-			}
-		}, 5 * 60 * 1000); // 5 minutes
-	}
-
-	override dispose() {
-		super.dispose();
-		clearTimeout(this._timeout);
-		clearInterval(this._telemetryEmitter);
-	}
+    private readonly _diagnosticCodesMap = new Map();
+    private readonly _diagnosticSnapshotsMap = new ResourceMap(uri => uri.toString(), { onCaseInsensitiveFileSystem: false });
+    private _timeout: NodeJS.Timeout | undefined;
+    private _telemetryEmitter: NodeJS.Timeout | undefined;
+    constructor(private readonly _telemetryReporter: TelemetryReporter, private readonly _diagnosticsCollection: vscode.DiagnosticCollection) {
+        super();
+        this._register(vscode.workspace.onDidChangeTextDocument(e => {
+            if (e.document.languageId === 'typescript' || e.document.languageId === 'typescriptreact') {
+                this._updateAllDiagnosticCodesAfterTimeout();
+            }
+        }));
+        this._updateAllDiagnosticCodesAfterTimeout();
+        this._registerTelemetryEventEmitter();
+    }
+    public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void {
+        for (const data of performanceData) {
+            /* __GDPR__
+                "diagnostics.performance" : {
+                    "owner": "mjbvz",
+                    "syntaxDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+                    "semanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+                    "suggestionDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+                    "regionSemanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+                    "fileLineCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+                    "${include}": [
+                        "${TypeScriptCommonProperties}"
+                    ]
+                }
+            */
+            this._telemetryReporter.logTelemetry('diagnostics.performance', {
+                syntaxDiagDuration: data.syntaxDiag,
+                semanticDiagDuration: data.semanticDiag,
+                suggestionDiagDuration: data.suggestionDiag,
+                regionSemanticDiagDuration: data.regionSemanticDiag,
+                fileLineCount: data.fileLineCount,
+            });
+        }
+    }
+    private _updateAllDiagnosticCodesAfterTimeout() {
+        clearTimeout(this._timeout);
+        this._timeout = setTimeout(() => this._updateDiagnosticCodes(), 5000);
+    }
+    private _increaseDiagnosticCodeCount(code: string | number | undefined) {
+        if (code === undefined) {
+            return;
+        }
+        this._diagnosticCodesMap.set(Number(code), (this._diagnosticCodesMap.get(Number(code)) || 0) + 1);
+    }
+    private _updateDiagnosticCodes() {
+        this._diagnosticsCollection.forEach((uri, diagnostics) => {
+            const previousDiagnostics = this._diagnosticSnapshotsMap.get(uri);
+            this._diagnosticSnapshotsMap.set(uri, diagnostics);
+            const diagnosticsDiff = diagnostics.filter((diagnostic) => !previousDiagnostics?.some((previousDiagnostic) => equals(diagnostic, previousDiagnostic)));
+            diagnosticsDiff.forEach((diagnostic) => {
+                const code = diagnostic.code;
+                this._increaseDiagnosticCodeCount(typeof code === 'string' || typeof code === 'number' ? code : code?.value);
+            });
+        });
+    }
+    private _registerTelemetryEventEmitter() {
+        this._telemetryEmitter = setInterval(() => {
+            if (this._diagnosticCodesMap.size > 0) {
+                let diagnosticCodes = '';
+                this._diagnosticCodesMap.forEach((value, key) => {
+                    diagnosticCodes += `${key}:${value},`;
+                });
+                this._diagnosticCodesMap.clear();
+                /* __GDPR__
+                    "typescript.diagnostics" : {
+                        "owner": "aiday-mar",
+                        "diagnosticCodes" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                        "${include}": [
+                            "${TypeScriptCommonProperties}"
+                        ]
+                    }
+                */
+                this._telemetryReporter.logTelemetry('typescript.diagnostics', {
+                    diagnosticCodes: diagnosticCodes
+                });
+            }
+        }, 5 * 60 * 1000); // 5 minutes
+    }
+    override dispose() {
+        super.dispose();
+        clearTimeout(this._timeout);
+        clearInterval(this._telemetryEmitter);
+    }
 }
-
 export class DiagnosticsManager extends Disposable {
-	private readonly _diagnostics: ResourceMap;
-	private readonly _settings = new DiagnosticSettings();
-	private readonly _currentDiagnostics: vscode.DiagnosticCollection;
-	private readonly _pendingUpdates: ResourceMap;
-
-	private readonly _updateDelay = 50;
-
-	private readonly _diagnosticsTelemetryManager: DiagnosticsTelemetryManager | undefined;
-
-	constructor(
-		owner: string,
-		configuration: TypeScriptServiceConfiguration,
-		telemetryReporter: TelemetryReporter,
-		onCaseInsensitiveFileSystem: boolean
-	) {
-		super();
-		this._diagnostics = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
-		this._pendingUpdates = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
-
-		this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner));
-		// Here we are selecting only 1 user out of 1000 to send telemetry diagnostics
-		if (Math.random() * 1000 <= 1 || configuration.enableDiagnosticsTelemetry) {
-			this._diagnosticsTelemetryManager = this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics));
-		}
-	}
-
-	public override dispose() {
-		super.dispose();
-
-		for (const value of this._pendingUpdates.values()) {
-			clearTimeout(value);
-		}
-		this._pendingUpdates.clear();
-	}
-
-	public reInitialize(): void {
-		this._currentDiagnostics.clear();
-		this._diagnostics.clear();
-	}
-
-	public setValidate(language: DiagnosticLanguage, value: boolean) {
-		const didUpdate = this._settings.setValidate(language, value);
-		if (didUpdate) {
-			this.rebuildAll();
-		}
-	}
-
-	public setEnableSuggestions(language: DiagnosticLanguage, value: boolean) {
-		const didUpdate = this._settings.setEnableSuggestions(language, value);
-		if (didUpdate) {
-			this.rebuildAll();
-		}
-	}
-
-	public updateDiagnostics(
-		file: vscode.Uri,
-		language: DiagnosticLanguage,
-		kind: DiagnosticKind,
-		diagnostics: ReadonlyArray,
-		ranges: ReadonlyArray | undefined,
-	): void {
-		let didUpdate = false;
-		const entry = this._diagnostics.get(file);
-		if (entry) {
-			didUpdate = entry.updateDiagnostics(language, kind, diagnostics, ranges);
-		} else if (diagnostics.length) {
-			const fileDiagnostics = new FileDiagnostics(file, language);
-			fileDiagnostics.updateDiagnostics(language, kind, diagnostics, ranges);
-			this._diagnostics.set(file, fileDiagnostics);
-			didUpdate = true;
-		}
-
-		if (didUpdate) {
-			this.scheduleDiagnosticsUpdate(file);
-		}
-	}
-
-	public configFileDiagnosticsReceived(
-		file: vscode.Uri,
-		diagnostics: ReadonlyArray
-	): void {
-		this._currentDiagnostics.set(file, diagnostics);
-	}
-
-	public deleteAllDiagnosticsInFile(resource: vscode.Uri): void {
-		this._currentDiagnostics.delete(resource);
-		this._diagnostics.delete(resource);
-	}
-
-	public deleteDiagnostic(resource: vscode.Uri, diagnostic: vscode.Diagnostic): void {
-		const fileDiagnostics = this._diagnostics.get(resource);
-		if (fileDiagnostics) {
-			fileDiagnostics.delete(diagnostic);
-			this.rebuildFile(fileDiagnostics);
-		}
-	}
-
-	public getDiagnostics(file: vscode.Uri): ReadonlyArray {
-		return this._currentDiagnostics.get(file) || [];
-	}
-
-	public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void {
-		this._diagnosticsTelemetryManager?.logDiagnosticsPerformanceTelemetry(performanceData);
-	}
-
-	private scheduleDiagnosticsUpdate(file: vscode.Uri) {
-		if (!this._pendingUpdates.has(file)) {
-			this._pendingUpdates.set(file, setTimeout(() => this.updateCurrentDiagnostics(file), this._updateDelay));
-		}
-	}
-
-	private updateCurrentDiagnostics(file: vscode.Uri): void {
-		if (this._pendingUpdates.has(file)) {
-			clearTimeout(this._pendingUpdates.get(file));
-			this._pendingUpdates.delete(file);
-		}
-
-		const fileDiagnostics = this._diagnostics.get(file);
-		this._currentDiagnostics.set(file, fileDiagnostics ? fileDiagnostics.getAllDiagnostics(this._settings) : []);
-	}
-
-	private rebuildAll(): void {
-		this._currentDiagnostics.clear();
-		for (const fileDiagnostic of this._diagnostics.values()) {
-			this.rebuildFile(fileDiagnostic);
-		}
-	}
-
-	private rebuildFile(fileDiagnostic: FileDiagnostics) {
-		this._currentDiagnostics.set(fileDiagnostic.file, fileDiagnostic.getAllDiagnostics(this._settings));
-	}
+    private readonly _diagnostics: ResourceMap;
+    private readonly _settings = new DiagnosticSettings();
+    private readonly _currentDiagnostics: vscode.DiagnosticCollection;
+    private readonly _pendingUpdates: ResourceMap;
+    private readonly _updateDelay = 50;
+    private readonly _diagnosticsTelemetryManager: DiagnosticsTelemetryManager | undefined;
+    constructor(owner: string, configuration: TypeScriptServiceConfiguration, telemetryReporter: TelemetryReporter, onCaseInsensitiveFileSystem: boolean) {
+        super();
+        this._diagnostics = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
+        this._pendingUpdates = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
+        this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner));
+        // Here we are selecting only 1 user out of 1000 to send telemetry diagnostics
+        if (Math.random() * 1000 <= 1 || configuration.enableDiagnosticsTelemetry) {
+            this._diagnosticsTelemetryManager = this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics));
+        }
+    }
+    public override dispose() {
+        super.dispose();
+        for (const value of this._pendingUpdates.values()) {
+            clearTimeout(value);
+        }
+        this._pendingUpdates.clear();
+    }
+    public reInitialize(): void {
+        this._currentDiagnostics.clear();
+        this._diagnostics.clear();
+    }
+    public setValidate(language: DiagnosticLanguage, value: boolean) {
+        const didUpdate = this._settings.setValidate(language, value);
+        if (didUpdate) {
+            this.rebuildAll();
+        }
+    }
+    public setEnableSuggestions(language: DiagnosticLanguage, value: boolean) {
+        const didUpdate = this._settings.setEnableSuggestions(language, value);
+        if (didUpdate) {
+            this.rebuildAll();
+        }
+    }
+    public updateDiagnostics(file: vscode.Uri, language: DiagnosticLanguage, kind: DiagnosticKind, diagnostics: ReadonlyArray, ranges: ReadonlyArray | undefined): void {
+        let didUpdate = false;
+        const entry = this._diagnostics.get(file);
+        if (entry) {
+            didUpdate = entry.updateDiagnostics(language, kind, diagnostics, ranges);
+        }
+        else if (diagnostics.length) {
+            const fileDiagnostics = new FileDiagnostics(file, language);
+            fileDiagnostics.updateDiagnostics(language, kind, diagnostics, ranges);
+            this._diagnostics.set(file, fileDiagnostics);
+            didUpdate = true;
+        }
+        if (didUpdate) {
+            this.scheduleDiagnosticsUpdate(file);
+        }
+    }
+    public configFileDiagnosticsReceived(file: vscode.Uri, diagnostics: ReadonlyArray): void {
+        this._currentDiagnostics.set(file, diagnostics);
+    }
+    public deleteAllDiagnosticsInFile(resource: vscode.Uri): void {
+        this._currentDiagnostics.delete(resource);
+        this._diagnostics.delete(resource);
+    }
+    public deleteDiagnostic(resource: vscode.Uri, diagnostic: vscode.Diagnostic): void {
+        const fileDiagnostics = this._diagnostics.get(resource);
+        if (fileDiagnostics) {
+            fileDiagnostics.delete(diagnostic);
+            this.rebuildFile(fileDiagnostics);
+        }
+    }
+    public getDiagnostics(file: vscode.Uri): ReadonlyArray {
+        return this._currentDiagnostics.get(file) || [];
+    }
+    public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void {
+        this._diagnosticsTelemetryManager?.logDiagnosticsPerformanceTelemetry(performanceData);
+    }
+    private scheduleDiagnosticsUpdate(file: vscode.Uri) {
+        if (!this._pendingUpdates.has(file)) {
+            this._pendingUpdates.set(file, setTimeout(() => this.updateCurrentDiagnostics(file), this._updateDelay));
+        }
+    }
+    private updateCurrentDiagnostics(file: vscode.Uri): void {
+        if (this._pendingUpdates.has(file)) {
+            clearTimeout(this._pendingUpdates.get(file));
+            this._pendingUpdates.delete(file);
+        }
+        const fileDiagnostics = this._diagnostics.get(file);
+        this._currentDiagnostics.set(file, fileDiagnostics ? fileDiagnostics.getAllDiagnostics(this._settings) : []);
+    }
+    private rebuildAll(): void {
+        this._currentDiagnostics.clear();
+        for (const fileDiagnostic of this._diagnostics.values()) {
+            this.rebuildFile(fileDiagnostic);
+        }
+    }
+    private rebuildFile(fileDiagnostic: FileDiagnostics) {
+        this._currentDiagnostics.set(fileDiagnostic.file, fileDiagnostic.getAllDiagnostics(this._settings));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/directiveCommentCompletions.ts b/extensions/typescript-language-features/Source/languageFeatures/directiveCommentCompletions.ts
index aa972ceb19943..c9abb77b01371 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/directiveCommentCompletions.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/directiveCommentCompletions.ts
@@ -2,79 +2,57 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { API } from '../tsServer/api';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
-
 interface Directive {
-	readonly value: string;
-	readonly description: string;
+    readonly value: string;
+    readonly description: string;
 }
-
 const tsDirectives: Directive[] = [
-	{
-		value: '@ts-check',
-		description: vscode.l10n.t("Enables semantic checking in a JavaScript file. Must be at the top of a file.")
-	}, {
-		value: '@ts-nocheck',
-		description: vscode.l10n.t("Disables semantic checking in a JavaScript file. Must be at the top of a file.")
-	}, {
-		value: '@ts-ignore',
-		description: vscode.l10n.t("Suppresses @ts-check errors on the next line of a file.")
-	}
+    {
+        value: '@ts-check',
+        description: vscode.l10n.t("Enables semantic checking in a JavaScript file. Must be at the top of a file.")
+    }, {
+        value: '@ts-nocheck',
+        description: vscode.l10n.t("Disables semantic checking in a JavaScript file. Must be at the top of a file.")
+    }, {
+        value: '@ts-ignore',
+        description: vscode.l10n.t("Suppresses @ts-check errors on the next line of a file.")
+    }
 ];
-
 const tsDirectives390: Directive[] = [
-	...tsDirectives,
-	{
-		value: '@ts-expect-error',
-		description: vscode.l10n.t("Suppresses @ts-check errors on the next line of a file, expecting at least one to exist.")
-	}
+    ...tsDirectives,
+    {
+        value: '@ts-expect-error',
+        description: vscode.l10n.t("Suppresses @ts-check errors on the next line of a file, expecting at least one to exist.")
+    }
 ];
-
 class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvider {
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-	) { }
-
-	public provideCompletionItems(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		_token: vscode.CancellationToken
-	): vscode.CompletionItem[] {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return [];
-		}
-
-		const line = document.lineAt(position.line).text;
-		const prefix = line.slice(0, position.character);
-		const match = prefix.match(/^\s*\/\/+\s?(@[a-zA-Z\-]*)?$/);
-		if (match) {
-			const directives = this.client.apiVersion.gte(API.v390)
-				? tsDirectives390
-				: tsDirectives;
-
-			return directives.map(directive => {
-				const item = new vscode.CompletionItem(directive.value, vscode.CompletionItemKind.Snippet);
-				item.detail = directive.description;
-				item.range = new vscode.Range(position.line, Math.max(0, position.character - (match[1] ? match[1].length : 0)), position.line, position.character);
-				return item;
-			});
-		}
-		return [];
-	}
+    constructor(private readonly client: ITypeScriptServiceClient) { }
+    public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): vscode.CompletionItem[] {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return [];
+        }
+        const line = document.lineAt(position.line).text;
+        const prefix = line.slice(0, position.character);
+        const match = prefix.match(/^\s*\/\/+\s?(@[a-zA-Z\-]*)?$/);
+        if (match) {
+            const directives = this.client.apiVersion.gte(API.v390)
+                ? tsDirectives390
+                : tsDirectives;
+            return directives.map(directive => {
+                const item = new vscode.CompletionItem(directive.value, vscode.CompletionItemKind.Snippet);
+                item.detail = directive.description;
+                item.range = new vscode.Range(position.line, Math.max(0, position.character - (match[1] ? match[1].length : 0)), position.line, position.character);
+                return item;
+            });
+        }
+        return [];
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return vscode.languages.registerCompletionItemProvider(selector.syntax,
-		new DirectiveCommentCompletionProvider(client),
-		'@');
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return vscode.languages.registerCompletionItemProvider(selector.syntax, new DirectiveCommentCompletionProvider(client), '@');
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/documentHighlight.ts b/extensions/typescript-language-features/Source/languageFeatures/documentHighlight.ts
index 2213e3b4ee26b..9ce0d53dc895c 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/documentHighlight.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/documentHighlight.ts
@@ -2,88 +2,50 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import type * as Proto from '../tsServer/protocol/protocol';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
 class TypeScriptDocumentHighlightProvider implements vscode.DocumentHighlightProvider, vscode.MultiDocumentHighlightProvider {
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async provideMultiDocumentHighlights(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		otherDocuments: vscode.TextDocument[],
-		token: vscode.CancellationToken
-	): Promise {
-		const allFiles = [document, ...otherDocuments].map(doc => this.client.toOpenTsFilePath(doc)).filter(file => !!file) as string[];
-		const file = this.client.toOpenTsFilePath(document);
-
-		if (!file || allFiles.length === 0) {
-			return [];
-		}
-
-		const args = {
-			...typeConverters.Position.toFileLocationRequestArgs(file, position),
-			filesToSearch: allFiles
-		};
-		const response = await this.client.execute('documentHighlights', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return [];
-		}
-
-		const result = response.body.map(highlightItem =>
-			new vscode.MultiDocumentHighlight(
-				vscode.Uri.file(highlightItem.file),
-				[...convertDocumentHighlight(highlightItem)]
-			)
-		);
-
-		return result;
-	}
-
-	public async provideDocumentHighlights(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return [];
-		}
-
-		const args = {
-			...typeConverters.Position.toFileLocationRequestArgs(file, position),
-			filesToSearch: [file]
-		};
-		const response = await this.client.execute('documentHighlights', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return [];
-		}
-
-		return response.body.flatMap(convertDocumentHighlight);
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async provideMultiDocumentHighlights(document: vscode.TextDocument, position: vscode.Position, otherDocuments: vscode.TextDocument[], token: vscode.CancellationToken): Promise {
+        const allFiles = [document, ...otherDocuments].map(doc => this.client.toOpenTsFilePath(doc)).filter(file => !!file) as string[];
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file || allFiles.length === 0) {
+            return [];
+        }
+        const args = {
+            ...typeConverters.Position.toFileLocationRequestArgs(file, position),
+            filesToSearch: allFiles
+        };
+        const response = await this.client.execute('documentHighlights', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return [];
+        }
+        const result = response.body.map(highlightItem => new vscode.MultiDocumentHighlight(vscode.Uri.file(highlightItem.file), [...convertDocumentHighlight(highlightItem)]));
+        return result;
+    }
+    public async provideDocumentHighlights(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return [];
+        }
+        const args = {
+            ...typeConverters.Position.toFileLocationRequestArgs(file, position),
+            filesToSearch: [file]
+        };
+        const response = await this.client.execute('documentHighlights', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return [];
+        }
+        return response.body.flatMap(convertDocumentHighlight);
+    }
 }
-
 function convertDocumentHighlight(highlight: Proto.DocumentHighlightsItem): ReadonlyArray {
-	return highlight.highlightSpans.map(span =>
-		new vscode.DocumentHighlight(
-			typeConverters.Range.fromTextSpan(span),
-			span.kind === 'writtenReference' ? vscode.DocumentHighlightKind.Write : vscode.DocumentHighlightKind.Read));
+    return highlight.highlightSpans.map(span => new vscode.DocumentHighlight(typeConverters.Range.fromTextSpan(span), span.kind === 'writtenReference' ? vscode.DocumentHighlightKind.Write : vscode.DocumentHighlightKind.Read));
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	const provider = new TypeScriptDocumentHighlightProvider(client);
-
-	return vscode.Disposable.from(
-		vscode.languages.registerDocumentHighlightProvider(selector.syntax, provider),
-		vscode.languages.registerMultiDocumentHighlightProvider(selector.syntax, provider)
-	);
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    const provider = new TypeScriptDocumentHighlightProvider(client);
+    return vscode.Disposable.from(vscode.languages.registerDocumentHighlightProvider(selector.syntax, provider), vscode.languages.registerMultiDocumentHighlightProvider(selector.syntax, provider));
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/documentSymbol.ts b/extensions/typescript-language-features/Source/languageFeatures/documentSymbol.ts
index ef2d47ef942fd..cccea1ebc904a 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/documentSymbol.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/documentSymbol.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { CachedResponse } from '../tsServer/cachedResponse';
@@ -11,124 +10,92 @@ import type * as Proto from '../tsServer/protocol/protocol';
 import * as PConst from '../tsServer/protocol/protocol.const';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
 const getSymbolKind = (kind: string): vscode.SymbolKind => {
-	switch (kind) {
-		case PConst.Kind.module: return vscode.SymbolKind.Module;
-		case PConst.Kind.class: return vscode.SymbolKind.Class;
-		case PConst.Kind.enum: return vscode.SymbolKind.Enum;
-		case PConst.Kind.interface: return vscode.SymbolKind.Interface;
-		case PConst.Kind.method: return vscode.SymbolKind.Method;
-		case PConst.Kind.memberVariable: return vscode.SymbolKind.Property;
-		case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property;
-		case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property;
-		case PConst.Kind.variable: return vscode.SymbolKind.Variable;
-		case PConst.Kind.const: return vscode.SymbolKind.Variable;
-		case PConst.Kind.localVariable: return vscode.SymbolKind.Variable;
-		case PConst.Kind.function: return vscode.SymbolKind.Function;
-		case PConst.Kind.localFunction: return vscode.SymbolKind.Function;
-		case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor;
-		case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor;
-	}
-	return vscode.SymbolKind.Variable;
+    switch (kind) {
+        case PConst.Kind.module: return vscode.SymbolKind.Module;
+        case PConst.Kind.class: return vscode.SymbolKind.Class;
+        case PConst.Kind.enum: return vscode.SymbolKind.Enum;
+        case PConst.Kind.interface: return vscode.SymbolKind.Interface;
+        case PConst.Kind.method: return vscode.SymbolKind.Method;
+        case PConst.Kind.memberVariable: return vscode.SymbolKind.Property;
+        case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property;
+        case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property;
+        case PConst.Kind.variable: return vscode.SymbolKind.Variable;
+        case PConst.Kind.const: return vscode.SymbolKind.Variable;
+        case PConst.Kind.localVariable: return vscode.SymbolKind.Variable;
+        case PConst.Kind.function: return vscode.SymbolKind.Function;
+        case PConst.Kind.localFunction: return vscode.SymbolKind.Function;
+        case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor;
+        case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor;
+    }
+    return vscode.SymbolKind.Variable;
 };
-
 class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly cachedResponse: CachedResponse,
-	) { }
-
-	public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		const args: Proto.FileRequestArgs = { file };
-		const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', args, token));
-		if (response.type !== 'response' || !response.body?.childItems) {
-			return undefined;
-		}
-
-		// The root represents the file. Ignore this when showing in the UI
-		const result: vscode.DocumentSymbol[] = [];
-		for (const item of response.body.childItems) {
-			TypeScriptDocumentSymbolProvider.convertNavTree(document.uri, result, item);
-		}
-		return result;
-	}
-
-	private static convertNavTree(
-		resource: vscode.Uri,
-		output: vscode.DocumentSymbol[],
-		item: Proto.NavigationTree,
-	): boolean {
-		let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item);
-		if (!shouldInclude && !item.childItems?.length) {
-			return false;
-		}
-
-		const children = new Set(item.childItems || []);
-		for (const span of item.spans) {
-			const range = typeConverters.Range.fromTextSpan(span);
-			const symbolInfo = TypeScriptDocumentSymbolProvider.convertSymbol(item, range);
-
-			for (const child of children) {
-				if (child.spans.some(span => !!range.intersection(typeConverters.Range.fromTextSpan(span)))) {
-					const includedChild = TypeScriptDocumentSymbolProvider.convertNavTree(resource, symbolInfo.children, child);
-					shouldInclude = shouldInclude || includedChild;
-					children.delete(child);
-				}
-			}
-
-			if (shouldInclude) {
-				output.push(symbolInfo);
-			}
-		}
-
-		return shouldInclude;
-	}
-
-	private static convertSymbol(item: Proto.NavigationTree, range: vscode.Range): vscode.DocumentSymbol {
-		const selectionRange = item.nameSpan ? typeConverters.Range.fromTextSpan(item.nameSpan) : range;
-		let label = item.text;
-
-		switch (item.kind) {
-			case PConst.Kind.memberGetAccessor: label = `(get) ${label}`; break;
-			case PConst.Kind.memberSetAccessor: label = `(set) ${label}`; break;
-		}
-
-		const symbolInfo = new vscode.DocumentSymbol(
-			label,
-			'',
-			getSymbolKind(item.kind),
-			range,
-			range.contains(selectionRange) ? selectionRange : range);
-
-
-		const kindModifiers = parseKindModifier(item.kindModifiers);
-		if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
-			symbolInfo.tags = [vscode.SymbolTag.Deprecated];
-		}
-
-		return symbolInfo;
-	}
-
-	private static shouldInclueEntry(item: Proto.NavigationTree | Proto.NavigationBarItem): boolean {
-		if (item.kind === PConst.Kind.alias) {
-			return false;
-		}
-		return !!(item.text && item.text !== '' && item.text !== '');
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient, private readonly cachedResponse: CachedResponse) { }
+    public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        const args: Proto.FileRequestArgs = { file };
+        const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', args, token));
+        if (response.type !== 'response' || !response.body?.childItems) {
+            return undefined;
+        }
+        // The root represents the file. Ignore this when showing in the UI
+        const result: vscode.DocumentSymbol[] = [];
+        for (const item of response.body.childItems) {
+            TypeScriptDocumentSymbolProvider.convertNavTree(document.uri, result, item);
+        }
+        return result;
+    }
+    private static convertNavTree(resource: vscode.Uri, output: vscode.DocumentSymbol[], item: Proto.NavigationTree): boolean {
+        let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item);
+        if (!shouldInclude && !item.childItems?.length) {
+            return false;
+        }
+        const children = new Set(item.childItems || []);
+        for (const span of item.spans) {
+            const range = typeConverters.Range.fromTextSpan(span);
+            const symbolInfo = TypeScriptDocumentSymbolProvider.convertSymbol(item, range);
+            for (const child of children) {
+                if (child.spans.some(span => !!range.intersection(typeConverters.Range.fromTextSpan(span)))) {
+                    const includedChild = TypeScriptDocumentSymbolProvider.convertNavTree(resource, symbolInfo.children, child);
+                    shouldInclude = shouldInclude || includedChild;
+                    children.delete(child);
+                }
+            }
+            if (shouldInclude) {
+                output.push(symbolInfo);
+            }
+        }
+        return shouldInclude;
+    }
+    private static convertSymbol(item: Proto.NavigationTree, range: vscode.Range): vscode.DocumentSymbol {
+        const selectionRange = item.nameSpan ? typeConverters.Range.fromTextSpan(item.nameSpan) : range;
+        let label = item.text;
+        switch (item.kind) {
+            case PConst.Kind.memberGetAccessor:
+                label = `(get) ${label}`;
+                break;
+            case PConst.Kind.memberSetAccessor:
+                label = `(set) ${label}`;
+                break;
+        }
+        const symbolInfo = new vscode.DocumentSymbol(label, '', getSymbolKind(item.kind), range, range.contains(selectionRange) ? selectionRange : range);
+        const kindModifiers = parseKindModifier(item.kindModifiers);
+        if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
+            symbolInfo.tags = [vscode.SymbolTag.Deprecated];
+        }
+        return symbolInfo;
+    }
+    private static shouldInclueEntry(item: Proto.NavigationTree | Proto.NavigationBarItem): boolean {
+        if (item.kind === PConst.Kind.alias) {
+            return false;
+        }
+        return !!(item.text && item.text !== '' && item.text !== '');
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-	cachedResponse: CachedResponse,
-) {
-	return vscode.languages.registerDocumentSymbolProvider(selector.syntax,
-		new TypeScriptDocumentSymbolProvider(client, cachedResponse), { label: 'TypeScript' });
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient, cachedResponse: CachedResponse) {
+    return vscode.languages.registerDocumentSymbolProvider(selector.syntax, new TypeScriptDocumentSymbolProvider(client, cachedResponse), { label: 'TypeScript' });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/Source/languageFeatures/fileConfigurationManager.ts
index 5d92a3a6163e1..7e8426812f154 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/fileConfigurationManager.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/fileConfigurationManager.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as vscode from 'vscode';
 import * as fileSchemes from '../configuration/fileSchemes';
@@ -13,302 +12,242 @@ import { ITypeScriptServiceClient } from '../typescriptService';
 import { Disposable } from '../utils/dispose';
 import { equals } from '../utils/objects';
 import { ResourceMap } from '../utils/resourceMap';
-
 interface FileConfiguration {
-	readonly formatOptions: Proto.FormatCodeSettings;
-	readonly preferences: Proto.UserPreferences;
+    readonly formatOptions: Proto.FormatCodeSettings;
+    readonly preferences: Proto.UserPreferences;
 }
-
 interface FormattingOptions {
-
-	readonly tabSize: number | undefined;
-
-	readonly insertSpaces: boolean | undefined;
+    readonly tabSize: number | undefined;
+    readonly insertSpaces: boolean | undefined;
 }
-
 function areFileConfigurationsEqual(a: FileConfiguration, b: FileConfiguration): boolean {
-	return equals(a, b);
+    return equals(a, b);
 }
-
 export default class FileConfigurationManager extends Disposable {
-	private readonly formatOptions: ResourceMap>;
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-		onCaseInsensitiveFileSystem: boolean
-	) {
-		super();
-		this.formatOptions = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
-		vscode.workspace.onDidCloseTextDocument(textDocument => {
-			// When a document gets closed delete the cached formatting options.
-			// This is necessary since the tsserver now closed a project when its
-			// last file in it closes which drops the stored formatting options
-			// as well.
-			this.formatOptions.delete(textDocument.uri);
-		}, undefined, this._disposables);
-	}
-
-	public async ensureConfigurationForDocument(
-		document: vscode.TextDocument,
-		token: vscode.CancellationToken
-	): Promise {
-		const formattingOptions = this.getFormattingOptions(document);
-		if (formattingOptions) {
-			return this.ensureConfigurationOptions(document, formattingOptions, token);
-		}
-	}
-
-	private getFormattingOptions(document: vscode.TextDocument): FormattingOptions | undefined {
-		const editor = vscode.window.visibleTextEditors.find(editor => editor.document.uri.toString() === document.uri.toString());
-		if (!editor) {
-			return undefined;
-		}
-
-		return {
-			tabSize: typeof editor.options.tabSize === 'number' ? editor.options.tabSize : undefined,
-			insertSpaces: typeof editor.options.insertSpaces === 'boolean' ? editor.options.insertSpaces : undefined,
-		};
-	}
-
-	public async ensureConfigurationOptions(
-		document: vscode.TextDocument,
-		options: FormattingOptions,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return;
-		}
-
-		const currentOptions = this.getFileOptions(document, options);
-		const cachedOptions = this.formatOptions.get(document.uri);
-		if (cachedOptions) {
-			const cachedOptionsValue = await cachedOptions;
-			if (token.isCancellationRequested) {
-				return;
-			}
-
-			if (cachedOptionsValue && areFileConfigurationsEqual(cachedOptionsValue, currentOptions)) {
-				return;
-			}
-		}
-
-		const task = (async () => {
-			try {
-				const response = await this.client.execute('configure', { file, ...currentOptions }, token);
-				return response.type === 'response' ? currentOptions : undefined;
-			} catch {
-				return undefined;
-			}
-		})();
-
-		this.formatOptions.set(document.uri, task);
-
-		await task;
-	}
-
-	public async setGlobalConfigurationFromDocument(
-		document: vscode.TextDocument,
-		token: vscode.CancellationToken,
-	): Promise {
-		const formattingOptions = this.getFormattingOptions(document);
-		if (!formattingOptions) {
-			return;
-		}
-
-		const args: Proto.ConfigureRequestArguments = {
-			file: undefined /*global*/,
-			...this.getFileOptions(document, formattingOptions),
-		};
-		await this.client.execute('configure', args, token);
-	}
-
-	public reset() {
-		this.formatOptions.clear();
-	}
-
-	private getFileOptions(
-		document: vscode.TextDocument,
-		options: FormattingOptions
-	): FileConfiguration {
-		return {
-			formatOptions: this.getFormatOptions(document, options),
-			preferences: this.getPreferences(document)
-		};
-	}
-
-	private getFormatOptions(
-		document: vscode.TextDocument,
-		options: FormattingOptions
-	): Proto.FormatCodeSettings {
-		const config = vscode.workspace.getConfiguration(
-			isTypeScriptDocument(document) ? 'typescript.format' : 'javascript.format',
-			document.uri);
-
-		return {
-			tabSize: options.tabSize,
-			indentSize: options.tabSize,
-			convertTabsToSpaces: options.insertSpaces,
-			// We can use \n here since the editor normalizes later on to its line endings.
-			newLineCharacter: '\n',
-			insertSpaceAfterCommaDelimiter: config.get('insertSpaceAfterCommaDelimiter'),
-			insertSpaceAfterConstructor: config.get('insertSpaceAfterConstructor'),
-			insertSpaceAfterSemicolonInForStatements: config.get('insertSpaceAfterSemicolonInForStatements'),
-			insertSpaceBeforeAndAfterBinaryOperators: config.get('insertSpaceBeforeAndAfterBinaryOperators'),
-			insertSpaceAfterKeywordsInControlFlowStatements: config.get('insertSpaceAfterKeywordsInControlFlowStatements'),
-			insertSpaceAfterFunctionKeywordForAnonymousFunctions: config.get('insertSpaceAfterFunctionKeywordForAnonymousFunctions'),
-			insertSpaceBeforeFunctionParenthesis: config.get('insertSpaceBeforeFunctionParenthesis'),
-			insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis'),
-			insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets'),
-			insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces'),
-			insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingEmptyBraces'),
-			insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces'),
-			insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces'),
-			insertSpaceAfterTypeAssertion: config.get('insertSpaceAfterTypeAssertion'),
-			placeOpenBraceOnNewLineForFunctions: config.get('placeOpenBraceOnNewLineForFunctions'),
-			placeOpenBraceOnNewLineForControlBlocks: config.get('placeOpenBraceOnNewLineForControlBlocks'),
-			semicolons: config.get('semicolons'),
-			indentSwitchCase: config.get('indentSwitchCase'),
-		};
-	}
-
-	private getPreferences(document: vscode.TextDocument): Proto.UserPreferences {
-		const config = vscode.workspace.getConfiguration(
-			isTypeScriptDocument(document) ? 'typescript' : 'javascript',
-			document);
-
-		const preferencesConfig = vscode.workspace.getConfiguration(
-			isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences',
-			document);
-
-		const preferences: Proto.UserPreferences = {
-			...config.get('unstable'),
-			quotePreference: this.getQuoteStylePreference(preferencesConfig),
-			importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig),
-			importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig),
-			jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(preferencesConfig),
-			allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file,
-			providePrefixAndSuffixTextForRename: preferencesConfig.get('renameShorthandProperties', true) === false ? false : preferencesConfig.get('useAliasesForRenames', true),
-			allowRenameOfImportPath: true,
-			includeAutomaticOptionalChainCompletions: config.get('suggest.includeAutomaticOptionalChainCompletions', true),
-			provideRefactorNotApplicableReason: true,
-			generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true),
-			includeCompletionsForImportStatements: config.get('suggest.includeCompletionsForImportStatements', true),
-			includeCompletionsWithSnippetText: true,
-			includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true),
-			includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true),
-			autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri),
-			autoImportSpecifierExcludeRegexes: preferencesConfig.get('autoImportSpecifierExcludeRegexes'),
-			preferTypeOnlyAutoImports: preferencesConfig.get('preferTypeOnlyAutoImports', false),
-			useLabelDetailsInCompletionEntries: true,
-			allowIncompleteCompletions: true,
-			displayPartsForJSDoc: true,
-			disableLineTextInReferences: true,
-			interactiveInlayHints: true,
-			includeCompletionsForModuleExports: config.get('suggest.autoImports'),
-			...getInlayHintsPreferences(config),
-			...this.getOrganizeImportsPreferences(preferencesConfig),
-		};
-
-		return preferences;
-	}
-
-	private getQuoteStylePreference(config: vscode.WorkspaceConfiguration) {
-		switch (config.get('quoteStyle')) {
-			case 'single': return 'single';
-			case 'double': return 'double';
-			default: return 'auto';
-		}
-	}
-
-	private getAutoImportFileExcludePatternsPreference(config: vscode.WorkspaceConfiguration, workspaceFolder: vscode.Uri | undefined): string[] | undefined {
-		return workspaceFolder && config.get('autoImportFileExcludePatterns')?.map(p => {
-			// Normalization rules: https://github.com/microsoft/TypeScript/pull/49578
-			const isRelative = /^\.\.?($|[\/\\])/.test(p);
-			// In TypeScript < 5.3, the first path component cannot be a wildcard, so we need to prefix
-			// it with a path root (e.g. `/` or `c:\`)
-			const wildcardPrefix = this.client.apiVersion.gte(API.v540)
-				? ''
-				: path.parse(this.client.toTsFilePath(workspaceFolder)!).root;
-			return path.isAbsolute(p) ? p :
-				p.startsWith('*') ? wildcardPrefix + p :
-					isRelative ? this.client.toTsFilePath(vscode.Uri.joinPath(workspaceFolder, p))! :
-						wildcardPrefix + '**' + path.sep + p;
-		});
-	}
-
-	private getOrganizeImportsPreferences(config: vscode.WorkspaceConfiguration): Proto.UserPreferences {
-		return {
-			// More specific settings
-			organizeImportsAccentCollation: config.get('organizeImports.accentCollation'),
-			organizeImportsCaseFirst: withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower'>('organizeImports.caseFirst', 'default'), 'default'),
-			organizeImportsCollation: config.get<'ordinal' | 'unicode'>('organizeImports.collation'),
-			organizeImportsIgnoreCase: withDefaultAsUndefined(config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'), 'auto'),
-			organizeImportsLocale: config.get('organizeImports.locale'),
-			organizeImportsNumericCollation: config.get('organizeImports.numericCollation'),
-			organizeImportsTypeOrder: withDefaultAsUndefined(config.get<'auto' | 'last' | 'inline' | 'first'>('organizeImports.typeOrder', 'auto'), 'auto'),
-		};
-	}
+    private readonly formatOptions: ResourceMap>;
+    public constructor(private readonly client: ITypeScriptServiceClient, onCaseInsensitiveFileSystem: boolean) {
+        super();
+        this.formatOptions = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
+        vscode.workspace.onDidCloseTextDocument(textDocument => {
+            // When a document gets closed delete the cached formatting options.
+            // This is necessary since the tsserver now closed a project when its
+            // last file in it closes which drops the stored formatting options
+            // as well.
+            this.formatOptions.delete(textDocument.uri);
+        }, undefined, this._disposables);
+    }
+    public async ensureConfigurationForDocument(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+        const formattingOptions = this.getFormattingOptions(document);
+        if (formattingOptions) {
+            return this.ensureConfigurationOptions(document, formattingOptions, token);
+        }
+    }
+    private getFormattingOptions(document: vscode.TextDocument): FormattingOptions | undefined {
+        const editor = vscode.window.visibleTextEditors.find(editor => editor.document.uri.toString() === document.uri.toString());
+        if (!editor) {
+            return undefined;
+        }
+        return {
+            tabSize: typeof editor.options.tabSize === 'number' ? editor.options.tabSize : undefined,
+            insertSpaces: typeof editor.options.insertSpaces === 'boolean' ? editor.options.insertSpaces : undefined,
+        };
+    }
+    public async ensureConfigurationOptions(document: vscode.TextDocument, options: FormattingOptions, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return;
+        }
+        const currentOptions = this.getFileOptions(document, options);
+        const cachedOptions = this.formatOptions.get(document.uri);
+        if (cachedOptions) {
+            const cachedOptionsValue = await cachedOptions;
+            if (token.isCancellationRequested) {
+                return;
+            }
+            if (cachedOptionsValue && areFileConfigurationsEqual(cachedOptionsValue, currentOptions)) {
+                return;
+            }
+        }
+        const task = (async () => {
+            try {
+                const response = await this.client.execute('configure', { file, ...currentOptions }, token);
+                return response.type === 'response' ? currentOptions : undefined;
+            }
+            catch {
+                return undefined;
+            }
+        })();
+        this.formatOptions.set(document.uri, task);
+        await task;
+    }
+    public async setGlobalConfigurationFromDocument(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+        const formattingOptions = this.getFormattingOptions(document);
+        if (!formattingOptions) {
+            return;
+        }
+        const args: Proto.ConfigureRequestArguments = {
+            file: undefined /*global*/,
+            ...this.getFileOptions(document, formattingOptions),
+        };
+        await this.client.execute('configure', args, token);
+    }
+    public reset() {
+        this.formatOptions.clear();
+    }
+    private getFileOptions(document: vscode.TextDocument, options: FormattingOptions): FileConfiguration {
+        return {
+            formatOptions: this.getFormatOptions(document, options),
+            preferences: this.getPreferences(document)
+        };
+    }
+    private getFormatOptions(document: vscode.TextDocument, options: FormattingOptions): Proto.FormatCodeSettings {
+        const config = vscode.workspace.getConfiguration(isTypeScriptDocument(document) ? 'typescript.format' : 'javascript.format', document.uri);
+        return {
+            tabSize: options.tabSize,
+            indentSize: options.tabSize,
+            convertTabsToSpaces: options.insertSpaces,
+            // We can use \n here since the editor normalizes later on to its line endings.
+            newLineCharacter: '\n',
+            insertSpaceAfterCommaDelimiter: config.get('insertSpaceAfterCommaDelimiter'),
+            insertSpaceAfterConstructor: config.get('insertSpaceAfterConstructor'),
+            insertSpaceAfterSemicolonInForStatements: config.get('insertSpaceAfterSemicolonInForStatements'),
+            insertSpaceBeforeAndAfterBinaryOperators: config.get('insertSpaceBeforeAndAfterBinaryOperators'),
+            insertSpaceAfterKeywordsInControlFlowStatements: config.get('insertSpaceAfterKeywordsInControlFlowStatements'),
+            insertSpaceAfterFunctionKeywordForAnonymousFunctions: config.get('insertSpaceAfterFunctionKeywordForAnonymousFunctions'),
+            insertSpaceBeforeFunctionParenthesis: config.get('insertSpaceBeforeFunctionParenthesis'),
+            insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis'),
+            insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets'),
+            insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces'),
+            insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingEmptyBraces'),
+            insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces'),
+            insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: config.get('insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces'),
+            insertSpaceAfterTypeAssertion: config.get('insertSpaceAfterTypeAssertion'),
+            placeOpenBraceOnNewLineForFunctions: config.get('placeOpenBraceOnNewLineForFunctions'),
+            placeOpenBraceOnNewLineForControlBlocks: config.get('placeOpenBraceOnNewLineForControlBlocks'),
+            semicolons: config.get('semicolons'),
+            indentSwitchCase: config.get('indentSwitchCase'),
+        };
+    }
+    private getPreferences(document: vscode.TextDocument): Proto.UserPreferences {
+        const config = vscode.workspace.getConfiguration(isTypeScriptDocument(document) ? 'typescript' : 'javascript', document);
+        const preferencesConfig = vscode.workspace.getConfiguration(isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences', document);
+        const preferences: Proto.UserPreferences = {
+            ...config.get('unstable'),
+            quotePreference: this.getQuoteStylePreference(preferencesConfig),
+            importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig),
+            importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig),
+            jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(preferencesConfig),
+            allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file,
+            providePrefixAndSuffixTextForRename: preferencesConfig.get('renameShorthandProperties', true) === false ? false : preferencesConfig.get('useAliasesForRenames', true),
+            allowRenameOfImportPath: true,
+            includeAutomaticOptionalChainCompletions: config.get('suggest.includeAutomaticOptionalChainCompletions', true),
+            provideRefactorNotApplicableReason: true,
+            generateReturnInDocTemplate: config.get('suggest.jsdoc.generateReturns', true),
+            includeCompletionsForImportStatements: config.get('suggest.includeCompletionsForImportStatements', true),
+            includeCompletionsWithSnippetText: true,
+            includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true),
+            includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true),
+            autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri),
+            autoImportSpecifierExcludeRegexes: preferencesConfig.get('autoImportSpecifierExcludeRegexes'),
+            preferTypeOnlyAutoImports: preferencesConfig.get('preferTypeOnlyAutoImports', false),
+            useLabelDetailsInCompletionEntries: true,
+            allowIncompleteCompletions: true,
+            displayPartsForJSDoc: true,
+            disableLineTextInReferences: true,
+            interactiveInlayHints: true,
+            includeCompletionsForModuleExports: config.get('suggest.autoImports'),
+            ...getInlayHintsPreferences(config),
+            ...this.getOrganizeImportsPreferences(preferencesConfig),
+        };
+        return preferences;
+    }
+    private getQuoteStylePreference(config: vscode.WorkspaceConfiguration) {
+        switch (config.get('quoteStyle')) {
+            case 'single': return 'single';
+            case 'double': return 'double';
+            default: return 'auto';
+        }
+    }
+    private getAutoImportFileExcludePatternsPreference(config: vscode.WorkspaceConfiguration, workspaceFolder: vscode.Uri | undefined): string[] | undefined {
+        return workspaceFolder && config.get('autoImportFileExcludePatterns')?.map(p => {
+            // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578
+            const isRelative = /^\.\.?($|[\/\\])/.test(p);
+            // In TypeScript < 5.3, the first path component cannot be a wildcard, so we need to prefix
+            // it with a path root (e.g. `/` or `c:\`)
+            const wildcardPrefix = this.client.apiVersion.gte(API.v540)
+                ? ''
+                : path.parse(this.client.toTsFilePath(workspaceFolder)!).root;
+            return path.isAbsolute(p) ? p :
+                p.startsWith('*') ? wildcardPrefix + p :
+                    isRelative ? this.client.toTsFilePath(vscode.Uri.joinPath(workspaceFolder, p))! :
+                        wildcardPrefix + '**' + path.sep + p;
+        });
+    }
+    private getOrganizeImportsPreferences(config: vscode.WorkspaceConfiguration): Proto.UserPreferences {
+        return {
+            // More specific settings
+            organizeImportsAccentCollation: config.get('organizeImports.accentCollation'),
+            organizeImportsCaseFirst: withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower'>('organizeImports.caseFirst', 'default'), 'default'),
+            organizeImportsCollation: config.get<'ordinal' | 'unicode'>('organizeImports.collation'),
+            organizeImportsIgnoreCase: withDefaultAsUndefined(config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'), 'auto'),
+            organizeImportsLocale: config.get('organizeImports.locale'),
+            organizeImportsNumericCollation: config.get('organizeImports.numericCollation'),
+            organizeImportsTypeOrder: withDefaultAsUndefined(config.get<'auto' | 'last' | 'inline' | 'first'>('organizeImports.typeOrder', 'auto'), 'auto'),
+        };
+    }
 }
-
 function withDefaultAsUndefined(value: T, def: O): Exclude | undefined {
-	return value === def ? undefined : value as Exclude;
+    return value === def ? undefined : value as Exclude;
 }
-
 export class InlayHintSettingNames {
-	static readonly parameterNamesSuppressWhenArgumentMatchesName = 'inlayHints.parameterNames.suppressWhenArgumentMatchesName';
-	static readonly parameterNamesEnabled = 'inlayHints.parameterTypes.enabled';
-	static readonly variableTypesEnabled = 'inlayHints.variableTypes.enabled';
-	static readonly variableTypesSuppressWhenTypeMatchesName = 'inlayHints.variableTypes.suppressWhenTypeMatchesName';
-	static readonly propertyDeclarationTypesEnabled = 'inlayHints.propertyDeclarationTypes.enabled';
-	static readonly functionLikeReturnTypesEnabled = 'inlayHints.functionLikeReturnTypes.enabled';
-	static readonly enumMemberValuesEnabled = 'inlayHints.enumMemberValues.enabled';
+    static readonly parameterNamesSuppressWhenArgumentMatchesName = 'inlayHints.parameterNames.suppressWhenArgumentMatchesName';
+    static readonly parameterNamesEnabled = 'inlayHints.parameterTypes.enabled';
+    static readonly variableTypesEnabled = 'inlayHints.variableTypes.enabled';
+    static readonly variableTypesSuppressWhenTypeMatchesName = 'inlayHints.variableTypes.suppressWhenTypeMatchesName';
+    static readonly propertyDeclarationTypesEnabled = 'inlayHints.propertyDeclarationTypes.enabled';
+    static readonly functionLikeReturnTypesEnabled = 'inlayHints.functionLikeReturnTypes.enabled';
+    static readonly enumMemberValuesEnabled = 'inlayHints.enumMemberValues.enabled';
 }
-
 export function getInlayHintsPreferences(config: vscode.WorkspaceConfiguration) {
-	return {
-		includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config),
-		includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get(InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName, true),
-		includeInlayFunctionParameterTypeHints: config.get(InlayHintSettingNames.parameterNamesEnabled, false),
-		includeInlayVariableTypeHints: config.get(InlayHintSettingNames.variableTypesEnabled, false),
-		includeInlayVariableTypeHintsWhenTypeMatchesName: !config.get(InlayHintSettingNames.variableTypesSuppressWhenTypeMatchesName, true),
-		includeInlayPropertyDeclarationTypeHints: config.get(InlayHintSettingNames.propertyDeclarationTypesEnabled, false),
-		includeInlayFunctionLikeReturnTypeHints: config.get(InlayHintSettingNames.functionLikeReturnTypesEnabled, false),
-		includeInlayEnumMemberValueHints: config.get(InlayHintSettingNames.enumMemberValuesEnabled, false),
-	} as const;
+    return {
+        includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config),
+        includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get(InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName, true),
+        includeInlayFunctionParameterTypeHints: config.get(InlayHintSettingNames.parameterNamesEnabled, false),
+        includeInlayVariableTypeHints: config.get(InlayHintSettingNames.variableTypesEnabled, false),
+        includeInlayVariableTypeHintsWhenTypeMatchesName: !config.get(InlayHintSettingNames.variableTypesSuppressWhenTypeMatchesName, true),
+        includeInlayPropertyDeclarationTypeHints: config.get(InlayHintSettingNames.propertyDeclarationTypesEnabled, false),
+        includeInlayFunctionLikeReturnTypeHints: config.get(InlayHintSettingNames.functionLikeReturnTypesEnabled, false),
+        includeInlayEnumMemberValueHints: config.get(InlayHintSettingNames.enumMemberValuesEnabled, false),
+    } as const;
 }
-
 function getInlayParameterNameHintsPreference(config: vscode.WorkspaceConfiguration) {
-	switch (config.get('inlayHints.parameterNames.enabled')) {
-		case 'none': return 'none';
-		case 'literals': return 'literals';
-		case 'all': return 'all';
-		default: return undefined;
-	}
+    switch (config.get('inlayHints.parameterNames.enabled')) {
+        case 'none': return 'none';
+        case 'literals': return 'literals';
+        case 'all': return 'all';
+        default: return undefined;
+    }
 }
-
 function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguration) {
-	switch (config.get('importModuleSpecifier')) {
-		case 'project-relative': return 'project-relative';
-		case 'relative': return 'relative';
-		case 'non-relative': return 'non-relative';
-		default: return undefined;
-	}
+    switch (config.get('importModuleSpecifier')) {
+        case 'project-relative': return 'project-relative';
+        case 'relative': return 'relative';
+        case 'non-relative': return 'non-relative';
+        default: return undefined;
+    }
 }
-
 function getImportModuleSpecifierEndingPreference(config: vscode.WorkspaceConfiguration) {
-	switch (config.get('importModuleSpecifierEnding')) {
-		case 'minimal': return 'minimal';
-		case 'index': return 'index';
-		case 'js': return 'js';
-		default: return 'auto';
-	}
+    switch (config.get('importModuleSpecifierEnding')) {
+        case 'minimal': return 'minimal';
+        case 'index': return 'index';
+        case 'js': return 'js';
+        default: return 'auto';
+    }
 }
-
 function getJsxAttributeCompletionStyle(config: vscode.WorkspaceConfiguration) {
-	switch (config.get('jsxAttributeCompletionStyle')) {
-		case 'braces': return 'braces';
-		case 'none': return 'none';
-		default: return 'auto';
-	}
+    switch (config.get('jsxAttributeCompletionStyle')) {
+        case 'braces': return 'braces';
+        case 'none': return 'none';
+        default: return 'auto';
+    }
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/fileReferences.ts b/extensions/typescript-language-features/Source/languageFeatures/fileReferences.ts
index 3a475ac257ba6..5b8576c57c193 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/fileReferences.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/fileReferences.ts
@@ -2,88 +2,65 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command, CommandManager } from '../commands/commandManager';
 import { isSupportedLanguageMode } from '../configuration/languageIds';
 import { API } from '../tsServer/api';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
-
 class FileReferencesCommand implements Command {
-
-	public static readonly context = 'tsSupportsFileReferences';
-	public static readonly minVersion = API.v420;
-
-	public readonly id = 'typescript.findAllFileReferences';
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async execute(resource?: vscode.Uri) {
-		if (this.client.apiVersion.lt(FileReferencesCommand.minVersion)) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. Requires TypeScript 4.2+."));
-			return;
-		}
-
-		resource ??= vscode.window.activeTextEditor?.document.uri;
-		if (!resource) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. No resource provided."));
-			return;
-		}
-
-		const document = await vscode.workspace.openTextDocument(resource);
-		if (!isSupportedLanguageMode(document)) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. Unsupported file type."));
-			return;
-		}
-
-		const openedFiledPath = this.client.toOpenTsFilePath(document);
-		if (!openedFiledPath) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. Unknown file type."));
-			return;
-		}
-
-		await vscode.window.withProgress({
-			location: vscode.ProgressLocation.Window,
-			title: vscode.l10n.t("Finding file references")
-		}, async (_progress, token) => {
-
-			const response = await this.client.execute('fileReferences', {
-				file: openedFiledPath
-			}, token);
-			if (response.type !== 'response' || !response.body) {
-				return;
-			}
-
-			const locations: vscode.Location[] = response.body.refs.map(reference =>
-				typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference));
-
-			const config = vscode.workspace.getConfiguration('references');
-			const existingSetting = config.inspect('preferredLocation');
-
-			await config.update('preferredLocation', 'view');
-			try {
-				await vscode.commands.executeCommand('editor.action.showReferences', resource, new vscode.Position(0, 0), locations);
-			} finally {
-				await config.update('preferredLocation', existingSetting?.workspaceFolderValue ?? existingSetting?.workspaceValue);
-			}
-		});
-	}
+    public static readonly context = 'tsSupportsFileReferences';
+    public static readonly minVersion = API.v420;
+    public readonly id = 'typescript.findAllFileReferences';
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async execute(resource?: vscode.Uri) {
+        if (this.client.apiVersion.lt(FileReferencesCommand.minVersion)) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. Requires TypeScript 4.2+."));
+            return;
+        }
+        resource ??= vscode.window.activeTextEditor?.document.uri;
+        if (!resource) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. No resource provided."));
+            return;
+        }
+        const document = await vscode.workspace.openTextDocument(resource);
+        if (!isSupportedLanguageMode(document)) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. Unsupported file type."));
+            return;
+        }
+        const openedFiledPath = this.client.toOpenTsFilePath(document);
+        if (!openedFiledPath) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. Unknown file type."));
+            return;
+        }
+        await vscode.window.withProgress({
+            location: vscode.ProgressLocation.Window,
+            title: vscode.l10n.t("Finding file references")
+        }, async (_progress, token) => {
+            const response = await this.client.execute('fileReferences', {
+                file: openedFiledPath
+            }, token);
+            if (response.type !== 'response' || !response.body) {
+                return;
+            }
+            const locations: vscode.Location[] = response.body.refs.map(reference => typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference));
+            const config = vscode.workspace.getConfiguration('references');
+            const existingSetting = config.inspect('preferredLocation');
+            await config.update('preferredLocation', 'view');
+            try {
+                await vscode.commands.executeCommand('editor.action.showReferences', resource, new vscode.Position(0, 0), locations);
+            }
+            finally {
+                await config.update('preferredLocation', existingSetting?.workspaceFolderValue ?? existingSetting?.workspaceValue);
+            }
+        });
+    }
 }
-
-
-export function register(
-	client: ITypeScriptServiceClient,
-	commandManager: CommandManager
-) {
-	function updateContext() {
-		vscode.commands.executeCommand('setContext', FileReferencesCommand.context, client.apiVersion.gte(FileReferencesCommand.minVersion));
-	}
-	updateContext();
-
-	commandManager.register(new FileReferencesCommand(client));
-	return client.onTsServerStarted(() => updateContext());
+export function register(client: ITypeScriptServiceClient, commandManager: CommandManager) {
+    function updateContext() {
+        vscode.commands.executeCommand('setContext', FileReferencesCommand.context, client.apiVersion.gte(FileReferencesCommand.minVersion));
+    }
+    updateContext();
+    commandManager.register(new FileReferencesCommand(client));
+    return client.onTsServerStarted(() => updateContext());
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/fixAll.ts b/extensions/typescript-language-features/Source/languageFeatures/fixAll.ts
index 09ab205d918b1..0560b8a3dc24f 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/fixAll.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/fixAll.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import * as errorCodes from '../tsServer/protocol/errorCodes';
@@ -13,245 +12,165 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService
 import { DiagnosticsManager } from './diagnostics';
 import FileConfigurationManager from './fileConfigurationManager';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
-
 interface AutoFix {
-	readonly codes: Set;
-	readonly fixName: string;
+    readonly codes: Set;
+    readonly fixName: string;
 }
-
-async function buildIndividualFixes(
-	fixes: readonly AutoFix[],
-	edit: vscode.WorkspaceEdit,
-	client: ITypeScriptServiceClient,
-	file: string,
-	diagnostics: readonly vscode.Diagnostic[],
-	token: vscode.CancellationToken,
-): Promise {
-	for (const diagnostic of diagnostics) {
-		for (const { codes, fixName } of fixes) {
-			if (token.isCancellationRequested) {
-				return;
-			}
-
-			if (!codes.has(diagnostic.code as number)) {
-				continue;
-			}
-
-			const args: Proto.CodeFixRequestArgs = {
-				...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
-				errorCodes: [+(diagnostic.code!)]
-			};
-
-			const response = await client.execute('getCodeFixes', args, token);
-			if (response.type !== 'response') {
-				continue;
-			}
-
-			const fix = response.body?.find(fix => fix.fixName === fixName);
-			if (fix) {
-				typeConverters.WorkspaceEdit.withFileCodeEdits(edit, client, fix.changes);
-				break;
-			}
-		}
-	}
+async function buildIndividualFixes(fixes: readonly AutoFix[], edit: vscode.WorkspaceEdit, client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
+    for (const diagnostic of diagnostics) {
+        for (const { codes, fixName } of fixes) {
+            if (token.isCancellationRequested) {
+                return;
+            }
+            if (!codes.has(diagnostic.code as number)) {
+                continue;
+            }
+            const args: Proto.CodeFixRequestArgs = {
+                ...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
+                errorCodes: [+(diagnostic.code!)]
+            };
+            const response = await client.execute('getCodeFixes', args, token);
+            if (response.type !== 'response') {
+                continue;
+            }
+            const fix = response.body?.find(fix => fix.fixName === fixName);
+            if (fix) {
+                typeConverters.WorkspaceEdit.withFileCodeEdits(edit, client, fix.changes);
+                break;
+            }
+        }
+    }
 }
-
-async function buildCombinedFix(
-	fixes: readonly AutoFix[],
-	edit: vscode.WorkspaceEdit,
-	client: ITypeScriptServiceClient,
-	file: string,
-	diagnostics: readonly vscode.Diagnostic[],
-	token: vscode.CancellationToken,
-): Promise {
-	for (const diagnostic of diagnostics) {
-		for (const { codes, fixName } of fixes) {
-			if (token.isCancellationRequested) {
-				return;
-			}
-
-			if (!codes.has(diagnostic.code as number)) {
-				continue;
-			}
-
-			const args: Proto.CodeFixRequestArgs = {
-				...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
-				errorCodes: [+(diagnostic.code!)]
-			};
-
-			const response = await client.execute('getCodeFixes', args, token);
-			if (response.type !== 'response' || !response.body?.length) {
-				continue;
-			}
-
-			const fix = response.body?.find(fix => fix.fixName === fixName);
-			if (!fix) {
-				continue;
-			}
-
-			if (!fix.fixId) {
-				typeConverters.WorkspaceEdit.withFileCodeEdits(edit, client, fix.changes);
-				return;
-			}
-
-			const combinedArgs: Proto.GetCombinedCodeFixRequestArgs = {
-				scope: {
-					type: 'file',
-					args: { file }
-				},
-				fixId: fix.fixId,
-			};
-
-			const combinedResponse = await client.execute('getCombinedCodeFix', combinedArgs, token);
-			if (combinedResponse.type !== 'response' || !combinedResponse.body) {
-				return;
-			}
-
-			typeConverters.WorkspaceEdit.withFileCodeEdits(edit, client, combinedResponse.body.changes);
-			return;
-		}
-	}
+async function buildCombinedFix(fixes: readonly AutoFix[], edit: vscode.WorkspaceEdit, client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
+    for (const diagnostic of diagnostics) {
+        for (const { codes, fixName } of fixes) {
+            if (token.isCancellationRequested) {
+                return;
+            }
+            if (!codes.has(diagnostic.code as number)) {
+                continue;
+            }
+            const args: Proto.CodeFixRequestArgs = {
+                ...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
+                errorCodes: [+(diagnostic.code!)]
+            };
+            const response = await client.execute('getCodeFixes', args, token);
+            if (response.type !== 'response' || !response.body?.length) {
+                continue;
+            }
+            const fix = response.body?.find(fix => fix.fixName === fixName);
+            if (!fix) {
+                continue;
+            }
+            if (!fix.fixId) {
+                typeConverters.WorkspaceEdit.withFileCodeEdits(edit, client, fix.changes);
+                return;
+            }
+            const combinedArgs: Proto.GetCombinedCodeFixRequestArgs = {
+                scope: {
+                    type: 'file',
+                    args: { file }
+                },
+                fixId: fix.fixId,
+            };
+            const combinedResponse = await client.execute('getCombinedCodeFix', combinedArgs, token);
+            if (combinedResponse.type !== 'response' || !combinedResponse.body) {
+                return;
+            }
+            typeConverters.WorkspaceEdit.withFileCodeEdits(edit, client, combinedResponse.body.changes);
+            return;
+        }
+    }
 }
-
 // #region Source Actions
-
 abstract class SourceAction extends vscode.CodeAction {
-	abstract build(
-		client: ITypeScriptServiceClient,
-		file: string,
-		diagnostics: readonly vscode.Diagnostic[],
-		token: vscode.CancellationToken,
-	): Promise;
+    abstract build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise;
 }
-
 class SourceFixAll extends SourceAction {
-
-	static readonly kind = vscode.CodeActionKind.SourceFixAll.append('ts');
-
-	constructor() {
-		super(vscode.l10n.t("Fix all fixable JS/TS issues"), SourceFixAll.kind);
-	}
-
-	async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
-		this.edit = new vscode.WorkspaceEdit();
-
-		await buildIndividualFixes([
-			{ codes: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface },
-			{ codes: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction },
-		], this.edit, client, file, diagnostics, token);
-
-		await buildCombinedFix([
-			{ codes: errorCodes.unreachableCode, fixName: fixNames.unreachableCode }
-		], this.edit, client, file, diagnostics, token);
-	}
+    static readonly kind = vscode.CodeActionKind.SourceFixAll.append('ts');
+    constructor() {
+        super(vscode.l10n.t("Fix all fixable JS/TS issues"), SourceFixAll.kind);
+    }
+    async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
+        this.edit = new vscode.WorkspaceEdit();
+        await buildIndividualFixes([
+            { codes: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface },
+            { codes: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction },
+        ], this.edit, client, file, diagnostics, token);
+        await buildCombinedFix([
+            { codes: errorCodes.unreachableCode, fixName: fixNames.unreachableCode }
+        ], this.edit, client, file, diagnostics, token);
+    }
 }
-
 class SourceRemoveUnused extends SourceAction {
-
-	static readonly kind = vscode.CodeActionKind.Source.append('removeUnused').append('ts');
-
-	constructor() {
-		super(vscode.l10n.t("Remove all unused code"), SourceRemoveUnused.kind);
-	}
-
-	async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
-		this.edit = new vscode.WorkspaceEdit();
-		await buildCombinedFix([
-			{ codes: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier },
-		], this.edit, client, file, diagnostics, token);
-	}
+    static readonly kind = vscode.CodeActionKind.Source.append('removeUnused').append('ts');
+    constructor() {
+        super(vscode.l10n.t("Remove all unused code"), SourceRemoveUnused.kind);
+    }
+    async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
+        this.edit = new vscode.WorkspaceEdit();
+        await buildCombinedFix([
+            { codes: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier },
+        ], this.edit, client, file, diagnostics, token);
+    }
 }
-
 class SourceAddMissingImports extends SourceAction {
-
-	static readonly kind = vscode.CodeActionKind.Source.append('addMissingImports').append('ts');
-
-	constructor() {
-		super(vscode.l10n.t("Add all missing imports"), SourceAddMissingImports.kind);
-	}
-
-	async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
-		this.edit = new vscode.WorkspaceEdit();
-		await buildCombinedFix([
-			{ codes: errorCodes.cannotFindName, fixName: fixNames.fixImport }
-		],
-			this.edit, client, file, diagnostics, token);
-	}
+    static readonly kind = vscode.CodeActionKind.Source.append('addMissingImports').append('ts');
+    constructor() {
+        super(vscode.l10n.t("Add all missing imports"), SourceAddMissingImports.kind);
+    }
+    async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise {
+        this.edit = new vscode.WorkspaceEdit();
+        await buildCombinedFix([
+            { codes: errorCodes.cannotFindName, fixName: fixNames.fixImport }
+        ], this.edit, client, file, diagnostics, token);
+    }
 }
-
 //#endregion
-
 class TypeScriptAutoFixProvider implements vscode.CodeActionProvider {
-
-	private static readonly kindProviders = [
-		SourceFixAll,
-		SourceRemoveUnused,
-		SourceAddMissingImports,
-	];
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-		private readonly diagnosticsManager: DiagnosticsManager,
-	) { }
-
-	public get metadata(): vscode.CodeActionProviderMetadata {
-		return {
-			providedCodeActionKinds: TypeScriptAutoFixProvider.kindProviders.map(x => x.kind),
-		};
-	}
-
-	public async provideCodeActions(
-		document: vscode.TextDocument,
-		_range: vscode.Range,
-		context: vscode.CodeActionContext,
-		token: vscode.CancellationToken
-	): Promise {
-		if (!context.only || !vscode.CodeActionKind.Source.intersects(context.only)) {
-			return undefined;
-		}
-
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		const actions = this.getFixAllActions(context.only);
-		const diagnostics = this.diagnosticsManager.getDiagnostics(document.uri);
-		if (!diagnostics.length) {
-			// Actions are a no-op in this case but we still want to return them
-			return actions;
-		}
-
-		await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
-
-		if (token.isCancellationRequested) {
-			return undefined;
-		}
-
-		await Promise.all(actions.map(action => action.build(this.client, file, diagnostics, token)));
-
-		return actions;
-	}
-
-	private getFixAllActions(only: vscode.CodeActionKind): SourceAction[] {
-		return TypeScriptAutoFixProvider.kindProviders
-			.filter(provider => only.intersects(provider.kind))
-			.map(provider => new provider());
-	}
+    private static readonly kindProviders = [
+        SourceFixAll,
+        SourceRemoveUnused,
+        SourceAddMissingImports,
+    ];
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager, private readonly diagnosticsManager: DiagnosticsManager) { }
+    public get metadata(): vscode.CodeActionProviderMetadata {
+        return {
+            providedCodeActionKinds: TypeScriptAutoFixProvider.kindProviders.map(x => x.kind),
+        };
+    }
+    public async provideCodeActions(document: vscode.TextDocument, _range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise {
+        if (!context.only || !vscode.CodeActionKind.Source.intersects(context.only)) {
+            return undefined;
+        }
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        const actions = this.getFixAllActions(context.only);
+        const diagnostics = this.diagnosticsManager.getDiagnostics(document.uri);
+        if (!diagnostics.length) {
+            // Actions are a no-op in this case but we still want to return them
+            return actions;
+        }
+        await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
+        if (token.isCancellationRequested) {
+            return undefined;
+        }
+        await Promise.all(actions.map(action => action.build(this.client, file, diagnostics, token)));
+        return actions;
+    }
+    private getFixAllActions(only: vscode.CodeActionKind): SourceAction[] {
+        return TypeScriptAutoFixProvider.kindProviders
+            .filter(provider => only.intersects(provider.kind))
+            .map(provider => new provider());
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-	diagnosticsManager: DiagnosticsManager,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		const provider = new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager);
-		return vscode.languages.registerCodeActionsProvider(selector.semantic, provider, provider.metadata);
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, diagnosticsManager: DiagnosticsManager) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        const provider = new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager);
+        return vscode.languages.registerCodeActionsProvider(selector.semantic, provider, provider.metadata);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/folding.ts b/extensions/typescript-language-features/Source/languageFeatures/folding.ts
index b8401d4ad60ad..d3bbd1b754d62 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/folding.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/folding.ts
@@ -2,88 +2,61 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import type * as Proto from '../tsServer/protocol/protocol';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import { coalesce } from '../utils/arrays';
-
 class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider {
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	async provideFoldingRanges(
-		document: vscode.TextDocument,
-		_context: vscode.FoldingContext,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return;
-		}
-
-		const args: Proto.FileRequestArgs = { file };
-		const response = await this.client.execute('getOutliningSpans', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return;
-		}
-
-		return coalesce(response.body.map(span => this.convertOutliningSpan(span, document)));
-	}
-
-	private convertOutliningSpan(
-		span: Proto.OutliningSpan,
-		document: vscode.TextDocument
-	): vscode.FoldingRange | undefined {
-		const range = typeConverters.Range.fromTextSpan(span.textSpan);
-		const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span);
-
-		// Workaround for #49904
-		if (span.kind === 'comment') {
-			const line = document.lineAt(range.start.line).text;
-			if (/\/\/\s*#endregion/gi.test(line)) {
-				return undefined;
-			}
-		}
-
-		const start = range.start.line;
-		const end = this.adjustFoldingEnd(range, document);
-		return new vscode.FoldingRange(start, end, kind);
-	}
-
-	private static readonly foldEndPairCharacters = ['}', ']', ')', '`', '>'];
-
-	private adjustFoldingEnd(range: vscode.Range, document: vscode.TextDocument) {
-		// workaround for #47240
-		if (range.end.character > 0) {
-			const foldEndCharacter = document.getText(new vscode.Range(range.end.translate(0, -1), range.end));
-			if (TypeScriptFoldingProvider.foldEndPairCharacters.includes(foldEndCharacter)) {
-				return Math.max(range.end.line - 1, range.start.line);
-			}
-		}
-
-		return range.end.line;
-	}
-
-	private static getFoldingRangeKind(span: Proto.OutliningSpan): vscode.FoldingRangeKind | undefined {
-		switch (span.kind) {
-			case 'comment': return vscode.FoldingRangeKind.Comment;
-			case 'region': return vscode.FoldingRangeKind.Region;
-			case 'imports': return vscode.FoldingRangeKind.Imports;
-			case 'code':
-			default: return undefined;
-		}
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    async provideFoldingRanges(document: vscode.TextDocument, _context: vscode.FoldingContext, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return;
+        }
+        const args: Proto.FileRequestArgs = { file };
+        const response = await this.client.execute('getOutliningSpans', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return;
+        }
+        return coalesce(response.body.map(span => this.convertOutliningSpan(span, document)));
+    }
+    private convertOutliningSpan(span: Proto.OutliningSpan, document: vscode.TextDocument): vscode.FoldingRange | undefined {
+        const range = typeConverters.Range.fromTextSpan(span.textSpan);
+        const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span);
+        // Workaround for #49904
+        if (span.kind === 'comment') {
+            const line = document.lineAt(range.start.line).text;
+            if (/\/\/\s*#endregion/gi.test(line)) {
+                return undefined;
+            }
+        }
+        const start = range.start.line;
+        const end = this.adjustFoldingEnd(range, document);
+        return new vscode.FoldingRange(start, end, kind);
+    }
+    private static readonly foldEndPairCharacters = ['}', ']', ')', '`', '>'];
+    private adjustFoldingEnd(range: vscode.Range, document: vscode.TextDocument) {
+        // workaround for #47240
+        if (range.end.character > 0) {
+            const foldEndCharacter = document.getText(new vscode.Range(range.end.translate(0, -1), range.end));
+            if (TypeScriptFoldingProvider.foldEndPairCharacters.includes(foldEndCharacter)) {
+                return Math.max(range.end.line - 1, range.start.line);
+            }
+        }
+        return range.end.line;
+    }
+    private static getFoldingRangeKind(span: Proto.OutliningSpan): vscode.FoldingRangeKind | undefined {
+        switch (span.kind) {
+            case 'comment': return vscode.FoldingRangeKind.Comment;
+            case 'region': return vscode.FoldingRangeKind.Region;
+            case 'imports': return vscode.FoldingRangeKind.Imports;
+            case 'code':
+            default: return undefined;
+        }
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-): vscode.Disposable {
-	return vscode.languages.registerFoldingRangeProvider(selector.syntax,
-		new TypeScriptFoldingProvider(client));
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient): vscode.Disposable {
+    return vscode.languages.registerFoldingRangeProvider(selector.syntax, new TypeScriptFoldingProvider(client));
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/formatting.ts b/extensions/typescript-language-features/Source/languageFeatures/formatting.ts
index 3b31499c37b1b..5ca558a46f9fa 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/formatting.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/formatting.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { LanguageDescription } from '../configuration/languageDescription';
@@ -11,93 +10,62 @@ import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import FileConfigurationManager from './fileConfigurationManager';
 import { conditionalRegistration, requireGlobalConfiguration } from './util/dependentRegistration';
-
 class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEditProvider, vscode.OnTypeFormattingEditProvider {
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly formattingOptionsManager: FileConfigurationManager
-	) { }
-
-	public async provideDocumentRangeFormattingEdits(
-		document: vscode.TextDocument,
-		range: vscode.Range,
-		options: vscode.FormattingOptions,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
-
-		const args = typeConverters.Range.toFormattingRequestArgs(file, range);
-		const response = await this.client.execute('format', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		return response.body.map(typeConverters.TextEdit.fromCodeEdit);
-	}
-
-	public async provideOnTypeFormattingEdits(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		ch: string,
-		options: vscode.FormattingOptions,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return [];
-		}
-
-		await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
-
-		const args: Proto.FormatOnKeyRequestArgs = {
-			...typeConverters.Position.toFileLocationRequestArgs(file, position),
-			key: ch
-		};
-		const response = await this.client.execute('formatonkey', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return [];
-		}
-
-		const result: vscode.TextEdit[] = [];
-		for (const edit of response.body) {
-			const textEdit = typeConverters.TextEdit.fromCodeEdit(edit);
-			const range = textEdit.range;
-			// Work around for https://github.com/microsoft/TypeScript/issues/6700.
-			// Check if we have an edit at the beginning of the line which only removes white spaces and leaves
-			// an empty line. Drop those edits
-			if (range.start.character === 0 && range.start.line === range.end.line && textEdit.newText === '') {
-				const lText = document.lineAt(range.start.line).text;
-				// If the edit leaves something on the line keep the edit (note that the end character is exclusive).
-				// Keep it also if it removes something else than whitespace
-				if (lText.trim().length > 0 || lText.length > range.end.character) {
-					result.push(textEdit);
-				}
-			} else {
-				result.push(textEdit);
-			}
-		}
-		return result;
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient, private readonly formattingOptionsManager: FileConfigurationManager) { }
+    public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
+        const args = typeConverters.Range.toFormattingRequestArgs(file, range);
+        const response = await this.client.execute('format', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        return response.body.map(typeConverters.TextEdit.fromCodeEdit);
+    }
+    public async provideOnTypeFormattingEdits(document: vscode.TextDocument, position: vscode.Position, ch: string, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return [];
+        }
+        await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token);
+        const args: Proto.FormatOnKeyRequestArgs = {
+            ...typeConverters.Position.toFileLocationRequestArgs(file, position),
+            key: ch
+        };
+        const response = await this.client.execute('formatonkey', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return [];
+        }
+        const result: vscode.TextEdit[] = [];
+        for (const edit of response.body) {
+            const textEdit = typeConverters.TextEdit.fromCodeEdit(edit);
+            const range = textEdit.range;
+            // Work around for https://github.com/microsoft/TypeScript/issues/6700.
+            // Check if we have an edit at the beginning of the line which only removes white spaces and leaves
+            // an empty line. Drop those edits
+            if (range.start.character === 0 && range.start.line === range.end.line && textEdit.newText === '') {
+                const lText = document.lineAt(range.start.line).text;
+                // If the edit leaves something on the line keep the edit (note that the end character is exclusive).
+                // Keep it also if it removes something else than whitespace
+                if (lText.trim().length > 0 || lText.length > range.end.character) {
+                    result.push(textEdit);
+                }
+            }
+            else {
+                result.push(textEdit);
+            }
+        }
+        return result;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager
-) {
-	return conditionalRegistration([
-		requireGlobalConfiguration(language.id, 'format.enable'),
-	], () => {
-		const formattingProvider = new TypeScriptFormattingProvider(client, fileConfigurationManager);
-		return vscode.Disposable.from(
-			vscode.languages.registerOnTypeFormattingEditProvider(selector.syntax, formattingProvider, ';', '}', '\n'),
-			vscode.languages.registerDocumentRangeFormattingEditProvider(selector.syntax, formattingProvider),
-		);
-	});
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager) {
+    return conditionalRegistration([
+        requireGlobalConfiguration(language.id, 'format.enable'),
+    ], () => {
+        const formattingProvider = new TypeScriptFormattingProvider(client, fileConfigurationManager);
+        return vscode.Disposable.from(vscode.languages.registerOnTypeFormattingEditProvider(selector.syntax, formattingProvider, ';', '}', '\n'), vscode.languages.registerDocumentRangeFormattingEditProvider(selector.syntax, formattingProvider));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/hover.ts b/extensions/typescript-language-features/Source/languageFeatures/hover.ts
index edb08d029faa2..29f2daf0dc495 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/hover.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/hover.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import type * as Proto from '../tsServer/protocol/protocol';
 import { ClientCapability, ITypeScriptServiceClient, ServerType } from '../typescriptService';
@@ -12,107 +11,70 @@ import { documentationToMarkdown } from './util/textRendering';
 import * as typeConverters from '../typeConverters';
 import FileConfigurationManager from './fileConfigurationManager';
 import { API } from '../tsServer/api';
-
-
 class TypeScriptHoverProvider implements vscode.HoverProvider {
-	private lastHoverAndLevel: [vscode.Hover, number] | undefined;
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-	) { }
-
-	public async provideHover(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken,
-		context?: vscode.HoverContext,
-	): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const enableExpandableHover = vscode.workspace.getConfiguration('typescript').get('experimental.expandableHover');
-		let verbosityLevel: number | undefined;
-		if (enableExpandableHover && this.client.apiVersion.gte(API.v570)) {
-			verbosityLevel = Math.max(0, this.getPreviousLevel(context?.previousHover) + (context?.verbosityDelta ?? 0));
-		}
-		const args = { ...typeConverters.Position.toFileLocationRequestArgs(filepath, position), verbosityLevel };
-
-		const response = await this.client.interruptGetErr(async () => {
-			await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
-
-			return this.client.execute('quickinfo', args, token);
-		});
-
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const contents = this.getContents(document.uri, response.body, response._serverType);
-		const range = typeConverters.Range.fromTextSpan(response.body);
-		const hover = verbosityLevel !== undefined ?
-			new vscode.VerboseHover(
-				contents,
-				range,
-				// @ts-expect-error
-				/*canIncreaseVerbosity*/ response.body.canIncreaseVerbosityLevel,
-				/*canDecreaseVerbosity*/ verbosityLevel !== 0
-			) : new vscode.Hover(
-				contents,
-				range
-			);
-
-		if (verbosityLevel !== undefined) {
-			this.lastHoverAndLevel = [hover, verbosityLevel];
-		}
-		return hover;
-	}
-
-	private getContents(
-		resource: vscode.Uri,
-		data: Proto.QuickInfoResponseBody,
-		source: ServerType | undefined,
-	) {
-		const parts: vscode.MarkdownString[] = [];
-
-		if (data.displayString) {
-			const displayParts: string[] = [];
-
-			if (source === ServerType.Syntax && this.client.hasCapabilityForResource(resource, ClientCapability.Semantic)) {
-				displayParts.push(
-					vscode.l10n.t({
-						message: "(loading...)",
-						comment: ['Prefix displayed for hover entries while the server is still loading']
-					}));
-			}
-
-			displayParts.push(data.displayString);
-			parts.push(new vscode.MarkdownString().appendCodeblock(displayParts.join(' '), 'typescript'));
-		}
-		const md = documentationToMarkdown(data.documentation, data.tags, this.client, resource);
-		parts.push(md);
-		return parts;
-	}
-
-	private getPreviousLevel(previousHover: vscode.Hover | undefined): number {
-		if (previousHover && this.lastHoverAndLevel && this.lastHoverAndLevel[0] === previousHover) {
-			return this.lastHoverAndLevel[1];
-		}
-		return 0;
-	}
+    private lastHoverAndLevel: [
+        vscode.Hover,
+        number
+    ] | undefined;
+    public constructor(private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager) { }
+    public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context?: vscode.HoverContext): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return undefined;
+        }
+        const enableExpandableHover = vscode.workspace.getConfiguration('typescript').get('experimental.expandableHover');
+        let verbosityLevel: number | undefined;
+        if (enableExpandableHover && this.client.apiVersion.gte(API.v570)) {
+            verbosityLevel = Math.max(0, this.getPreviousLevel(context?.previousHover) + (context?.verbosityDelta ?? 0));
+        }
+        const args = { ...typeConverters.Position.toFileLocationRequestArgs(filepath, position), verbosityLevel };
+        const response = await this.client.interruptGetErr(async () => {
+            await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
+            return this.client.execute('quickinfo', args, token);
+        });
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const contents = this.getContents(document.uri, response.body, response._serverType);
+        const range = typeConverters.Range.fromTextSpan(response.body);
+        const hover = verbosityLevel !== undefined ?
+            new vscode.VerboseHover(contents, range, 
+            // @ts-expect-error
+            /*canIncreaseVerbosity*/ response.body.canIncreaseVerbosityLevel, 
+            /*canDecreaseVerbosity*/ verbosityLevel !== 0) : new vscode.Hover(contents, range);
+        if (verbosityLevel !== undefined) {
+            this.lastHoverAndLevel = [hover, verbosityLevel];
+        }
+        return hover;
+    }
+    private getContents(resource: vscode.Uri, data: Proto.QuickInfoResponseBody, source: ServerType | undefined) {
+        const parts: vscode.MarkdownString[] = [];
+        if (data.displayString) {
+            const displayParts: string[] = [];
+            if (source === ServerType.Syntax && this.client.hasCapabilityForResource(resource, ClientCapability.Semantic)) {
+                displayParts.push(vscode.l10n.t({
+                    message: "(loading...)",
+                    comment: ['Prefix displayed for hover entries while the server is still loading']
+                }));
+            }
+            displayParts.push(data.displayString);
+            parts.push(new vscode.MarkdownString().appendCodeblock(displayParts.join(' '), 'typescript'));
+        }
+        const md = documentationToMarkdown(data.documentation, data.tags, this.client, resource);
+        parts.push(md);
+        return parts;
+    }
+    private getPreviousLevel(previousHover: vscode.Hover | undefined): number {
+        if (previousHover && this.lastHoverAndLevel && this.lastHoverAndLevel[0] === previousHover) {
+            return this.lastHoverAndLevel[1];
+        }
+        return 0;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-): vscode.Disposable {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerHoverProvider(selector.syntax,
-			new TypeScriptHoverProvider(client, fileConfigurationManager));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager): vscode.Disposable {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerHoverProvider(selector.syntax, new TypeScriptHoverProvider(client, fileConfigurationManager));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/implementations.ts b/extensions/typescript-language-features/Source/languageFeatures/implementations.ts
index b6b8aac1d2974..46d5f06dbece6 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/implementations.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/implementations.ts
@@ -2,27 +2,20 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import DefinitionProviderBase from './definitionProviderBase';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
 class TypeScriptImplementationProvider extends DefinitionProviderBase implements vscode.ImplementationProvider {
-	public provideImplementation(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
-		return this.getSymbolLocations('implementation', document, position, token);
-	}
+    public provideImplementation(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        return this.getSymbolLocations('implementation', document, position, token);
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerImplementationProvider(selector.semantic,
-			new TypeScriptImplementationProvider(client));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerImplementationProvider(selector.semantic, new TypeScriptImplementationProvider(client));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/inlayHints.ts b/extensions/typescript-language-features/Source/languageFeatures/inlayHints.ts
index 4fa38e4986b9a..7557c0033be9a 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/inlayHints.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/inlayHints.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { LanguageDescription } from '../configuration/languageDescription';
@@ -14,147 +13,110 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService
 import { Disposable } from '../utils/dispose';
 import FileConfigurationManager, { InlayHintSettingNames, getInlayHintsPreferences } from './fileConfigurationManager';
 import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration';
-
-
 const inlayHintSettingNames = Object.freeze([
-	InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName,
-	InlayHintSettingNames.parameterNamesEnabled,
-	InlayHintSettingNames.variableTypesEnabled,
-	InlayHintSettingNames.variableTypesSuppressWhenTypeMatchesName,
-	InlayHintSettingNames.propertyDeclarationTypesEnabled,
-	InlayHintSettingNames.functionLikeReturnTypesEnabled,
-	InlayHintSettingNames.enumMemberValuesEnabled,
+    InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName,
+    InlayHintSettingNames.parameterNamesEnabled,
+    InlayHintSettingNames.variableTypesEnabled,
+    InlayHintSettingNames.variableTypesSuppressWhenTypeMatchesName,
+    InlayHintSettingNames.propertyDeclarationTypesEnabled,
+    InlayHintSettingNames.functionLikeReturnTypesEnabled,
+    InlayHintSettingNames.enumMemberValuesEnabled,
 ]);
-
 class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHintsProvider {
-
-	public static readonly minVersion = API.v440;
-
-	private readonly _onDidChangeInlayHints = this._register(new vscode.EventEmitter());
-	public readonly onDidChangeInlayHints = this._onDidChangeInlayHints.event;
-
-	private hasReportedTelemetry = false;
-
-	constructor(
-		private readonly language: LanguageDescription,
-		private readonly client: ITypeScriptServiceClient,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-		private readonly telemetryReporter: TelemetryReporter,
-	) {
-		super();
-
-		this._register(vscode.workspace.onDidChangeConfiguration(e => {
-			if (inlayHintSettingNames.some(settingName => e.affectsConfiguration(language.id + '.' + settingName))) {
-				this._onDidChangeInlayHints.fire();
-			}
-		}));
-
-		// When a JS/TS file changes, change inlay hints for all visible editors
-		// since changes in one file can effect the hints the others.
-		this._register(vscode.workspace.onDidChangeTextDocument(e => {
-			if (language.languageIds.includes(e.document.languageId)) {
-				this._onDidChangeInlayHints.fire();
-			}
-		}));
-	}
-
-	async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise {
-		const filepath = this.client.toOpenTsFilePath(model);
-		if (!filepath) {
-			return;
-		}
-
-		if (!areInlayHintsEnabledForFile(this.language, model)) {
-			return;
-		}
-
-		const start = model.offsetAt(range.start);
-		const length = model.offsetAt(range.end) - start;
-
-		await this.fileConfigurationManager.ensureConfigurationForDocument(model, token);
-		if (token.isCancellationRequested) {
-			return;
-		}
-
-		if (!this.hasReportedTelemetry) {
-			this.hasReportedTelemetry = true;
-			/* __GDPR__
-				"inlayHints.provide" : {
-					"owner": "mjbvz",
-					"${include}": [
-						"${TypeScriptCommonProperties}"
-					]
-				}
-			*/
-			this.telemetryReporter.logTelemetry('inlayHints.provide', {});
-		}
-
-		const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token);
-		if (response.type !== 'response' || !response.success || !response.body) {
-			return;
-		}
-
-		return response.body.map(hint => {
-			const result = new vscode.InlayHint(
-				Position.fromLocation(hint.position),
-				this.convertInlayHintText(hint),
-				fromProtocolInlayHintKind(hint.kind)
-			);
-			result.paddingLeft = hint.whitespaceBefore;
-			result.paddingRight = hint.whitespaceAfter;
-			return result;
-		});
-	}
-
-	private convertInlayHintText(tsHint: Proto.InlayHintItem): string | vscode.InlayHintLabelPart[] {
-		if (tsHint.displayParts) {
-			return tsHint.displayParts.map((part): vscode.InlayHintLabelPart => {
-				const out = new vscode.InlayHintLabelPart(part.text);
-				if (part.span) {
-					out.location = Location.fromTextSpan(this.client.toResource(part.span.file), part.span);
-				}
-				return out;
-			});
-		}
-
-		return tsHint.text;
-	}
+    public static readonly minVersion = API.v440;
+    private readonly _onDidChangeInlayHints = this._register(new vscode.EventEmitter());
+    public readonly onDidChangeInlayHints = this._onDidChangeInlayHints.event;
+    private hasReportedTelemetry = false;
+    constructor(private readonly language: LanguageDescription, private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager, private readonly telemetryReporter: TelemetryReporter) {
+        super();
+        this._register(vscode.workspace.onDidChangeConfiguration(e => {
+            if (inlayHintSettingNames.some(settingName => e.affectsConfiguration(language.id + '.' + settingName))) {
+                this._onDidChangeInlayHints.fire();
+            }
+        }));
+        // When a JS/TS file changes, change inlay hints for all visible editors
+        // since changes in one file can effect the hints the others.
+        this._register(vscode.workspace.onDidChangeTextDocument(e => {
+            if (language.languageIds.includes(e.document.languageId)) {
+                this._onDidChangeInlayHints.fire();
+            }
+        }));
+    }
+    async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toOpenTsFilePath(model);
+        if (!filepath) {
+            return;
+        }
+        if (!areInlayHintsEnabledForFile(this.language, model)) {
+            return;
+        }
+        const start = model.offsetAt(range.start);
+        const length = model.offsetAt(range.end) - start;
+        await this.fileConfigurationManager.ensureConfigurationForDocument(model, token);
+        if (token.isCancellationRequested) {
+            return;
+        }
+        if (!this.hasReportedTelemetry) {
+            this.hasReportedTelemetry = true;
+            /* __GDPR__
+                "inlayHints.provide" : {
+                    "owner": "mjbvz",
+                    "${include}": [
+                        "${TypeScriptCommonProperties}"
+                    ]
+                }
+            */
+            this.telemetryReporter.logTelemetry('inlayHints.provide', {});
+        }
+        const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token);
+        if (response.type !== 'response' || !response.success || !response.body) {
+            return;
+        }
+        return response.body.map(hint => {
+            const result = new vscode.InlayHint(Position.fromLocation(hint.position), this.convertInlayHintText(hint), fromProtocolInlayHintKind(hint.kind));
+            result.paddingLeft = hint.whitespaceBefore;
+            result.paddingRight = hint.whitespaceAfter;
+            return result;
+        });
+    }
+    private convertInlayHintText(tsHint: Proto.InlayHintItem): string | vscode.InlayHintLabelPart[] {
+        if (tsHint.displayParts) {
+            return tsHint.displayParts.map((part): vscode.InlayHintLabelPart => {
+                const out = new vscode.InlayHintLabelPart(part.text);
+                if (part.span) {
+                    out.location = Location.fromTextSpan(this.client.toResource(part.span.file), part.span);
+                }
+                return out;
+            });
+        }
+        return tsHint.text;
+    }
 }
-
 function fromProtocolInlayHintKind(kind: Proto.InlayHintKind): vscode.InlayHintKind | undefined {
-	switch (kind) {
-		case 'Parameter': return vscode.InlayHintKind.Parameter;
-		case 'Type': return vscode.InlayHintKind.Type;
-		case 'Enum': return undefined;
-		default: return undefined;
-	}
+    switch (kind) {
+        case 'Parameter': return vscode.InlayHintKind.Parameter;
+        case 'Type': return vscode.InlayHintKind.Type;
+        case 'Enum': return undefined;
+        default: return undefined;
+    }
 }
-
 function areInlayHintsEnabledForFile(language: LanguageDescription, document: vscode.TextDocument) {
-	const config = vscode.workspace.getConfiguration(language.id, document);
-	const preferences = getInlayHintsPreferences(config);
-
-	return preferences.includeInlayParameterNameHints === 'literals' ||
-		preferences.includeInlayParameterNameHints === 'all' ||
-		preferences.includeInlayEnumMemberValueHints ||
-		preferences.includeInlayFunctionLikeReturnTypeHints ||
-		preferences.includeInlayFunctionParameterTypeHints ||
-		preferences.includeInlayPropertyDeclarationTypeHints ||
-		preferences.includeInlayVariableTypeHints;
+    const config = vscode.workspace.getConfiguration(language.id, document);
+    const preferences = getInlayHintsPreferences(config);
+    return preferences.includeInlayParameterNameHints === 'literals' ||
+        preferences.includeInlayParameterNameHints === 'all' ||
+        preferences.includeInlayEnumMemberValueHints ||
+        preferences.includeInlayFunctionLikeReturnTypeHints ||
+        preferences.includeInlayFunctionParameterTypeHints ||
+        preferences.includeInlayPropertyDeclarationTypeHints ||
+        preferences.includeInlayVariableTypeHints;
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-	telemetryReporter: TelemetryReporter,
-) {
-	return conditionalRegistration([
-		requireMinVersion(client, TypeScriptInlayHintsProvider.minVersion),
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		const provider = new TypeScriptInlayHintsProvider(language, client, fileConfigurationManager, telemetryReporter);
-		return vscode.languages.registerInlayHintsProvider(selector.semantic, provider);
-	});
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, telemetryReporter: TelemetryReporter) {
+    return conditionalRegistration([
+        requireMinVersion(client, TypeScriptInlayHintsProvider.minVersion),
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        const provider = new TypeScriptInlayHintsProvider(language, client, fileConfigurationManager, telemetryReporter);
+        return vscode.languages.registerInlayHintsProvider(selector.semantic, provider);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/jsDocCompletions.ts b/extensions/typescript-language-features/Source/languageFeatures/jsDocCompletions.ts
index f2c1b49c67f2e..bfa826a6b0e86 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/jsDocCompletions.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/jsDocCompletions.ts
@@ -2,134 +2,92 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { LanguageDescription } from '../configuration/languageDescription';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import FileConfigurationManager from './fileConfigurationManager';
-
-
-
 const defaultJsDoc = new vscode.SnippetString(`/**\n * $0\n */`);
-
 class JsDocCompletionItem extends vscode.CompletionItem {
-	constructor(
-		public readonly document: vscode.TextDocument,
-		public readonly position: vscode.Position
-	) {
-		super('/** */', vscode.CompletionItemKind.Text);
-		this.detail = vscode.l10n.t("JSDoc comment");
-		this.sortText = '\0';
-
-		const line = document.lineAt(position.line).text;
-		const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
-		const suffix = line.slice(position.character).match(/^\s*\**\//);
-		const start = position.translate(0, prefix ? -prefix[0].length : 0);
-		const range = new vscode.Range(start, position.translate(0, suffix ? suffix[0].length : 0));
-		this.range = { inserting: range, replacing: range };
-	}
+    constructor(public readonly document: vscode.TextDocument, public readonly position: vscode.Position) {
+        super('/** */', vscode.CompletionItemKind.Text);
+        this.detail = vscode.l10n.t("JSDoc comment");
+        this.sortText = '\0';
+        const line = document.lineAt(position.line).text;
+        const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
+        const suffix = line.slice(position.character).match(/^\s*\**\//);
+        const start = position.translate(0, prefix ? -prefix[0].length : 0);
+        const range = new vscode.Range(start, position.translate(0, suffix ? suffix[0].length : 0));
+        this.range = { inserting: range, replacing: range };
+    }
 }
-
 class JsDocCompletionProvider implements vscode.CompletionItemProvider {
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly language: LanguageDescription,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-	) { }
-
-	public async provideCompletionItems(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		if (!vscode.workspace.getConfiguration(this.language.id, document).get('suggest.completeJSDocs')) {
-			return undefined;
-		}
-
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		if (!this.isPotentiallyValidDocCompletionPosition(document, position)) {
-			return undefined;
-		}
-
-		const response = await this.client.interruptGetErr(async () => {
-			await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
-
-			const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
-			return this.client.execute('docCommentTemplate', args, token);
-		});
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const item = new JsDocCompletionItem(document, position);
-
-		// Workaround for #43619
-		// docCommentTemplate previously returned undefined for empty jsdoc templates.
-		// TS 2.7 now returns a single line doc comment, which breaks indentation.
-		if (response.body.newText === '/** */') {
-			item.insertText = defaultJsDoc;
-		} else {
-			item.insertText = templateToSnippet(response.body.newText);
-		}
-
-		return [item];
-	}
-
-	private isPotentiallyValidDocCompletionPosition(
-		document: vscode.TextDocument,
-		position: vscode.Position
-	): boolean {
-		// Only show the JSdoc completion when the everything before the cursor is whitespace
-		// or could be the opening of a comment
-		const line = document.lineAt(position.line).text;
-		const prefix = line.slice(0, position.character);
-		if (!/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/.test(prefix)) {
-			return false;
-		}
-
-		// And everything after is possibly a closing comment or more whitespace
-		const suffix = line.slice(position.character);
-		return /^\s*(\*+\/)?\s*$/.test(suffix);
-	}
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly language: LanguageDescription, private readonly fileConfigurationManager: FileConfigurationManager) { }
+    public async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        if (!vscode.workspace.getConfiguration(this.language.id, document).get('suggest.completeJSDocs')) {
+            return undefined;
+        }
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        if (!this.isPotentiallyValidDocCompletionPosition(document, position)) {
+            return undefined;
+        }
+        const response = await this.client.interruptGetErr(async () => {
+            await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
+            const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
+            return this.client.execute('docCommentTemplate', args, token);
+        });
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const item = new JsDocCompletionItem(document, position);
+        // Workaround for #43619
+        // docCommentTemplate previously returned undefined for empty jsdoc templates.
+        // TS 2.7 now returns a single line doc comment, which breaks indentation.
+        if (response.body.newText === '/** */') {
+            item.insertText = defaultJsDoc;
+        }
+        else {
+            item.insertText = templateToSnippet(response.body.newText);
+        }
+        return [item];
+    }
+    private isPotentiallyValidDocCompletionPosition(document: vscode.TextDocument, position: vscode.Position): boolean {
+        // Only show the JSdoc completion when the everything before the cursor is whitespace
+        // or could be the opening of a comment
+        const line = document.lineAt(position.line).text;
+        const prefix = line.slice(0, position.character);
+        if (!/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/.test(prefix)) {
+            return false;
+        }
+        // And everything after is possibly a closing comment or more whitespace
+        const suffix = line.slice(position.character);
+        return /^\s*(\*+\/)?\s*$/.test(suffix);
+    }
 }
-
 export function templateToSnippet(template: string): vscode.SnippetString {
-	// TODO: use append placeholder
-	let snippetIndex = 1;
-	template = template.replace(/\$/g, '\\$'); // CodeQL [SM02383] This is only used for text which is put into the editor. It is not for rendered html
-	template = template.replace(/^[ \t]*(?=(\/|[ ]\*))/gm, '');
-	template = template.replace(/^(\/\*\*\s*\*[ ]*)$/m, (x) => x + `\$0`);
-	template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)[ \t]*$/gm, (_param, type, post) => {
-		let out = '* @param ';
-		if (type === ' {any}' || type === ' {*}') {
-			out += `{\$\{${snippetIndex++}:*\}} `;
-		} else if (type) {
-			out += type + ' ';
-		}
-		out += post + ` \${${snippetIndex++}}`;
-		return out;
-	});
-
-	template = template.replace(/\* @returns[ \t]*$/gm, `* @returns \${${snippetIndex++}}`);
-
-	return new vscode.SnippetString(template);
+    // TODO: use append placeholder
+    let snippetIndex = 1;
+    template = template.replace(/\$/g, '\\$'); // CodeQL [SM02383] This is only used for text which is put into the editor. It is not for rendered html
+    template = template.replace(/^[ \t]*(?=(\/|[ ]\*))/gm, '');
+    template = template.replace(/^(\/\*\*\s*\*[ ]*)$/m, (x) => x + `\$0`);
+    template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)[ \t]*$/gm, (_param, type, post) => {
+        let out = '* @param ';
+        if (type === ' {any}' || type === ' {*}') {
+            out += `{\$\{${snippetIndex++}:*\}} `;
+        }
+        else if (type) {
+            out += type + ' ';
+        }
+        out += post + ` \${${snippetIndex++}}`;
+        return out;
+    });
+    template = template.replace(/\* @returns[ \t]*$/gm, `* @returns \${${snippetIndex++}}`);
+    return new vscode.SnippetString(template);
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-
-): vscode.Disposable {
-	return vscode.languages.registerCompletionItemProvider(selector.syntax,
-		new JsDocCompletionProvider(client, language, fileConfigurationManager),
-		'*');
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager): vscode.Disposable {
+    return vscode.languages.registerCompletionItemProvider(selector.syntax, new JsDocCompletionProvider(client, language, fileConfigurationManager), '*');
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/linkedEditing.ts b/extensions/typescript-language-features/Source/languageFeatures/linkedEditing.ts
index 9576cad1c7c2e..3cf370d00e7b8 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/linkedEditing.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/linkedEditing.ts
@@ -2,48 +2,34 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { API } from '../tsServer/api';
 import * as typeConverters from '../typeConverters';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration';
-
 class LinkedEditingSupport implements vscode.LinkedEditingRangeProvider {
-
-	public static readonly minVersion = API.v510;
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	async provideLinkedEditingRanges(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
-		const response = await this.client.execute('linkedEditingRange', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const wordPattern = response.body.wordPattern ? new RegExp(response.body.wordPattern) : undefined;
-		return new vscode.LinkedEditingRanges(response.body.ranges.map(range => typeConverters.Range.fromTextSpan(range)), wordPattern);
-	}
+    public static readonly minVersion = API.v510;
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    async provideLinkedEditingRanges(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return undefined;
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
+        const response = await this.client.execute('linkedEditingRange', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const wordPattern = response.body.wordPattern ? new RegExp(response.body.wordPattern) : undefined;
+        return new vscode.LinkedEditingRanges(response.body.ranges.map(range => typeConverters.Range.fromTextSpan(range)), wordPattern);
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient
-) {
-	return conditionalRegistration([
-		requireMinVersion(client, LinkedEditingSupport.minVersion),
-		requireSomeCapability(client, ClientCapability.Syntax),
-	], () => {
-		return vscode.languages.registerLinkedEditingRangeProvider(selector.syntax,
-			new LinkedEditingSupport(client));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireMinVersion(client, LinkedEditingSupport.minVersion),
+        requireSomeCapability(client, ClientCapability.Syntax),
+    ], () => {
+        return vscode.languages.registerLinkedEditingRangeProvider(selector.syntax, new LinkedEditingSupport(client));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/mappedCodeEditProvider.ts b/extensions/typescript-language-features/Source/languageFeatures/mappedCodeEditProvider.ts
index 06ce5557b6c3c..85016a0e8a9da 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/mappedCodeEditProvider.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/mappedCodeEditProvider.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { API } from '../tsServer/api';
 import { FileSpan } from '../tsServer/protocol/protocol';
@@ -10,57 +9,45 @@ import { ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireMinVersion } from './util/dependentRegistration';
 import { Range, WorkspaceEdit } from '../typeConverters';
 import { DocumentSelector } from '../configuration/documentSelector';
-
 class TsMappedEditsProvider implements vscode.MappedEditsProvider {
-	constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	async provideMappedEdits(document: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, token: vscode.CancellationToken): Promise {
-		if (!this.isEnabled()) {
-			return;
-		}
-
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return;
-		}
-
-		const response = await this.client.execute('mapCode', {
-			file,
-			mapping: {
-				contents: codeBlocks,
-				focusLocations: context.documents.map(documents => {
-					return documents.flatMap((contextItem): FileSpan[] => {
-						const file = this.client.toTsFilePath(contextItem.uri);
-						if (!file) {
-							return [];
-						}
-						return contextItem.ranges.map((range): FileSpan => ({ file, ...Range.toTextSpan(range) }));
-					});
-				}),
-			}
-		}, token);
-		if (response.type !== 'response' || !response.body) {
-			return;
-		}
-
-		return WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
-	}
-
-	private isEnabled(): boolean {
-		return vscode.workspace.getConfiguration('typescript').get('experimental.mappedCodeEdits.enabled', false);
-	}
+    constructor(private readonly client: ITypeScriptServiceClient) { }
+    async provideMappedEdits(document: vscode.TextDocument, codeBlocks: string[], context: vscode.MappedEditsContext, token: vscode.CancellationToken): Promise {
+        if (!this.isEnabled()) {
+            return;
+        }
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return;
+        }
+        const response = await this.client.execute('mapCode', {
+            file,
+            mapping: {
+                contents: codeBlocks,
+                focusLocations: context.documents.map(documents => {
+                    return documents.flatMap((contextItem): FileSpan[] => {
+                        const file = this.client.toTsFilePath(contextItem.uri);
+                        if (!file) {
+                            return [];
+                        }
+                        return contextItem.ranges.map((range): FileSpan => ({ file, ...Range.toTextSpan(range) }));
+                    });
+                }),
+            }
+        }, token);
+        if (response.type !== 'response' || !response.body) {
+            return;
+        }
+        return WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
+    }
+    private isEnabled(): boolean {
+        return vscode.workspace.getConfiguration('typescript').get('experimental.mappedCodeEdits.enabled', false);
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireMinVersion(client, API.v540)
-	], () => {
-		const provider = new TsMappedEditsProvider(client);
-		return vscode.chat.registerMappedEditsProvider(selector.semantic, provider);
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireMinVersion(client, API.v540)
+    ], () => {
+        const provider = new TsMappedEditsProvider(client);
+        return vscode.chat.registerMappedEditsProvider(selector.semantic, provider);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/organizeImports.ts b/extensions/typescript-language-features/Source/languageFeatures/organizeImports.ts
index 8219943856375..f574f08a5a237 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/organizeImports.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/organizeImports.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command, CommandManager } from '../commands/commandManager';
 import { DocumentSelector } from '../configuration/documentSelector';
@@ -15,173 +14,121 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService
 import { nulToken } from '../utils/cancellation';
 import FileConfigurationManager from './fileConfigurationManager';
 import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration';
-
-
 interface OrganizeImportsCommandMetadata {
-	readonly commandIds: readonly string[];
-	readonly title: string;
-	readonly minVersion?: API;
-	readonly kind: vscode.CodeActionKind;
-	readonly mode: OrganizeImportsMode;
+    readonly commandIds: readonly string[];
+    readonly title: string;
+    readonly minVersion?: API;
+    readonly kind: vscode.CodeActionKind;
+    readonly mode: OrganizeImportsMode;
 }
-
 const organizeImportsCommand: OrganizeImportsCommandMetadata = {
-	commandIds: [], // We use the generic 'Organize imports' command
-	title: vscode.l10n.t("Organize Imports"),
-	kind: vscode.CodeActionKind.SourceOrganizeImports,
-	mode: OrganizeImportsMode.All,
+    commandIds: [], // We use the generic 'Organize imports' command
+    title: vscode.l10n.t("Organize Imports"),
+    kind: vscode.CodeActionKind.SourceOrganizeImports,
+    mode: OrganizeImportsMode.All,
 };
-
 const sortImportsCommand: OrganizeImportsCommandMetadata = {
-	commandIds: ['typescript.sortImports', 'javascript.sortImports'],
-	minVersion: API.v430,
-	title: vscode.l10n.t("Sort Imports"),
-	kind: vscode.CodeActionKind.Source.append('sortImports'),
-	mode: OrganizeImportsMode.SortAndCombine,
+    commandIds: ['typescript.sortImports', 'javascript.sortImports'],
+    minVersion: API.v430,
+    title: vscode.l10n.t("Sort Imports"),
+    kind: vscode.CodeActionKind.Source.append('sortImports'),
+    mode: OrganizeImportsMode.SortAndCombine,
 };
-
 const removeUnusedImportsCommand: OrganizeImportsCommandMetadata = {
-	commandIds: ['typescript.removeUnusedImports', 'javascript.removeUnusedImports'],
-	minVersion: API.v490,
-	title: vscode.l10n.t("Remove Unused Imports"),
-	kind: vscode.CodeActionKind.Source.append('removeUnusedImports'),
-	mode: OrganizeImportsMode.RemoveUnused,
+    commandIds: ['typescript.removeUnusedImports', 'javascript.removeUnusedImports'],
+    minVersion: API.v490,
+    title: vscode.l10n.t("Remove Unused Imports"),
+    kind: vscode.CodeActionKind.Source.append('removeUnusedImports'),
+    mode: OrganizeImportsMode.RemoveUnused,
 };
-
 class DidOrganizeImportsCommand implements Command {
-
-	public static readonly ID = '_typescript.didOrganizeImports';
-	public readonly id = DidOrganizeImportsCommand.ID;
-
-	constructor(
-		private readonly telemetryReporter: TelemetryReporter,
-	) { }
-
-	public async execute(): Promise {
-		/* __GDPR__
-			"organizeImports.execute" : {
-				"owner": "mjbvz",
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		this.telemetryReporter.logTelemetry('organizeImports.execute', {});
-	}
+    public static readonly ID = '_typescript.didOrganizeImports';
+    public readonly id = DidOrganizeImportsCommand.ID;
+    constructor(private readonly telemetryReporter: TelemetryReporter) { }
+    public async execute(): Promise {
+        /* __GDPR__
+            "organizeImports.execute" : {
+                "owner": "mjbvz",
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        this.telemetryReporter.logTelemetry('organizeImports.execute', {});
+    }
 }
-
 class ImportCodeAction extends vscode.CodeAction {
-	constructor(
-		title: string,
-		kind: vscode.CodeActionKind,
-		public readonly document: vscode.TextDocument,
-	) {
-		super(title, kind);
-	}
+    constructor(title: string, kind: vscode.CodeActionKind, public readonly document: vscode.TextDocument) {
+        super(title, kind);
+    }
 }
-
 class ImportsCodeActionProvider implements vscode.CodeActionProvider {
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly commandMetadata: OrganizeImportsCommandMetadata,
-		commandManager: CommandManager,
-		private readonly fileConfigManager: FileConfigurationManager,
-		telemetryReporter: TelemetryReporter,
-	) {
-		commandManager.register(new DidOrganizeImportsCommand(telemetryReporter));
-	}
-
-	public provideCodeActions(
-		document: vscode.TextDocument,
-		_range: vscode.Range,
-		context: vscode.CodeActionContext,
-		_token: vscode.CancellationToken
-	): ImportCodeAction[] {
-		if (!context.only?.contains(this.commandMetadata.kind)) {
-			return [];
-		}
-
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return [];
-		}
-
-		return [new ImportCodeAction(this.commandMetadata.title, this.commandMetadata.kind, document)];
-	}
-
-	async resolveCodeAction(codeAction: ImportCodeAction, token: vscode.CancellationToken): Promise {
-		const response = await this.client.interruptGetErr(async () => {
-			await this.fileConfigManager.ensureConfigurationForDocument(codeAction.document, token);
-			if (token.isCancellationRequested) {
-				return;
-			}
-
-			const file = this.client.toOpenTsFilePath(codeAction.document);
-			if (!file) {
-				return;
-			}
-
-			const args: Proto.OrganizeImportsRequestArgs = {
-				scope: {
-					type: 'file',
-					args: { file }
-				},
-				// Deprecated in 4.9; `mode` takes priority
-				skipDestructiveCodeActions: this.commandMetadata.mode === OrganizeImportsMode.SortAndCombine,
-				mode: typeConverters.OrganizeImportsMode.toProtocolOrganizeImportsMode(this.commandMetadata.mode),
-			};
-
-			return this.client.execute('organizeImports', args, nulToken);
-		});
-		if (response?.type !== 'response' || !response.body || token.isCancellationRequested) {
-			return;
-		}
-
-		if (response.body.length) {
-			codeAction.edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
-		}
-
-		codeAction.command = { command: DidOrganizeImportsCommand.ID, title: '', arguments: [] };
-
-		return codeAction;
-	}
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly commandMetadata: OrganizeImportsCommandMetadata, commandManager: CommandManager, private readonly fileConfigManager: FileConfigurationManager, telemetryReporter: TelemetryReporter) {
+        commandManager.register(new DidOrganizeImportsCommand(telemetryReporter));
+    }
+    public provideCodeActions(document: vscode.TextDocument, _range: vscode.Range, context: vscode.CodeActionContext, _token: vscode.CancellationToken): ImportCodeAction[] {
+        if (!context.only?.contains(this.commandMetadata.kind)) {
+            return [];
+        }
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return [];
+        }
+        return [new ImportCodeAction(this.commandMetadata.title, this.commandMetadata.kind, document)];
+    }
+    async resolveCodeAction(codeAction: ImportCodeAction, token: vscode.CancellationToken): Promise {
+        const response = await this.client.interruptGetErr(async () => {
+            await this.fileConfigManager.ensureConfigurationForDocument(codeAction.document, token);
+            if (token.isCancellationRequested) {
+                return;
+            }
+            const file = this.client.toOpenTsFilePath(codeAction.document);
+            if (!file) {
+                return;
+            }
+            const args: Proto.OrganizeImportsRequestArgs = {
+                scope: {
+                    type: 'file',
+                    args: { file }
+                },
+                // Deprecated in 4.9; `mode` takes priority
+                skipDestructiveCodeActions: this.commandMetadata.mode === OrganizeImportsMode.SortAndCombine,
+                mode: typeConverters.OrganizeImportsMode.toProtocolOrganizeImportsMode(this.commandMetadata.mode),
+            };
+            return this.client.execute('organizeImports', args, nulToken);
+        });
+        if (response?.type !== 'response' || !response.body || token.isCancellationRequested) {
+            return;
+        }
+        if (response.body.length) {
+            codeAction.edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
+        }
+        codeAction.command = { command: DidOrganizeImportsCommand.ID, title: '', arguments: [] };
+        return codeAction;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-	commandManager: CommandManager,
-	fileConfigurationManager: FileConfigurationManager,
-	telemetryReporter: TelemetryReporter,
-): vscode.Disposable {
-	const disposables: vscode.Disposable[] = [];
-
-	for (const command of [organizeImportsCommand, sortImportsCommand, removeUnusedImportsCommand]) {
-		disposables.push(
-			conditionalRegistration([
-				requireMinVersion(client, command.minVersion ?? API.defaultVersion),
-				requireSomeCapability(client, ClientCapability.Semantic),
-			], () => {
-				const provider = new ImportsCodeActionProvider(client, command, commandManager, fileConfigurationManager, telemetryReporter);
-				return vscode.Disposable.from(
-					vscode.languages.registerCodeActionsProvider(selector.semantic, provider, {
-						providedCodeActionKinds: [command.kind]
-					}));
-			}),
-			// Always register these commands. We will show a warning if the user tries to run them on an unsupported version
-			...command.commandIds.map(id =>
-				commandManager.register({
-					id,
-					execute() {
-						return vscode.commands.executeCommand('editor.action.sourceAction', {
-							kind: command.kind.value,
-							apply: 'first',
-						});
-					}
-				}))
-		);
-	}
-
-	return vscode.Disposable.from(...disposables);
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient, commandManager: CommandManager, fileConfigurationManager: FileConfigurationManager, telemetryReporter: TelemetryReporter): vscode.Disposable {
+    const disposables: vscode.Disposable[] = [];
+    for (const command of [organizeImportsCommand, sortImportsCommand, removeUnusedImportsCommand]) {
+        disposables.push(conditionalRegistration([
+            requireMinVersion(client, command.minVersion ?? API.defaultVersion),
+            requireSomeCapability(client, ClientCapability.Semantic),
+        ], () => {
+            const provider = new ImportsCodeActionProvider(client, command, commandManager, fileConfigurationManager, telemetryReporter);
+            return vscode.Disposable.from(vscode.languages.registerCodeActionsProvider(selector.semantic, provider, {
+                providedCodeActionKinds: [command.kind]
+            }));
+        }), 
+        // Always register these commands. We will show a warning if the user tries to run them on an unsupported version
+        ...command.commandIds.map(id => commandManager.register({
+            id,
+            execute() {
+                return vscode.commands.executeCommand('editor.action.sourceAction', {
+                    kind: command.kind.value,
+                    apply: 'first',
+                });
+            }
+        })));
+    }
+    return vscode.Disposable.from(...disposables);
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/quickFix.ts b/extensions/typescript-language-features/Source/languageFeatures/quickFix.ts
index f724cfd8c4456..fa70c0c4ab718 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/quickFix.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/quickFix.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command, CommandManager } from '../commands/commandManager';
 import { DocumentSelector } from '../configuration/documentSelector';
@@ -19,542 +18,418 @@ import FileConfigurationManager from './fileConfigurationManager';
 import { applyCodeActionCommands, getEditForCodeAction } from './util/codeAction';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
 import { Expand, EditorChatFollowUp_Args, CompositeCommand, EditorChatFollowUp } from './util/copilot';
-
 type ApplyCodeActionCommand_args = {
-	readonly document: vscode.TextDocument;
-	readonly diagnostic: vscode.Diagnostic;
-	readonly action: Proto.CodeFixAction;
-	readonly followupAction?: Command;
+    readonly document: vscode.TextDocument;
+    readonly diagnostic: vscode.Diagnostic;
+    readonly action: Proto.CodeFixAction;
+    readonly followupAction?: Command;
 };
-
 class ApplyCodeActionCommand implements Command {
-	public static readonly ID = '_typescript.applyCodeActionCommand';
-	public readonly id = ApplyCodeActionCommand.ID;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly diagnosticManager: DiagnosticsManager,
-		private readonly telemetryReporter: TelemetryReporter,
-	) { }
-
-	public async execute({ document, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise {
-		/* __GDPR__
-			"quickFix.execute" : {
-				"owner": "mjbvz",
-				"fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		this.telemetryReporter.logTelemetry('quickFix.execute', {
-			fixName: action.fixName
-		});
-
-		this.diagnosticManager.deleteDiagnostic(document.uri, diagnostic);
-		const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken);
-		await followupAction?.execute();
-		return codeActionResult;
-	}
+    public static readonly ID = '_typescript.applyCodeActionCommand';
+    public readonly id = ApplyCodeActionCommand.ID;
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly diagnosticManager: DiagnosticsManager, private readonly telemetryReporter: TelemetryReporter) { }
+    public async execute({ document, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise {
+        /* __GDPR__
+            "quickFix.execute" : {
+                "owner": "mjbvz",
+                "fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        this.telemetryReporter.logTelemetry('quickFix.execute', {
+            fixName: action.fixName
+        });
+        this.diagnosticManager.deleteDiagnostic(document.uri, diagnostic);
+        const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken);
+        await followupAction?.execute();
+        return codeActionResult;
+    }
 }
-
 type ApplyFixAllCodeAction_args = {
-	readonly action: VsCodeFixAllCodeAction;
+    readonly action: VsCodeFixAllCodeAction;
 };
-
 class ApplyFixAllCodeAction implements Command {
-	public static readonly ID = '_typescript.applyFixAllCodeAction';
-	public readonly id = ApplyFixAllCodeAction.ID;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly telemetryReporter: TelemetryReporter,
-	) { }
-
-	public async execute(args: ApplyFixAllCodeAction_args): Promise {
-		/* __GDPR__
-			"quickFixAll.execute" : {
-				"owner": "mjbvz",
-				"fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		this.telemetryReporter.logTelemetry('quickFixAll.execute', {
-			fixName: args.action.tsAction.fixName
-		});
-
-		if (args.action.combinedResponse) {
-			await applyCodeActionCommands(this.client, args.action.combinedResponse.body.commands, nulToken);
-		}
-	}
+    public static readonly ID = '_typescript.applyFixAllCodeAction';
+    public readonly id = ApplyFixAllCodeAction.ID;
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly telemetryReporter: TelemetryReporter) { }
+    public async execute(args: ApplyFixAllCodeAction_args): Promise {
+        /* __GDPR__
+            "quickFixAll.execute" : {
+                "owner": "mjbvz",
+                "fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        this.telemetryReporter.logTelemetry('quickFixAll.execute', {
+            fixName: args.action.tsAction.fixName
+        });
+        if (args.action.combinedResponse) {
+            await applyCodeActionCommands(this.client, args.action.combinedResponse.body.commands, nulToken);
+        }
+    }
 }
-
 /**
  * Unique set of diagnostics keyed on diagnostic range and error code.
  */
 class DiagnosticsSet {
-	public static from(diagnostics: vscode.Diagnostic[]) {
-		const values = new Map();
-		for (const diagnostic of diagnostics) {
-			values.set(DiagnosticsSet.key(diagnostic), diagnostic);
-		}
-		return new DiagnosticsSet(values);
-	}
-
-	private static key(diagnostic: vscode.Diagnostic) {
-		const { start, end } = diagnostic.range;
-		return `${diagnostic.code}-${start.line},${start.character}-${end.line},${end.character}`;
-	}
-
-	private constructor(
-		private readonly _values: Map
-	) { }
-
-	public get values(): Iterable {
-		return this._values.values();
-	}
-
-	public get size() {
-		return this._values.size;
-	}
+    public static from(diagnostics: vscode.Diagnostic[]) {
+        const values = new Map();
+        for (const diagnostic of diagnostics) {
+            values.set(DiagnosticsSet.key(diagnostic), diagnostic);
+        }
+        return new DiagnosticsSet(values);
+    }
+    private static key(diagnostic: vscode.Diagnostic) {
+        const { start, end } = diagnostic.range;
+        return `${diagnostic.code}-${start.line},${start.character}-${end.line},${end.character}`;
+    }
+    private constructor(private readonly _values: Map) { }
+    public get values(): Iterable {
+        return this._values.values();
+    }
+    public get size() {
+        return this._values.size;
+    }
 }
-
 class VsCodeCodeAction extends vscode.CodeAction {
-	constructor(
-		public readonly tsAction: Proto.CodeFixAction,
-		title: string,
-		kind: vscode.CodeActionKind
-	) {
-		super(title, kind);
-	}
+    constructor(public readonly tsAction: Proto.CodeFixAction, title: string, kind: vscode.CodeActionKind) {
+        super(title, kind);
+    }
 }
-
 class VsCodeFixAllCodeAction extends VsCodeCodeAction {
-	constructor(
-		tsAction: Proto.CodeFixAction,
-		public readonly file: string,
-		title: string,
-		kind: vscode.CodeActionKind
-	) {
-		super(tsAction, title, kind);
-	}
-
-	public combinedResponse?: Proto.GetCombinedCodeFixResponse;
+    constructor(tsAction: Proto.CodeFixAction, public readonly file: string, title: string, kind: vscode.CodeActionKind) {
+        super(tsAction, title, kind);
+    }
+    public combinedResponse?: Proto.GetCombinedCodeFixResponse;
 }
-
 class CodeActionSet {
-	private readonly _actions = new Set();
-	private readonly _fixAllActions = new Map<{}, VsCodeCodeAction>();
-	private readonly _aiActions = new Set();
-
-	public *values(): Iterable {
-		yield* this._actions;
-		yield* this._aiActions;
-	}
-
-	public addAction(action: VsCodeCodeAction) {
-		if (action.isAI) {
-			// there are no separate fixAllActions for AI, and no duplicates, so return immediately
-			this._aiActions.add(action);
-			return;
-		}
-		for (const existing of this._actions) {
-			if (action.tsAction.fixName === existing.tsAction.fixName && equals(action.edit, existing.edit)) {
-				this._actions.delete(existing);
-			}
-		}
-
-		this._actions.add(action);
-
-		if (action.tsAction.fixId) {
-			// If we have an existing fix all action, then make sure it follows this action
-			const existingFixAll = this._fixAllActions.get(action.tsAction.fixId);
-			if (existingFixAll) {
-				this._actions.delete(existingFixAll);
-				this._actions.add(existingFixAll);
-			}
-		}
-	}
-
-	public addFixAllAction(fixId: {}, action: VsCodeCodeAction) {
-		const existing = this._fixAllActions.get(fixId);
-		if (existing) {
-			// reinsert action at back of actions list
-			this._actions.delete(existing);
-		}
-		this.addAction(action);
-		this._fixAllActions.set(fixId, action);
-	}
-
-	public hasFixAllAction(fixId: {}) {
-		return this._fixAllActions.has(fixId);
-	}
+    private readonly _actions = new Set();
+    private readonly _fixAllActions = new Map<{}, VsCodeCodeAction>();
+    private readonly _aiActions = new Set();
+    public *values(): Iterable {
+        yield* this._actions;
+        yield* this._aiActions;
+    }
+    public addAction(action: VsCodeCodeAction) {
+        if (action.isAI) {
+            // there are no separate fixAllActions for AI, and no duplicates, so return immediately
+            this._aiActions.add(action);
+            return;
+        }
+        for (const existing of this._actions) {
+            if (action.tsAction.fixName === existing.tsAction.fixName && equals(action.edit, existing.edit)) {
+                this._actions.delete(existing);
+            }
+        }
+        this._actions.add(action);
+        if (action.tsAction.fixId) {
+            // If we have an existing fix all action, then make sure it follows this action
+            const existingFixAll = this._fixAllActions.get(action.tsAction.fixId);
+            if (existingFixAll) {
+                this._actions.delete(existingFixAll);
+                this._actions.add(existingFixAll);
+            }
+        }
+    }
+    public addFixAllAction(fixId: {}, action: VsCodeCodeAction) {
+        const existing = this._fixAllActions.get(fixId);
+        if (existing) {
+            // reinsert action at back of actions list
+            this._actions.delete(existing);
+        }
+        this.addAction(action);
+        this._fixAllActions.set(fixId, action);
+    }
+    public hasFixAllAction(fixId: {}) {
+        return this._fixAllActions.has(fixId);
+    }
 }
-
 class SupportedCodeActionProvider {
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async getFixableDiagnosticsForContext(diagnostics: readonly vscode.Diagnostic[]): Promise {
-		const fixableCodes = await this.fixableDiagnosticCodes;
-		return DiagnosticsSet.from(
-			diagnostics.filter(diagnostic => typeof diagnostic.code !== 'undefined' && fixableCodes.has(diagnostic.code + '')));
-	}
-
-	@memoize
-	private get fixableDiagnosticCodes(): Thenable> {
-		return this.client.execute('getSupportedCodeFixes', null, nulToken)
-			.then(response => response.type === 'response' ? response.body || [] : [])
-			.then(codes => new Set(codes));
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async getFixableDiagnosticsForContext(diagnostics: readonly vscode.Diagnostic[]): Promise {
+        const fixableCodes = await this.fixableDiagnosticCodes;
+        return DiagnosticsSet.from(diagnostics.filter(diagnostic => typeof diagnostic.code !== 'undefined' && fixableCodes.has(diagnostic.code + '')));
+    }
+    @memoize
+    private get fixableDiagnosticCodes(): Thenable> {
+        return this.client.execute('getSupportedCodeFixes', null, nulToken)
+            .then(response => response.type === 'response' ? response.body || [] : [])
+            .then(codes => new Set(codes));
+    }
 }
-
 class TypeScriptQuickFixProvider implements vscode.CodeActionProvider {
-
-	private static readonly _maxCodeActionsPerFile: number = 1000;
-
-	public static readonly metadata: vscode.CodeActionProviderMetadata = {
-		providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
-	};
-
-	private readonly supportedCodeActionProvider: SupportedCodeActionProvider;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly formattingConfigurationManager: FileConfigurationManager,
-		commandManager: CommandManager,
-		private readonly diagnosticsManager: DiagnosticsManager,
-		telemetryReporter: TelemetryReporter
-	) {
-		commandManager.register(new CompositeCommand());
-		commandManager.register(new ApplyCodeActionCommand(client, diagnosticsManager, telemetryReporter));
-		commandManager.register(new ApplyFixAllCodeAction(client, telemetryReporter));
-		commandManager.register(new EditorChatFollowUp(client, telemetryReporter));
-
-		this.supportedCodeActionProvider = new SupportedCodeActionProvider(client);
-	}
-
-	public async provideCodeActions(
-		document: vscode.TextDocument,
-		range: vscode.Range,
-		context: vscode.CodeActionContext,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return;
-		}
-
-		let diagnostics = context.diagnostics;
-		if (this.client.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
-			// Delay for 500ms when there are pending diagnostics before recomputing up-to-date diagnostics.
-			await new Promise((resolve) => {
-				setTimeout(resolve, 500);
-			});
-
-			if (token.isCancellationRequested) {
-				return;
-			}
-			const allDiagnostics: vscode.Diagnostic[] = [];
-
-			// Match ranges again after getting new diagnostics
-			for (const diagnostic of this.diagnosticsManager.getDiagnostics(document.uri)) {
-				if (range.intersection(diagnostic.range)) {
-					const newLen = allDiagnostics.push(diagnostic);
-					if (newLen > TypeScriptQuickFixProvider._maxCodeActionsPerFile) {
-						break;
-					}
-				}
-			}
-			diagnostics = allDiagnostics;
-		}
-
-		const fixableDiagnostics = await this.supportedCodeActionProvider.getFixableDiagnosticsForContext(diagnostics);
-		if (!fixableDiagnostics.size || token.isCancellationRequested) {
-			return;
-		}
-
-		await this.formattingConfigurationManager.ensureConfigurationForDocument(document, token);
-		if (token.isCancellationRequested) {
-			return;
-		}
-
-		const results = new CodeActionSet();
-		for (const diagnostic of fixableDiagnostics.values) {
-			await this.getFixesForDiagnostic(document, file, diagnostic, results, token);
-			if (token.isCancellationRequested) {
-				return;
-			}
-		}
-
-		const allActions = Array.from(results.values());
-		for (const action of allActions) {
-			action.isPreferred = isPreferredFix(action, allActions);
-		}
-		return allActions;
-	}
-
-	public async resolveCodeAction(codeAction: VsCodeCodeAction, token: vscode.CancellationToken): Promise {
-		if (!(codeAction instanceof VsCodeFixAllCodeAction) || !codeAction.tsAction.fixId) {
-			return codeAction;
-		}
-
-		const arg: Proto.GetCombinedCodeFixRequestArgs = {
-			scope: {
-				type: 'file',
-				args: { file: codeAction.file }
-			},
-			fixId: codeAction.tsAction.fixId,
-		};
-
-		const response = await this.client.execute('getCombinedCodeFix', arg, token);
-		if (response.type === 'response') {
-			codeAction.combinedResponse = response;
-			codeAction.edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body.changes);
-		}
-
-		return codeAction;
-	}
-
-	private async getFixesForDiagnostic(
-		document: vscode.TextDocument,
-		file: string,
-		diagnostic: vscode.Diagnostic,
-		results: CodeActionSet,
-		token: vscode.CancellationToken,
-	): Promise {
-		const args: Proto.CodeFixRequestArgs = {
-			...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
-			errorCodes: [+(diagnostic.code!)]
-		};
-		const response = await this.client.execute('getCodeFixes', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return results;
-		}
-
-		for (const tsCodeFix of response.body) {
-			for (const action of this.getFixesForTsCodeAction(document, diagnostic, tsCodeFix)) {
-				results.addAction(action);
-			}
-			this.addFixAllForTsCodeAction(results, document.uri, file, diagnostic, tsCodeFix as Proto.CodeFixAction);
-		}
-		return results;
-	}
-
-	private getFixesForTsCodeAction(
-		document: vscode.TextDocument,
-		diagnostic: vscode.Diagnostic,
-		action: Proto.CodeFixAction
-	): VsCodeCodeAction[] {
-		const actions: VsCodeCodeAction[] = [];
-		const codeAction = new VsCodeCodeAction(action, action.description, vscode.CodeActionKind.QuickFix);
-		codeAction.edit = getEditForCodeAction(this.client, action);
-		codeAction.diagnostics = [diagnostic];
-		codeAction.ranges = [diagnostic.range];
-		codeAction.command = {
-			command: ApplyCodeActionCommand.ID,
-			arguments: [{ action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
-			title: ''
-		};
-		actions.push(codeAction);
-
-		const copilot = vscode.extensions.getExtension('github.copilot-chat');
-		if (copilot?.isActive) {
-			let message: string | undefined;
-			let expand: Expand | undefined;
-			let title = action.description;
-			if (action.fixName === fixNames.classIncorrectlyImplementsInterface) {
-				title += ' with Copilot';
-				message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
-				expand = { kind: 'code-action', action };
-			}
-			else if (action.fixName === fixNames.fixClassDoesntImplementInheritedAbstractMember) {
-				title += ' with Copilot';
-				message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
-				expand = { kind: 'code-action', action };
-			}
-			else if (action.fixName === fixNames.fixMissingFunctionDeclaration) {
-				title = `Implement missing function declaration '${document.getText(diagnostic.range)}' using Copilot`;
-				message = `Provide a reasonable implementation of the function ${document.getText(diagnostic.range)} given its type and the context it's called in.`;
-				expand = { kind: 'code-action', action };
-			}
-			else if (action.fixName === fixNames.inferFromUsage) {
-				const inferFromBody = new VsCodeCodeAction(action, 'Infer types using Copilot', vscode.CodeActionKind.QuickFix);
-				inferFromBody.edit = new vscode.WorkspaceEdit();
-				inferFromBody.diagnostics = [diagnostic];
-				inferFromBody.ranges = [diagnostic.range];
-				inferFromBody.isAI = true;
-				inferFromBody.command = {
-					command: EditorChatFollowUp.ID,
-					arguments: [{
-						message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.',
-						expand: { kind: 'navtree-function', pos: diagnostic.range.start },
-						document,
-						action: { type: 'quickfix', quickfix: action }
-					} satisfies EditorChatFollowUp_Args],
-					title: ''
-				};
-				actions.push(inferFromBody);
-			}
-			else if (action.fixName === fixNames.addNameToNamelessParameter) {
-				const newText = action.changes.map(change => change.textChanges.map(textChange => textChange.newText).join('')).join('');
-				title = 'Add meaningful parameter name with Copilot';
-				message = `Rename the parameter ${newText} with a more meaningful name.`;
-				expand = {
-					kind: 'navtree-function',
-					pos: diagnostic.range.start
-				};
-			}
-			if (expand && message !== undefined) {
-				const aiCodeAction = new VsCodeCodeAction(action, title, vscode.CodeActionKind.QuickFix);
-				aiCodeAction.edit = getEditForCodeAction(this.client, action);
-				aiCodeAction.edit?.insert(document.uri, diagnostic.range.start, '');
-				aiCodeAction.diagnostics = [diagnostic];
-				aiCodeAction.ranges = [diagnostic.range];
-				aiCodeAction.isAI = true;
-				aiCodeAction.command = {
-					command: CompositeCommand.ID,
-					title: '',
-					arguments: [{
-						command: ApplyCodeActionCommand.ID,
-						arguments: [{ action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
-						title: ''
-					}, {
-						command: EditorChatFollowUp.ID,
-						title: '',
-						arguments: [{
-							message,
-							expand,
-							document,
-							action: { type: 'quickfix', quickfix: action }
-						} satisfies EditorChatFollowUp_Args],
-					}],
-				};
-				actions.push(aiCodeAction);
-			}
-		}
-		return actions;
-	}
-
-	private addFixAllForTsCodeAction(
-		results: CodeActionSet,
-		resource: vscode.Uri,
-		file: string,
-		diagnostic: vscode.Diagnostic,
-		tsAction: Proto.CodeFixAction,
-	): CodeActionSet {
-		if (!tsAction.fixId || results.hasFixAllAction(tsAction.fixId)) {
-			return results;
-		}
-
-		// Make sure there are multiple diagnostics of the same type in the file
-		if (!this.diagnosticsManager.getDiagnostics(resource).some(x => {
-			if (x === diagnostic) {
-				return false;
-			}
-			return x.code === diagnostic.code
-				|| (fixAllErrorCodes.has(x.code as number) && fixAllErrorCodes.get(x.code as number) === fixAllErrorCodes.get(diagnostic.code as number));
-		})) {
-			return results;
-		}
-
-		const action = new VsCodeFixAllCodeAction(
-			tsAction,
-			file,
-			tsAction.fixAllDescription || vscode.l10n.t("{0} (Fix all in file)", tsAction.description),
-			vscode.CodeActionKind.QuickFix);
-
-		action.diagnostics = [diagnostic];
-		action.ranges = [diagnostic.range];
-		action.command = {
-			command: ApplyFixAllCodeAction.ID,
-			arguments: [{ action } satisfies ApplyFixAllCodeAction_args],
-			title: ''
-		};
-		results.addFixAllAction(tsAction.fixId, action);
-		return results;
-	}
+    private static readonly _maxCodeActionsPerFile: number = 1000;
+    public static readonly metadata: vscode.CodeActionProviderMetadata = {
+        providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
+    };
+    private readonly supportedCodeActionProvider: SupportedCodeActionProvider;
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly formattingConfigurationManager: FileConfigurationManager, commandManager: CommandManager, private readonly diagnosticsManager: DiagnosticsManager, telemetryReporter: TelemetryReporter) {
+        commandManager.register(new CompositeCommand());
+        commandManager.register(new ApplyCodeActionCommand(client, diagnosticsManager, telemetryReporter));
+        commandManager.register(new ApplyFixAllCodeAction(client, telemetryReporter));
+        commandManager.register(new EditorChatFollowUp(client, telemetryReporter));
+        this.supportedCodeActionProvider = new SupportedCodeActionProvider(client);
+    }
+    public async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return;
+        }
+        let diagnostics = context.diagnostics;
+        if (this.client.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
+            // Delay for 500ms when there are pending diagnostics before recomputing up-to-date diagnostics.
+            await new Promise((resolve) => {
+                setTimeout(resolve, 500);
+            });
+            if (token.isCancellationRequested) {
+                return;
+            }
+            const allDiagnostics: vscode.Diagnostic[] = [];
+            // Match ranges again after getting new diagnostics
+            for (const diagnostic of this.diagnosticsManager.getDiagnostics(document.uri)) {
+                if (range.intersection(diagnostic.range)) {
+                    const newLen = allDiagnostics.push(diagnostic);
+                    if (newLen > TypeScriptQuickFixProvider._maxCodeActionsPerFile) {
+                        break;
+                    }
+                }
+            }
+            diagnostics = allDiagnostics;
+        }
+        const fixableDiagnostics = await this.supportedCodeActionProvider.getFixableDiagnosticsForContext(diagnostics);
+        if (!fixableDiagnostics.size || token.isCancellationRequested) {
+            return;
+        }
+        await this.formattingConfigurationManager.ensureConfigurationForDocument(document, token);
+        if (token.isCancellationRequested) {
+            return;
+        }
+        const results = new CodeActionSet();
+        for (const diagnostic of fixableDiagnostics.values) {
+            await this.getFixesForDiagnostic(document, file, diagnostic, results, token);
+            if (token.isCancellationRequested) {
+                return;
+            }
+        }
+        const allActions = Array.from(results.values());
+        for (const action of allActions) {
+            action.isPreferred = isPreferredFix(action, allActions);
+        }
+        return allActions;
+    }
+    public async resolveCodeAction(codeAction: VsCodeCodeAction, token: vscode.CancellationToken): Promise {
+        if (!(codeAction instanceof VsCodeFixAllCodeAction) || !codeAction.tsAction.fixId) {
+            return codeAction;
+        }
+        const arg: Proto.GetCombinedCodeFixRequestArgs = {
+            scope: {
+                type: 'file',
+                args: { file: codeAction.file }
+            },
+            fixId: codeAction.tsAction.fixId,
+        };
+        const response = await this.client.execute('getCombinedCodeFix', arg, token);
+        if (response.type === 'response') {
+            codeAction.combinedResponse = response;
+            codeAction.edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body.changes);
+        }
+        return codeAction;
+    }
+    private async getFixesForDiagnostic(document: vscode.TextDocument, file: string, diagnostic: vscode.Diagnostic, results: CodeActionSet, token: vscode.CancellationToken): Promise {
+        const args: Proto.CodeFixRequestArgs = {
+            ...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
+            errorCodes: [+(diagnostic.code!)]
+        };
+        const response = await this.client.execute('getCodeFixes', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return results;
+        }
+        for (const tsCodeFix of response.body) {
+            for (const action of this.getFixesForTsCodeAction(document, diagnostic, tsCodeFix)) {
+                results.addAction(action);
+            }
+            this.addFixAllForTsCodeAction(results, document.uri, file, diagnostic, tsCodeFix as Proto.CodeFixAction);
+        }
+        return results;
+    }
+    private getFixesForTsCodeAction(document: vscode.TextDocument, diagnostic: vscode.Diagnostic, action: Proto.CodeFixAction): VsCodeCodeAction[] {
+        const actions: VsCodeCodeAction[] = [];
+        const codeAction = new VsCodeCodeAction(action, action.description, vscode.CodeActionKind.QuickFix);
+        codeAction.edit = getEditForCodeAction(this.client, action);
+        codeAction.diagnostics = [diagnostic];
+        codeAction.ranges = [diagnostic.range];
+        codeAction.command = {
+            command: ApplyCodeActionCommand.ID,
+            arguments: [{ action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
+            title: ''
+        };
+        actions.push(codeAction);
+        const copilot = vscode.extensions.getExtension('github.copilot-chat');
+        if (copilot?.isActive) {
+            let message: string | undefined;
+            let expand: Expand | undefined;
+            let title = action.description;
+            if (action.fixName === fixNames.classIncorrectlyImplementsInterface) {
+                title += ' with Copilot';
+                message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
+                expand = { kind: 'code-action', action };
+            }
+            else if (action.fixName === fixNames.fixClassDoesntImplementInheritedAbstractMember) {
+                title += ' with Copilot';
+                message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
+                expand = { kind: 'code-action', action };
+            }
+            else if (action.fixName === fixNames.fixMissingFunctionDeclaration) {
+                title = `Implement missing function declaration '${document.getText(diagnostic.range)}' using Copilot`;
+                message = `Provide a reasonable implementation of the function ${document.getText(diagnostic.range)} given its type and the context it's called in.`;
+                expand = { kind: 'code-action', action };
+            }
+            else if (action.fixName === fixNames.inferFromUsage) {
+                const inferFromBody = new VsCodeCodeAction(action, 'Infer types using Copilot', vscode.CodeActionKind.QuickFix);
+                inferFromBody.edit = new vscode.WorkspaceEdit();
+                inferFromBody.diagnostics = [diagnostic];
+                inferFromBody.ranges = [diagnostic.range];
+                inferFromBody.isAI = true;
+                inferFromBody.command = {
+                    command: EditorChatFollowUp.ID,
+                    arguments: [{
+                            message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.',
+                            expand: { kind: 'navtree-function', pos: diagnostic.range.start },
+                            document,
+                            action: { type: 'quickfix', quickfix: action }
+                        } satisfies EditorChatFollowUp_Args],
+                    title: ''
+                };
+                actions.push(inferFromBody);
+            }
+            else if (action.fixName === fixNames.addNameToNamelessParameter) {
+                const newText = action.changes.map(change => change.textChanges.map(textChange => textChange.newText).join('')).join('');
+                title = 'Add meaningful parameter name with Copilot';
+                message = `Rename the parameter ${newText} with a more meaningful name.`;
+                expand = {
+                    kind: 'navtree-function',
+                    pos: diagnostic.range.start
+                };
+            }
+            if (expand && message !== undefined) {
+                const aiCodeAction = new VsCodeCodeAction(action, title, vscode.CodeActionKind.QuickFix);
+                aiCodeAction.edit = getEditForCodeAction(this.client, action);
+                aiCodeAction.edit?.insert(document.uri, diagnostic.range.start, '');
+                aiCodeAction.diagnostics = [diagnostic];
+                aiCodeAction.ranges = [diagnostic.range];
+                aiCodeAction.isAI = true;
+                aiCodeAction.command = {
+                    command: CompositeCommand.ID,
+                    title: '',
+                    arguments: [{
+                            command: ApplyCodeActionCommand.ID,
+                            arguments: [{ action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
+                            title: ''
+                        }, {
+                            command: EditorChatFollowUp.ID,
+                            title: '',
+                            arguments: [{
+                                    message,
+                                    expand,
+                                    document,
+                                    action: { type: 'quickfix', quickfix: action }
+                                } satisfies EditorChatFollowUp_Args],
+                        }],
+                };
+                actions.push(aiCodeAction);
+            }
+        }
+        return actions;
+    }
+    private addFixAllForTsCodeAction(results: CodeActionSet, resource: vscode.Uri, file: string, diagnostic: vscode.Diagnostic, tsAction: Proto.CodeFixAction): CodeActionSet {
+        if (!tsAction.fixId || results.hasFixAllAction(tsAction.fixId)) {
+            return results;
+        }
+        // Make sure there are multiple diagnostics of the same type in the file
+        if (!this.diagnosticsManager.getDiagnostics(resource).some(x => {
+            if (x === diagnostic) {
+                return false;
+            }
+            return x.code === diagnostic.code
+                || (fixAllErrorCodes.has(x.code as number) && fixAllErrorCodes.get(x.code as number) === fixAllErrorCodes.get(diagnostic.code as number));
+        })) {
+            return results;
+        }
+        const action = new VsCodeFixAllCodeAction(tsAction, file, tsAction.fixAllDescription || vscode.l10n.t("{0} (Fix all in file)", tsAction.description), vscode.CodeActionKind.QuickFix);
+        action.diagnostics = [diagnostic];
+        action.ranges = [diagnostic.range];
+        action.command = {
+            command: ApplyFixAllCodeAction.ID,
+            arguments: [{ action } satisfies ApplyFixAllCodeAction_args],
+            title: ''
+        };
+        results.addFixAllAction(tsAction.fixId, action);
+        return results;
+    }
 }
-
 // Some fix all actions can actually fix multiple differnt diagnostics. Make sure we still show the fix all action
 // in such cases
 const fixAllErrorCodes = new Map([
-	// Missing async
-	[2339, 2339],
-	[2345, 2339],
+    // Missing async
+    [2339, 2339],
+    [2345, 2339],
 ]);
-
-const preferredFixes = new Map([
-	[fixNames.annotateWithTypeFromJSDoc, { priority: 2 }],
-	[fixNames.constructorForDerivedNeedSuperCall, { priority: 2 }],
-	[fixNames.extendsInterfaceBecomesImplements, { priority: 2 }],
-	[fixNames.awaitInSyncFunction, { priority: 2 }],
-	[fixNames.removeUnnecessaryAwait, { priority: 2 }],
-	[fixNames.classIncorrectlyImplementsInterface, { priority: 3 }],
-	[fixNames.classDoesntImplementInheritedAbstractMember, { priority: 3 }],
-	[fixNames.unreachableCode, { priority: 2 }],
-	[fixNames.unusedIdentifier, { priority: 2 }],
-	[fixNames.forgottenThisPropertyAccess, { priority: 2 }],
-	[fixNames.spelling, { priority: 0 }],
-	[fixNames.addMissingAwait, { priority: 2 }],
-	[fixNames.addMissingOverride, { priority: 2 }],
-	[fixNames.addMissingNewOperator, { priority: 2 }],
-	[fixNames.fixImport, { priority: 1, thereCanOnlyBeOne: true }],
+const preferredFixes = new Map([
+    [fixNames.annotateWithTypeFromJSDoc, { priority: 2 }],
+    [fixNames.constructorForDerivedNeedSuperCall, { priority: 2 }],
+    [fixNames.extendsInterfaceBecomesImplements, { priority: 2 }],
+    [fixNames.awaitInSyncFunction, { priority: 2 }],
+    [fixNames.removeUnnecessaryAwait, { priority: 2 }],
+    [fixNames.classIncorrectlyImplementsInterface, { priority: 3 }],
+    [fixNames.classDoesntImplementInheritedAbstractMember, { priority: 3 }],
+    [fixNames.unreachableCode, { priority: 2 }],
+    [fixNames.unusedIdentifier, { priority: 2 }],
+    [fixNames.forgottenThisPropertyAccess, { priority: 2 }],
+    [fixNames.spelling, { priority: 0 }],
+    [fixNames.addMissingAwait, { priority: 2 }],
+    [fixNames.addMissingOverride, { priority: 2 }],
+    [fixNames.addMissingNewOperator, { priority: 2 }],
+    [fixNames.fixImport, { priority: 1, thereCanOnlyBeOne: true }],
 ]);
-
-function isPreferredFix(
-	action: VsCodeCodeAction,
-	allActions: readonly VsCodeCodeAction[]
-): boolean {
-	if (action instanceof VsCodeFixAllCodeAction) {
-		return false;
-	}
-
-	const fixPriority = preferredFixes.get(action.tsAction.fixName);
-	if (!fixPriority) {
-		return false;
-	}
-
-	return allActions.every(otherAction => {
-		if (otherAction === action) {
-			return true;
-		}
-
-		if (otherAction instanceof VsCodeFixAllCodeAction) {
-			return true;
-		}
-
-		const otherFixPriority = preferredFixes.get(otherAction.tsAction.fixName);
-		if (!otherFixPriority || otherFixPriority.priority < fixPriority.priority) {
-			return true;
-		} else if (otherFixPriority.priority > fixPriority.priority) {
-			return false;
-		}
-
-		if (fixPriority.thereCanOnlyBeOne && action.tsAction.fixName === otherAction.tsAction.fixName) {
-			return false;
-		}
-
-		return true;
-	});
+function isPreferredFix(action: VsCodeCodeAction, allActions: readonly VsCodeCodeAction[]): boolean {
+    if (action instanceof VsCodeFixAllCodeAction) {
+        return false;
+    }
+    const fixPriority = preferredFixes.get(action.tsAction.fixName);
+    if (!fixPriority) {
+        return false;
+    }
+    return allActions.every(otherAction => {
+        if (otherAction === action) {
+            return true;
+        }
+        if (otherAction instanceof VsCodeFixAllCodeAction) {
+            return true;
+        }
+        const otherFixPriority = preferredFixes.get(otherAction.tsAction.fixName);
+        if (!otherFixPriority || otherFixPriority.priority < fixPriority.priority) {
+            return true;
+        }
+        else if (otherFixPriority.priority > fixPriority.priority) {
+            return false;
+        }
+        if (fixPriority.thereCanOnlyBeOne && action.tsAction.fixName === otherAction.tsAction.fixName) {
+            return false;
+        }
+        return true;
+    });
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-	commandManager: CommandManager,
-	diagnosticsManager: DiagnosticsManager,
-	telemetryReporter: TelemetryReporter
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerCodeActionsProvider(selector.semantic,
-			new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter),
-			TypeScriptQuickFixProvider.metadata);
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, commandManager: CommandManager, diagnosticsManager: DiagnosticsManager, telemetryReporter: TelemetryReporter) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerCodeActionsProvider(selector.semantic, new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, telemetryReporter), TypeScriptQuickFixProvider.metadata);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/refactor.ts b/extensions/typescript-language-features/Source/languageFeatures/refactor.ts
index 0364b7fad3ef9..a2c6f5d8ede13 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/refactor.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/refactor.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as vscode from 'vscode';
 import { Utils } from 'vscode-uri';
@@ -23,760 +22,615 @@ import { nulToken } from '../utils/cancellation';
 import FormattingOptionsManager from './fileConfigurationManager';
 import { CompositeCommand, EditorChatFollowUp } from './util/copilot';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
 function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto.FileCodeEdits[]): vscode.WorkspaceEdit {
-	const workspaceEdit = new vscode.WorkspaceEdit();
-	for (const edit of edits) {
-		const resource = client.toResource(edit.fileName);
-		if (resource.scheme === fileSchemes.file) {
-			workspaceEdit.createFile(resource, { ignoreIfExists: true });
-		}
-	}
-	typeConverters.WorkspaceEdit.withFileCodeEdits(workspaceEdit, client, edits);
-	return workspaceEdit;
+    const workspaceEdit = new vscode.WorkspaceEdit();
+    for (const edit of edits) {
+        const resource = client.toResource(edit.fileName);
+        if (resource.scheme === fileSchemes.file) {
+            workspaceEdit.createFile(resource, { ignoreIfExists: true });
+        }
+    }
+    typeConverters.WorkspaceEdit.withFileCodeEdits(workspaceEdit, client, edits);
+    return workspaceEdit;
 }
-
-
 namespace DidApplyRefactoringCommand {
-	export interface Args {
-		readonly action: string;
-		readonly trigger: vscode.CodeActionTriggerKind;
-	}
+    export interface Args {
+        readonly action: string;
+        readonly trigger: vscode.CodeActionTriggerKind;
+    }
 }
-
 class DidApplyRefactoringCommand implements Command {
-	public static readonly ID = '_typescript.didApplyRefactoring';
-	public readonly id = DidApplyRefactoringCommand.ID;
-
-	constructor(
-		private readonly telemetryReporter: TelemetryReporter
-	) { }
-
-	public async execute(args: DidApplyRefactoringCommand.Args): Promise {
-		/* __GDPR__
-			"refactor.execute" : {
-				"owner": "mjbvz",
-				"action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-				"trigger" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		this.telemetryReporter.logTelemetry('refactor.execute', {
-			action: args.action,
-			trigger: args.trigger,
-		});
-	}
+    public static readonly ID = '_typescript.didApplyRefactoring';
+    public readonly id = DidApplyRefactoringCommand.ID;
+    constructor(private readonly telemetryReporter: TelemetryReporter) { }
+    public async execute(args: DidApplyRefactoringCommand.Args): Promise {
+        /* __GDPR__
+            "refactor.execute" : {
+                "owner": "mjbvz",
+                "action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                "trigger" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        this.telemetryReporter.logTelemetry('refactor.execute', {
+            action: args.action,
+            trigger: args.trigger,
+        });
+    }
 }
 namespace SelectRefactorCommand {
-	export interface Args {
-		readonly document: vscode.TextDocument;
-		readonly refactor: Proto.ApplicableRefactorInfo;
-		readonly rangeOrSelection: vscode.Range | vscode.Selection;
-		readonly trigger: vscode.CodeActionTriggerKind;
-	}
+    export interface Args {
+        readonly document: vscode.TextDocument;
+        readonly refactor: Proto.ApplicableRefactorInfo;
+        readonly rangeOrSelection: vscode.Range | vscode.Selection;
+        readonly trigger: vscode.CodeActionTriggerKind;
+    }
 }
-
 class SelectRefactorCommand implements Command {
-	public static readonly ID = '_typescript.selectRefactoring';
-	public readonly id = SelectRefactorCommand.ID;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-	) { }
-
-	public async execute(args: SelectRefactorCommand.Args): Promise {
-		const file = this.client.toOpenTsFilePath(args.document);
-		if (!file) {
-			return;
-		}
-
-		const selected = await vscode.window.showQuickPick(args.refactor.actions.map((action): vscode.QuickPickItem & { action: Proto.RefactorActionInfo } => ({
-			action,
-			label: action.name,
-			description: action.description,
-		})));
-		if (!selected) {
-			return;
-		}
-
-		const tsAction = new InlinedCodeAction(this.client, args.document, args.refactor, selected.action, args.rangeOrSelection, args.trigger);
-		await tsAction.resolve(nulToken);
-
-		if (tsAction.edit) {
-			if (!(await vscode.workspace.applyEdit(tsAction.edit, { isRefactoring: true }))) {
-				vscode.window.showErrorMessage(vscode.l10n.t("Could not apply refactoring"));
-				return;
-			}
-		}
-
-		if (tsAction.command) {
-			await vscode.commands.executeCommand(tsAction.command.command, ...(tsAction.command.arguments ?? []));
-		}
-	}
+    public static readonly ID = '_typescript.selectRefactoring';
+    public readonly id = SelectRefactorCommand.ID;
+    constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async execute(args: SelectRefactorCommand.Args): Promise {
+        const file = this.client.toOpenTsFilePath(args.document);
+        if (!file) {
+            return;
+        }
+        const selected = await vscode.window.showQuickPick(args.refactor.actions.map((action): vscode.QuickPickItem & {
+            action: Proto.RefactorActionInfo;
+        } => ({
+            action,
+            label: action.name,
+            description: action.description,
+        })));
+        if (!selected) {
+            return;
+        }
+        const tsAction = new InlinedCodeAction(this.client, args.document, args.refactor, selected.action, args.rangeOrSelection, args.trigger);
+        await tsAction.resolve(nulToken);
+        if (tsAction.edit) {
+            if (!(await vscode.workspace.applyEdit(tsAction.edit, { isRefactoring: true }))) {
+                vscode.window.showErrorMessage(vscode.l10n.t("Could not apply refactoring"));
+                return;
+            }
+        }
+        if (tsAction.command) {
+            await vscode.commands.executeCommand(tsAction.command.command, ...(tsAction.command.arguments ?? []));
+        }
+    }
 }
-
 namespace MoveToFileRefactorCommand {
-	export interface Args {
-		readonly document: vscode.TextDocument;
-		readonly action: Proto.RefactorActionInfo;
-		readonly range: vscode.Range;
-		readonly trigger: vscode.CodeActionTriggerKind;
-	}
+    export interface Args {
+        readonly document: vscode.TextDocument;
+        readonly action: Proto.RefactorActionInfo;
+        readonly range: vscode.Range;
+        readonly trigger: vscode.CodeActionTriggerKind;
+    }
 }
-
 class MoveToFileRefactorCommand implements Command {
-	public static readonly ID = '_typescript.moveToFileRefactoring';
-	public readonly id = MoveToFileRefactorCommand.ID;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly didApplyCommand: DidApplyRefactoringCommand
-	) { }
-
-	public async execute(args: MoveToFileRefactorCommand.Args): Promise {
-		const file = this.client.toOpenTsFilePath(args.document);
-		if (!file) {
-			return;
-		}
-
-		const targetFile = await this.getTargetFile(args.document, file, args.range);
-		if (!targetFile || targetFile.toString() === file.toString()) {
-			return;
-		}
-
-		const fileSuggestionArgs: Proto.GetEditsForRefactorRequestArgs = {
-			...typeConverters.Range.toFileRangeRequestArgs(file, args.range),
-			action: 'Move to file',
-			refactor: 'Move to file',
-			interactiveRefactorArguments: { targetFile },
-		};
-
-		const response = await this.client.execute('getEditsForRefactor', fileSuggestionArgs, nulToken);
-		if (response.type !== 'response' || !response.body) {
-			return;
-		}
-		const edit = toWorkspaceEdit(this.client, response.body.edits);
-		if (!(await vscode.workspace.applyEdit(edit, { isRefactoring: true }))) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Could not apply refactoring"));
-			return;
-		}
-
-		await this.didApplyCommand.execute({ action: args.action.name, trigger: args.trigger });
-	}
-
-	private async getTargetFile(document: vscode.TextDocument, file: string, range: vscode.Range): Promise {
-		const args = typeConverters.Range.toFileRangeRequestArgs(file, range);
-		const response = await this.client.execute('getMoveToRefactoringFileSuggestions', args, nulToken);
-		if (response.type !== 'response' || !response.body) {
-			return;
-		}
-		const body = response.body;
-
-		type DestinationItem = vscode.QuickPickItem & { readonly file?: string };
-		const selectExistingFileItem: vscode.QuickPickItem = { label: vscode.l10n.t("Select existing file...") };
-		const selectNewFileItem: vscode.QuickPickItem = { label: vscode.l10n.t("Enter new file path...") };
-
-		const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
-		const quickPick = vscode.window.createQuickPick();
-		quickPick.ignoreFocusOut = true;
-
-		// true so we don't skip computing in the first call
-		let quickPickInRelativeMode = true;
-		const updateItems = () => {
-			const relativeQuery = ['./', '../'].find(str => quickPick.value.startsWith(str));
-			if (quickPickInRelativeMode === false && !!relativeQuery === false) {
-				return;
-			}
-			quickPickInRelativeMode = !!relativeQuery;
-			const destinationItems = body.files.map((file): DestinationItem | undefined => {
-				const uri = this.client.toResource(file);
-				const parentDir = Utils.dirname(uri);
-				const filename = Utils.basename(uri);
-
-				let description: string | undefined;
-				if (workspaceFolder) {
-					if (uri.scheme === Schemes.file) {
-						description = path.relative(workspaceFolder.uri.fsPath, parentDir.fsPath);
-					} else {
-						description = path.posix.relative(workspaceFolder.uri.path, parentDir.path);
-					}
-					if (relativeQuery) {
-						const convertRelativePath = (str: string) => {
-							return !str.startsWith('../') ? `./${str}` : str;
-						};
-
-						const relativePath = convertRelativePath(path.relative(path.dirname(document.uri.fsPath), uri.fsPath));
-						if (!relativePath.startsWith(relativeQuery)) {
-							return;
-						}
-						description = relativePath;
-					}
-				} else {
-					description = parentDir.fsPath;
-				}
-
-				return {
-					file,
-					label: Utils.basename(uri),
-					description: relativeQuery ? description : path.join(description, filename),
-				};
-			});
-			quickPick.items = [
-				selectExistingFileItem,
-				selectNewFileItem,
-				{ label: vscode.l10n.t("destination files"), kind: vscode.QuickPickItemKind.Separator },
-				...coalesce(destinationItems)
-			];
-		};
-		quickPick.title = vscode.l10n.t("Move to File");
-		quickPick.placeholder = vscode.l10n.t("Enter file path");
-		quickPick.matchOnDescription = true;
-		quickPick.onDidChangeValue(updateItems);
-		updateItems();
-
-		const picked = await new Promise(resolve => {
-			quickPick.onDidAccept(() => {
-				resolve(quickPick.selectedItems[0]);
-				quickPick.dispose();
-			});
-			quickPick.onDidHide(() => {
-				resolve(undefined);
-				quickPick.dispose();
-			});
-			quickPick.show();
-		});
-		if (!picked) {
-			return;
-		}
-
-		if (picked === selectExistingFileItem) {
-			const picked = await vscode.window.showOpenDialog({
-				title: vscode.l10n.t("Select move destination"),
-				openLabel: vscode.l10n.t("Move to File"),
-				defaultUri: Utils.dirname(document.uri),
-			});
-			return picked?.length ? this.client.toTsFilePath(picked[0]) : undefined;
-		} else if (picked === selectNewFileItem) {
-			const picked = await vscode.window.showSaveDialog({
-				title: vscode.l10n.t("Select move destination"),
-				saveLabel: vscode.l10n.t("Move to File"),
-				defaultUri: this.client.toResource(response.body.newFileName),
-			});
-			return picked ? this.client.toTsFilePath(picked) : undefined;
-		} else {
-			return picked.file;
-		}
-	}
+    public static readonly ID = '_typescript.moveToFileRefactoring';
+    public readonly id = MoveToFileRefactorCommand.ID;
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly didApplyCommand: DidApplyRefactoringCommand) { }
+    public async execute(args: MoveToFileRefactorCommand.Args): Promise {
+        const file = this.client.toOpenTsFilePath(args.document);
+        if (!file) {
+            return;
+        }
+        const targetFile = await this.getTargetFile(args.document, file, args.range);
+        if (!targetFile || targetFile.toString() === file.toString()) {
+            return;
+        }
+        const fileSuggestionArgs: Proto.GetEditsForRefactorRequestArgs = {
+            ...typeConverters.Range.toFileRangeRequestArgs(file, args.range),
+            action: 'Move to file',
+            refactor: 'Move to file',
+            interactiveRefactorArguments: { targetFile },
+        };
+        const response = await this.client.execute('getEditsForRefactor', fileSuggestionArgs, nulToken);
+        if (response.type !== 'response' || !response.body) {
+            return;
+        }
+        const edit = toWorkspaceEdit(this.client, response.body.edits);
+        if (!(await vscode.workspace.applyEdit(edit, { isRefactoring: true }))) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Could not apply refactoring"));
+            return;
+        }
+        await this.didApplyCommand.execute({ action: args.action.name, trigger: args.trigger });
+    }
+    private async getTargetFile(document: vscode.TextDocument, file: string, range: vscode.Range): Promise {
+        const args = typeConverters.Range.toFileRangeRequestArgs(file, range);
+        const response = await this.client.execute('getMoveToRefactoringFileSuggestions', args, nulToken);
+        if (response.type !== 'response' || !response.body) {
+            return;
+        }
+        const body = response.body;
+        type DestinationItem = vscode.QuickPickItem & {
+            readonly file?: string;
+        };
+        const selectExistingFileItem: vscode.QuickPickItem = { label: vscode.l10n.t("Select existing file...") };
+        const selectNewFileItem: vscode.QuickPickItem = { label: vscode.l10n.t("Enter new file path...") };
+        const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
+        const quickPick = vscode.window.createQuickPick();
+        quickPick.ignoreFocusOut = true;
+        // true so we don't skip computing in the first call
+        let quickPickInRelativeMode = true;
+        const updateItems = () => {
+            const relativeQuery = ['./', '../'].find(str => quickPick.value.startsWith(str));
+            if (quickPickInRelativeMode === false && !!relativeQuery === false) {
+                return;
+            }
+            quickPickInRelativeMode = !!relativeQuery;
+            const destinationItems = body.files.map((file): DestinationItem | undefined => {
+                const uri = this.client.toResource(file);
+                const parentDir = Utils.dirname(uri);
+                const filename = Utils.basename(uri);
+                let description: string | undefined;
+                if (workspaceFolder) {
+                    if (uri.scheme === Schemes.file) {
+                        description = path.relative(workspaceFolder.uri.fsPath, parentDir.fsPath);
+                    }
+                    else {
+                        description = path.posix.relative(workspaceFolder.uri.path, parentDir.path);
+                    }
+                    if (relativeQuery) {
+                        const convertRelativePath = (str: string) => {
+                            return !str.startsWith('../') ? `./${str}` : str;
+                        };
+                        const relativePath = convertRelativePath(path.relative(path.dirname(document.uri.fsPath), uri.fsPath));
+                        if (!relativePath.startsWith(relativeQuery)) {
+                            return;
+                        }
+                        description = relativePath;
+                    }
+                }
+                else {
+                    description = parentDir.fsPath;
+                }
+                return {
+                    file,
+                    label: Utils.basename(uri),
+                    description: relativeQuery ? description : path.join(description, filename),
+                };
+            });
+            quickPick.items = [
+                selectExistingFileItem,
+                selectNewFileItem,
+                { label: vscode.l10n.t("destination files"), kind: vscode.QuickPickItemKind.Separator },
+                ...coalesce(destinationItems)
+            ];
+        };
+        quickPick.title = vscode.l10n.t("Move to File");
+        quickPick.placeholder = vscode.l10n.t("Enter file path");
+        quickPick.matchOnDescription = true;
+        quickPick.onDidChangeValue(updateItems);
+        updateItems();
+        const picked = await new Promise(resolve => {
+            quickPick.onDidAccept(() => {
+                resolve(quickPick.selectedItems[0]);
+                quickPick.dispose();
+            });
+            quickPick.onDidHide(() => {
+                resolve(undefined);
+                quickPick.dispose();
+            });
+            quickPick.show();
+        });
+        if (!picked) {
+            return;
+        }
+        if (picked === selectExistingFileItem) {
+            const picked = await vscode.window.showOpenDialog({
+                title: vscode.l10n.t("Select move destination"),
+                openLabel: vscode.l10n.t("Move to File"),
+                defaultUri: Utils.dirname(document.uri),
+            });
+            return picked?.length ? this.client.toTsFilePath(picked[0]) : undefined;
+        }
+        else if (picked === selectNewFileItem) {
+            const picked = await vscode.window.showSaveDialog({
+                title: vscode.l10n.t("Select move destination"),
+                saveLabel: vscode.l10n.t("Move to File"),
+                defaultUri: this.client.toResource(response.body.newFileName),
+            });
+            return picked ? this.client.toTsFilePath(picked) : undefined;
+        }
+        else {
+            return picked.file;
+        }
+    }
 }
-
 interface CodeActionKind {
-	readonly kind: vscode.CodeActionKind;
-	matches(refactor: Proto.RefactorActionInfo): boolean;
+    readonly kind: vscode.CodeActionKind;
+    matches(refactor: Proto.RefactorActionInfo): boolean;
 }
-
 const Extract_Function = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorExtract.append('function'),
-	matches: refactor => refactor.name.startsWith('function_')
+    kind: vscode.CodeActionKind.RefactorExtract.append('function'),
+    matches: refactor => refactor.name.startsWith('function_')
 });
-
 const Extract_Constant = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorExtract.append('constant'),
-	matches: refactor => refactor.name.startsWith('constant_')
+    kind: vscode.CodeActionKind.RefactorExtract.append('constant'),
+    matches: refactor => refactor.name.startsWith('constant_')
 });
-
 const Extract_Type = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorExtract.append('type'),
-	matches: refactor => refactor.name.startsWith('Extract to type alias')
+    kind: vscode.CodeActionKind.RefactorExtract.append('type'),
+    matches: refactor => refactor.name.startsWith('Extract to type alias')
 });
-
 const Extract_Interface = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorExtract.append('interface'),
-	matches: refactor => refactor.name.startsWith('Extract to interface')
+    kind: vscode.CodeActionKind.RefactorExtract.append('interface'),
+    matches: refactor => refactor.name.startsWith('Extract to interface')
 });
-
 const Move_File = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorMove.append('file'),
-	matches: refactor => refactor.name.startsWith('Move to file')
+    kind: vscode.CodeActionKind.RefactorMove.append('file'),
+    matches: refactor => refactor.name.startsWith('Move to file')
 });
-
 const Move_NewFile = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorMove.append('newFile'),
-	matches: refactor => refactor.name.startsWith('Move to a new file')
+    kind: vscode.CodeActionKind.RefactorMove.append('newFile'),
+    matches: refactor => refactor.name.startsWith('Move to a new file')
 });
-
 const Rewrite_Import = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorRewrite.append('import'),
-	matches: refactor => refactor.name.startsWith('Convert namespace import') || refactor.name.startsWith('Convert named imports')
+    kind: vscode.CodeActionKind.RefactorRewrite.append('import'),
+    matches: refactor => refactor.name.startsWith('Convert namespace import') || refactor.name.startsWith('Convert named imports')
 });
-
 const Rewrite_Export = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorRewrite.append('export'),
-	matches: refactor => refactor.name.startsWith('Convert default export') || refactor.name.startsWith('Convert named export')
+    kind: vscode.CodeActionKind.RefactorRewrite.append('export'),
+    matches: refactor => refactor.name.startsWith('Convert default export') || refactor.name.startsWith('Convert named export')
 });
-
 const Rewrite_Arrow_Braces = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorRewrite.append('arrow').append('braces'),
-	matches: refactor => refactor.name.startsWith('Convert default export') || refactor.name.startsWith('Convert named export')
+    kind: vscode.CodeActionKind.RefactorRewrite.append('arrow').append('braces'),
+    matches: refactor => refactor.name.startsWith('Convert default export') || refactor.name.startsWith('Convert named export')
 });
-
 const Rewrite_Parameters_ToDestructured = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorRewrite.append('parameters').append('toDestructured'),
-	matches: refactor => refactor.name.startsWith('Convert parameters to destructured object')
+    kind: vscode.CodeActionKind.RefactorRewrite.append('parameters').append('toDestructured'),
+    matches: refactor => refactor.name.startsWith('Convert parameters to destructured object')
 });
-
 const Rewrite_Property_GenerateAccessors = Object.freeze({
-	kind: vscode.CodeActionKind.RefactorRewrite.append('property').append('generateAccessors'),
-	matches: refactor => refactor.name.startsWith('Generate \'get\' and \'set\' accessors')
+    kind: vscode.CodeActionKind.RefactorRewrite.append('property').append('generateAccessors'),
+    matches: refactor => refactor.name.startsWith('Generate \'get\' and \'set\' accessors')
 });
-
 const allKnownCodeActionKinds = [
-	Extract_Function,
-	Extract_Constant,
-	Extract_Type,
-	Extract_Interface,
-	Move_File,
-	Move_NewFile,
-	Rewrite_Import,
-	Rewrite_Export,
-	Rewrite_Arrow_Braces,
-	Rewrite_Parameters_ToDestructured,
-	Rewrite_Property_GenerateAccessors
+    Extract_Function,
+    Extract_Constant,
+    Extract_Type,
+    Extract_Interface,
+    Move_File,
+    Move_NewFile,
+    Rewrite_Import,
+    Rewrite_Export,
+    Rewrite_Arrow_Braces,
+    Rewrite_Parameters_ToDestructured,
+    Rewrite_Property_GenerateAccessors
 ];
-
 class InlinedCodeAction extends vscode.CodeAction {
-	constructor(
-		public readonly client: ITypeScriptServiceClient,
-		public readonly document: vscode.TextDocument,
-		public readonly refactor: Proto.ApplicableRefactorInfo,
-		public readonly action: Proto.RefactorActionInfo,
-		public readonly range: vscode.Range,
-		trigger: vscode.CodeActionTriggerKind,
-	) {
-		const title = action.description;
-		super(title, InlinedCodeAction.getKind(action));
-
-		if (action.notApplicableReason) {
-			this.disabled = { reason: action.notApplicableReason };
-		}
-
-		this.command = {
-			title,
-			command: DidApplyRefactoringCommand.ID,
-			arguments: [{ action: action.name, trigger } satisfies DidApplyRefactoringCommand.Args],
-		};
-	}
-
-	public async resolve(token: vscode.CancellationToken): Promise {
-		const file = this.client.toOpenTsFilePath(this.document);
-		if (!file) {
-			return;
-		}
-
-		const args: Proto.GetEditsForRefactorRequestArgs = {
-			...typeConverters.Range.toFileRangeRequestArgs(file, this.range),
-			refactor: this.refactor.name,
-			action: this.action.name,
-		};
-
-		const response = await this.client.execute('getEditsForRefactor', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return;
-		}
-
-		this.edit = toWorkspaceEdit(this.client, response.body.edits);
-		if (!this.edit.size) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Could not apply refactoring"));
-			return;
-		}
-
-		if (response.body.renameLocation) {
-			// Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137
-			if (this.document.uri.scheme !== fileSchemes.walkThroughSnippet) {
-				this.command = {
-					command: CompositeCommand.ID,
-					title: '',
-					arguments: coalesce([
-						this.command,
-						{
-							command: 'editor.action.rename',
-							arguments: [[
-								this.document.uri,
-								typeConverters.Position.fromLocation(response.body.renameLocation)
-							]]
-						},
-					])
-				};
-			}
-		}
-	}
-
-	private static getKind(refactor: Proto.RefactorActionInfo) {
-		if ((refactor as Proto.RefactorActionInfo & { kind?: string }).kind) {
-			return vscode.CodeActionKind.Empty.append((refactor as Proto.RefactorActionInfo & { kind?: string }).kind!);
-		}
-		const match = allKnownCodeActionKinds.find(kind => kind.matches(refactor));
-		return match ? match.kind : vscode.CodeActionKind.Refactor;
-	}
+    constructor(public readonly client: ITypeScriptServiceClient, public readonly document: vscode.TextDocument, public readonly refactor: Proto.ApplicableRefactorInfo, public readonly action: Proto.RefactorActionInfo, public readonly range: vscode.Range, trigger: vscode.CodeActionTriggerKind) {
+        const title = action.description;
+        super(title, InlinedCodeAction.getKind(action));
+        if (action.notApplicableReason) {
+            this.disabled = { reason: action.notApplicableReason };
+        }
+        this.command = {
+            title,
+            command: DidApplyRefactoringCommand.ID,
+            arguments: [{ action: action.name, trigger } satisfies DidApplyRefactoringCommand.Args],
+        };
+    }
+    public async resolve(token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(this.document);
+        if (!file) {
+            return;
+        }
+        const args: Proto.GetEditsForRefactorRequestArgs = {
+            ...typeConverters.Range.toFileRangeRequestArgs(file, this.range),
+            refactor: this.refactor.name,
+            action: this.action.name,
+        };
+        const response = await this.client.execute('getEditsForRefactor', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return;
+        }
+        this.edit = toWorkspaceEdit(this.client, response.body.edits);
+        if (!this.edit.size) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Could not apply refactoring"));
+            return;
+        }
+        if (response.body.renameLocation) {
+            // Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137
+            if (this.document.uri.scheme !== fileSchemes.walkThroughSnippet) {
+                this.command = {
+                    command: CompositeCommand.ID,
+                    title: '',
+                    arguments: coalesce([
+                        this.command,
+                        {
+                            command: 'editor.action.rename',
+                            arguments: [[
+                                    this.document.uri,
+                                    typeConverters.Position.fromLocation(response.body.renameLocation)
+                                ]]
+                        },
+                    ])
+                };
+            }
+        }
+    }
+    private static getKind(refactor: Proto.RefactorActionInfo) {
+        if ((refactor as Proto.RefactorActionInfo & {
+            kind?: string;
+        }).kind) {
+            return vscode.CodeActionKind.Empty.append((refactor as Proto.RefactorActionInfo & {
+                kind?: string;
+            }).kind!);
+        }
+        const match = allKnownCodeActionKinds.find(kind => kind.matches(refactor));
+        return match ? match.kind : vscode.CodeActionKind.Refactor;
+    }
 }
-
 class MoveToFileCodeAction extends vscode.CodeAction {
-	constructor(
-		document: vscode.TextDocument,
-		action: Proto.RefactorActionInfo,
-		range: vscode.Range,
-		trigger: vscode.CodeActionTriggerKind,
-	) {
-		super(action.description, Move_File.kind);
-
-		if (action.notApplicableReason) {
-			this.disabled = { reason: action.notApplicableReason };
-		}
-
-		this.command = {
-			title: action.description,
-			command: MoveToFileRefactorCommand.ID,
-			arguments: [{ action, document, range, trigger } satisfies MoveToFileRefactorCommand.Args]
-		};
-	}
+    constructor(document: vscode.TextDocument, action: Proto.RefactorActionInfo, range: vscode.Range, trigger: vscode.CodeActionTriggerKind) {
+        super(action.description, Move_File.kind);
+        if (action.notApplicableReason) {
+            this.disabled = { reason: action.notApplicableReason };
+        }
+        this.command = {
+            title: action.description,
+            command: MoveToFileRefactorCommand.ID,
+            arguments: [{ action, document, range, trigger } satisfies MoveToFileRefactorCommand.Args]
+        };
+    }
 }
-
 class SelectCodeAction extends vscode.CodeAction {
-	constructor(
-		info: Proto.ApplicableRefactorInfo,
-		document: vscode.TextDocument,
-		rangeOrSelection: vscode.Range | vscode.Selection,
-		trigger: vscode.CodeActionTriggerKind,
-	) {
-		super(info.description, vscode.CodeActionKind.Refactor);
-		this.command = {
-			title: info.description,
-			command: SelectRefactorCommand.ID,
-			arguments: [{ document, refactor: info, rangeOrSelection, trigger } satisfies SelectRefactorCommand.Args]
-		};
-	}
+    constructor(info: Proto.ApplicableRefactorInfo, document: vscode.TextDocument, rangeOrSelection: vscode.Range | vscode.Selection, trigger: vscode.CodeActionTriggerKind) {
+        super(info.description, vscode.CodeActionKind.Refactor);
+        this.command = {
+            title: info.description,
+            command: SelectRefactorCommand.ID,
+            arguments: [{ document, refactor: info, rangeOrSelection, trigger } satisfies SelectRefactorCommand.Args]
+        };
+    }
 }
 type TsCodeAction = InlinedCodeAction | MoveToFileCodeAction | SelectCodeAction;
-
 class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
-
-	private static readonly _declarationKinds = new Set([
-		PConst.Kind.module,
-		PConst.Kind.class,
-		PConst.Kind.interface,
-		PConst.Kind.function,
-		PConst.Kind.enum,
-		PConst.Kind.type,
-		PConst.Kind.const,
-		PConst.Kind.variable,
-		PConst.Kind.let,
-	]);
-
-	private static isOnSignatureName(node: Proto.NavigationTree, range: vscode.Range): boolean {
-		if (this._declarationKinds.has(node.kind)) {
-			// Show when on the name span
-			if (node.nameSpan) {
-				const convertedSpan = typeConverters.Range.fromTextSpan(node.nameSpan);
-				if (range.intersection(convertedSpan)) {
-					return true;
-				}
-			}
-
-			// Show when on the same line as an exported symbols without a name (handles default exports)
-			if (!node.nameSpan && /\bexport\b/.test(node.kindModifiers) && node.spans.length) {
-				const convertedSpan = typeConverters.Range.fromTextSpan(node.spans[0]);
-				if (range.intersection(new vscode.Range(convertedSpan.start.line, 0, convertedSpan.start.line, Number.MAX_SAFE_INTEGER))) {
-					return true;
-				}
-			}
-		}
-
-		// Show if on the signature of any children
-		return node.childItems?.some(child => this.isOnSignatureName(child, range)) ?? false;
-	}
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly cachedNavTree: CachedResponse,
-		private readonly formattingOptionsManager: FormattingOptionsManager,
-		commandManager: CommandManager,
-		telemetryReporter: TelemetryReporter
-	) {
-		const didApplyRefactoringCommand = new DidApplyRefactoringCommand(telemetryReporter);
-		commandManager.register(didApplyRefactoringCommand);
-
-		commandManager.register(new CompositeCommand());
-		commandManager.register(new SelectRefactorCommand(this.client));
-		commandManager.register(new MoveToFileRefactorCommand(this.client, didApplyRefactoringCommand));
-		commandManager.register(new EditorChatFollowUp(this.client, telemetryReporter));
-	}
-
-	public static readonly metadata: vscode.CodeActionProviderMetadata = {
-		providedCodeActionKinds: [
-			vscode.CodeActionKind.Refactor,
-			...allKnownCodeActionKinds.map(x => x.kind),
-		],
-		documentation: [
-			{
-				kind: vscode.CodeActionKind.Refactor,
-				command: {
-					command: LearnMoreAboutRefactoringsCommand.id,
-					title: vscode.l10n.t("Learn more about JS/TS refactorings")
-				}
-			}
-		]
-	};
-
-	public async provideCodeActions(
-		document: vscode.TextDocument,
-		rangeOrSelection: vscode.Range | vscode.Selection,
-		context: vscode.CodeActionContext,
-		token: vscode.CancellationToken
-	): Promise {
-		if (!this.shouldTrigger(context, rangeOrSelection)) {
-			return undefined;
-		}
-		if (!this.client.toOpenTsFilePath(document)) {
-			return undefined;
-		}
-
-		const response = await this.interruptGetErrIfNeeded(context, () => {
-			const file = this.client.toOpenTsFilePath(document);
-			if (!file) {
-				return undefined;
-			}
-
-			this.formattingOptionsManager.ensureConfigurationForDocument(document, token);
-
-			const args: Proto.GetApplicableRefactorsRequestArgs = {
-				...typeConverters.Range.toFileRangeRequestArgs(file, rangeOrSelection),
-				triggerReason: this.toTsTriggerReason(context),
-				kind: context.only?.value,
-				includeInteractiveActions: this.client.apiVersion.gte(API.v520),
-			};
-			return this.client.execute('getApplicableRefactors', args, token);
-		});
-		if (response?.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const applicableRefactors = this.convertApplicableRefactors(document, context, response.body, rangeOrSelection);
-		const actions = coalesce(await Promise.all(Array.from(applicableRefactors, async action => {
-			if (this.client.apiVersion.lt(API.v430)) {
-				// Don't show 'infer return type' refactoring unless it has been explicitly requested
-				// https://github.com/microsoft/TypeScript/issues/42993
-				if (!context.only && action.kind?.value === 'refactor.rewrite.function.returnType') {
-					return undefined;
-				}
-			}
-
-			// Don't include move actions on auto light bulb unless you are on a declaration name
-			if (this.client.apiVersion.lt(API.v540) && context.triggerKind === vscode.CodeActionTriggerKind.Automatic) {
-				if (action.kind?.value === Move_NewFile.kind.value || action.kind?.value === Move_File.kind.value) {
-					const file = this.client.toOpenTsFilePath(document);
-					if (!file) {
-						return undefined;
-					}
-
-					const navTree = await this.cachedNavTree.execute(document, () => this.client.execute('navtree', { file }, token));
-					if (navTree.type !== 'response' || !navTree.body || !TypeScriptRefactorProvider.isOnSignatureName(navTree.body, rangeOrSelection)) {
-						return undefined;
-					}
-				}
-			}
-
-			return action;
-		})));
-
-		if (!context.only) {
-			return actions;
-		}
-
-		return this.pruneInvalidActions(this.appendInvalidActions(actions), context.only, /* numberOfInvalid = */ 5);
-	}
-
-	private interruptGetErrIfNeeded(context: vscode.CodeActionContext, f: () => R): R {
-		// Only interrupt diagnostics computation when code actions are explicitly
-		// (such as using the refactor command or a keybinding). This is a clear
-		// user action so we want to return results as quickly as possible.
-		if (context.triggerKind === vscode.CodeActionTriggerKind.Invoke) {
-			return this.client.interruptGetErr(f);
-		} else {
-			return f();
-		}
-	}
-
-	public async resolveCodeAction(
-		codeAction: TsCodeAction,
-		token: vscode.CancellationToken,
-	): Promise {
-		if (codeAction instanceof InlinedCodeAction) {
-			await codeAction.resolve(token);
-		}
-		return codeAction;
-	}
-
-	private toTsTriggerReason(context: vscode.CodeActionContext): Proto.RefactorTriggerReason | undefined {
-		return context.triggerKind === vscode.CodeActionTriggerKind.Invoke ? 'invoked' : 'implicit';
-	}
-
-	private *convertApplicableRefactors(
-		document: vscode.TextDocument,
-		context: vscode.CodeActionContext,
-		refactors: readonly Proto.ApplicableRefactorInfo[],
-		rangeOrSelection: vscode.Range | vscode.Selection
-	): Iterable {
-		for (const refactor of refactors) {
-			if (refactor.inlineable === false) {
-				yield new SelectCodeAction(refactor, document, rangeOrSelection, context.triggerKind);
-			} else {
-				for (const action of refactor.actions) {
-					for (const codeAction of this.refactorActionToCodeActions(document, context, refactor, action, rangeOrSelection, refactor.actions)) {
-						yield codeAction;
-					}
-				}
-			}
-		}
-	}
-
-	private refactorActionToCodeActions(
-		document: vscode.TextDocument,
-		context: vscode.CodeActionContext,
-		refactor: Proto.ApplicableRefactorInfo,
-		action: Proto.RefactorActionInfo,
-		rangeOrSelection: vscode.Range | vscode.Selection,
-		allActions: readonly Proto.RefactorActionInfo[],
-	): TsCodeAction[] {
-		const codeActions: TsCodeAction[] = [];
-		if (action.name === 'Move to file') {
-			codeActions.push(new MoveToFileCodeAction(document, action, rangeOrSelection, context.triggerKind));
-		} else {
-			codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, context.triggerKind));
-		}
-		for (const codeAction of codeActions) {
-			codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions);
-		}
-		return codeActions;
-	}
-
-	private shouldTrigger(context: vscode.CodeActionContext, rangeOrSelection: vscode.Range | vscode.Selection) {
-		if (context.only && !vscode.CodeActionKind.Refactor.contains(context.only)) {
-			return false;
-		}
-		if (context.triggerKind === vscode.CodeActionTriggerKind.Invoke) {
-			return true;
-		}
-		return rangeOrSelection instanceof vscode.Selection;
-	}
-
-	private static isPreferred(
-		action: Proto.RefactorActionInfo,
-		allActions: readonly Proto.RefactorActionInfo[],
-	): boolean {
-		if (Extract_Constant.matches(action)) {
-			// Only mark the action with the lowest scope as preferred
-			const getScope = (name: string) => {
-				const scope = name.match(/scope_(\d)/)?.[1];
-				return scope ? +scope : undefined;
-			};
-			const scope = getScope(action.name);
-			if (typeof scope !== 'number') {
-				return false;
-			}
-
-			return allActions
-				.filter(otherAtion => otherAtion !== action && Extract_Constant.matches(otherAtion))
-				.every(otherAction => {
-					const otherScope = getScope(otherAction.name);
-					return typeof otherScope === 'number' ? scope < otherScope : true;
-				});
-		}
-		if (Extract_Type.matches(action) || Extract_Interface.matches(action)) {
-			return true;
-		}
-		return false;
-	}
-
-	private appendInvalidActions(actions: vscode.CodeAction[]): vscode.CodeAction[] {
-		if (this.client.apiVersion.gte(API.v400)) {
-			// Invalid actions come from TS server instead
-			return actions;
-		}
-
-		if (!actions.some(action => action.kind && Extract_Constant.kind.contains(action.kind))) {
-			const disabledAction = new vscode.CodeAction(
-				vscode.l10n.t("Extract to constant"),
-				Extract_Constant.kind);
-
-			disabledAction.disabled = {
-				reason: vscode.l10n.t("The current selection cannot be extracted"),
-			};
-			disabledAction.isPreferred = true;
-
-			actions.push(disabledAction);
-		}
-
-		if (!actions.some(action => action.kind && Extract_Function.kind.contains(action.kind))) {
-			const disabledAction = new vscode.CodeAction(
-				vscode.l10n.t("Extract to function"),
-				Extract_Function.kind);
-
-			disabledAction.disabled = {
-				reason: vscode.l10n.t("The current selection cannot be extracted"),
-			};
-			actions.push(disabledAction);
-		}
-		return actions;
-	}
-
-	private pruneInvalidActions(actions: vscode.CodeAction[], only?: vscode.CodeActionKind, numberOfInvalid?: number): vscode.CodeAction[] {
-		if (this.client.apiVersion.lt(API.v400)) {
-			// Older TS version don't return extra actions
-			return actions;
-		}
-
-		const availableActions: vscode.CodeAction[] = [];
-		const invalidCommonActions: vscode.CodeAction[] = [];
-		const invalidUncommonActions: vscode.CodeAction[] = [];
-		for (const action of actions) {
-			if (!action.disabled) {
-				availableActions.push(action);
-				continue;
-			}
-
-			// These are the common refactors that we should always show if applicable.
-			if (action.kind && (Extract_Constant.kind.contains(action.kind) || Extract_Function.kind.contains(action.kind))) {
-				invalidCommonActions.push(action);
-				continue;
-			}
-
-			// These are the remaining refactors that we can show if we haven't reached the max limit with just common refactors.
-			invalidUncommonActions.push(action);
-		}
-
-		const prioritizedActions: vscode.CodeAction[] = [];
-		prioritizedActions.push(...invalidCommonActions);
-		prioritizedActions.push(...invalidUncommonActions);
-		const topNInvalid = prioritizedActions.filter(action => !only || (action.kind && only.contains(action.kind))).slice(0, numberOfInvalid);
-		availableActions.push(...topNInvalid);
-		return availableActions;
-	}
+    private static readonly _declarationKinds = new Set([
+        PConst.Kind.module,
+        PConst.Kind.class,
+        PConst.Kind.interface,
+        PConst.Kind.function,
+        PConst.Kind.enum,
+        PConst.Kind.type,
+        PConst.Kind.const,
+        PConst.Kind.variable,
+        PConst.Kind.let,
+    ]);
+    private static isOnSignatureName(node: Proto.NavigationTree, range: vscode.Range): boolean {
+        if (this._declarationKinds.has(node.kind)) {
+            // Show when on the name span
+            if (node.nameSpan) {
+                const convertedSpan = typeConverters.Range.fromTextSpan(node.nameSpan);
+                if (range.intersection(convertedSpan)) {
+                    return true;
+                }
+            }
+            // Show when on the same line as an exported symbols without a name (handles default exports)
+            if (!node.nameSpan && /\bexport\b/.test(node.kindModifiers) && node.spans.length) {
+                const convertedSpan = typeConverters.Range.fromTextSpan(node.spans[0]);
+                if (range.intersection(new vscode.Range(convertedSpan.start.line, 0, convertedSpan.start.line, Number.MAX_SAFE_INTEGER))) {
+                    return true;
+                }
+            }
+        }
+        // Show if on the signature of any children
+        return node.childItems?.some(child => this.isOnSignatureName(child, range)) ?? false;
+    }
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly cachedNavTree: CachedResponse, private readonly formattingOptionsManager: FormattingOptionsManager, commandManager: CommandManager, telemetryReporter: TelemetryReporter) {
+        const didApplyRefactoringCommand = new DidApplyRefactoringCommand(telemetryReporter);
+        commandManager.register(didApplyRefactoringCommand);
+        commandManager.register(new CompositeCommand());
+        commandManager.register(new SelectRefactorCommand(this.client));
+        commandManager.register(new MoveToFileRefactorCommand(this.client, didApplyRefactoringCommand));
+        commandManager.register(new EditorChatFollowUp(this.client, telemetryReporter));
+    }
+    public static readonly metadata: vscode.CodeActionProviderMetadata = {
+        providedCodeActionKinds: [
+            vscode.CodeActionKind.Refactor,
+            ...allKnownCodeActionKinds.map(x => x.kind),
+        ],
+        documentation: [
+            {
+                kind: vscode.CodeActionKind.Refactor,
+                command: {
+                    command: LearnMoreAboutRefactoringsCommand.id,
+                    title: vscode.l10n.t("Learn more about JS/TS refactorings")
+                }
+            }
+        ]
+    };
+    public async provideCodeActions(document: vscode.TextDocument, rangeOrSelection: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise {
+        if (!this.shouldTrigger(context, rangeOrSelection)) {
+            return undefined;
+        }
+        if (!this.client.toOpenTsFilePath(document)) {
+            return undefined;
+        }
+        const response = await this.interruptGetErrIfNeeded(context, () => {
+            const file = this.client.toOpenTsFilePath(document);
+            if (!file) {
+                return undefined;
+            }
+            this.formattingOptionsManager.ensureConfigurationForDocument(document, token);
+            const args: Proto.GetApplicableRefactorsRequestArgs = {
+                ...typeConverters.Range.toFileRangeRequestArgs(file, rangeOrSelection),
+                triggerReason: this.toTsTriggerReason(context),
+                kind: context.only?.value,
+                includeInteractiveActions: this.client.apiVersion.gte(API.v520),
+            };
+            return this.client.execute('getApplicableRefactors', args, token);
+        });
+        if (response?.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const applicableRefactors = this.convertApplicableRefactors(document, context, response.body, rangeOrSelection);
+        const actions = coalesce(await Promise.all(Array.from(applicableRefactors, async (action) => {
+            if (this.client.apiVersion.lt(API.v430)) {
+                // Don't show 'infer return type' refactoring unless it has been explicitly requested
+                // https://github.com/microsoft/TypeScript/issues/42993
+                if (!context.only && action.kind?.value === 'refactor.rewrite.function.returnType') {
+                    return undefined;
+                }
+            }
+            // Don't include move actions on auto light bulb unless you are on a declaration name
+            if (this.client.apiVersion.lt(API.v540) && context.triggerKind === vscode.CodeActionTriggerKind.Automatic) {
+                if (action.kind?.value === Move_NewFile.kind.value || action.kind?.value === Move_File.kind.value) {
+                    const file = this.client.toOpenTsFilePath(document);
+                    if (!file) {
+                        return undefined;
+                    }
+                    const navTree = await this.cachedNavTree.execute(document, () => this.client.execute('navtree', { file }, token));
+                    if (navTree.type !== 'response' || !navTree.body || !TypeScriptRefactorProvider.isOnSignatureName(navTree.body, rangeOrSelection)) {
+                        return undefined;
+                    }
+                }
+            }
+            return action;
+        })));
+        if (!context.only) {
+            return actions;
+        }
+        return this.pruneInvalidActions(this.appendInvalidActions(actions), context.only, /* numberOfInvalid = */ 5);
+    }
+    private interruptGetErrIfNeeded(context: vscode.CodeActionContext, f: () => R): R {
+        // Only interrupt diagnostics computation when code actions are explicitly
+        // (such as using the refactor command or a keybinding). This is a clear
+        // user action so we want to return results as quickly as possible.
+        if (context.triggerKind === vscode.CodeActionTriggerKind.Invoke) {
+            return this.client.interruptGetErr(f);
+        }
+        else {
+            return f();
+        }
+    }
+    public async resolveCodeAction(codeAction: TsCodeAction, token: vscode.CancellationToken): Promise {
+        if (codeAction instanceof InlinedCodeAction) {
+            await codeAction.resolve(token);
+        }
+        return codeAction;
+    }
+    private toTsTriggerReason(context: vscode.CodeActionContext): Proto.RefactorTriggerReason | undefined {
+        return context.triggerKind === vscode.CodeActionTriggerKind.Invoke ? 'invoked' : 'implicit';
+    }
+    private *convertApplicableRefactors(document: vscode.TextDocument, context: vscode.CodeActionContext, refactors: readonly Proto.ApplicableRefactorInfo[], rangeOrSelection: vscode.Range | vscode.Selection): Iterable {
+        for (const refactor of refactors) {
+            if (refactor.inlineable === false) {
+                yield new SelectCodeAction(refactor, document, rangeOrSelection, context.triggerKind);
+            }
+            else {
+                for (const action of refactor.actions) {
+                    for (const codeAction of this.refactorActionToCodeActions(document, context, refactor, action, rangeOrSelection, refactor.actions)) {
+                        yield codeAction;
+                    }
+                }
+            }
+        }
+    }
+    private refactorActionToCodeActions(document: vscode.TextDocument, context: vscode.CodeActionContext, refactor: Proto.ApplicableRefactorInfo, action: Proto.RefactorActionInfo, rangeOrSelection: vscode.Range | vscode.Selection, allActions: readonly Proto.RefactorActionInfo[]): TsCodeAction[] {
+        const codeActions: TsCodeAction[] = [];
+        if (action.name === 'Move to file') {
+            codeActions.push(new MoveToFileCodeAction(document, action, rangeOrSelection, context.triggerKind));
+        }
+        else {
+            codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, context.triggerKind));
+        }
+        for (const codeAction of codeActions) {
+            codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions);
+        }
+        return codeActions;
+    }
+    private shouldTrigger(context: vscode.CodeActionContext, rangeOrSelection: vscode.Range | vscode.Selection) {
+        if (context.only && !vscode.CodeActionKind.Refactor.contains(context.only)) {
+            return false;
+        }
+        if (context.triggerKind === vscode.CodeActionTriggerKind.Invoke) {
+            return true;
+        }
+        return rangeOrSelection instanceof vscode.Selection;
+    }
+    private static isPreferred(action: Proto.RefactorActionInfo, allActions: readonly Proto.RefactorActionInfo[]): boolean {
+        if (Extract_Constant.matches(action)) {
+            // Only mark the action with the lowest scope as preferred
+            const getScope = (name: string) => {
+                const scope = name.match(/scope_(\d)/)?.[1];
+                return scope ? +scope : undefined;
+            };
+            const scope = getScope(action.name);
+            if (typeof scope !== 'number') {
+                return false;
+            }
+            return allActions
+                .filter(otherAtion => otherAtion !== action && Extract_Constant.matches(otherAtion))
+                .every(otherAction => {
+                const otherScope = getScope(otherAction.name);
+                return typeof otherScope === 'number' ? scope < otherScope : true;
+            });
+        }
+        if (Extract_Type.matches(action) || Extract_Interface.matches(action)) {
+            return true;
+        }
+        return false;
+    }
+    private appendInvalidActions(actions: vscode.CodeAction[]): vscode.CodeAction[] {
+        if (this.client.apiVersion.gte(API.v400)) {
+            // Invalid actions come from TS server instead
+            return actions;
+        }
+        if (!actions.some(action => action.kind && Extract_Constant.kind.contains(action.kind))) {
+            const disabledAction = new vscode.CodeAction(vscode.l10n.t("Extract to constant"), Extract_Constant.kind);
+            disabledAction.disabled = {
+                reason: vscode.l10n.t("The current selection cannot be extracted"),
+            };
+            disabledAction.isPreferred = true;
+            actions.push(disabledAction);
+        }
+        if (!actions.some(action => action.kind && Extract_Function.kind.contains(action.kind))) {
+            const disabledAction = new vscode.CodeAction(vscode.l10n.t("Extract to function"), Extract_Function.kind);
+            disabledAction.disabled = {
+                reason: vscode.l10n.t("The current selection cannot be extracted"),
+            };
+            actions.push(disabledAction);
+        }
+        return actions;
+    }
+    private pruneInvalidActions(actions: vscode.CodeAction[], only?: vscode.CodeActionKind, numberOfInvalid?: number): vscode.CodeAction[] {
+        if (this.client.apiVersion.lt(API.v400)) {
+            // Older TS version don't return extra actions
+            return actions;
+        }
+        const availableActions: vscode.CodeAction[] = [];
+        const invalidCommonActions: vscode.CodeAction[] = [];
+        const invalidUncommonActions: vscode.CodeAction[] = [];
+        for (const action of actions) {
+            if (!action.disabled) {
+                availableActions.push(action);
+                continue;
+            }
+            // These are the common refactors that we should always show if applicable.
+            if (action.kind && (Extract_Constant.kind.contains(action.kind) || Extract_Function.kind.contains(action.kind))) {
+                invalidCommonActions.push(action);
+                continue;
+            }
+            // These are the remaining refactors that we can show if we haven't reached the max limit with just common refactors.
+            invalidUncommonActions.push(action);
+        }
+        const prioritizedActions: vscode.CodeAction[] = [];
+        prioritizedActions.push(...invalidCommonActions);
+        prioritizedActions.push(...invalidUncommonActions);
+        const topNInvalid = prioritizedActions.filter(action => !only || (action.kind && only.contains(action.kind))).slice(0, numberOfInvalid);
+        availableActions.push(...topNInvalid);
+        return availableActions;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-	cachedNavTree: CachedResponse,
-	formattingOptionsManager: FormattingOptionsManager,
-	commandManager: CommandManager,
-	telemetryReporter: TelemetryReporter,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerCodeActionsProvider(selector.semantic,
-			new TypeScriptRefactorProvider(client, cachedNavTree, formattingOptionsManager, commandManager, telemetryReporter),
-			TypeScriptRefactorProvider.metadata);
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient, cachedNavTree: CachedResponse, formattingOptionsManager: FormattingOptionsManager, commandManager: CommandManager, telemetryReporter: TelemetryReporter) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerCodeActionsProvider(selector.semantic, new TypeScriptRefactorProvider(client, cachedNavTree, formattingOptionsManager, commandManager, telemetryReporter), TypeScriptRefactorProvider.metadata);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/references.ts b/extensions/typescript-language-features/Source/languageFeatures/references.ts
index 1a39ffbc2eb4b..ece1aad166ee2 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/references.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/references.ts
@@ -2,55 +2,39 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import * as typeConverters from '../typeConverters';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
 class TypeScriptReferenceSupport implements vscode.ReferenceProvider {
-	public constructor(
-		private readonly client: ITypeScriptServiceClient) { }
-
-	public async provideReferences(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		options: vscode.ReferenceContext,
-		token: vscode.CancellationToken
-	): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return [];
-		}
-
-		const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
-		const response = await this.client.execute('references', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return [];
-		}
-
-		const result: vscode.Location[] = [];
-		for (const ref of response.body.refs) {
-			if (!options.includeDeclaration && ref.isDefinition) {
-				continue;
-			}
-			const url = this.client.toResource(ref.file);
-			const location = typeConverters.Location.fromTextSpan(url, ref);
-			result.push(location);
-		}
-		return result;
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async provideReferences(document: vscode.TextDocument, position: vscode.Position, options: vscode.ReferenceContext, token: vscode.CancellationToken): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return [];
+        }
+        const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
+        const response = await this.client.execute('references', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return [];
+        }
+        const result: vscode.Location[] = [];
+        for (const ref of response.body.refs) {
+            if (!options.includeDeclaration && ref.isDefinition) {
+                continue;
+            }
+            const url = this.client.toResource(ref.file);
+            const location = typeConverters.Location.fromTextSpan(url, ref);
+            result.push(location);
+        }
+        return result;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerReferenceProvider(selector.syntax,
-			new TypeScriptReferenceSupport(client));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerReferenceProvider(selector.syntax, new TypeScriptReferenceSupport(client));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/rename.ts b/extensions/typescript-language-features/Source/languageFeatures/rename.ts
index 5c86190a53e74..ddd22e28b9fa6 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/rename.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/rename.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
@@ -14,199 +13,146 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService
 import FileConfigurationManager from './fileConfigurationManager';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
 import { LanguageDescription } from '../configuration/languageDescription';
-
 type RenameResponse = {
-	readonly type: 'rename';
-	readonly body: Proto.RenameResponseBody;
+    readonly type: 'rename';
+    readonly body: Proto.RenameResponseBody;
 } | {
-	readonly type: 'jsxLinkedEditing';
-	readonly spans: readonly Proto.TextSpan[];
+    readonly type: 'jsxLinkedEditing';
+    readonly spans: readonly Proto.TextSpan[];
 };
-
 class TypeScriptRenameProvider implements vscode.RenameProvider {
-
-	public constructor(
-		private readonly language: LanguageDescription,
-		private readonly client: ITypeScriptServiceClient,
-		private readonly fileConfigurationManager: FileConfigurationManager
-	) { }
-
-	public async prepareRename(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		const response = await this.execRename(document, position, token);
-		if (!response) {
-			return undefined;
-		}
-
-		switch (response.type) {
-			case 'rename': {
-				const renameInfo = response.body.info;
-				if (!renameInfo.canRename) {
-					return Promise.reject(renameInfo.localizedErrorMessage);
-				}
-				return typeConverters.Range.fromTextSpan(renameInfo.triggerSpan);
-			}
-			case 'jsxLinkedEditing': {
-				return response.spans
-					.map(typeConverters.Range.fromTextSpan)
-					.find(range => range.contains(position));
-			}
-		}
-	}
-
-	public async provideRenameEdits(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		newName: string,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		const response = await this.execRename(document, position, token);
-		if (!response || token.isCancellationRequested) {
-			return undefined;
-		}
-
-		switch (response.type) {
-			case 'rename': {
-				const renameInfo = response.body.info;
-				if (!renameInfo.canRename) {
-					return Promise.reject(renameInfo.localizedErrorMessage);
-				}
-
-				if (renameInfo.fileToRename) {
-					const edits = await this.renameFile(renameInfo.fileToRename, renameInfo.fullDisplayName, newName, token);
-					if (edits) {
-						return edits;
-					} else {
-						return Promise.reject(vscode.l10n.t("An error occurred while renaming file"));
-					}
-				}
-
-				return this.updateLocs(response.body.locs, newName);
-			}
-			case 'jsxLinkedEditing': {
-				return this.updateLocs([{
-					file,
-					locs: response.spans.map((span): Proto.RenameTextSpan => ({ ...span })),
-				}], newName);
-			}
-		}
-	}
-
-	public async execRename(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		// Prefer renaming matching jsx tag when available
-		if (this.client.apiVersion.gte(API.v510) &&
-			vscode.workspace.getConfiguration(this.language.id).get('preferences.renameMatchingJsxTags', true) &&
-			this.looksLikePotentialJsxTagContext(document, position)
-		) {
-			const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
-			const response = await this.client.execute('linkedEditingRange', args, token);
-			if (response.type !== 'response' || !response.body) {
-				return undefined;
-			}
-
-			return { type: 'jsxLinkedEditing', spans: response.body.ranges };
-		}
-
-		const args: Proto.RenameRequestArgs = {
-			...typeConverters.Position.toFileLocationRequestArgs(file, position),
-			findInStrings: false,
-			findInComments: false
-		};
-
-		return this.client.interruptGetErr(async () => {
-			this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
-			const response = await this.client.execute('rename', args, token);
-			if (response.type !== 'response' || !response.body) {
-				return undefined;
-			}
-			return { type: 'rename', body: response.body };
-		});
-	}
-
-	private looksLikePotentialJsxTagContext(document: vscode.TextDocument, position: vscode.Position): boolean {
-		if (![languageIds.typescriptreact, languageIds.javascript, languageIds.javascriptreact].includes(document.languageId)) {
-			return false;
-		}
-
-		const prefix = document.getText(new vscode.Range(position.line, 0, position.line, position.character));
-		return /\<\/?\s*[\w\d_$.]*$/.test(prefix);
-	}
-
-	private updateLocs(
-		locations: ReadonlyArray,
-		newName: string
-	) {
-		const edit = new vscode.WorkspaceEdit();
-		for (const spanGroup of locations) {
-			const resource = this.client.toResource(spanGroup.file);
-			for (const textSpan of spanGroup.locs) {
-				edit.replace(resource, typeConverters.Range.fromTextSpan(textSpan),
-					(textSpan.prefixText || '') + newName + (textSpan.suffixText || ''));
-			}
-		}
-		return edit;
-	}
-
-	private async renameFile(
-		fileToRename: string,
-		fullDisplayName: string,
-		newName: string,
-		token: vscode.CancellationToken,
-	): Promise {
-		// Make sure we preserve file extension if extension is unchanged or none provided
-		if (!path.extname(newName)) {
-			newName += path.extname(fileToRename);
-		}
-		else if (path.extname(newName) === path.extname(fullDisplayName)) {
-			newName = newName.slice(0, newName.length - path.extname(newName).length) + path.extname(fileToRename);
-		}
-
-		const dirname = path.dirname(fileToRename);
-		const newFilePath = path.join(dirname, newName);
-
-		const args: Proto.GetEditsForFileRenameRequestArgs & { file: string } = {
-			file: fileToRename,
-			oldFilePath: fileToRename,
-			newFilePath: newFilePath,
-		};
-		const response = await this.client.execute('getEditsForFileRename', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const edits = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
-		edits.renameFile(vscode.Uri.file(fileToRename), vscode.Uri.file(newFilePath));
-		return edits;
-	}
+    public constructor(private readonly language: LanguageDescription, private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager) { }
+    public async prepareRename(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const response = await this.execRename(document, position, token);
+        if (!response) {
+            return undefined;
+        }
+        switch (response.type) {
+            case 'rename': {
+                const renameInfo = response.body.info;
+                if (!renameInfo.canRename) {
+                    return Promise.reject(renameInfo.localizedErrorMessage);
+                }
+                return typeConverters.Range.fromTextSpan(renameInfo.triggerSpan);
+            }
+            case 'jsxLinkedEditing': {
+                return response.spans
+                    .map(typeConverters.Range.fromTextSpan)
+                    .find(range => range.contains(position));
+            }
+        }
+    }
+    public async provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        const response = await this.execRename(document, position, token);
+        if (!response || token.isCancellationRequested) {
+            return undefined;
+        }
+        switch (response.type) {
+            case 'rename': {
+                const renameInfo = response.body.info;
+                if (!renameInfo.canRename) {
+                    return Promise.reject(renameInfo.localizedErrorMessage);
+                }
+                if (renameInfo.fileToRename) {
+                    const edits = await this.renameFile(renameInfo.fileToRename, renameInfo.fullDisplayName, newName, token);
+                    if (edits) {
+                        return edits;
+                    }
+                    else {
+                        return Promise.reject(vscode.l10n.t("An error occurred while renaming file"));
+                    }
+                }
+                return this.updateLocs(response.body.locs, newName);
+            }
+            case 'jsxLinkedEditing': {
+                return this.updateLocs([{
+                        file,
+                        locs: response.spans.map((span): Proto.RenameTextSpan => ({ ...span })),
+                    }], newName);
+            }
+        }
+    }
+    public async execRename(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        // Prefer renaming matching jsx tag when available
+        if (this.client.apiVersion.gte(API.v510) &&
+            vscode.workspace.getConfiguration(this.language.id).get('preferences.renameMatchingJsxTags', true) &&
+            this.looksLikePotentialJsxTagContext(document, position)) {
+            const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
+            const response = await this.client.execute('linkedEditingRange', args, token);
+            if (response.type !== 'response' || !response.body) {
+                return undefined;
+            }
+            return { type: 'jsxLinkedEditing', spans: response.body.ranges };
+        }
+        const args: Proto.RenameRequestArgs = {
+            ...typeConverters.Position.toFileLocationRequestArgs(file, position),
+            findInStrings: false,
+            findInComments: false
+        };
+        return this.client.interruptGetErr(async () => {
+            this.fileConfigurationManager.ensureConfigurationForDocument(document, token);
+            const response = await this.client.execute('rename', args, token);
+            if (response.type !== 'response' || !response.body) {
+                return undefined;
+            }
+            return { type: 'rename', body: response.body };
+        });
+    }
+    private looksLikePotentialJsxTagContext(document: vscode.TextDocument, position: vscode.Position): boolean {
+        if (![languageIds.typescriptreact, languageIds.javascript, languageIds.javascriptreact].includes(document.languageId)) {
+            return false;
+        }
+        const prefix = document.getText(new vscode.Range(position.line, 0, position.line, position.character));
+        return /\<\/?\s*[\w\d_$.]*$/.test(prefix);
+    }
+    private updateLocs(locations: ReadonlyArray, newName: string) {
+        const edit = new vscode.WorkspaceEdit();
+        for (const spanGroup of locations) {
+            const resource = this.client.toResource(spanGroup.file);
+            for (const textSpan of spanGroup.locs) {
+                edit.replace(resource, typeConverters.Range.fromTextSpan(textSpan), (textSpan.prefixText || '') + newName + (textSpan.suffixText || ''));
+            }
+        }
+        return edit;
+    }
+    private async renameFile(fileToRename: string, fullDisplayName: string, newName: string, token: vscode.CancellationToken): Promise {
+        // Make sure we preserve file extension if extension is unchanged or none provided
+        if (!path.extname(newName)) {
+            newName += path.extname(fileToRename);
+        }
+        else if (path.extname(newName) === path.extname(fullDisplayName)) {
+            newName = newName.slice(0, newName.length - path.extname(newName).length) + path.extname(fileToRename);
+        }
+        const dirname = path.dirname(fileToRename);
+        const newFilePath = path.join(dirname, newName);
+        const args: Proto.GetEditsForFileRenameRequestArgs & {
+            file: string;
+        } = {
+            file: fileToRename,
+            oldFilePath: fileToRename,
+            newFilePath: newFilePath,
+        };
+        const response = await this.client.execute('getEditsForFileRename', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const edits = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body);
+        edits.renameFile(vscode.Uri.file(fileToRename), vscode.Uri.file(newFilePath));
+        return edits;
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerRenameProvider(selector.semantic,
-			new TypeScriptRenameProvider(language, client, fileConfigurationManager));
-	});
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerRenameProvider(selector.semantic, new TypeScriptRenameProvider(language, client, fileConfigurationManager));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/semanticTokens.ts b/extensions/typescript-language-features/Source/languageFeatures/semanticTokens.ts
index 7f8d60d38751a..783b7f4116482 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/semanticTokens.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/semanticTokens.ts
@@ -2,177 +2,140 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import * as Proto from '../tsServer/protocol/protocol';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
 // as we don't do deltas, for performance reasons, don't compute semantic tokens for documents above that limit
 const CONTENT_LENGTH_LIMIT = 100000;
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		const provider = new DocumentSemanticTokensProvider(client);
-		return vscode.languages.registerDocumentRangeSemanticTokensProvider(selector.semantic, provider, provider.getLegend());
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        const provider = new DocumentSemanticTokensProvider(client);
+        return vscode.languages.registerDocumentRangeSemanticTokensProvider(selector.semantic, provider, provider.getLegend());
+    });
 }
-
 class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider, vscode.DocumentRangeSemanticTokensProvider {
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public getLegend(): vscode.SemanticTokensLegend {
-		return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
-	}
-
-	public async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file || document.getText().length > CONTENT_LENGTH_LIMIT) {
-			return null;
-		}
-		return this.provideSemanticTokens(document, { file, start: 0, length: document.getText().length }, token);
-	}
-
-	public async provideDocumentRangeSemanticTokens(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file || (document.offsetAt(range.end) - document.offsetAt(range.start) > CONTENT_LENGTH_LIMIT)) {
-			return null;
-		}
-
-		const start = document.offsetAt(range.start);
-		const length = document.offsetAt(range.end) - start;
-		return this.provideSemanticTokens(document, { file, start, length }, token);
-	}
-
-	private async provideSemanticTokens(document: vscode.TextDocument, requestArg: Proto.EncodedSemanticClassificationsRequestArgs, token: vscode.CancellationToken): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return null;
-		}
-
-		const versionBeforeRequest = document.version;
-
-		const response = await this.client.execute('encodedSemanticClassifications-full', { ...requestArg, format: '2020' }, token, {
-			cancelOnResourceChange: document.uri
-		});
-		if (response.type !== 'response' || !response.body) {
-			return null;
-		}
-
-		const versionAfterRequest = document.version;
-
-		if (versionBeforeRequest !== versionAfterRequest) {
-			// cannot convert result's offsets to (line;col) values correctly
-			// a new request will come in soon...
-			//
-			// here we cannot return null, because returning null would remove all semantic tokens.
-			// we must throw to indicate that the semantic tokens should not be removed.
-			// using the string busy here because it is not logged to error telemetry if the error text contains busy.
-
-			// as the new request will come in right after our response, we first wait for the document activity to stop
-			await waitForDocumentChangesToEnd(document);
-
-			throw new vscode.CancellationError();
-		}
-
-		const tokenSpan = response.body.spans;
-
-		const builder = new vscode.SemanticTokensBuilder();
-		for (let i = 0; i < tokenSpan.length;) {
-			const offset = tokenSpan[i++];
-			const length = tokenSpan[i++];
-			const tsClassification = tokenSpan[i++];
-
-			const tokenType = getTokenTypeFromClassification(tsClassification);
-			if (tokenType === undefined) {
-				continue;
-			}
-
-			const tokenModifiers = getTokenModifierFromClassification(tsClassification);
-
-			// we can use the document's range conversion methods because the result is at the same version as the document
-			const startPos = document.positionAt(offset);
-			const endPos = document.positionAt(offset + length);
-
-			for (let line = startPos.line; line <= endPos.line; line++) {
-				const startCharacter = (line === startPos.line ? startPos.character : 0);
-				const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length);
-				builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers);
-			}
-		}
-
-		return builder.build();
-	}
+    constructor(private readonly client: ITypeScriptServiceClient) { }
+    public getLegend(): vscode.SemanticTokensLegend {
+        return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
+    }
+    public async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file || document.getText().length > CONTENT_LENGTH_LIMIT) {
+            return null;
+        }
+        return this.provideSemanticTokens(document, { file, start: 0, length: document.getText().length }, token);
+    }
+    public async provideDocumentRangeSemanticTokens(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file || (document.offsetAt(range.end) - document.offsetAt(range.start) > CONTENT_LENGTH_LIMIT)) {
+            return null;
+        }
+        const start = document.offsetAt(range.start);
+        const length = document.offsetAt(range.end) - start;
+        return this.provideSemanticTokens(document, { file, start, length }, token);
+    }
+    private async provideSemanticTokens(document: vscode.TextDocument, requestArg: Proto.EncodedSemanticClassificationsRequestArgs, token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return null;
+        }
+        const versionBeforeRequest = document.version;
+        const response = await this.client.execute('encodedSemanticClassifications-full', { ...requestArg, format: '2020' }, token, {
+            cancelOnResourceChange: document.uri
+        });
+        if (response.type !== 'response' || !response.body) {
+            return null;
+        }
+        const versionAfterRequest = document.version;
+        if (versionBeforeRequest !== versionAfterRequest) {
+            // cannot convert result's offsets to (line;col) values correctly
+            // a new request will come in soon...
+            //
+            // here we cannot return null, because returning null would remove all semantic tokens.
+            // we must throw to indicate that the semantic tokens should not be removed.
+            // using the string busy here because it is not logged to error telemetry if the error text contains busy.
+            // as the new request will come in right after our response, we first wait for the document activity to stop
+            await waitForDocumentChangesToEnd(document);
+            throw new vscode.CancellationError();
+        }
+        const tokenSpan = response.body.spans;
+        const builder = new vscode.SemanticTokensBuilder();
+        for (let i = 0; i < tokenSpan.length;) {
+            const offset = tokenSpan[i++];
+            const length = tokenSpan[i++];
+            const tsClassification = tokenSpan[i++];
+            const tokenType = getTokenTypeFromClassification(tsClassification);
+            if (tokenType === undefined) {
+                continue;
+            }
+            const tokenModifiers = getTokenModifierFromClassification(tsClassification);
+            // we can use the document's range conversion methods because the result is at the same version as the document
+            const startPos = document.positionAt(offset);
+            const endPos = document.positionAt(offset + length);
+            for (let line = startPos.line; line <= endPos.line; line++) {
+                const startCharacter = (line === startPos.line ? startPos.character : 0);
+                const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length);
+                builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers);
+            }
+        }
+        return builder.build();
+    }
 }
-
 function waitForDocumentChangesToEnd(document: vscode.TextDocument) {
-	let version = document.version;
-	return new Promise((resolve) => {
-		const iv = setInterval(_ => {
-			if (document.version === version) {
-				clearInterval(iv);
-				resolve();
-			}
-			version = document.version;
-		}, 400);
-	});
+    let version = document.version;
+    return new Promise((resolve) => {
+        const iv = setInterval(_ => {
+            if (document.version === version) {
+                clearInterval(iv);
+                resolve();
+            }
+            version = document.version;
+        }, 400);
+    });
 }
-
-
 // typescript encodes type and modifiers in the classification:
 // TSClassification = (TokenType + 1) << 8 + TokenModifier
-
 const enum TokenType {
-	class = 0,
-	enum = 1,
-	interface = 2,
-	namespace = 3,
-	typeParameter = 4,
-	type = 5,
-	parameter = 6,
-	variable = 7,
-	enumMember = 8,
-	property = 9,
-	function = 10,
-	method = 11,
-	_ = 12
+    class = 0,
+    enum = 1,
+    interface = 2,
+    namespace = 3,
+    typeParameter = 4,
+    type = 5,
+    parameter = 6,
+    variable = 7,
+    enumMember = 8,
+    property = 9,
+    function = 10,
+    method = 11,
+    _ = 12
 }
-
 const enum TokenModifier {
-	declaration = 0,
-	static = 1,
-	async = 2,
-	readonly = 3,
-	defaultLibrary = 4,
-	local = 5,
-	_ = 6
+    declaration = 0,
+    static = 1,
+    async = 2,
+    readonly = 3,
+    defaultLibrary = 4,
+    local = 5,
+    _ = 6
 }
-
 const enum TokenEncodingConsts {
-	typeOffset = 8,
-	modifierMask = 255
+    typeOffset = 8,
+    modifierMask = 255
 }
-
 function getTokenTypeFromClassification(tsClassification: number): number | undefined {
-	if (tsClassification > TokenEncodingConsts.modifierMask) {
-		return (tsClassification >> TokenEncodingConsts.typeOffset) - 1;
-	}
-	return undefined;
+    if (tsClassification > TokenEncodingConsts.modifierMask) {
+        return (tsClassification >> TokenEncodingConsts.typeOffset) - 1;
+    }
+    return undefined;
 }
-
 function getTokenModifierFromClassification(tsClassification: number) {
-	return tsClassification & TokenEncodingConsts.modifierMask;
+    return tsClassification & TokenEncodingConsts.modifierMask;
 }
-
 const tokenTypes: string[] = [];
 tokenTypes[TokenType.class] = 'class';
 tokenTypes[TokenType.enum] = 'enum';
@@ -186,7 +149,6 @@ tokenTypes[TokenType.enumMember] = 'enumMember';
 tokenTypes[TokenType.property] = 'property';
 tokenTypes[TokenType.function] = 'function';
 tokenTypes[TokenType.method] = 'method';
-
 const tokenModifiers: string[] = [];
 tokenModifiers[TokenModifier.async] = 'async';
 tokenModifiers[TokenModifier.declaration] = 'declaration';
diff --git a/extensions/typescript-language-features/Source/languageFeatures/signatureHelp.ts b/extensions/typescript-language-features/Source/languageFeatures/signatureHelp.ts
index 62aeba68b5558..b8d9e1044857e 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/signatureHelp.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/signatureHelp.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import type * as Proto from '../tsServer/protocol/protocol';
@@ -10,128 +9,95 @@ import * as typeConverters from '../typeConverters';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
 import * as Previewer from './util/textRendering';
-
 class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider {
-
-	public static readonly triggerCharacters = ['(', ',', '<'];
-	public static readonly retriggerCharacters = [')'];
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async provideSignatureHelp(
-		document: vscode.TextDocument,
-		position: vscode.Position,
-		token: vscode.CancellationToken,
-		context: vscode.SignatureHelpContext,
-	): Promise {
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return undefined;
-		}
-
-		const args: Proto.SignatureHelpRequestArgs = {
-			...typeConverters.Position.toFileLocationRequestArgs(filepath, position),
-			triggerReason: toTsTriggerReason(context)
-		};
-		const response = await this.client.interruptGetErr(() => this.client.execute('signatureHelp', args, token));
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-
-		const info = response.body;
-		const result = new vscode.SignatureHelp();
-		result.signatures = info.items.map(signature => this.convertSignature(signature, document.uri));
-		result.activeSignature = this.getActiveSignature(context, info, result.signatures);
-		result.activeParameter = this.getActiveParameter(info);
-
-		return result;
-	}
-
-	private getActiveSignature(context: vscode.SignatureHelpContext, info: Proto.SignatureHelpItems, signatures: readonly vscode.SignatureInformation[]): number {
-		// Try matching the previous active signature's label to keep it selected
-		const previouslyActiveSignature = context.activeSignatureHelp?.signatures[context.activeSignatureHelp.activeSignature];
-		if (previouslyActiveSignature && context.isRetrigger) {
-			const existingIndex = signatures.findIndex(other => other.label === previouslyActiveSignature?.label);
-			if (existingIndex >= 0) {
-				return existingIndex;
-			}
-		}
-
-		return info.selectedItemIndex;
-	}
-
-	private getActiveParameter(info: Proto.SignatureHelpItems): number {
-		const activeSignature = info.items[info.selectedItemIndex];
-		if (activeSignature?.isVariadic) {
-			return Math.min(info.argumentIndex, activeSignature.parameters.length - 1);
-		}
-		return info.argumentIndex;
-	}
-
-	private convertSignature(item: Proto.SignatureHelpItem, baseUri: vscode.Uri) {
-		const signature = new vscode.SignatureInformation(
-			Previewer.asPlainTextWithLinks(item.prefixDisplayParts, this.client),
-			Previewer.documentationToMarkdown(item.documentation, item.tags.filter(x => x.name !== 'param'), this.client, baseUri));
-
-		let textIndex = signature.label.length;
-		const separatorLabel = Previewer.asPlainTextWithLinks(item.separatorDisplayParts, this.client);
-		for (let i = 0; i < item.parameters.length; ++i) {
-			const parameter = item.parameters[i];
-			const label = Previewer.asPlainTextWithLinks(parameter.displayParts, this.client);
-
-			signature.parameters.push(
-				new vscode.ParameterInformation(
-					[textIndex, textIndex + label.length],
-					Previewer.documentationToMarkdown(parameter.documentation, [], this.client, baseUri)));
-
-			textIndex += label.length;
-			signature.label += label;
-
-			if (i !== item.parameters.length - 1) {
-				signature.label += separatorLabel;
-				textIndex += separatorLabel.length;
-			}
-		}
-
-		signature.label += Previewer.asPlainTextWithLinks(item.suffixDisplayParts, this.client);
-		return signature;
-	}
+    public static readonly triggerCharacters = ['(', ',', '<'];
+    public static readonly retriggerCharacters = [')'];
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async provideSignatureHelp(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.SignatureHelpContext): Promise {
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return undefined;
+        }
+        const args: Proto.SignatureHelpRequestArgs = {
+            ...typeConverters.Position.toFileLocationRequestArgs(filepath, position),
+            triggerReason: toTsTriggerReason(context)
+        };
+        const response = await this.client.interruptGetErr(() => this.client.execute('signatureHelp', args, token));
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        const info = response.body;
+        const result = new vscode.SignatureHelp();
+        result.signatures = info.items.map(signature => this.convertSignature(signature, document.uri));
+        result.activeSignature = this.getActiveSignature(context, info, result.signatures);
+        result.activeParameter = this.getActiveParameter(info);
+        return result;
+    }
+    private getActiveSignature(context: vscode.SignatureHelpContext, info: Proto.SignatureHelpItems, signatures: readonly vscode.SignatureInformation[]): number {
+        // Try matching the previous active signature's label to keep it selected
+        const previouslyActiveSignature = context.activeSignatureHelp?.signatures[context.activeSignatureHelp.activeSignature];
+        if (previouslyActiveSignature && context.isRetrigger) {
+            const existingIndex = signatures.findIndex(other => other.label === previouslyActiveSignature?.label);
+            if (existingIndex >= 0) {
+                return existingIndex;
+            }
+        }
+        return info.selectedItemIndex;
+    }
+    private getActiveParameter(info: Proto.SignatureHelpItems): number {
+        const activeSignature = info.items[info.selectedItemIndex];
+        if (activeSignature?.isVariadic) {
+            return Math.min(info.argumentIndex, activeSignature.parameters.length - 1);
+        }
+        return info.argumentIndex;
+    }
+    private convertSignature(item: Proto.SignatureHelpItem, baseUri: vscode.Uri) {
+        const signature = new vscode.SignatureInformation(Previewer.asPlainTextWithLinks(item.prefixDisplayParts, this.client), Previewer.documentationToMarkdown(item.documentation, item.tags.filter(x => x.name !== 'param'), this.client, baseUri));
+        let textIndex = signature.label.length;
+        const separatorLabel = Previewer.asPlainTextWithLinks(item.separatorDisplayParts, this.client);
+        for (let i = 0; i < item.parameters.length; ++i) {
+            const parameter = item.parameters[i];
+            const label = Previewer.asPlainTextWithLinks(parameter.displayParts, this.client);
+            signature.parameters.push(new vscode.ParameterInformation([textIndex, textIndex + label.length], Previewer.documentationToMarkdown(parameter.documentation, [], this.client, baseUri)));
+            textIndex += label.length;
+            signature.label += label;
+            if (i !== item.parameters.length - 1) {
+                signature.label += separatorLabel;
+                textIndex += separatorLabel.length;
+            }
+        }
+        signature.label += Previewer.asPlainTextWithLinks(item.suffixDisplayParts, this.client);
+        return signature;
+    }
 }
-
 function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.SignatureHelpTriggerReason {
-	switch (context.triggerKind) {
-		case vscode.SignatureHelpTriggerKind.TriggerCharacter:
-			if (context.triggerCharacter) {
-				if (context.isRetrigger) {
-					return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as any };
-				} else {
-					return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any };
-				}
-			} else {
-				return { kind: 'invoked' };
-			}
-
-		case vscode.SignatureHelpTriggerKind.ContentChange:
-			return context.isRetrigger ? { kind: 'retrigger' } : { kind: 'invoked' };
-
-		case vscode.SignatureHelpTriggerKind.Invoke:
-		default:
-			return { kind: 'invoked' };
-	}
+    switch (context.triggerKind) {
+        case vscode.SignatureHelpTriggerKind.TriggerCharacter:
+            if (context.triggerCharacter) {
+                if (context.isRetrigger) {
+                    return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as any };
+                }
+                else {
+                    return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any };
+                }
+            }
+            else {
+                return { kind: 'invoked' };
+            }
+        case vscode.SignatureHelpTriggerKind.ContentChange:
+            return context.isRetrigger ? { kind: 'retrigger' } : { kind: 'invoked' };
+        case vscode.SignatureHelpTriggerKind.Invoke:
+        default:
+            return { kind: 'invoked' };
+    }
 }
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerSignatureHelpProvider(selector.syntax,
-			new TypeScriptSignatureHelpProvider(client), {
-			triggerCharacters: TypeScriptSignatureHelpProvider.triggerCharacters,
-			retriggerCharacters: TypeScriptSignatureHelpProvider.retriggerCharacters
-		});
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerSignatureHelpProvider(selector.syntax, new TypeScriptSignatureHelpProvider(client), {
+            triggerCharacters: TypeScriptSignatureHelpProvider.triggerCharacters,
+            retriggerCharacters: TypeScriptSignatureHelpProvider.retriggerCharacters
+        });
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/smartSelect.ts b/extensions/typescript-language-features/Source/languageFeatures/smartSelect.ts
index fa6a6096d37d8..73fc326791352 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/smartSelect.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/smartSelect.ts
@@ -2,53 +2,32 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import type * as Proto from '../tsServer/protocol/protocol';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
 class SmartSelection implements vscode.SelectionRangeProvider {
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async provideSelectionRanges(
-		document: vscode.TextDocument,
-		positions: vscode.Position[],
-		token: vscode.CancellationToken,
-	): Promise {
-		const file = this.client.toOpenTsFilePath(document);
-		if (!file) {
-			return undefined;
-		}
-
-		const args: Proto.SelectionRangeRequestArgs = {
-			file,
-			locations: positions.map(typeConverters.Position.toLocation)
-		};
-		const response = await this.client.execute('selectionRange', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return undefined;
-		}
-		return response.body.map(SmartSelection.convertSelectionRange);
-	}
-
-	private static convertSelectionRange(
-		selectionRange: Proto.SelectionRange
-	): vscode.SelectionRange {
-		return new vscode.SelectionRange(
-			typeConverters.Range.fromTextSpan(selectionRange.textSpan),
-			selectionRange.parent ? SmartSelection.convertSelectionRange(selectionRange.parent) : undefined,
-		);
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async provideSelectionRanges(document: vscode.TextDocument, positions: vscode.Position[], token: vscode.CancellationToken): Promise {
+        const file = this.client.toOpenTsFilePath(document);
+        if (!file) {
+            return undefined;
+        }
+        const args: Proto.SelectionRangeRequestArgs = {
+            file,
+            locations: positions.map(typeConverters.Position.toLocation)
+        };
+        const response = await this.client.execute('selectionRange', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return undefined;
+        }
+        return response.body.map(SmartSelection.convertSelectionRange);
+    }
+    private static convertSelectionRange(selectionRange: Proto.SelectionRange): vscode.SelectionRange {
+        return new vscode.SelectionRange(typeConverters.Range.fromTextSpan(selectionRange.textSpan), selectionRange.parent ? SmartSelection.convertSelectionRange(selectionRange.parent) : undefined);
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return vscode.languages.registerSelectionRangeProvider(selector.syntax, new SmartSelection(client));
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return vscode.languages.registerSelectionRangeProvider(selector.syntax, new SmartSelection(client));
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/sourceDefinition.ts b/extensions/typescript-language-features/Source/languageFeatures/sourceDefinition.ts
index 301f8607a1e54..2096410ed73d0 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/sourceDefinition.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/sourceDefinition.ts
@@ -2,90 +2,68 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command, CommandManager } from '../commands/commandManager';
 import { isSupportedLanguageMode } from '../configuration/languageIds';
 import { API } from '../tsServer/api';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
-
 class SourceDefinitionCommand implements Command {
-
-	public static readonly context = 'tsSupportsSourceDefinition';
-	public static readonly minVersion = API.v470;
-
-	public readonly id = 'typescript.goToSourceDefinition';
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient
-	) { }
-
-	public async execute() {
-		if (this.client.apiVersion.lt(SourceDefinitionCommand.minVersion)) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Requires TypeScript 4.7+."));
-			return;
-		}
-
-		const activeEditor = vscode.window.activeTextEditor;
-		if (!activeEditor) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. No resource provided."));
-			return;
-		}
-
-		const resource = activeEditor.document.uri;
-		const document = await vscode.workspace.openTextDocument(resource);
-		if (!isSupportedLanguageMode(document)) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Unsupported file type."));
-			return;
-		}
-
-		const openedFiledPath = this.client.toOpenTsFilePath(document);
-		if (!openedFiledPath) {
-			vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Unknown file type."));
-			return;
-		}
-
-		await vscode.window.withProgress({
-			location: vscode.ProgressLocation.Window,
-			title: vscode.l10n.t("Finding source definitions")
-		}, async (_progress, token) => {
-
-			const position = activeEditor.selection.anchor;
-			const args = typeConverters.Position.toFileLocationRequestArgs(openedFiledPath, position);
-			const response = await this.client.execute('findSourceDefinition', args, token);
-			if (response.type === 'response' && response.body) {
-				const locations: vscode.Location[] = response.body.map(reference =>
-					typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference));
-
-				if (locations.length) {
-					if (locations.length === 1) {
-						vscode.commands.executeCommand('vscode.open', locations[0].uri.with({
-							fragment: `L${locations[0].range.start.line + 1},${locations[0].range.start.character + 1}`
-						}));
-					} else {
-						vscode.commands.executeCommand('editor.action.showReferences', resource, position, locations);
-					}
-					return;
-				}
-			}
-
-			vscode.window.showErrorMessage(vscode.l10n.t("No source definitions found."));
-		});
-	}
+    public static readonly context = 'tsSupportsSourceDefinition';
+    public static readonly minVersion = API.v470;
+    public readonly id = 'typescript.goToSourceDefinition';
+    public constructor(private readonly client: ITypeScriptServiceClient) { }
+    public async execute() {
+        if (this.client.apiVersion.lt(SourceDefinitionCommand.minVersion)) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Requires TypeScript 4.7+."));
+            return;
+        }
+        const activeEditor = vscode.window.activeTextEditor;
+        if (!activeEditor) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. No resource provided."));
+            return;
+        }
+        const resource = activeEditor.document.uri;
+        const document = await vscode.workspace.openTextDocument(resource);
+        if (!isSupportedLanguageMode(document)) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Unsupported file type."));
+            return;
+        }
+        const openedFiledPath = this.client.toOpenTsFilePath(document);
+        if (!openedFiledPath) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Unknown file type."));
+            return;
+        }
+        await vscode.window.withProgress({
+            location: vscode.ProgressLocation.Window,
+            title: vscode.l10n.t("Finding source definitions")
+        }, async (_progress, token) => {
+            const position = activeEditor.selection.anchor;
+            const args = typeConverters.Position.toFileLocationRequestArgs(openedFiledPath, position);
+            const response = await this.client.execute('findSourceDefinition', args, token);
+            if (response.type === 'response' && response.body) {
+                const locations: vscode.Location[] = response.body.map(reference => typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference));
+                if (locations.length) {
+                    if (locations.length === 1) {
+                        vscode.commands.executeCommand('vscode.open', locations[0].uri.with({
+                            fragment: `L${locations[0].range.start.line + 1},${locations[0].range.start.character + 1}`
+                        }));
+                    }
+                    else {
+                        vscode.commands.executeCommand('editor.action.showReferences', resource, position, locations);
+                    }
+                    return;
+                }
+            }
+            vscode.window.showErrorMessage(vscode.l10n.t("No source definitions found."));
+        });
+    }
 }
-
-
-export function register(
-	client: ITypeScriptServiceClient,
-	commandManager: CommandManager
-) {
-	function updateContext() {
-		vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, client.apiVersion.gte(SourceDefinitionCommand.minVersion));
-	}
-	updateContext();
-
-	commandManager.register(new SourceDefinitionCommand(client));
-	return client.onTsServerStarted(() => updateContext());
+export function register(client: ITypeScriptServiceClient, commandManager: CommandManager) {
+    function updateContext() {
+        vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, client.apiVersion.gte(SourceDefinitionCommand.minVersion));
+    }
+    updateContext();
+    commandManager.register(new SourceDefinitionCommand(client));
+    return client.onTsServerStarted(() => updateContext());
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/tagClosing.ts b/extensions/typescript-language-features/Source/languageFeatures/tagClosing.ts
index 6b47feb3d0005..32b073598f2f8 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/tagClosing.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/tagClosing.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { LanguageDescription } from '../configuration/languageDescription';
@@ -11,160 +10,114 @@ import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import { Disposable } from '../utils/dispose';
 import { Condition, conditionalRegistration } from './util/dependentRegistration';
-
 class TagClosing extends Disposable {
-
-	private _disposed = false;
-	private _timeout: NodeJS.Timeout | undefined = undefined;
-	private _cancel: vscode.CancellationTokenSource | undefined = undefined;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient
-	) {
-		super();
-		vscode.workspace.onDidChangeTextDocument(
-			event => this.onDidChangeTextDocument(event),
-			null,
-			this._disposables);
-	}
-
-	public override dispose() {
-		super.dispose();
-		this._disposed = true;
-
-		if (this._timeout) {
-			clearTimeout(this._timeout);
-			this._timeout = undefined;
-		}
-
-		if (this._cancel) {
-			this._cancel.cancel();
-			this._cancel.dispose();
-			this._cancel = undefined;
-		}
-	}
-
-	private onDidChangeTextDocument(
-		{ document, contentChanges, reason }: vscode.TextDocumentChangeEvent
-	) {
-		if (contentChanges.length === 0 || reason === vscode.TextDocumentChangeReason.Undo || reason === vscode.TextDocumentChangeReason.Redo) {
-			return;
-		}
-
-		const activeDocument = vscode.window.activeTextEditor?.document;
-		if (document !== activeDocument) {
-			return;
-		}
-
-		const filepath = this.client.toOpenTsFilePath(document);
-		if (!filepath) {
-			return;
-		}
-
-		if (typeof this._timeout !== 'undefined') {
-			clearTimeout(this._timeout);
-		}
-
-		if (this._cancel) {
-			this._cancel.cancel();
-			this._cancel.dispose();
-			this._cancel = undefined;
-		}
-
-		const lastChange = contentChanges[contentChanges.length - 1];
-		const lastCharacter = lastChange.text[lastChange.text.length - 1];
-		if (lastChange.rangeLength > 0 || lastCharacter !== '>' && lastCharacter !== '/') {
-			return;
-		}
-
-		const priorCharacter = lastChange.range.start.character > 0
-			? document.getText(new vscode.Range(lastChange.range.start.translate({ characterDelta: -1 }), lastChange.range.start))
-			: '';
-		if (priorCharacter === '>') {
-			return;
-		}
-
-		const version = document.version;
-		this._timeout = setTimeout(async () => {
-			this._timeout = undefined;
-
-			if (this._disposed) {
-				return;
-			}
-
-			const addedLines = lastChange.text.split(/\r\n|\n/g);
-			const position = addedLines.length <= 1
-				? lastChange.range.start.translate({ characterDelta: lastChange.text.length })
-				: new vscode.Position(lastChange.range.start.line + addedLines.length - 1, addedLines[addedLines.length - 1].length);
-
-			const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
-			this._cancel = new vscode.CancellationTokenSource();
-			const response = await this.client.execute('jsxClosingTag', args, this._cancel.token);
-			if (response.type !== 'response' || !response.body) {
-				return;
-			}
-
-			if (this._disposed) {
-				return;
-			}
-
-			const activeEditor = vscode.window.activeTextEditor;
-			if (!activeEditor) {
-				return;
-			}
-
-			const insertion = response.body;
-			const activeDocument = activeEditor.document;
-			if (document === activeDocument && activeDocument.version === version) {
-				activeEditor.insertSnippet(
-					this.getTagSnippet(insertion),
-					this.getInsertionPositions(activeEditor, position));
-			}
-		}, 100);
-	}
-
-	private getTagSnippet(closingTag: Proto.TextInsertion): vscode.SnippetString {
-		const snippet = new vscode.SnippetString();
-		snippet.appendPlaceholder('', 0);
-		snippet.appendText(closingTag.newText);
-		return snippet;
-	}
-
-	private getInsertionPositions(editor: vscode.TextEditor, position: vscode.Position) {
-		const activeSelectionPositions = editor.selections.map(s => s.active);
-		return activeSelectionPositions.some(p => p.isEqual(position))
-			? activeSelectionPositions
-			: position;
-	}
+    private _disposed = false;
+    private _timeout: NodeJS.Timeout | undefined = undefined;
+    private _cancel: vscode.CancellationTokenSource | undefined = undefined;
+    constructor(private readonly client: ITypeScriptServiceClient) {
+        super();
+        vscode.workspace.onDidChangeTextDocument(event => this.onDidChangeTextDocument(event), null, this._disposables);
+    }
+    public override dispose() {
+        super.dispose();
+        this._disposed = true;
+        if (this._timeout) {
+            clearTimeout(this._timeout);
+            this._timeout = undefined;
+        }
+        if (this._cancel) {
+            this._cancel.cancel();
+            this._cancel.dispose();
+            this._cancel = undefined;
+        }
+    }
+    private onDidChangeTextDocument({ document, contentChanges, reason }: vscode.TextDocumentChangeEvent) {
+        if (contentChanges.length === 0 || reason === vscode.TextDocumentChangeReason.Undo || reason === vscode.TextDocumentChangeReason.Redo) {
+            return;
+        }
+        const activeDocument = vscode.window.activeTextEditor?.document;
+        if (document !== activeDocument) {
+            return;
+        }
+        const filepath = this.client.toOpenTsFilePath(document);
+        if (!filepath) {
+            return;
+        }
+        if (typeof this._timeout !== 'undefined') {
+            clearTimeout(this._timeout);
+        }
+        if (this._cancel) {
+            this._cancel.cancel();
+            this._cancel.dispose();
+            this._cancel = undefined;
+        }
+        const lastChange = contentChanges[contentChanges.length - 1];
+        const lastCharacter = lastChange.text[lastChange.text.length - 1];
+        if (lastChange.rangeLength > 0 || lastCharacter !== '>' && lastCharacter !== '/') {
+            return;
+        }
+        const priorCharacter = lastChange.range.start.character > 0
+            ? document.getText(new vscode.Range(lastChange.range.start.translate({ characterDelta: -1 }), lastChange.range.start))
+            : '';
+        if (priorCharacter === '>') {
+            return;
+        }
+        const version = document.version;
+        this._timeout = setTimeout(async () => {
+            this._timeout = undefined;
+            if (this._disposed) {
+                return;
+            }
+            const addedLines = lastChange.text.split(/\r\n|\n/g);
+            const position = addedLines.length <= 1
+                ? lastChange.range.start.translate({ characterDelta: lastChange.text.length })
+                : new vscode.Position(lastChange.range.start.line + addedLines.length - 1, addedLines[addedLines.length - 1].length);
+            const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
+            this._cancel = new vscode.CancellationTokenSource();
+            const response = await this.client.execute('jsxClosingTag', args, this._cancel.token);
+            if (response.type !== 'response' || !response.body) {
+                return;
+            }
+            if (this._disposed) {
+                return;
+            }
+            const activeEditor = vscode.window.activeTextEditor;
+            if (!activeEditor) {
+                return;
+            }
+            const insertion = response.body;
+            const activeDocument = activeEditor.document;
+            if (document === activeDocument && activeDocument.version === version) {
+                activeEditor.insertSnippet(this.getTagSnippet(insertion), this.getInsertionPositions(activeEditor, position));
+            }
+        }, 100);
+    }
+    private getTagSnippet(closingTag: Proto.TextInsertion): vscode.SnippetString {
+        const snippet = new vscode.SnippetString();
+        snippet.appendPlaceholder('', 0);
+        snippet.appendText(closingTag.newText);
+        return snippet;
+    }
+    private getInsertionPositions(editor: vscode.TextEditor, position: vscode.Position) {
+        const activeSelectionPositions = editor.selections.map(s => s.active);
+        return activeSelectionPositions.some(p => p.isEqual(position))
+            ? activeSelectionPositions
+            : position;
+    }
 }
-
-function requireActiveDocumentSetting(
-	selector: vscode.DocumentSelector,
-	language: LanguageDescription,
-) {
-	return new Condition(
-		() => {
-			const editor = vscode.window.activeTextEditor;
-			if (!editor || !vscode.languages.match(selector, editor.document)) {
-				return false;
-			}
-
-			return !!vscode.workspace.getConfiguration(language.id, editor.document).get('autoClosingTags');
-		},
-		handler => {
-			return vscode.Disposable.from(
-				vscode.window.onDidChangeActiveTextEditor(handler),
-				vscode.workspace.onDidOpenTextDocument(handler),
-				vscode.workspace.onDidChangeConfiguration(handler));
-		});
+function requireActiveDocumentSetting(selector: vscode.DocumentSelector, language: LanguageDescription) {
+    return new Condition(() => {
+        const editor = vscode.window.activeTextEditor;
+        if (!editor || !vscode.languages.match(selector, editor.document)) {
+            return false;
+        }
+        return !!vscode.workspace.getConfiguration(language.id, editor.document).get('autoClosingTags');
+    }, handler => {
+        return vscode.Disposable.from(vscode.window.onDidChangeActiveTextEditor(handler), vscode.workspace.onDidOpenTextDocument(handler), vscode.workspace.onDidChangeConfiguration(handler));
+    });
 }
-
-export function register(
-	selector: DocumentSelector,
-	language: LanguageDescription,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireActiveDocumentSetting(selector.syntax, language)
-	], () => new TagClosing(client));
+export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireActiveDocumentSetting(selector.syntax, language)
+    ], () => new TagClosing(client));
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/tsconfig.ts b/extensions/typescript-language-features/Source/languageFeatures/tsconfig.ts
index 85fe3f6de5fe7..f7fc12239e6b3 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/tsconfig.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/tsconfig.ts
@@ -2,216 +2,168 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as jsonc from 'jsonc-parser';
 import { isAbsolute, posix } from 'path';
 import * as vscode from 'vscode';
 import { Utils } from 'vscode-uri';
 import { coalesce } from '../utils/arrays';
 import { exists, looksLikeAbsoluteWindowsPath } from '../utils/fs';
-
 function mapChildren(node: jsonc.Node | undefined, f: (x: jsonc.Node) => R): R[] {
-	return node && node.type === 'array' && node.children
-		? node.children.map(f)
-		: [];
+    return node && node.type === 'array' && node.children
+        ? node.children.map(f)
+        : [];
 }
-
 const openExtendsLinkCommandId = '_typescript.openExtendsLink';
-
 enum TsConfigLinkType {
-	Extends,
-	References
+    Extends,
+    References
 }
-
 type OpenExtendsLinkCommandArgs = {
-	readonly resourceUri: vscode.Uri;
-	readonly extendsValue: string;
-	readonly linkType: TsConfigLinkType;
+    readonly resourceUri: vscode.Uri;
+    readonly extendsValue: string;
+    readonly linkType: TsConfigLinkType;
 };
-
-
 class TsconfigLinkProvider implements vscode.DocumentLinkProvider {
-
-	public provideDocumentLinks(
-		document: vscode.TextDocument,
-		_token: vscode.CancellationToken
-	): vscode.DocumentLink[] {
-		const root = jsonc.parseTree(document.getText());
-		if (!root) {
-			return [];
-		}
-
-		return coalesce([
-			this.getExtendsLink(document, root),
-			...this.getFilesLinks(document, root),
-			...this.getReferencesLinks(document, root)
-		]);
-	}
-
-	private getExtendsLink(document: vscode.TextDocument, root: jsonc.Node): vscode.DocumentLink | undefined {
-		const node = jsonc.findNodeAtLocation(root, ['extends']);
-		return node && this.tryCreateTsConfigLink(document, node, TsConfigLinkType.Extends);
-	}
-
-	private getReferencesLinks(document: vscode.TextDocument, root: jsonc.Node) {
-		return mapChildren(
-			jsonc.findNodeAtLocation(root, ['references']),
-			child => {
-				const pathNode = jsonc.findNodeAtLocation(child, ['path']);
-				return pathNode && this.tryCreateTsConfigLink(document, pathNode, TsConfigLinkType.References);
-			});
-	}
-
-	private tryCreateTsConfigLink(document: vscode.TextDocument, node: jsonc.Node, linkType: TsConfigLinkType): vscode.DocumentLink | undefined {
-		if (!this.isPathValue(node)) {
-			return undefined;
-		}
-
-		const args: OpenExtendsLinkCommandArgs = {
-			resourceUri: { ...document.uri.toJSON(), $mid: undefined },
-			extendsValue: node.value,
-			linkType
-		};
-
-		const link = new vscode.DocumentLink(
-			this.getRange(document, node),
-			vscode.Uri.parse(`command:${openExtendsLinkCommandId}?${JSON.stringify(args)}`));
-		link.tooltip = vscode.l10n.t("Follow link");
-		return link;
-	}
-
-	private getFilesLinks(document: vscode.TextDocument, root: jsonc.Node) {
-		return mapChildren(
-			jsonc.findNodeAtLocation(root, ['files']),
-			child => this.pathNodeToLink(document, child));
-	}
-
-	private pathNodeToLink(
-		document: vscode.TextDocument,
-		node: jsonc.Node | undefined
-	): vscode.DocumentLink | undefined {
-		return this.isPathValue(node)
-			? new vscode.DocumentLink(this.getRange(document, node), this.getFileTarget(document, node))
-			: undefined;
-	}
-
-	private isPathValue(node: jsonc.Node | undefined): node is jsonc.Node {
-		return node
-			&& node.type === 'string'
-			&& node.value
-			&& !(node.value as string).includes('*'); // don't treat globs as links.
-	}
-
-	private getFileTarget(document: vscode.TextDocument, node: jsonc.Node): vscode.Uri {
-		if (isAbsolute(node.value)) {
-			return vscode.Uri.file(node.value);
-		}
-
-		return vscode.Uri.joinPath(Utils.dirname(document.uri), node.value);
-	}
-
-	private getRange(document: vscode.TextDocument, node: jsonc.Node) {
-		const offset = node.offset;
-		const start = document.positionAt(offset + 1);
-		const end = document.positionAt(offset + (node.length - 1));
-		return new vscode.Range(start, end);
-	}
+    public provideDocumentLinks(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.DocumentLink[] {
+        const root = jsonc.parseTree(document.getText());
+        if (!root) {
+            return [];
+        }
+        return coalesce([
+            this.getExtendsLink(document, root),
+            ...this.getFilesLinks(document, root),
+            ...this.getReferencesLinks(document, root)
+        ]);
+    }
+    private getExtendsLink(document: vscode.TextDocument, root: jsonc.Node): vscode.DocumentLink | undefined {
+        const node = jsonc.findNodeAtLocation(root, ['extends']);
+        return node && this.tryCreateTsConfigLink(document, node, TsConfigLinkType.Extends);
+    }
+    private getReferencesLinks(document: vscode.TextDocument, root: jsonc.Node) {
+        return mapChildren(jsonc.findNodeAtLocation(root, ['references']), child => {
+            const pathNode = jsonc.findNodeAtLocation(child, ['path']);
+            return pathNode && this.tryCreateTsConfigLink(document, pathNode, TsConfigLinkType.References);
+        });
+    }
+    private tryCreateTsConfigLink(document: vscode.TextDocument, node: jsonc.Node, linkType: TsConfigLinkType): vscode.DocumentLink | undefined {
+        if (!this.isPathValue(node)) {
+            return undefined;
+        }
+        const args: OpenExtendsLinkCommandArgs = {
+            resourceUri: { ...document.uri.toJSON(), $mid: undefined },
+            extendsValue: node.value,
+            linkType
+        };
+        const link = new vscode.DocumentLink(this.getRange(document, node), vscode.Uri.parse(`command:${openExtendsLinkCommandId}?${JSON.stringify(args)}`));
+        link.tooltip = vscode.l10n.t("Follow link");
+        return link;
+    }
+    private getFilesLinks(document: vscode.TextDocument, root: jsonc.Node) {
+        return mapChildren(jsonc.findNodeAtLocation(root, ['files']), child => this.pathNodeToLink(document, child));
+    }
+    private pathNodeToLink(document: vscode.TextDocument, node: jsonc.Node | undefined): vscode.DocumentLink | undefined {
+        return this.isPathValue(node)
+            ? new vscode.DocumentLink(this.getRange(document, node), this.getFileTarget(document, node))
+            : undefined;
+    }
+    private isPathValue(node: jsonc.Node | undefined): node is jsonc.Node {
+        return node
+            && node.type === 'string'
+            && node.value
+            && !(node.value as string).includes('*'); // don't treat globs as links.
+    }
+    private getFileTarget(document: vscode.TextDocument, node: jsonc.Node): vscode.Uri {
+        if (isAbsolute(node.value)) {
+            return vscode.Uri.file(node.value);
+        }
+        return vscode.Uri.joinPath(Utils.dirname(document.uri), node.value);
+    }
+    private getRange(document: vscode.TextDocument, node: jsonc.Node) {
+        const offset = node.offset;
+        const start = document.positionAt(offset + 1);
+        const end = document.positionAt(offset + (node.length - 1));
+        return new vscode.Range(start, end);
+    }
 }
-
 async function resolveNodeModulesPath(baseDirUri: vscode.Uri, pathCandidates: string[]): Promise {
-	let currentUri = baseDirUri;
-	const baseCandidate = pathCandidates[0];
-	const sepIndex = baseCandidate.startsWith('@') ? 2 : 1;
-	const moduleBasePath = baseCandidate.split(posix.sep).slice(0, sepIndex).join(posix.sep);
-	while (true) {
-		const moduleAbsoluteUrl = vscode.Uri.joinPath(currentUri, 'node_modules', moduleBasePath);
-		let moduleStat: vscode.FileStat | undefined;
-		try {
-			moduleStat = await vscode.workspace.fs.stat(moduleAbsoluteUrl);
-		} catch (err) {
-			// noop
-		}
-
-		if (moduleStat && (moduleStat.type & vscode.FileType.Directory)) {
-			for (const uriCandidate of pathCandidates
-				.map((relativePath) => relativePath.split(posix.sep).slice(sepIndex).join(posix.sep))
-				// skip empty paths within module
-				.filter(Boolean)
-				.map((relativeModulePath) => vscode.Uri.joinPath(moduleAbsoluteUrl, relativeModulePath))
-			) {
-				if (await exists(uriCandidate)) {
-					return uriCandidate;
-				}
-			}
-			// Continue to looking for potentially another version
-		}
-
-		const oldUri = currentUri;
-		currentUri = vscode.Uri.joinPath(currentUri, '..');
-
-		// Can't go next. Reached the system root
-		if (oldUri.path === currentUri.path) {
-			return;
-		}
-	}
+    let currentUri = baseDirUri;
+    const baseCandidate = pathCandidates[0];
+    const sepIndex = baseCandidate.startsWith('@') ? 2 : 1;
+    const moduleBasePath = baseCandidate.split(posix.sep).slice(0, sepIndex).join(posix.sep);
+    while (true) {
+        const moduleAbsoluteUrl = vscode.Uri.joinPath(currentUri, 'node_modules', moduleBasePath);
+        let moduleStat: vscode.FileStat | undefined;
+        try {
+            moduleStat = await vscode.workspace.fs.stat(moduleAbsoluteUrl);
+        }
+        catch (err) {
+            // noop
+        }
+        if (moduleStat && (moduleStat.type & vscode.FileType.Directory)) {
+            for (const uriCandidate of pathCandidates
+                .map((relativePath) => relativePath.split(posix.sep).slice(sepIndex).join(posix.sep))
+                // skip empty paths within module
+                .filter(Boolean)
+                .map((relativeModulePath) => vscode.Uri.joinPath(moduleAbsoluteUrl, relativeModulePath))) {
+                if (await exists(uriCandidate)) {
+                    return uriCandidate;
+                }
+            }
+            // Continue to looking for potentially another version
+        }
+        const oldUri = currentUri;
+        currentUri = vscode.Uri.joinPath(currentUri, '..');
+        // Can't go next. Reached the system root
+        if (oldUri.path === currentUri.path) {
+            return;
+        }
+    }
 }
-
 // Reference Extends:https://github.com/microsoft/TypeScript/blob/febfd442cdba343771f478cf433b0892f213ad2f/src/compiler/commandLineParser.ts#L3005
 // Reference Project References: https://github.com/microsoft/TypeScript/blob/7377f5cb9db19d79a6167065b323a45611c812b5/src/compiler/tsbuild.ts#L188C1-L194C2
 /**
 * @returns Returns undefined in case of lack of result while trying to resolve from node_modules
 */
 async function getTsconfigPath(baseDirUri: vscode.Uri, pathValue: string, linkType: TsConfigLinkType): Promise {
-	async function resolve(absolutePath: vscode.Uri): Promise {
-		if (absolutePath.path.endsWith('.json') || await exists(absolutePath)) {
-			return absolutePath;
-		}
-		return absolutePath.with({
-			path: `${absolutePath.path}${linkType === TsConfigLinkType.References ? '/tsconfig.json' : '.json'}`
-		});
-	}
-
-	const isRelativePath = ['./', '../'].some(str => pathValue.startsWith(str));
-	if (isRelativePath) {
-		return resolve(vscode.Uri.joinPath(baseDirUri, pathValue));
-	}
-
-	if (pathValue.startsWith('/') || looksLikeAbsoluteWindowsPath(pathValue)) {
-		return resolve(vscode.Uri.file(pathValue));
-	}
-
-	// Otherwise resolve like a module
-	return resolveNodeModulesPath(baseDirUri, [
-		pathValue,
-		...pathValue.endsWith('.json') ? [] : [
-			`${pathValue}.json`,
-			`${pathValue}/tsconfig.json`,
-		]
-	]);
+    async function resolve(absolutePath: vscode.Uri): Promise {
+        if (absolutePath.path.endsWith('.json') || await exists(absolutePath)) {
+            return absolutePath;
+        }
+        return absolutePath.with({
+            path: `${absolutePath.path}${linkType === TsConfigLinkType.References ? '/tsconfig.json' : '.json'}`
+        });
+    }
+    const isRelativePath = ['./', '../'].some(str => pathValue.startsWith(str));
+    if (isRelativePath) {
+        return resolve(vscode.Uri.joinPath(baseDirUri, pathValue));
+    }
+    if (pathValue.startsWith('/') || looksLikeAbsoluteWindowsPath(pathValue)) {
+        return resolve(vscode.Uri.file(pathValue));
+    }
+    // Otherwise resolve like a module
+    return resolveNodeModulesPath(baseDirUri, [
+        pathValue,
+        ...pathValue.endsWith('.json') ? [] : [
+            `${pathValue}.json`,
+            `${pathValue}/tsconfig.json`,
+        ]
+    ]);
 }
-
 export function register() {
-	const patterns: vscode.GlobPattern[] = [
-		'**/[jt]sconfig.json',
-		'**/[jt]sconfig.*.json',
-	];
-
-	const languages = ['json', 'jsonc'];
-
-	const selector: vscode.DocumentSelector =
-		languages.map(language => patterns.map((pattern): vscode.DocumentFilter => ({ language, pattern })))
-			.flat();
-
-	return vscode.Disposable.from(
-		vscode.commands.registerCommand(openExtendsLinkCommandId, async ({ resourceUri, extendsValue, linkType }: OpenExtendsLinkCommandArgs) => {
-			const tsconfigPath = await getTsconfigPath(Utils.dirname(vscode.Uri.from(resourceUri)), extendsValue, linkType);
-			if (tsconfigPath === undefined) {
-				vscode.window.showErrorMessage(vscode.l10n.t("Failed to resolve {0} as module", extendsValue));
-				return;
-			}
-			// Will suggest to create a .json variant if it doesn't exist yet (but only for relative paths)
-			await vscode.commands.executeCommand('vscode.open', tsconfigPath);
-		}),
-		vscode.languages.registerDocumentLinkProvider(selector, new TsconfigLinkProvider()),
-	);
+    const patterns: vscode.GlobPattern[] = [
+        '**/[jt]sconfig.json',
+        '**/[jt]sconfig.*.json',
+    ];
+    const languages = ['json', 'jsonc'];
+    const selector: vscode.DocumentSelector = languages.map(language => patterns.map((pattern): vscode.DocumentFilter => ({ language, pattern })))
+        .flat();
+    return vscode.Disposable.from(vscode.commands.registerCommand(openExtendsLinkCommandId, async ({ resourceUri, extendsValue, linkType }: OpenExtendsLinkCommandArgs) => {
+        const tsconfigPath = await getTsconfigPath(Utils.dirname(vscode.Uri.from(resourceUri)), extendsValue, linkType);
+        if (tsconfigPath === undefined) {
+            vscode.window.showErrorMessage(vscode.l10n.t("Failed to resolve {0} as module", extendsValue));
+            return;
+        }
+        // Will suggest to create a .json variant if it doesn't exist yet (but only for relative paths)
+        await vscode.commands.executeCommand('vscode.open', tsconfigPath);
+    }), vscode.languages.registerDocumentLinkProvider(selector, new TsconfigLinkProvider()));
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/typeDefinitions.ts b/extensions/typescript-language-features/Source/languageFeatures/typeDefinitions.ts
index e65647490063b..24f652123c4fc 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/typeDefinitions.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/typeDefinitions.ts
@@ -2,27 +2,20 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { DocumentSelector } from '../configuration/documentSelector';
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import DefinitionProviderBase from './definitionProviderBase';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
 export default class TypeScriptTypeDefinitionProvider extends DefinitionProviderBase implements vscode.TypeDefinitionProvider {
-	public provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
-		return this.getSymbolLocations('typeDefinition', document, position, token);
-	}
+    public provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise {
+        return this.getSymbolLocations('typeDefinition', document, position, token);
+    }
 }
-
-export function register(
-	selector: DocumentSelector,
-	client: ITypeScriptServiceClient,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
-	], () => {
-		return vscode.languages.registerTypeDefinitionProvider(selector.syntax,
-			new TypeScriptTypeDefinitionProvider(client));
-	});
+export function register(selector: DocumentSelector, client: ITypeScriptServiceClient) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.EnhancedSyntax, ClientCapability.Semantic),
+    ], () => {
+        return vscode.languages.registerTypeDefinitionProvider(selector.syntax, new TypeScriptTypeDefinitionProvider(client));
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/updatePathsOnRename.ts b/extensions/typescript-language-features/Source/languageFeatures/updatePathsOnRename.ts
index bdaa1fc69ff79..a337e6806a737 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/updatePathsOnRename.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/updatePathsOnRename.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as vscode from 'vscode';
 import * as fileSchemes from '../configuration/fileSchemes';
@@ -15,280 +14,216 @@ import { nulToken } from '../utils/cancellation';
 import { Disposable } from '../utils/dispose';
 import FileConfigurationManager from './fileConfigurationManager';
 import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration';
-
-
 const updateImportsOnFileMoveName = 'updateImportsOnFileMove.enabled';
-
 async function isDirectory(resource: vscode.Uri): Promise {
-	try {
-		return (await vscode.workspace.fs.stat(resource)).type === vscode.FileType.Directory;
-	} catch {
-		return false;
-	}
+    try {
+        return (await vscode.workspace.fs.stat(resource)).type === vscode.FileType.Directory;
+    }
+    catch {
+        return false;
+    }
 }
-
 const enum UpdateImportsOnFileMoveSetting {
-	Prompt = 'prompt',
-	Always = 'always',
-	Never = 'never',
+    Prompt = 'prompt',
+    Always = 'always',
+    Never = 'never'
 }
-
 interface RenameAction {
-	readonly oldUri: vscode.Uri;
-	readonly newUri: vscode.Uri;
-	readonly newFilePath: string;
-	readonly oldFilePath: string;
-	readonly jsTsFileThatIsBeingMoved: vscode.Uri;
+    readonly oldUri: vscode.Uri;
+    readonly newUri: vscode.Uri;
+    readonly newFilePath: string;
+    readonly oldFilePath: string;
+    readonly jsTsFileThatIsBeingMoved: vscode.Uri;
 }
-
 class UpdateImportsOnFileRenameHandler extends Disposable {
-
-	private readonly _delayer = new Delayer(50);
-	private readonly _pendingRenames = new Set();
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-		private readonly _handles: (uri: vscode.Uri) => Promise,
-	) {
-		super();
-
-		this._register(vscode.workspace.onDidRenameFiles(async (e) => {
-			for (const { newUri, oldUri } of e.files) {
-				const newFilePath = this.client.toTsFilePath(newUri);
-				if (!newFilePath) {
-					continue;
-				}
-
-				const oldFilePath = this.client.toTsFilePath(oldUri);
-				if (!oldFilePath) {
-					continue;
-				}
-
-				const config = this.getConfiguration(newUri);
-				const setting = config.get(updateImportsOnFileMoveName);
-				if (setting === UpdateImportsOnFileMoveSetting.Never) {
-					continue;
-				}
-
-				// Try to get a js/ts file that is being moved
-				// For directory moves, this returns a js/ts file under the directory.
-				const jsTsFileThatIsBeingMoved = await this.getJsTsFileBeingMoved(newUri);
-				if (!jsTsFileThatIsBeingMoved || !this.client.toTsFilePath(jsTsFileThatIsBeingMoved)) {
-					continue;
-				}
-
-				this._pendingRenames.add({ oldUri, newUri, newFilePath, oldFilePath, jsTsFileThatIsBeingMoved });
-
-				this._delayer.trigger(() => {
-					vscode.window.withProgress({
-						location: vscode.ProgressLocation.Window,
-						title: vscode.l10n.t("Checking for update of JS/TS imports")
-					}, () => this.flushRenames());
-				});
-			}
-		}));
-	}
-
-	private async flushRenames(): Promise {
-		const renames = Array.from(this._pendingRenames);
-		this._pendingRenames.clear();
-		for (const group of this.groupRenames(renames)) {
-			const edits = new vscode.WorkspaceEdit();
-			const resourcesBeingRenamed: vscode.Uri[] = [];
-
-			for (const { oldUri, newUri, newFilePath, oldFilePath, jsTsFileThatIsBeingMoved } of group) {
-				const document = await vscode.workspace.openTextDocument(jsTsFileThatIsBeingMoved);
-
-				// Make sure TS knows about file
-				this.client.bufferSyncSupport.closeResource(oldUri);
-				this.client.bufferSyncSupport.openTextDocument(document);
-
-				if (await this.withEditsForFileRename(edits, document, oldFilePath, newFilePath)) {
-					resourcesBeingRenamed.push(newUri);
-				}
-			}
-
-			if (edits.size) {
-				if (await this.confirmActionWithUser(resourcesBeingRenamed)) {
-					await vscode.workspace.applyEdit(edits, { isRefactoring: true });
-				}
-			}
-		}
-	}
-
-	private async confirmActionWithUser(newResources: readonly vscode.Uri[]): Promise {
-		if (!newResources.length) {
-			return false;
-		}
-
-		const config = this.getConfiguration(newResources[0]);
-		const setting = config.get(updateImportsOnFileMoveName);
-		switch (setting) {
-			case UpdateImportsOnFileMoveSetting.Always:
-				return true;
-			case UpdateImportsOnFileMoveSetting.Never:
-				return false;
-			case UpdateImportsOnFileMoveSetting.Prompt:
-			default:
-				return this.promptUser(newResources);
-		}
-	}
-
-	private getConfiguration(resource: vscode.Uri) {
-		return vscode.workspace.getConfiguration(doesResourceLookLikeATypeScriptFile(resource) ? 'typescript' : 'javascript', resource);
-	}
-
-	private async promptUser(newResources: readonly vscode.Uri[]): Promise {
-		if (!newResources.length) {
-			return false;
-		}
-
-		const rejectItem: vscode.MessageItem = {
-			title: vscode.l10n.t("No"),
-			isCloseAffordance: true,
-		};
-
-		const acceptItem: vscode.MessageItem = {
-			title: vscode.l10n.t("Yes"),
-		};
-
-		const alwaysItem: vscode.MessageItem = {
-			title: vscode.l10n.t("Always"),
-		};
-
-		const neverItem: vscode.MessageItem = {
-			title: vscode.l10n.t("Never"),
-		};
-
-		const response = await vscode.window.showInformationMessage(
-			newResources.length === 1
-				? vscode.l10n.t("Update imports for '{0}'?", path.basename(newResources[0].fsPath))
-				: this.getConfirmMessage(vscode.l10n.t("Update imports for the following {0} files?", newResources.length), newResources), {
-			modal: true,
-		}, rejectItem, acceptItem, alwaysItem, neverItem);
-
-
-		switch (response) {
-			case acceptItem: {
-				return true;
-			}
-			case rejectItem: {
-				return false;
-			}
-			case alwaysItem: {
-				const config = this.getConfiguration(newResources[0]);
-				config.update(
-					updateImportsOnFileMoveName,
-					UpdateImportsOnFileMoveSetting.Always,
-					this.getConfigTargetScope(config, updateImportsOnFileMoveName));
-				return true;
-			}
-			case neverItem: {
-				const config = this.getConfiguration(newResources[0]);
-				config.update(
-					updateImportsOnFileMoveName,
-					UpdateImportsOnFileMoveSetting.Never,
-					this.getConfigTargetScope(config, updateImportsOnFileMoveName));
-				return false;
-			}
-			default: {
-				return false;
-			}
-		}
-	}
-
-	private async getJsTsFileBeingMoved(resource: vscode.Uri): Promise {
-		if (resource.scheme !== fileSchemes.file) {
-			return undefined;
-		}
-
-		if (await isDirectory(resource)) {
-			const files = await vscode.workspace.findFiles(new vscode.RelativePattern(resource, '**/*.{ts,tsx,js,jsx}'), '**/node_modules/**', 1);
-			return files[0];
-		}
-
-		return (await this._handles(resource)) ? resource : undefined;
-	}
-
-	private async withEditsForFileRename(
-		edits: vscode.WorkspaceEdit,
-		document: vscode.TextDocument,
-		oldFilePath: string,
-		newFilePath: string,
-	): Promise {
-		const response = await this.client.interruptGetErr(() => {
-			this.fileConfigurationManager.setGlobalConfigurationFromDocument(document, nulToken);
-			const args: Proto.GetEditsForFileRenameRequestArgs = {
-				oldFilePath,
-				newFilePath,
-			};
-			return this.client.execute('getEditsForFileRename', args, nulToken);
-		});
-		if (response.type !== 'response' || !response.body.length) {
-			return false;
-		}
-
-		typeConverters.WorkspaceEdit.withFileCodeEdits(edits, this.client, response.body);
-		return true;
-	}
-
-	private groupRenames(renames: Iterable): Iterable> {
-		const groups = new Map>();
-
-		for (const rename of renames) {
-			// Group renames by type (js/ts) and by workspace.
-			const key = `${this.client.getWorkspaceRootForResource(rename.jsTsFileThatIsBeingMoved)?.fsPath}@@@${doesResourceLookLikeATypeScriptFile(rename.jsTsFileThatIsBeingMoved)}`;
-			if (!groups.has(key)) {
-				groups.set(key, new Set());
-			}
-			groups.get(key)!.add(rename);
-		}
-
-		return groups.values();
-	}
-
-	private getConfirmMessage(start: string, resourcesToConfirm: readonly vscode.Uri[]): string {
-		const MAX_CONFIRM_FILES = 10;
-
-		const paths = [start];
-		paths.push('');
-		paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => path.basename(r.fsPath)));
-
-		if (resourcesToConfirm.length > MAX_CONFIRM_FILES) {
-			if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) {
-				paths.push(vscode.l10n.t("...1 additional file not shown"));
-			} else {
-				paths.push(vscode.l10n.t("...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES));
-			}
-		}
-
-		paths.push('');
-		return paths.join('\n');
-	}
-
-	private getConfigTargetScope(config: vscode.WorkspaceConfiguration, settingsName: string): vscode.ConfigurationTarget {
-		const inspected = config.inspect(settingsName);
-		if (inspected?.workspaceFolderValue) {
-			return vscode.ConfigurationTarget.WorkspaceFolder;
-		}
-
-		if (inspected?.workspaceValue) {
-			return vscode.ConfigurationTarget.Workspace;
-		}
-
-		return vscode.ConfigurationTarget.Global;
-	}
+    private readonly _delayer = new Delayer(50);
+    private readonly _pendingRenames = new Set();
+    public constructor(private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager, private readonly _handles: (uri: vscode.Uri) => Promise) {
+        super();
+        this._register(vscode.workspace.onDidRenameFiles(async (e) => {
+            for (const { newUri, oldUri } of e.files) {
+                const newFilePath = this.client.toTsFilePath(newUri);
+                if (!newFilePath) {
+                    continue;
+                }
+                const oldFilePath = this.client.toTsFilePath(oldUri);
+                if (!oldFilePath) {
+                    continue;
+                }
+                const config = this.getConfiguration(newUri);
+                const setting = config.get(updateImportsOnFileMoveName);
+                if (setting === UpdateImportsOnFileMoveSetting.Never) {
+                    continue;
+                }
+                // Try to get a js/ts file that is being moved
+                // For directory moves, this returns a js/ts file under the directory.
+                const jsTsFileThatIsBeingMoved = await this.getJsTsFileBeingMoved(newUri);
+                if (!jsTsFileThatIsBeingMoved || !this.client.toTsFilePath(jsTsFileThatIsBeingMoved)) {
+                    continue;
+                }
+                this._pendingRenames.add({ oldUri, newUri, newFilePath, oldFilePath, jsTsFileThatIsBeingMoved });
+                this._delayer.trigger(() => {
+                    vscode.window.withProgress({
+                        location: vscode.ProgressLocation.Window,
+                        title: vscode.l10n.t("Checking for update of JS/TS imports")
+                    }, () => this.flushRenames());
+                });
+            }
+        }));
+    }
+    private async flushRenames(): Promise {
+        const renames = Array.from(this._pendingRenames);
+        this._pendingRenames.clear();
+        for (const group of this.groupRenames(renames)) {
+            const edits = new vscode.WorkspaceEdit();
+            const resourcesBeingRenamed: vscode.Uri[] = [];
+            for (const { oldUri, newUri, newFilePath, oldFilePath, jsTsFileThatIsBeingMoved } of group) {
+                const document = await vscode.workspace.openTextDocument(jsTsFileThatIsBeingMoved);
+                // Make sure TS knows about file
+                this.client.bufferSyncSupport.closeResource(oldUri);
+                this.client.bufferSyncSupport.openTextDocument(document);
+                if (await this.withEditsForFileRename(edits, document, oldFilePath, newFilePath)) {
+                    resourcesBeingRenamed.push(newUri);
+                }
+            }
+            if (edits.size) {
+                if (await this.confirmActionWithUser(resourcesBeingRenamed)) {
+                    await vscode.workspace.applyEdit(edits, { isRefactoring: true });
+                }
+            }
+        }
+    }
+    private async confirmActionWithUser(newResources: readonly vscode.Uri[]): Promise {
+        if (!newResources.length) {
+            return false;
+        }
+        const config = this.getConfiguration(newResources[0]);
+        const setting = config.get(updateImportsOnFileMoveName);
+        switch (setting) {
+            case UpdateImportsOnFileMoveSetting.Always:
+                return true;
+            case UpdateImportsOnFileMoveSetting.Never:
+                return false;
+            case UpdateImportsOnFileMoveSetting.Prompt:
+            default:
+                return this.promptUser(newResources);
+        }
+    }
+    private getConfiguration(resource: vscode.Uri) {
+        return vscode.workspace.getConfiguration(doesResourceLookLikeATypeScriptFile(resource) ? 'typescript' : 'javascript', resource);
+    }
+    private async promptUser(newResources: readonly vscode.Uri[]): Promise {
+        if (!newResources.length) {
+            return false;
+        }
+        const rejectItem: vscode.MessageItem = {
+            title: vscode.l10n.t("No"),
+            isCloseAffordance: true,
+        };
+        const acceptItem: vscode.MessageItem = {
+            title: vscode.l10n.t("Yes"),
+        };
+        const alwaysItem: vscode.MessageItem = {
+            title: vscode.l10n.t("Always"),
+        };
+        const neverItem: vscode.MessageItem = {
+            title: vscode.l10n.t("Never"),
+        };
+        const response = await vscode.window.showInformationMessage(newResources.length === 1
+            ? vscode.l10n.t("Update imports for '{0}'?", path.basename(newResources[0].fsPath))
+            : this.getConfirmMessage(vscode.l10n.t("Update imports for the following {0} files?", newResources.length), newResources), {
+            modal: true,
+        }, rejectItem, acceptItem, alwaysItem, neverItem);
+        switch (response) {
+            case acceptItem: {
+                return true;
+            }
+            case rejectItem: {
+                return false;
+            }
+            case alwaysItem: {
+                const config = this.getConfiguration(newResources[0]);
+                config.update(updateImportsOnFileMoveName, UpdateImportsOnFileMoveSetting.Always, this.getConfigTargetScope(config, updateImportsOnFileMoveName));
+                return true;
+            }
+            case neverItem: {
+                const config = this.getConfiguration(newResources[0]);
+                config.update(updateImportsOnFileMoveName, UpdateImportsOnFileMoveSetting.Never, this.getConfigTargetScope(config, updateImportsOnFileMoveName));
+                return false;
+            }
+            default: {
+                return false;
+            }
+        }
+    }
+    private async getJsTsFileBeingMoved(resource: vscode.Uri): Promise {
+        if (resource.scheme !== fileSchemes.file) {
+            return undefined;
+        }
+        if (await isDirectory(resource)) {
+            const files = await vscode.workspace.findFiles(new vscode.RelativePattern(resource, '**/*.{ts,tsx,js,jsx}'), '**/node_modules/**', 1);
+            return files[0];
+        }
+        return (await this._handles(resource)) ? resource : undefined;
+    }
+    private async withEditsForFileRename(edits: vscode.WorkspaceEdit, document: vscode.TextDocument, oldFilePath: string, newFilePath: string): Promise {
+        const response = await this.client.interruptGetErr(() => {
+            this.fileConfigurationManager.setGlobalConfigurationFromDocument(document, nulToken);
+            const args: Proto.GetEditsForFileRenameRequestArgs = {
+                oldFilePath,
+                newFilePath,
+            };
+            return this.client.execute('getEditsForFileRename', args, nulToken);
+        });
+        if (response.type !== 'response' || !response.body.length) {
+            return false;
+        }
+        typeConverters.WorkspaceEdit.withFileCodeEdits(edits, this.client, response.body);
+        return true;
+    }
+    private groupRenames(renames: Iterable): Iterable> {
+        const groups = new Map>();
+        for (const rename of renames) {
+            // Group renames by type (js/ts) and by workspace.
+            const key = `${this.client.getWorkspaceRootForResource(rename.jsTsFileThatIsBeingMoved)?.fsPath}@@@${doesResourceLookLikeATypeScriptFile(rename.jsTsFileThatIsBeingMoved)}`;
+            if (!groups.has(key)) {
+                groups.set(key, new Set());
+            }
+            groups.get(key)!.add(rename);
+        }
+        return groups.values();
+    }
+    private getConfirmMessage(start: string, resourcesToConfirm: readonly vscode.Uri[]): string {
+        const MAX_CONFIRM_FILES = 10;
+        const paths = [start];
+        paths.push('');
+        paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => path.basename(r.fsPath)));
+        if (resourcesToConfirm.length > MAX_CONFIRM_FILES) {
+            if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) {
+                paths.push(vscode.l10n.t("...1 additional file not shown"));
+            }
+            else {
+                paths.push(vscode.l10n.t("...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES));
+            }
+        }
+        paths.push('');
+        return paths.join('\n');
+    }
+    private getConfigTargetScope(config: vscode.WorkspaceConfiguration, settingsName: string): vscode.ConfigurationTarget {
+        const inspected = config.inspect(settingsName);
+        if (inspected?.workspaceFolderValue) {
+            return vscode.ConfigurationTarget.WorkspaceFolder;
+        }
+        if (inspected?.workspaceValue) {
+            return vscode.ConfigurationTarget.Workspace;
+        }
+        return vscode.ConfigurationTarget.Global;
+    }
 }
-
-export function register(
-	client: ITypeScriptServiceClient,
-	fileConfigurationManager: FileConfigurationManager,
-	handles: (uri: vscode.Uri) => Promise,
-) {
-	return conditionalRegistration([
-		requireSomeCapability(client, ClientCapability.Semantic),
-	], () => {
-		return new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles);
-	});
+export function register(client: ITypeScriptServiceClient, fileConfigurationManager: FileConfigurationManager, handles: (uri: vscode.Uri) => Promise) {
+    return conditionalRegistration([
+        requireSomeCapability(client, ClientCapability.Semantic),
+    ], () => {
+        return new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles);
+    });
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/util/codeAction.ts b/extensions/typescript-language-features/Source/languageFeatures/util/codeAction.ts
index c2197d593db06..4145c8b42bf68 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/util/codeAction.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/util/codeAction.ts
@@ -2,44 +2,29 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import type * as Proto from '../../tsServer/protocol/protocol';
 import * as typeConverters from '../../typeConverters';
 import { ITypeScriptServiceClient } from '../../typescriptService';
-
-export function getEditForCodeAction(
-	client: ITypeScriptServiceClient,
-	action: Proto.CodeAction
-): vscode.WorkspaceEdit | undefined {
-	return action.changes?.length
-		? typeConverters.WorkspaceEdit.fromFileCodeEdits(client, action.changes)
-		: undefined;
+export function getEditForCodeAction(client: ITypeScriptServiceClient, action: Proto.CodeAction): vscode.WorkspaceEdit | undefined {
+    return action.changes?.length
+        ? typeConverters.WorkspaceEdit.fromFileCodeEdits(client, action.changes)
+        : undefined;
 }
-
-export async function applyCodeAction(
-	client: ITypeScriptServiceClient,
-	action: Proto.CodeAction,
-	token: vscode.CancellationToken
-): Promise {
-	const workspaceEdit = getEditForCodeAction(client, action);
-	if (workspaceEdit) {
-		if (!(await vscode.workspace.applyEdit(workspaceEdit))) {
-			return false;
-		}
-	}
-	return applyCodeActionCommands(client, action.commands, token);
+export async function applyCodeAction(client: ITypeScriptServiceClient, action: Proto.CodeAction, token: vscode.CancellationToken): Promise {
+    const workspaceEdit = getEditForCodeAction(client, action);
+    if (workspaceEdit) {
+        if (!(await vscode.workspace.applyEdit(workspaceEdit))) {
+            return false;
+        }
+    }
+    return applyCodeActionCommands(client, action.commands, token);
 }
-
-export async function applyCodeActionCommands(
-	client: ITypeScriptServiceClient,
-	commands: ReadonlyArray<{}> | undefined,
-	token: vscode.CancellationToken,
-): Promise {
-	if (commands?.length) {
-		for (const command of commands) {
-			await client.execute('applyCodeActionCommand', { command }, token);
-		}
-	}
-	return true;
+export async function applyCodeActionCommands(client: ITypeScriptServiceClient, commands: ReadonlyArray<{}> | undefined, token: vscode.CancellationToken): Promise {
+    if (commands?.length) {
+        for (const command of commands) {
+            await client.execute('applyCodeActionCommand', { command }, token);
+        }
+    }
+    return true;
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/util/copilot.ts b/extensions/typescript-language-features/Source/languageFeatures/util/copilot.ts
index 416ed00bedb5d..8fdd541ac2b49 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/util/copilot.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/util/copilot.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Command } from '../../commands/commandManager';
 import { nulToken } from '../../utils/cancellation';
@@ -10,179 +9,138 @@ import type * as Proto from '../../tsServer/protocol/protocol';
 import * as typeConverters from '../../typeConverters';
 import { ITypeScriptServiceClient } from '../../typescriptService';
 import { TelemetryReporter } from '../../logging/telemetry';
-
 export class EditorChatFollowUp implements Command {
-	public static readonly ID = '_typescript.quickFix.editorChatReplacement2';
-	public readonly id = EditorChatFollowUp.ID;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly telemetryReporter: TelemetryReporter,
-	) { }
-
-	async execute({ message, document, expand, action }: EditorChatFollowUp_Args) {
-		if (action.type === 'quickfix') {
-			/* __GDPR__
-				"aiQuickfix.execute" : {
-					"owner": "mjbvz",
-					"action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-					"${include}": [
-						"${TypeScriptCommonProperties}"
-					]
-				}
-			*/
-			this.telemetryReporter.logTelemetry('aiQuickfix.execute', {
-				action: action.quickfix.fixName,
-			});
-		} else {
-			/* __GDPR__
-				"aiRefactor.execute" : {
-					"owner": "mjbvz",
-					"action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-					"${include}": [
-						"${TypeScriptCommonProperties}"
-					]
-				}
-			*/
-			this.telemetryReporter.logTelemetry('aiRefactor.execute', {
-				action: action.refactor.name,
-			});
-		}
-
-		const initialRange =
-			expand.kind === 'navtree-function'
-				? await findScopeEndLineFromNavTree(
-					this.client,
-					document,
-					expand.pos.line
-				)
-				: expand.kind === 'refactor-info'
-					? await findEditScope(
-						this.client,
-						document,
-						expand.refactor.edits.flatMap((e) => e.textChanges)
-					)
-					: expand.kind === 'code-action'
-						? await findEditScope(
-							this.client,
-							document,
-							expand.action.changes.flatMap((c) => c.textChanges)
-						)
-						: expand.range;
-		const initialSelection = initialRange ? new vscode.Selection(initialRange.start, initialRange.end) : undefined;
-		await vscode.commands.executeCommand('vscode.editorChat.start', {
-			initialRange,
-			initialSelection,
-			message,
-			autoSend: true,
-		});
-	}
+    public static readonly ID = '_typescript.quickFix.editorChatReplacement2';
+    public readonly id = EditorChatFollowUp.ID;
+    constructor(private readonly client: ITypeScriptServiceClient, private readonly telemetryReporter: TelemetryReporter) { }
+    async execute({ message, document, expand, action }: EditorChatFollowUp_Args) {
+        if (action.type === 'quickfix') {
+            /* __GDPR__
+                "aiQuickfix.execute" : {
+                    "owner": "mjbvz",
+                    "action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                    "${include}": [
+                        "${TypeScriptCommonProperties}"
+                    ]
+                }
+            */
+            this.telemetryReporter.logTelemetry('aiQuickfix.execute', {
+                action: action.quickfix.fixName,
+            });
+        }
+        else {
+            /* __GDPR__
+                "aiRefactor.execute" : {
+                    "owner": "mjbvz",
+                    "action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                    "${include}": [
+                        "${TypeScriptCommonProperties}"
+                    ]
+                }
+            */
+            this.telemetryReporter.logTelemetry('aiRefactor.execute', {
+                action: action.refactor.name,
+            });
+        }
+        const initialRange = expand.kind === 'navtree-function'
+            ? await findScopeEndLineFromNavTree(this.client, document, expand.pos.line)
+            : expand.kind === 'refactor-info'
+                ? await findEditScope(this.client, document, expand.refactor.edits.flatMap((e) => e.textChanges))
+                : expand.kind === 'code-action'
+                    ? await findEditScope(this.client, document, expand.action.changes.flatMap((c) => c.textChanges))
+                    : expand.range;
+        const initialSelection = initialRange ? new vscode.Selection(initialRange.start, initialRange.end) : undefined;
+        await vscode.commands.executeCommand('vscode.editorChat.start', {
+            initialRange,
+            initialSelection,
+            message,
+            autoSend: true,
+        });
+    }
 }
 export interface EditorChatFollowUp_Args {
-	readonly message: string;
-	readonly document: vscode.TextDocument;
-	readonly expand: Expand;
-	readonly action: {
-		readonly type: 'refactor';
-		readonly refactor: Proto.RefactorActionInfo;
-	} | {
-		readonly type: 'quickfix';
-		readonly quickfix: Proto.CodeFixAction;
-	};
+    readonly message: string;
+    readonly document: vscode.TextDocument;
+    readonly expand: Expand;
+    readonly action: {
+        readonly type: 'refactor';
+        readonly refactor: Proto.RefactorActionInfo;
+    } | {
+        readonly type: 'quickfix';
+        readonly quickfix: Proto.CodeFixAction;
+    };
 }
-
 export class CompositeCommand implements Command {
-	public static readonly ID = '_typescript.compositeCommand';
-	public readonly id = CompositeCommand.ID;
-
-	public async execute(...commands: vscode.Command[]): Promise {
-		for (const command of commands) {
-			await vscode.commands.executeCommand(
-				command.command,
-				...(command.arguments ?? [])
-			);
-		}
-	}
+    public static readonly ID = '_typescript.compositeCommand';
+    public readonly id = CompositeCommand.ID;
+    public async execute(...commands: vscode.Command[]): Promise {
+        for (const command of commands) {
+            await vscode.commands.executeCommand(command.command, ...(command.arguments ?? []));
+        }
+    }
 }
-
-export type Expand =
-	| { kind: 'none'; readonly range: vscode.Range }
-	| { kind: 'navtree-function'; readonly pos: vscode.Position }
-	| { kind: 'refactor-info'; readonly refactor: Proto.RefactorEditInfo }
-	| { kind: 'code-action'; readonly action: Proto.CodeAction };
-
-function findScopeEndLineFromNavTreeWorker(
-	startLine: number,
-	navigationTree: Proto.NavigationTree[]
-): vscode.Range | undefined {
-	for (const node of navigationTree) {
-		const range = typeConverters.Range.fromTextSpan(node.spans[0]);
-		if (startLine === range.start.line) {
-			return range;
-		} else if (
-			startLine > range.start.line &&
-			startLine <= range.end.line &&
-			node.childItems
-		) {
-			return findScopeEndLineFromNavTreeWorker(startLine, node.childItems);
-		}
-	}
-	return undefined;
+export type Expand = {
+    kind: 'none';
+    readonly range: vscode.Range;
+} | {
+    kind: 'navtree-function';
+    readonly pos: vscode.Position;
+} | {
+    kind: 'refactor-info';
+    readonly refactor: Proto.RefactorEditInfo;
+} | {
+    kind: 'code-action';
+    readonly action: Proto.CodeAction;
+};
+function findScopeEndLineFromNavTreeWorker(startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined {
+    for (const node of navigationTree) {
+        const range = typeConverters.Range.fromTextSpan(node.spans[0]);
+        if (startLine === range.start.line) {
+            return range;
+        }
+        else if (startLine > range.start.line &&
+            startLine <= range.end.line &&
+            node.childItems) {
+            return findScopeEndLineFromNavTreeWorker(startLine, node.childItems);
+        }
+    }
+    return undefined;
 }
-
-async function findScopeEndLineFromNavTree(
-	client: ITypeScriptServiceClient,
-	document: vscode.TextDocument,
-	startLine: number
-) {
-	const filepath = client.toOpenTsFilePath(document);
-	if (!filepath) {
-		return;
-	}
-	const response = await client.execute(
-		'navtree',
-		{ file: filepath },
-		nulToken
-	);
-	if (response.type !== 'response' || !response.body?.childItems) {
-		return;
-	}
-	return findScopeEndLineFromNavTreeWorker(startLine, response.body.childItems);
+async function findScopeEndLineFromNavTree(client: ITypeScriptServiceClient, document: vscode.TextDocument, startLine: number) {
+    const filepath = client.toOpenTsFilePath(document);
+    if (!filepath) {
+        return;
+    }
+    const response = await client.execute('navtree', { file: filepath }, nulToken);
+    if (response.type !== 'response' || !response.body?.childItems) {
+        return;
+    }
+    return findScopeEndLineFromNavTreeWorker(startLine, response.body.childItems);
 }
-
-async function findEditScope(
-	client: ITypeScriptServiceClient,
-	document: vscode.TextDocument,
-	edits: Proto.CodeEdit[]
-): Promise {
-	let first = typeConverters.Position.fromLocation(edits[0].start);
-	let firstEdit = edits[0];
-	let lastEdit = edits[0];
-	let last = typeConverters.Position.fromLocation(edits[0].start);
-	for (const edit of edits) {
-		const start = typeConverters.Position.fromLocation(edit.start);
-		const end = typeConverters.Position.fromLocation(edit.end);
-		if (start.compareTo(first) < 0) {
-			first = start;
-			firstEdit = edit;
-		}
-		if (end.compareTo(last) > 0) {
-			last = end;
-			lastEdit = edit;
-		}
-	}
-	const text = document.getText();
-	const startIndex = text.indexOf(firstEdit.newText);
-	const start = startIndex > -1 ? document.positionAt(startIndex) : first;
-	const endIndex = text.lastIndexOf(lastEdit.newText);
-	const end =
-		endIndex > -1
-			? document.positionAt(endIndex + lastEdit.newText.length)
-			: last;
-	const expandEnd = await findScopeEndLineFromNavTree(
-		client,
-		document,
-		end.line
-	);
-	return new vscode.Range(start, expandEnd?.end ?? end);
+async function findEditScope(client: ITypeScriptServiceClient, document: vscode.TextDocument, edits: Proto.CodeEdit[]): Promise {
+    let first = typeConverters.Position.fromLocation(edits[0].start);
+    let firstEdit = edits[0];
+    let lastEdit = edits[0];
+    let last = typeConverters.Position.fromLocation(edits[0].start);
+    for (const edit of edits) {
+        const start = typeConverters.Position.fromLocation(edit.start);
+        const end = typeConverters.Position.fromLocation(edit.end);
+        if (start.compareTo(first) < 0) {
+            first = start;
+            firstEdit = edit;
+        }
+        if (end.compareTo(last) > 0) {
+            last = end;
+            lastEdit = edit;
+        }
+    }
+    const text = document.getText();
+    const startIndex = text.indexOf(firstEdit.newText);
+    const start = startIndex > -1 ? document.positionAt(startIndex) : first;
+    const endIndex = text.lastIndexOf(lastEdit.newText);
+    const end = endIndex > -1
+        ? document.positionAt(endIndex + lastEdit.newText.length)
+        : last;
+    const expandEnd = await findScopeEndLineFromNavTree(client, document, end.line);
+    return new vscode.Range(start, expandEnd?.end ?? end);
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/util/dependentRegistration.ts b/extensions/typescript-language-features/Source/languageFeatures/util/dependentRegistration.ts
index e234acd1ab405..66dc8d64b9780 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/util/dependentRegistration.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/util/dependentRegistration.ts
@@ -2,102 +2,62 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { API } from '../../tsServer/api';
 import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService';
 import { Disposable } from '../../utils/dispose';
-
 export class Condition extends Disposable {
-	private _value: boolean;
-
-	constructor(
-		private readonly getValue: () => boolean,
-		onUpdate: (handler: () => void) => void,
-	) {
-		super();
-		this._value = this.getValue();
-
-		onUpdate(() => {
-			const newValue = this.getValue();
-			if (newValue !== this._value) {
-				this._value = newValue;
-				this._onDidChange.fire();
-			}
-		});
-	}
-
-	public get value(): boolean { return this._value; }
-
-	private readonly _onDidChange = this._register(new vscode.EventEmitter());
-	public readonly onDidChange = this._onDidChange.event;
+    private _value: boolean;
+    constructor(private readonly getValue: () => boolean, onUpdate: (handler: () => void) => void) {
+        super();
+        this._value = this.getValue();
+        onUpdate(() => {
+            const newValue = this.getValue();
+            if (newValue !== this._value) {
+                this._value = newValue;
+                this._onDidChange.fire();
+            }
+        });
+    }
+    public get value(): boolean { return this._value; }
+    private readonly _onDidChange = this._register(new vscode.EventEmitter());
+    public readonly onDidChange = this._onDidChange.event;
 }
-
 class ConditionalRegistration {
-	private registration: vscode.Disposable | undefined = undefined;
-
-	public constructor(
-		private readonly conditions: readonly Condition[],
-		private readonly doRegister: () => vscode.Disposable
-	) {
-		for (const condition of conditions) {
-			condition.onDidChange(() => this.update());
-		}
-		this.update();
-	}
-
-	public dispose() {
-		this.registration?.dispose();
-		this.registration = undefined;
-	}
-
-	private update() {
-		const enabled = this.conditions.every(condition => condition.value);
-		if (enabled) {
-			this.registration ??= this.doRegister();
-		} else {
-			this.registration?.dispose();
-			this.registration = undefined;
-		}
-	}
+    private registration: vscode.Disposable | undefined = undefined;
+    public constructor(private readonly conditions: readonly Condition[], private readonly doRegister: () => vscode.Disposable) {
+        for (const condition of conditions) {
+            condition.onDidChange(() => this.update());
+        }
+        this.update();
+    }
+    public dispose() {
+        this.registration?.dispose();
+        this.registration = undefined;
+    }
+    private update() {
+        const enabled = this.conditions.every(condition => condition.value);
+        if (enabled) {
+            this.registration ??= this.doRegister();
+        }
+        else {
+            this.registration?.dispose();
+            this.registration = undefined;
+        }
+    }
 }
-
-export function conditionalRegistration(
-	conditions: readonly Condition[],
-	doRegister: () => vscode.Disposable,
-): vscode.Disposable {
-	return new ConditionalRegistration(conditions, doRegister);
+export function conditionalRegistration(conditions: readonly Condition[], doRegister: () => vscode.Disposable): vscode.Disposable {
+    return new ConditionalRegistration(conditions, doRegister);
 }
-
-export function requireMinVersion(
-	client: ITypeScriptServiceClient,
-	minVersion: API,
-) {
-	return new Condition(
-		() => client.apiVersion.gte(minVersion),
-		client.onTsServerStarted
-	);
+export function requireMinVersion(client: ITypeScriptServiceClient, minVersion: API) {
+    return new Condition(() => client.apiVersion.gte(minVersion), client.onTsServerStarted);
 }
-
-export function requireGlobalConfiguration(
-	section: string,
-	configValue: string,
-) {
-	return new Condition(
-		() => {
-			const config = vscode.workspace.getConfiguration(section, null);
-			return !!config.get(configValue);
-		},
-		vscode.workspace.onDidChangeConfiguration
-	);
+export function requireGlobalConfiguration(section: string, configValue: string) {
+    return new Condition(() => {
+        const config = vscode.workspace.getConfiguration(section, null);
+        return !!config.get(configValue);
+    }, vscode.workspace.onDidChangeConfiguration);
 }
-
-export function requireSomeCapability(
-	client: ITypeScriptServiceClient,
-	...capabilities: readonly ClientCapability[]
-) {
-	return new Condition(
-		() => capabilities.some(requiredCapability => client.capabilities.has(requiredCapability)),
-		client.onDidChangeCapabilities
-	);
+export function requireSomeCapability(client: ITypeScriptServiceClient, ...capabilities: readonly ClientCapability[]) {
+    return new Condition(() => capabilities.some(requiredCapability => client.capabilities.has(requiredCapability)), client.onDidChangeCapabilities);
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/util/snippetForFunctionCall.ts b/extensions/typescript-language-features/Source/languageFeatures/util/snippetForFunctionCall.ts
index a00d04e7a554c..107ef53c2a1a3 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/util/snippetForFunctionCall.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/util/snippetForFunctionCall.ts
@@ -2,118 +2,108 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import type * as Proto from '../../tsServer/protocol/protocol';
 import * as PConst from '../../tsServer/protocol/protocol.const';
-
-export function snippetForFunctionCall(
-	item: { insertText?: string | vscode.SnippetString; label: string },
-	displayParts: ReadonlyArray
-): { snippet: vscode.SnippetString; parameterCount: number } {
-	if (item.insertText && typeof item.insertText !== 'string') {
-		return { snippet: item.insertText, parameterCount: 0 };
-	}
-
-	const parameterListParts = getParameterListParts(displayParts);
-	const snippet = new vscode.SnippetString();
-	snippet.appendText(`${item.insertText || item.label}(`);
-	appendJoinedPlaceholders(snippet, parameterListParts.parts, ', ');
-	if (parameterListParts.hasOptionalParameters) {
-		snippet.appendTabstop();
-	}
-	snippet.appendText(')');
-	snippet.appendTabstop(0);
-	return { snippet, parameterCount: parameterListParts.parts.length + (parameterListParts.hasOptionalParameters ? 1 : 0) };
+export function snippetForFunctionCall(item: {
+    insertText?: string | vscode.SnippetString;
+    label: string;
+}, displayParts: ReadonlyArray): {
+    snippet: vscode.SnippetString;
+    parameterCount: number;
+} {
+    if (item.insertText && typeof item.insertText !== 'string') {
+        return { snippet: item.insertText, parameterCount: 0 };
+    }
+    const parameterListParts = getParameterListParts(displayParts);
+    const snippet = new vscode.SnippetString();
+    snippet.appendText(`${item.insertText || item.label}(`);
+    appendJoinedPlaceholders(snippet, parameterListParts.parts, ', ');
+    if (parameterListParts.hasOptionalParameters) {
+        snippet.appendTabstop();
+    }
+    snippet.appendText(')');
+    snippet.appendTabstop(0);
+    return { snippet, parameterCount: parameterListParts.parts.length + (parameterListParts.hasOptionalParameters ? 1 : 0) };
 }
-
-function appendJoinedPlaceholders(
-	snippet: vscode.SnippetString,
-	parts: ReadonlyArray,
-	joiner: string
-) {
-	for (let i = 0; i < parts.length; ++i) {
-		const paramterPart = parts[i];
-		snippet.appendPlaceholder(paramterPart.text);
-		if (i !== parts.length - 1) {
-			snippet.appendText(joiner);
-		}
-	}
+function appendJoinedPlaceholders(snippet: vscode.SnippetString, parts: ReadonlyArray, joiner: string) {
+    for (let i = 0; i < parts.length; ++i) {
+        const paramterPart = parts[i];
+        snippet.appendPlaceholder(paramterPart.text);
+        if (i !== parts.length - 1) {
+            snippet.appendText(joiner);
+        }
+    }
 }
-
 interface ParamterListParts {
-	readonly parts: ReadonlyArray;
-	readonly hasOptionalParameters: boolean;
+    readonly parts: ReadonlyArray;
+    readonly hasOptionalParameters: boolean;
 }
-
-function getParameterListParts(
-	displayParts: ReadonlyArray
-): ParamterListParts {
-	const parts: Proto.SymbolDisplayPart[] = [];
-	let optionalParams: Proto.SymbolDisplayPart[] = [];
-	let isInMethod = false;
-	let hasOptionalParameters = false;
-	let parenCount = 0;
-	let braceCount = 0;
-
-	outer: for (let i = 0; i < displayParts.length; ++i) {
-		const part = displayParts[i];
-		switch (part.kind) {
-			case PConst.DisplayPartKind.methodName:
-			case PConst.DisplayPartKind.functionName:
-			case PConst.DisplayPartKind.text:
-			case PConst.DisplayPartKind.propertyName:
-				if (parenCount === 0 && braceCount === 0) {
-					isInMethod = true;
-				}
-				break;
-
-			case PConst.DisplayPartKind.parameterName:
-				if (parenCount === 1 && braceCount === 0 && isInMethod) {
-					// Only take top level paren names
-					const next = displayParts[i + 1];
-					// Skip optional parameters
-					const nameIsFollowedByOptionalIndicator = next && next.text === '?';
-					// Skip this parameter
-					const nameIsThis = part.text === 'this';
-
-					/* Add optional param to temp array. Once a non-optional param is encountered,
-					this means that previous optional params were mid-list ones, thus they should
-					be displayed */
-					if (nameIsFollowedByOptionalIndicator) {
-						optionalParams.push(part);
-					} else {
-						parts.push(...optionalParams);
-						optionalParams = [];
-					}
-
-					if (!nameIsFollowedByOptionalIndicator && !nameIsThis) {
-						parts.push(part);
-					}
-					hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator;
-				}
-				break;
-
-			case PConst.DisplayPartKind.punctuation:
-				if (part.text === '(') {
-					++parenCount;
-				} else if (part.text === ')') {
-					--parenCount;
-					if (parenCount <= 0 && isInMethod) {
-						break outer;
-					}
-				} else if (part.text === '...' && parenCount === 1) {
-					// Found rest parmeter. Do not fill in any further arguments
-					hasOptionalParameters = true;
-					break outer;
-				} else if (part.text === '{') {
-					++braceCount;
-				} else if (part.text === '}') {
-					--braceCount;
-				}
-				break;
-		}
-	}
-
-	return { hasOptionalParameters, parts };
+function getParameterListParts(displayParts: ReadonlyArray): ParamterListParts {
+    const parts: Proto.SymbolDisplayPart[] = [];
+    let optionalParams: Proto.SymbolDisplayPart[] = [];
+    let isInMethod = false;
+    let hasOptionalParameters = false;
+    let parenCount = 0;
+    let braceCount = 0;
+    outer: for (let i = 0; i < displayParts.length; ++i) {
+        const part = displayParts[i];
+        switch (part.kind) {
+            case PConst.DisplayPartKind.methodName:
+            case PConst.DisplayPartKind.functionName:
+            case PConst.DisplayPartKind.text:
+            case PConst.DisplayPartKind.propertyName:
+                if (parenCount === 0 && braceCount === 0) {
+                    isInMethod = true;
+                }
+                break;
+            case PConst.DisplayPartKind.parameterName:
+                if (parenCount === 1 && braceCount === 0 && isInMethod) {
+                    // Only take top level paren names
+                    const next = displayParts[i + 1];
+                    // Skip optional parameters
+                    const nameIsFollowedByOptionalIndicator = next && next.text === '?';
+                    // Skip this parameter
+                    const nameIsThis = part.text === 'this';
+                    /* Add optional param to temp array. Once a non-optional param is encountered,
+                    this means that previous optional params were mid-list ones, thus they should
+                    be displayed */
+                    if (nameIsFollowedByOptionalIndicator) {
+                        optionalParams.push(part);
+                    }
+                    else {
+                        parts.push(...optionalParams);
+                        optionalParams = [];
+                    }
+                    if (!nameIsFollowedByOptionalIndicator && !nameIsThis) {
+                        parts.push(part);
+                    }
+                    hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator;
+                }
+                break;
+            case PConst.DisplayPartKind.punctuation:
+                if (part.text === '(') {
+                    ++parenCount;
+                }
+                else if (part.text === ')') {
+                    --parenCount;
+                    if (parenCount <= 0 && isInMethod) {
+                        break outer;
+                    }
+                }
+                else if (part.text === '...' && parenCount === 1) {
+                    // Found rest parmeter. Do not fill in any further arguments
+                    hasOptionalParameters = true;
+                    break outer;
+                }
+                else if (part.text === '{') {
+                    ++braceCount;
+                }
+                else if (part.text === '}') {
+                    --braceCount;
+                }
+                break;
+        }
+    }
+    return { hasOptionalParameters, parts };
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/util/textRendering.ts b/extensions/typescript-language-features/Source/languageFeatures/util/textRendering.ts
index af1a7e601b4e7..10efb25e0c052 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/util/textRendering.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/util/textRendering.ts
@@ -2,255 +2,213 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { OpenJsDocLinkCommand, OpenJsDocLinkCommand_Args } from '../../commands/openJsDocLink';
 import type * as Proto from '../../tsServer/protocol/protocol';
 import * as typeConverters from '../../typeConverters';
-
 export interface IFilePathToResourceConverter {
-	/**
-	 * Convert a typescript filepath to a VS Code resource.
-	 */
-	toResource(filepath: string): vscode.Uri;
+    /**
+     * Convert a typescript filepath to a VS Code resource.
+     */
+    toResource(filepath: string): vscode.Uri;
 }
-
-function getTagBodyText(
-	tag: Proto.JSDocTagInfo,
-	filePathConverter: IFilePathToResourceConverter,
-): string | undefined {
-	if (!tag.text) {
-		return undefined;
-	}
-
-	// Convert to markdown code block if it does not already contain one
-	function makeCodeblock(text: string): string {
-		if (/^\s*[~`]{3}/m.test(text)) {
-			return text;
-		}
-		return '```\n' + text + '\n```';
-	}
-
-	let text = convertLinkTags(tag.text, filePathConverter);
-	switch (tag.name) {
-		case 'example': {
-			// Example text does not support `{@link}` as it is considered code.
-			// TODO: should we support it if it appears outside of an explicit code block?
-			text = asPlainText(tag.text);
-
-			// check for caption tags, fix for #79704
-			const captionTagMatches = text.match(/(.*?)<\/caption>\s*(\r\n|\n)/);
-			if (captionTagMatches && captionTagMatches.index === 0) {
-				return captionTagMatches[1] + '\n' + makeCodeblock(text.substr(captionTagMatches[0].length));
-			} else {
-				return makeCodeblock(text);
-			}
-		}
-		case 'author': {
-			// fix obsucated email address, #80898
-			const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/);
-			if (emailMatch === null) {
-				return text;
-			} else {
-				return `${emailMatch[1]} ${emailMatch[2]}`;
-			}
-		}
-		case 'default': {
-			return makeCodeblock(text);
-		}
-		default: {
-			return text;
-		}
-	}
+function getTagBodyText(tag: Proto.JSDocTagInfo, filePathConverter: IFilePathToResourceConverter): string | undefined {
+    if (!tag.text) {
+        return undefined;
+    }
+    // Convert to markdown code block if it does not already contain one
+    function makeCodeblock(text: string): string {
+        if (/^\s*[~`]{3}/m.test(text)) {
+            return text;
+        }
+        return '```\n' + text + '\n```';
+    }
+    let text = convertLinkTags(tag.text, filePathConverter);
+    switch (tag.name) {
+        case 'example': {
+            // Example text does not support `{@link}` as it is considered code.
+            // TODO: should we support it if it appears outside of an explicit code block?
+            text = asPlainText(tag.text);
+            // check for caption tags, fix for #79704
+            const captionTagMatches = text.match(/(.*?)<\/caption>\s*(\r\n|\n)/);
+            if (captionTagMatches && captionTagMatches.index === 0) {
+                return captionTagMatches[1] + '\n' + makeCodeblock(text.substr(captionTagMatches[0].length));
+            }
+            else {
+                return makeCodeblock(text);
+            }
+        }
+        case 'author': {
+            // fix obsucated email address, #80898
+            const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/);
+            if (emailMatch === null) {
+                return text;
+            }
+            else {
+                return `${emailMatch[1]} ${emailMatch[2]}`;
+            }
+        }
+        case 'default': {
+            return makeCodeblock(text);
+        }
+        default: {
+            return text;
+        }
+    }
 }
-
-function getTagDocumentation(
-	tag: Proto.JSDocTagInfo,
-	filePathConverter: IFilePathToResourceConverter,
-): string | undefined {
-	switch (tag.name) {
-		case 'augments':
-		case 'extends':
-		case 'param':
-		case 'template': {
-			const body = getTagBody(tag, filePathConverter);
-			if (body?.length === 3) {
-				const param = body[1];
-				const doc = body[2];
-				const label = `*@${tag.name}* \`${param}\``;
-				if (!doc) {
-					return label;
-				}
-				return label + (doc.match(/\r\n|\n/g) ? '  \n' + doc : ` \u2014 ${doc}`);
-			}
-			break;
-		}
-		case 'return':
-		case 'returns': {
-			// For return(s), we require a non-empty body
-			if (!tag.text?.length) {
-				return undefined;
-			}
-
-			break;
-		}
-	}
-
-
-	// Generic tag
-	const label = `*@${tag.name}*`;
-	const text = getTagBodyText(tag, filePathConverter);
-	if (!text) {
-		return label;
-	}
-	return label + (text.match(/\r\n|\n/g) ? '  \n' + text : ` \u2014 ${text}`);
+function getTagDocumentation(tag: Proto.JSDocTagInfo, filePathConverter: IFilePathToResourceConverter): string | undefined {
+    switch (tag.name) {
+        case 'augments':
+        case 'extends':
+        case 'param':
+        case 'template': {
+            const body = getTagBody(tag, filePathConverter);
+            if (body?.length === 3) {
+                const param = body[1];
+                const doc = body[2];
+                const label = `*@${tag.name}* \`${param}\``;
+                if (!doc) {
+                    return label;
+                }
+                return label + (doc.match(/\r\n|\n/g) ? '  \n' + doc : ` \u2014 ${doc}`);
+            }
+            break;
+        }
+        case 'return':
+        case 'returns': {
+            // For return(s), we require a non-empty body
+            if (!tag.text?.length) {
+                return undefined;
+            }
+            break;
+        }
+    }
+    // Generic tag
+    const label = `*@${tag.name}*`;
+    const text = getTagBodyText(tag, filePathConverter);
+    if (!text) {
+        return label;
+    }
+    return label + (text.match(/\r\n|\n/g) ? '  \n' + text : ` \u2014 ${text}`);
 }
-
 function getTagBody(tag: Proto.JSDocTagInfo, filePathConverter: IFilePathToResourceConverter): Array | undefined {
-	if (tag.name === 'template') {
-		const parts = tag.text;
-		if (parts && typeof (parts) !== 'string') {
-			const params = parts.filter(p => p.kind === 'typeParameterName').map(p => p.text).join(', ');
-			const docs = parts.filter(p => p.kind === 'text').map(p => convertLinkTags(p.text.replace(/^\s*-?\s*/, ''), filePathConverter)).join(' ');
-			return params ? ['', params, docs] : undefined;
-		}
-	}
-	return (convertLinkTags(tag.text, filePathConverter)).split(/^(\S+)\s*-?\s*/);
+    if (tag.name === 'template') {
+        const parts = tag.text;
+        if (parts && typeof (parts) !== 'string') {
+            const params = parts.filter(p => p.kind === 'typeParameterName').map(p => p.text).join(', ');
+            const docs = parts.filter(p => p.kind === 'text').map(p => convertLinkTags(p.text.replace(/^\s*-?\s*/, ''), filePathConverter)).join(' ');
+            return params ? ['', params, docs] : undefined;
+        }
+    }
+    return (convertLinkTags(tag.text, filePathConverter)).split(/^(\S+)\s*-?\s*/);
 }
-
 function asPlainText(parts: readonly Proto.SymbolDisplayPart[] | string): string {
-	if (typeof parts === 'string') {
-		return parts;
-	}
-	return parts.map(part => part.text).join('');
+    if (typeof parts === 'string') {
+        return parts;
+    }
+    return parts.map(part => part.text).join('');
 }
-
-export function asPlainTextWithLinks(
-	parts: readonly Proto.SymbolDisplayPart[] | string,
-	filePathConverter: IFilePathToResourceConverter,
-): string {
-	return convertLinkTags(parts, filePathConverter);
+export function asPlainTextWithLinks(parts: readonly Proto.SymbolDisplayPart[] | string, filePathConverter: IFilePathToResourceConverter): string {
+    return convertLinkTags(parts, filePathConverter);
 }
-
 /**
  * Convert `@link` inline tags to markdown links
  */
-function convertLinkTags(
-	parts: readonly Proto.SymbolDisplayPart[] | string | undefined,
-	filePathConverter: IFilePathToResourceConverter,
-): string {
-	if (!parts) {
-		return '';
-	}
-
-	if (typeof parts === 'string') {
-		return parts;
-	}
-
-	const out: string[] = [];
-
-	let currentLink: { name?: string; target?: Proto.FileSpan; text?: string; readonly linkcode: boolean } | undefined;
-	for (const part of parts) {
-		switch (part.kind) {
-			case 'link':
-				if (currentLink) {
-					if (currentLink.target) {
-						const file = filePathConverter.toResource(currentLink.target.file);
-						const args: OpenJsDocLinkCommand_Args = {
-							file: { ...file.toJSON(), $mid: undefined }, // Prevent VS Code from trying to transform the uri,
-							position: typeConverters.Position.fromLocation(currentLink.target.start)
-						};
-						const command = `command:${OpenJsDocLinkCommand.id}?${encodeURIComponent(JSON.stringify([args]))}`;
-
-						const linkText = currentLink.text ? currentLink.text : escapeMarkdownSyntaxTokensForCode(currentLink.name ?? '');
-						out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${command})`);
-					} else {
-						const text = currentLink.text ?? currentLink.name;
-						if (text) {
-							if (/^https?:/.test(text)) {
-								const parts = text.split(' ');
-								if (parts.length === 1 && !currentLink.linkcode) {
-									out.push(`<${parts[0]}>`);
-								} else {
-									const linkText = parts.length > 1 ? parts.slice(1).join(' ') : parts[0];
-									out.push(`[${currentLink.linkcode ? '`' + escapeMarkdownSyntaxTokensForCode(linkText) + '`' : linkText}](${parts[0]})`);
-								}
-							} else {
-								out.push(escapeMarkdownSyntaxTokensForCode(text));
-							}
-						}
-					}
-					currentLink = undefined;
-				} else {
-					currentLink = {
-						linkcode: part.text === '{@linkcode '
-					};
-				}
-				break;
-
-			case 'linkName':
-				if (currentLink) {
-					currentLink.name = part.text;
-					currentLink.target = (part as Proto.JSDocLinkDisplayPart).target;
-				}
-				break;
-
-			case 'linkText':
-				if (currentLink) {
-					currentLink.text = part.text;
-				}
-				break;
-
-			default:
-				out.push(part.text);
-				break;
-		}
-	}
-	return out.join('');
+function convertLinkTags(parts: readonly Proto.SymbolDisplayPart[] | string | undefined, filePathConverter: IFilePathToResourceConverter): string {
+    if (!parts) {
+        return '';
+    }
+    if (typeof parts === 'string') {
+        return parts;
+    }
+    const out: string[] = [];
+    let currentLink: {
+        name?: string;
+        target?: Proto.FileSpan;
+        text?: string;
+        readonly linkcode: boolean;
+    } | undefined;
+    for (const part of parts) {
+        switch (part.kind) {
+            case 'link':
+                if (currentLink) {
+                    if (currentLink.target) {
+                        const file = filePathConverter.toResource(currentLink.target.file);
+                        const args: OpenJsDocLinkCommand_Args = {
+                            file: { ...file.toJSON(), $mid: undefined }, // Prevent VS Code from trying to transform the uri,
+                            position: typeConverters.Position.fromLocation(currentLink.target.start)
+                        };
+                        const command = `command:${OpenJsDocLinkCommand.id}?${encodeURIComponent(JSON.stringify([args]))}`;
+                        const linkText = currentLink.text ? currentLink.text : escapeMarkdownSyntaxTokensForCode(currentLink.name ?? '');
+                        out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${command})`);
+                    }
+                    else {
+                        const text = currentLink.text ?? currentLink.name;
+                        if (text) {
+                            if (/^https?:/.test(text)) {
+                                const parts = text.split(' ');
+                                if (parts.length === 1 && !currentLink.linkcode) {
+                                    out.push(`<${parts[0]}>`);
+                                }
+                                else {
+                                    const linkText = parts.length > 1 ? parts.slice(1).join(' ') : parts[0];
+                                    out.push(`[${currentLink.linkcode ? '`' + escapeMarkdownSyntaxTokensForCode(linkText) + '`' : linkText}](${parts[0]})`);
+                                }
+                            }
+                            else {
+                                out.push(escapeMarkdownSyntaxTokensForCode(text));
+                            }
+                        }
+                    }
+                    currentLink = undefined;
+                }
+                else {
+                    currentLink = {
+                        linkcode: part.text === '{@linkcode '
+                    };
+                }
+                break;
+            case 'linkName':
+                if (currentLink) {
+                    currentLink.name = part.text;
+                    currentLink.target = (part as Proto.JSDocLinkDisplayPart).target;
+                }
+                break;
+            case 'linkText':
+                if (currentLink) {
+                    currentLink.text = part.text;
+                }
+                break;
+            default:
+                out.push(part.text);
+                break;
+        }
+    }
+    return out.join('');
 }
-
 function escapeMarkdownSyntaxTokensForCode(text: string): string {
-	return text.replace(/`/g, '\\$&'); // CodeQL [SM02383] This is only meant to escape backticks. The Markdown is fully sanitized after being rendered.
+    return text.replace(/`/g, '\\$&'); // CodeQL [SM02383] This is only meant to escape backticks. The Markdown is fully sanitized after being rendered.
 }
-
-export function tagsToMarkdown(
-	tags: readonly Proto.JSDocTagInfo[],
-	filePathConverter: IFilePathToResourceConverter,
-): string {
-	return tags.map(tag => getTagDocumentation(tag, filePathConverter)).join('  \n\n');
+export function tagsToMarkdown(tags: readonly Proto.JSDocTagInfo[], filePathConverter: IFilePathToResourceConverter): string {
+    return tags.map(tag => getTagDocumentation(tag, filePathConverter)).join('  \n\n');
 }
-
-export function documentationToMarkdown(
-	documentation: readonly Proto.SymbolDisplayPart[] | string,
-	tags: readonly Proto.JSDocTagInfo[],
-	filePathConverter: IFilePathToResourceConverter,
-	baseUri: vscode.Uri | undefined,
-): vscode.MarkdownString {
-	const out = new vscode.MarkdownString();
-	appendDocumentationAsMarkdown(out, documentation, tags, filePathConverter);
-	out.baseUri = baseUri;
-	out.isTrusted = { enabledCommands: [OpenJsDocLinkCommand.id] };
-	return out;
+export function documentationToMarkdown(documentation: readonly Proto.SymbolDisplayPart[] | string, tags: readonly Proto.JSDocTagInfo[], filePathConverter: IFilePathToResourceConverter, baseUri: vscode.Uri | undefined): vscode.MarkdownString {
+    const out = new vscode.MarkdownString();
+    appendDocumentationAsMarkdown(out, documentation, tags, filePathConverter);
+    out.baseUri = baseUri;
+    out.isTrusted = { enabledCommands: [OpenJsDocLinkCommand.id] };
+    return out;
 }
-
-export function appendDocumentationAsMarkdown(
-	out: vscode.MarkdownString,
-	documentation: readonly Proto.SymbolDisplayPart[] | string | undefined,
-	tags: readonly Proto.JSDocTagInfo[] | undefined,
-	converter: IFilePathToResourceConverter,
-): vscode.MarkdownString {
-	if (documentation) {
-		out.appendMarkdown(asPlainTextWithLinks(documentation, converter));
-	}
-
-	if (tags) {
-		const tagsPreview = tagsToMarkdown(tags, converter);
-		if (tagsPreview) {
-			out.appendMarkdown('\n\n' + tagsPreview);
-		}
-	}
-
-	out.isTrusted = { enabledCommands: [OpenJsDocLinkCommand.id] };
-
-	return out;
+export function appendDocumentationAsMarkdown(out: vscode.MarkdownString, documentation: readonly Proto.SymbolDisplayPart[] | string | undefined, tags: readonly Proto.JSDocTagInfo[] | undefined, converter: IFilePathToResourceConverter): vscode.MarkdownString {
+    if (documentation) {
+        out.appendMarkdown(asPlainTextWithLinks(documentation, converter));
+    }
+    if (tags) {
+        const tagsPreview = tagsToMarkdown(tags, converter);
+        if (tagsPreview) {
+            out.appendMarkdown('\n\n' + tagsPreview);
+        }
+    }
+    out.isTrusted = { enabledCommands: [OpenJsDocLinkCommand.id] };
+    return out;
 }
diff --git a/extensions/typescript-language-features/Source/languageFeatures/workspaceSymbols.ts b/extensions/typescript-language-features/Source/languageFeatures/workspaceSymbols.ts
index 24fb7068b7438..0ca491323f294 100644
--- a/extensions/typescript-language-features/Source/languageFeatures/workspaceSymbols.ts
+++ b/extensions/typescript-language-features/Source/languageFeatures/workspaceSymbols.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as fileSchemes from '../configuration/fileSchemes';
 import { doesResourceLookLikeAJavaScriptFile, doesResourceLookLikeATypeScriptFile } from '../configuration/languageDescription';
@@ -13,138 +12,108 @@ import * as PConst from '../tsServer/protocol/protocol.const';
 import * as typeConverters from '../typeConverters';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import { coalesce } from '../utils/arrays';
-
 function getSymbolKind(item: Proto.NavtoItem): vscode.SymbolKind {
-	switch (item.kind) {
-		case PConst.Kind.method: return vscode.SymbolKind.Method;
-		case PConst.Kind.enum: return vscode.SymbolKind.Enum;
-		case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember;
-		case PConst.Kind.function: return vscode.SymbolKind.Function;
-		case PConst.Kind.class: return vscode.SymbolKind.Class;
-		case PConst.Kind.interface: return vscode.SymbolKind.Interface;
-		case PConst.Kind.type: return vscode.SymbolKind.Class;
-		case PConst.Kind.memberVariable: return vscode.SymbolKind.Field;
-		case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Field;
-		case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Field;
-		case PConst.Kind.variable: return vscode.SymbolKind.Variable;
-		default: return vscode.SymbolKind.Variable;
-	}
+    switch (item.kind) {
+        case PConst.Kind.method: return vscode.SymbolKind.Method;
+        case PConst.Kind.enum: return vscode.SymbolKind.Enum;
+        case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember;
+        case PConst.Kind.function: return vscode.SymbolKind.Function;
+        case PConst.Kind.class: return vscode.SymbolKind.Class;
+        case PConst.Kind.interface: return vscode.SymbolKind.Interface;
+        case PConst.Kind.type: return vscode.SymbolKind.Class;
+        case PConst.Kind.memberVariable: return vscode.SymbolKind.Field;
+        case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Field;
+        case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Field;
+        case PConst.Kind.variable: return vscode.SymbolKind.Variable;
+        default: return vscode.SymbolKind.Variable;
+    }
 }
-
 class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
-
-	public constructor(
-		private readonly client: ITypeScriptServiceClient,
-		private readonly modeIds: readonly string[],
-	) { }
-
-	public async provideWorkspaceSymbols(
-		search: string,
-		token: vscode.CancellationToken
-	): Promise {
-		let file: string | undefined;
-		if (this.searchAllOpenProjects) {
-			file = undefined;
-		} else {
-			const document = this.getDocument();
-			file = document ? await this.toOpenedFiledPath(document) : undefined;
-
-			if (!file && this.client.apiVersion.lt(API.v390)) {
-				return [];
-			}
-		}
-
-		const args: Proto.NavtoRequestArgs = {
-			file,
-			searchValue: search,
-			maxResultCount: 256,
-		};
-
-		const response = await this.client.execute('navto', args, token);
-		if (response.type !== 'response' || !response.body) {
-			return [];
-		}
-
-		return coalesce(response.body.map(item => this.toSymbolInformation(item)));
-	}
-
-	private get searchAllOpenProjects() {
-		return this.client.apiVersion.gte(API.v390)
-			&& vscode.workspace.getConfiguration('typescript').get('workspaceSymbols.scope', 'allOpenProjects') === 'allOpenProjects';
-	}
-
-	private async toOpenedFiledPath(document: vscode.TextDocument) {
-		if (document.uri.scheme === fileSchemes.git) {
-			try {
-				const path = vscode.Uri.file(JSON.parse(document.uri.query)?.path);
-				if (doesResourceLookLikeATypeScriptFile(path) || doesResourceLookLikeAJavaScriptFile(path)) {
-					const document = await vscode.workspace.openTextDocument(path);
-					return this.client.toOpenTsFilePath(document);
-				}
-			} catch {
-				// noop
-			}
-		}
-		return this.client.toOpenTsFilePath(document);
-	}
-
-	private toSymbolInformation(item: Proto.NavtoItem): vscode.SymbolInformation | undefined {
-		if (item.kind === 'alias' && !item.containerName) {
-			return;
-		}
-
-		const uri = this.client.toResource(item.file);
-		if (fileSchemes.isOfScheme(uri, fileSchemes.chatCodeBlock)) {
-			return;
-		}
-
-		const label = TypeScriptWorkspaceSymbolProvider.getLabel(item);
-		const info = new vscode.SymbolInformation(
-			label,
-			getSymbolKind(item),
-			item.containerName || '',
-			typeConverters.Location.fromTextSpan(uri, item));
-		const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined;
-		if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
-			info.tags = [vscode.SymbolTag.Deprecated];
-		}
-		return info;
-	}
-
-	private static getLabel(item: Proto.NavtoItem) {
-		const label = item.name;
-		if (item.kind === 'method' || item.kind === 'function') {
-			return label + '()';
-		}
-		return label;
-	}
-
-	private getDocument(): vscode.TextDocument | undefined {
-		// typescript wants to have a resource even when asking
-		// general questions so we check the active editor. If this
-		// doesn't match we take the first TS document.
-
-		const activeDocument = vscode.window.activeTextEditor?.document;
-		if (activeDocument) {
-			if (this.modeIds.includes(activeDocument.languageId)) {
-				return activeDocument;
-			}
-		}
-
-		const documents = vscode.workspace.textDocuments;
-		for (const document of documents) {
-			if (this.modeIds.includes(document.languageId)) {
-				return document;
-			}
-		}
-		return undefined;
-	}
+    public constructor(private readonly client: ITypeScriptServiceClient, private readonly modeIds: readonly string[]) { }
+    public async provideWorkspaceSymbols(search: string, token: vscode.CancellationToken): Promise {
+        let file: string | undefined;
+        if (this.searchAllOpenProjects) {
+            file = undefined;
+        }
+        else {
+            const document = this.getDocument();
+            file = document ? await this.toOpenedFiledPath(document) : undefined;
+            if (!file && this.client.apiVersion.lt(API.v390)) {
+                return [];
+            }
+        }
+        const args: Proto.NavtoRequestArgs = {
+            file,
+            searchValue: search,
+            maxResultCount: 256,
+        };
+        const response = await this.client.execute('navto', args, token);
+        if (response.type !== 'response' || !response.body) {
+            return [];
+        }
+        return coalesce(response.body.map(item => this.toSymbolInformation(item)));
+    }
+    private get searchAllOpenProjects() {
+        return this.client.apiVersion.gte(API.v390)
+            && vscode.workspace.getConfiguration('typescript').get('workspaceSymbols.scope', 'allOpenProjects') === 'allOpenProjects';
+    }
+    private async toOpenedFiledPath(document: vscode.TextDocument) {
+        if (document.uri.scheme === fileSchemes.git) {
+            try {
+                const path = vscode.Uri.file(JSON.parse(document.uri.query)?.path);
+                if (doesResourceLookLikeATypeScriptFile(path) || doesResourceLookLikeAJavaScriptFile(path)) {
+                    const document = await vscode.workspace.openTextDocument(path);
+                    return this.client.toOpenTsFilePath(document);
+                }
+            }
+            catch {
+                // noop
+            }
+        }
+        return this.client.toOpenTsFilePath(document);
+    }
+    private toSymbolInformation(item: Proto.NavtoItem): vscode.SymbolInformation | undefined {
+        if (item.kind === 'alias' && !item.containerName) {
+            return;
+        }
+        const uri = this.client.toResource(item.file);
+        if (fileSchemes.isOfScheme(uri, fileSchemes.chatCodeBlock)) {
+            return;
+        }
+        const label = TypeScriptWorkspaceSymbolProvider.getLabel(item);
+        const info = new vscode.SymbolInformation(label, getSymbolKind(item), item.containerName || '', typeConverters.Location.fromTextSpan(uri, item));
+        const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined;
+        if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
+            info.tags = [vscode.SymbolTag.Deprecated];
+        }
+        return info;
+    }
+    private static getLabel(item: Proto.NavtoItem) {
+        const label = item.name;
+        if (item.kind === 'method' || item.kind === 'function') {
+            return label + '()';
+        }
+        return label;
+    }
+    private getDocument(): vscode.TextDocument | undefined {
+        // typescript wants to have a resource even when asking
+        // general questions so we check the active editor. If this
+        // doesn't match we take the first TS document.
+        const activeDocument = vscode.window.activeTextEditor?.document;
+        if (activeDocument) {
+            if (this.modeIds.includes(activeDocument.languageId)) {
+                return activeDocument;
+            }
+        }
+        const documents = vscode.workspace.textDocuments;
+        for (const document of documents) {
+            if (this.modeIds.includes(document.languageId)) {
+                return document;
+            }
+        }
+        return undefined;
+    }
 }
-
-export function register(
-	client: ITypeScriptServiceClient,
-	modeIds: readonly string[],
-) {
-	return vscode.languages.registerWorkspaceSymbolProvider(
-		new TypeScriptWorkspaceSymbolProvider(client, modeIds));
+export function register(client: ITypeScriptServiceClient, modeIds: readonly string[]) {
+    return vscode.languages.registerWorkspaceSymbolProvider(new TypeScriptWorkspaceSymbolProvider(client, modeIds));
 }
diff --git a/extensions/typescript-language-features/Source/languageProvider.ts b/extensions/typescript-language-features/Source/languageProvider.ts
index 09a4fe3ccc9c6..e79cbccbd5e19 100644
--- a/extensions/typescript-language-features/Source/languageProvider.ts
+++ b/extensions/typescript-language-features/Source/languageProvider.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { basename, extname } from 'path';
 import * as vscode from 'vscode';
 import { CommandManager } from './commands/commandManager';
@@ -19,175 +18,138 @@ import TypeScriptServiceClient from './typescriptServiceClient';
 import TypingsStatus from './ui/typingsStatus';
 import { Disposable } from './utils/dispose';
 import { isWeb, isWebAndHasSharedArrayBuffers, supportsReadableByteStreams } from './utils/platform';
-
-
 const validateSetting = 'validate.enable';
 const suggestionSetting = 'suggestionActions.enabled';
-
 export default class LanguageProvider extends Disposable {
-
-	constructor(
-		private readonly client: TypeScriptServiceClient,
-		private readonly description: LanguageDescription,
-		private readonly commandManager: CommandManager,
-		private readonly telemetryReporter: TelemetryReporter,
-		private readonly typingsStatus: TypingsStatus,
-		private readonly fileConfigurationManager: FileConfigurationManager,
-		private readonly onCompletionAccepted: (item: vscode.CompletionItem) => void,
-	) {
-		super();
-		vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
-		this.configurationChanged();
-
-		client.onReady(() => this.registerProviders());
-	}
-
-	private get documentSelector(): DocumentSelector {
-		const semantic: vscode.DocumentFilter[] = [];
-		const syntax: vscode.DocumentFilter[] = [];
-		for (const language of this.description.languageIds) {
-			syntax.push({ language });
-			for (const scheme of fileSchemes.getSemanticSupportedSchemes()) {
-				semantic.push({ language, scheme });
-			}
-		}
-
-		return { semantic, syntax };
-	}
-
-	private async registerProviders(): Promise {
-		const selector = this.documentSelector;
-
-		const cachedNavTreeResponse = new CachedResponse();
-
-		await Promise.all([
-			import('./languageFeatures/callHierarchy').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/codeLens/implementationsCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
-			import('./languageFeatures/codeLens/referencesCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
-			import('./languageFeatures/completions').then(provider => this._register(provider.register(selector, this.description, this.client, this.typingsStatus, this.fileConfigurationManager, this.commandManager, this.telemetryReporter, this.onCompletionAccepted))),
-			import('./languageFeatures/copyPaste').then(provider => this._register(provider.register(selector, this.description, this.client))),
-			import('./languageFeatures/definitions').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/directiveCommentCompletions').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/documentHighlight').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/documentSymbol').then(provider => this._register(provider.register(selector, this.client, cachedNavTreeResponse))),
-			import('./languageFeatures/fileReferences').then(provider => this._register(provider.register(this.client, this.commandManager))),
-			import('./languageFeatures/fixAll').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.client.diagnosticsManager))),
-			import('./languageFeatures/folding').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
-			import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))),
-			import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager, this.telemetryReporter))),
-			import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
-			import('./languageFeatures/linkedEditing').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/mappedCodeEditProvider').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))),
-			import('./languageFeatures/quickFix').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter))),
-			import('./languageFeatures/refactor').then(provider => this._register(provider.register(selector, this.client, cachedNavTreeResponse, this.fileConfigurationManager, this.commandManager, this.telemetryReporter))),
-			import('./languageFeatures/references').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/rename').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
-			import('./languageFeatures/semanticTokens').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/signatureHelp').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/smartSelect').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/sourceDefinition').then(provider => this._register(provider.register(this.client, this.commandManager))),
-			import('./languageFeatures/tagClosing').then(provider => this._register(provider.register(selector, this.description, this.client))),
-			import('./languageFeatures/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))),
-			import('./languageFeatures/copilotRelated').then(provider => this._register(provider.register(selector, this.client))),
-		]);
-	}
-
-	private configurationChanged(): void {
-		const config = vscode.workspace.getConfiguration(this.id, null);
-		this.updateValidate(config.get(validateSetting, true));
-		this.updateSuggestionDiagnostics(config.get(suggestionSetting, true));
-	}
-
-	public handlesUri(resource: vscode.Uri): boolean {
-		const ext = extname(resource.path).slice(1).toLowerCase();
-		return this.description.standardFileExtensions.includes(ext) || this.handlesConfigFile(resource);
-	}
-
-	public handlesDocument(doc: vscode.TextDocument): boolean {
-		return this.description.languageIds.includes(doc.languageId) || this.handlesConfigFile(doc.uri);
-	}
-
-	private handlesConfigFile(resource: vscode.Uri) {
-		const base = basename(resource.fsPath);
-		return !!base && (!!this.description.configFilePattern && this.description.configFilePattern.test(base));
-	}
-
-	private get id(): string {
-		return this.description.id;
-	}
-
-	public get diagnosticSource(): string {
-		return this.description.diagnosticSource;
-	}
-
-	private updateValidate(value: boolean) {
-		this.client.diagnosticsManager.setValidate(this._diagnosticLanguage, value);
-	}
-
-	private updateSuggestionDiagnostics(value: boolean) {
-		this.client.diagnosticsManager.setEnableSuggestions(this._diagnosticLanguage, value);
-	}
-
-	public reInitialize(): void {
-		this.client.diagnosticsManager.reInitialize();
-	}
-
-	public triggerAllDiagnostics(): void {
-		this.client.bufferSyncSupport.requestAllDiagnostics();
-	}
-
-	public diagnosticsReceived(
-		diagnosticsKind: DiagnosticKind,
-		file: vscode.Uri,
-		diagnostics: (vscode.Diagnostic & { reportUnnecessary: any; reportDeprecated: any })[],
-		ranges: vscode.Range[] | undefined): void {
-		if (diagnosticsKind !== DiagnosticKind.Syntax && !this.client.hasCapabilityForResource(file, ClientCapability.Semantic)) {
-			return;
-		}
-
-		if (diagnosticsKind === DiagnosticKind.Semantic && isWeb()) {
-			if (
-				!isWebAndHasSharedArrayBuffers()
-				|| !supportsReadableByteStreams() // No ata. Will result in lots of false positives
-				|| this.client.configuration.webProjectWideIntellisenseSuppressSemanticErrors
-				|| !this.client.configuration.webProjectWideIntellisenseEnabled
-			) {
-				return;
-			}
-		}
-
-		// Disable semantic errors in notebooks until we have better notebook support
-		if (diagnosticsKind === DiagnosticKind.Semantic && file.scheme === Schemes.notebookCell) {
-			return;
-		}
-
-		const config = vscode.workspace.getConfiguration(this.id, file);
-		const reportUnnecessary = config.get('showUnused', true);
-		const reportDeprecated = config.get('showDeprecated', true);
-		this.client.diagnosticsManager.updateDiagnostics(file, this._diagnosticLanguage, diagnosticsKind, diagnostics.filter(diag => {
-			// Don't bother reporting diagnostics we know will not be rendered
-			if (!reportUnnecessary) {
-				if (diag.reportUnnecessary && diag.severity === vscode.DiagnosticSeverity.Hint) {
-					return false;
-				}
-			}
-			if (!reportDeprecated) {
-				if (diag.reportDeprecated && diag.severity === vscode.DiagnosticSeverity.Hint) {
-					return false;
-				}
-			}
-			return true;
-		}), ranges);
-	}
-
-	public configFileDiagnosticsReceived(file: vscode.Uri, diagnostics: vscode.Diagnostic[]): void {
-		this.client.diagnosticsManager.configFileDiagnosticsReceived(file, diagnostics);
-	}
-
-	private get _diagnosticLanguage() {
-		return this.description.diagnosticLanguage;
-	}
+    constructor(private readonly client: TypeScriptServiceClient, private readonly description: LanguageDescription, private readonly commandManager: CommandManager, private readonly telemetryReporter: TelemetryReporter, private readonly typingsStatus: TypingsStatus, private readonly fileConfigurationManager: FileConfigurationManager, private readonly onCompletionAccepted: (item: vscode.CompletionItem) => void) {
+        super();
+        vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
+        this.configurationChanged();
+        client.onReady(() => this.registerProviders());
+    }
+    private get documentSelector(): DocumentSelector {
+        const semantic: vscode.DocumentFilter[] = [];
+        const syntax: vscode.DocumentFilter[] = [];
+        for (const language of this.description.languageIds) {
+            syntax.push({ language });
+            for (const scheme of fileSchemes.getSemanticSupportedSchemes()) {
+                semantic.push({ language, scheme });
+            }
+        }
+        return { semantic, syntax };
+    }
+    private async registerProviders(): Promise {
+        const selector = this.documentSelector;
+        const cachedNavTreeResponse = new CachedResponse();
+        await Promise.all([
+            import('./languageFeatures/callHierarchy').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/codeLens/implementationsCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
+            import('./languageFeatures/codeLens/referencesCodeLens').then(provider => this._register(provider.register(selector, this.description, this.client, cachedNavTreeResponse))),
+            import('./languageFeatures/completions').then(provider => this._register(provider.register(selector, this.description, this.client, this.typingsStatus, this.fileConfigurationManager, this.commandManager, this.telemetryReporter, this.onCompletionAccepted))),
+            import('./languageFeatures/copyPaste').then(provider => this._register(provider.register(selector, this.description, this.client))),
+            import('./languageFeatures/definitions').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/directiveCommentCompletions').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/documentHighlight').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/documentSymbol').then(provider => this._register(provider.register(selector, this.client, cachedNavTreeResponse))),
+            import('./languageFeatures/fileReferences').then(provider => this._register(provider.register(this.client, this.commandManager))),
+            import('./languageFeatures/fixAll').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.client.diagnosticsManager))),
+            import('./languageFeatures/folding').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
+            import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))),
+            import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager, this.telemetryReporter))),
+            import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
+            import('./languageFeatures/linkedEditing').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/mappedCodeEditProvider').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))),
+            import('./languageFeatures/quickFix').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager, this.commandManager, this.client.diagnosticsManager, this.telemetryReporter))),
+            import('./languageFeatures/refactor').then(provider => this._register(provider.register(selector, this.client, cachedNavTreeResponse, this.fileConfigurationManager, this.commandManager, this.telemetryReporter))),
+            import('./languageFeatures/references').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/rename').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
+            import('./languageFeatures/semanticTokens').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/signatureHelp').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/smartSelect').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/sourceDefinition').then(provider => this._register(provider.register(this.client, this.commandManager))),
+            import('./languageFeatures/tagClosing').then(provider => this._register(provider.register(selector, this.description, this.client))),
+            import('./languageFeatures/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))),
+            import('./languageFeatures/copilotRelated').then(provider => this._register(provider.register(selector, this.client))),
+        ]);
+    }
+    private configurationChanged(): void {
+        const config = vscode.workspace.getConfiguration(this.id, null);
+        this.updateValidate(config.get(validateSetting, true));
+        this.updateSuggestionDiagnostics(config.get(suggestionSetting, true));
+    }
+    public handlesUri(resource: vscode.Uri): boolean {
+        const ext = extname(resource.path).slice(1).toLowerCase();
+        return this.description.standardFileExtensions.includes(ext) || this.handlesConfigFile(resource);
+    }
+    public handlesDocument(doc: vscode.TextDocument): boolean {
+        return this.description.languageIds.includes(doc.languageId) || this.handlesConfigFile(doc.uri);
+    }
+    private handlesConfigFile(resource: vscode.Uri) {
+        const base = basename(resource.fsPath);
+        return !!base && (!!this.description.configFilePattern && this.description.configFilePattern.test(base));
+    }
+    private get id(): string {
+        return this.description.id;
+    }
+    public get diagnosticSource(): string {
+        return this.description.diagnosticSource;
+    }
+    private updateValidate(value: boolean) {
+        this.client.diagnosticsManager.setValidate(this._diagnosticLanguage, value);
+    }
+    private updateSuggestionDiagnostics(value: boolean) {
+        this.client.diagnosticsManager.setEnableSuggestions(this._diagnosticLanguage, value);
+    }
+    public reInitialize(): void {
+        this.client.diagnosticsManager.reInitialize();
+    }
+    public triggerAllDiagnostics(): void {
+        this.client.bufferSyncSupport.requestAllDiagnostics();
+    }
+    public diagnosticsReceived(diagnosticsKind: DiagnosticKind, file: vscode.Uri, diagnostics: (vscode.Diagnostic & {
+        reportUnnecessary: any;
+        reportDeprecated: any;
+    })[], ranges: vscode.Range[] | undefined): void {
+        if (diagnosticsKind !== DiagnosticKind.Syntax && !this.client.hasCapabilityForResource(file, ClientCapability.Semantic)) {
+            return;
+        }
+        if (diagnosticsKind === DiagnosticKind.Semantic && isWeb()) {
+            if (!isWebAndHasSharedArrayBuffers()
+                || !supportsReadableByteStreams() // No ata. Will result in lots of false positives
+                || this.client.configuration.webProjectWideIntellisenseSuppressSemanticErrors
+                || !this.client.configuration.webProjectWideIntellisenseEnabled) {
+                return;
+            }
+        }
+        // Disable semantic errors in notebooks until we have better notebook support
+        if (diagnosticsKind === DiagnosticKind.Semantic && file.scheme === Schemes.notebookCell) {
+            return;
+        }
+        const config = vscode.workspace.getConfiguration(this.id, file);
+        const reportUnnecessary = config.get('showUnused', true);
+        const reportDeprecated = config.get('showDeprecated', true);
+        this.client.diagnosticsManager.updateDiagnostics(file, this._diagnosticLanguage, diagnosticsKind, diagnostics.filter(diag => {
+            // Don't bother reporting diagnostics we know will not be rendered
+            if (!reportUnnecessary) {
+                if (diag.reportUnnecessary && diag.severity === vscode.DiagnosticSeverity.Hint) {
+                    return false;
+                }
+            }
+            if (!reportDeprecated) {
+                if (diag.reportDeprecated && diag.severity === vscode.DiagnosticSeverity.Hint) {
+                    return false;
+                }
+            }
+            return true;
+        }), ranges);
+    }
+    public configFileDiagnosticsReceived(file: vscode.Uri, diagnostics: vscode.Diagnostic[]): void {
+        this.client.diagnosticsManager.configFileDiagnosticsReceived(file, diagnostics);
+    }
+    private get _diagnosticLanguage() {
+        return this.description.diagnosticLanguage;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/lazyClientHost.ts b/extensions/typescript-language-features/Source/lazyClientHost.ts
index be832b3e16979..b011252fe6749 100644
--- a/extensions/typescript-language-features/Source/lazyClientHost.ts
+++ b/extensions/typescript-language-features/Source/lazyClientHost.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { CommandManager } from './commands/commandManager';
 import { IExperimentationTelemetryReporter } from './experimentTelemetryReporter';
@@ -19,84 +18,54 @@ import { standardLanguageDescriptions } from './configuration/languageDescriptio
 import { Lazy, lazy } from './utils/lazy';
 import { Logger } from './logging/logger';
 import { PluginManager } from './tsServer/plugins';
-
-export function createLazyClientHost(
-	context: vscode.ExtensionContext,
-	onCaseInsensitiveFileSystem: boolean,
-	services: {
-		pluginManager: PluginManager;
-		commandManager: CommandManager;
-		logDirectoryProvider: ILogDirectoryProvider;
-		cancellerFactory: OngoingRequestCancellerFactory;
-		versionProvider: ITypeScriptVersionProvider;
-		processFactory: TsServerProcessFactory;
-		activeJsTsEditorTracker: ActiveJsTsEditorTracker;
-		serviceConfigurationProvider: ServiceConfigurationProvider;
-		experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
-		logger: Logger;
-	},
-	onCompletionAccepted: (item: vscode.CompletionItem) => void,
-): Lazy {
-	return lazy(() => {
-		const clientHost = new TypeScriptServiceClientHost(
-			standardLanguageDescriptions,
-			context,
-			onCaseInsensitiveFileSystem,
-			services,
-			onCompletionAccepted);
-
-		context.subscriptions.push(clientHost);
-
-		return clientHost;
-	});
+export function createLazyClientHost(context: vscode.ExtensionContext, onCaseInsensitiveFileSystem: boolean, services: {
+    pluginManager: PluginManager;
+    commandManager: CommandManager;
+    logDirectoryProvider: ILogDirectoryProvider;
+    cancellerFactory: OngoingRequestCancellerFactory;
+    versionProvider: ITypeScriptVersionProvider;
+    processFactory: TsServerProcessFactory;
+    activeJsTsEditorTracker: ActiveJsTsEditorTracker;
+    serviceConfigurationProvider: ServiceConfigurationProvider;
+    experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
+    logger: Logger;
+}, onCompletionAccepted: (item: vscode.CompletionItem) => void): Lazy {
+    return lazy(() => {
+        const clientHost = new TypeScriptServiceClientHost(standardLanguageDescriptions, context, onCaseInsensitiveFileSystem, services, onCompletionAccepted);
+        context.subscriptions.push(clientHost);
+        return clientHost;
+    });
 }
-
-export function lazilyActivateClient(
-	lazyClientHost: Lazy,
-	pluginManager: PluginManager,
-	activeJsTsEditorTracker: ActiveJsTsEditorTracker,
-	onActivate: () => Promise = () => Promise.resolve(),
-): vscode.Disposable {
-	const disposables: vscode.Disposable[] = [];
-
-	const supportedLanguage = [
-		...standardLanguageDescriptions.map(x => x.languageIds),
-		...pluginManager.plugins.map(x => x.languages)
-	].flat();
-
-	let hasActivated = false;
-	const maybeActivate = (textDocument: vscode.TextDocument): boolean => {
-		if (!hasActivated && isSupportedDocument(supportedLanguage, textDocument)) {
-			hasActivated = true;
-
-			onActivate().then(() => {
-				// Force activation
-				void lazyClientHost.value;
-
-				disposables.push(new ManagedFileContextManager(activeJsTsEditorTracker));
-			});
-
-			return true;
-		}
-		return false;
-	};
-
-	const didActivate = vscode.workspace.textDocuments.some(maybeActivate);
-	if (!didActivate) {
-		const openListener = vscode.workspace.onDidOpenTextDocument(doc => {
-			if (maybeActivate(doc)) {
-				openListener.dispose();
-			}
-		}, undefined, disposables);
-	}
-
-	return vscode.Disposable.from(...disposables);
+export function lazilyActivateClient(lazyClientHost: Lazy, pluginManager: PluginManager, activeJsTsEditorTracker: ActiveJsTsEditorTracker, onActivate: () => Promise = () => Promise.resolve()): vscode.Disposable {
+    const disposables: vscode.Disposable[] = [];
+    const supportedLanguage = [
+        ...standardLanguageDescriptions.map(x => x.languageIds),
+        ...pluginManager.plugins.map(x => x.languages)
+    ].flat();
+    let hasActivated = false;
+    const maybeActivate = (textDocument: vscode.TextDocument): boolean => {
+        if (!hasActivated && isSupportedDocument(supportedLanguage, textDocument)) {
+            hasActivated = true;
+            onActivate().then(() => {
+                // Force activation
+                void lazyClientHost.value;
+                disposables.push(new ManagedFileContextManager(activeJsTsEditorTracker));
+            });
+            return true;
+        }
+        return false;
+    };
+    const didActivate = vscode.workspace.textDocuments.some(maybeActivate);
+    if (!didActivate) {
+        const openListener = vscode.workspace.onDidOpenTextDocument(doc => {
+            if (maybeActivate(doc)) {
+                openListener.dispose();
+            }
+        }, undefined, disposables);
+    }
+    return vscode.Disposable.from(...disposables);
 }
-
-function isSupportedDocument(
-	supportedLanguage: readonly string[],
-	document: vscode.TextDocument
-): boolean {
-	return supportedLanguage.indexOf(document.languageId) >= 0
-		&& !fileSchemes.disabledSchemes.has(document.uri.scheme);
+function isSupportedDocument(supportedLanguage: readonly string[], document: vscode.TextDocument): boolean {
+    return supportedLanguage.indexOf(document.languageId) >= 0
+        && !fileSchemes.disabledSchemes.has(document.uri.scheme);
 }
diff --git a/extensions/typescript-language-features/Source/logging/logLevelMonitor.ts b/extensions/typescript-language-features/Source/logging/logLevelMonitor.ts
index 09d566b05bfea..a9854736f637a 100644
--- a/extensions/typescript-language-features/Source/logging/logLevelMonitor.ts
+++ b/extensions/typescript-language-features/Source/logging/logLevelMonitor.ts
@@ -2,99 +2,81 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TsServerLogLevel } from '../configuration/configuration';
 import { Disposable } from '../utils/dispose';
-
-
 export class LogLevelMonitor extends Disposable {
-
-	private static readonly logLevelConfigKey = 'typescript.tsserver.log';
-	private static readonly logLevelChangedStorageKey = 'typescript.tsserver.logLevelChanged';
-	private static readonly doNotPromptLogLevelStorageKey = 'typescript.tsserver.doNotPromptLogLevel';
-
-	constructor(private readonly context: vscode.ExtensionContext) {
-		super();
-
-		this._register(vscode.workspace.onDidChangeConfiguration(this.onConfigurationChange, this, this._disposables));
-
-		if (this.shouldNotifyExtendedLogging()) {
-			this.notifyExtendedLogging();
-		}
-	}
-
-	private onConfigurationChange(event: vscode.ConfigurationChangeEvent) {
-		const logLevelChanged = event.affectsConfiguration(LogLevelMonitor.logLevelConfigKey);
-		if (!logLevelChanged) {
-			return;
-		}
-		this.context.globalState.update(LogLevelMonitor.logLevelChangedStorageKey, new Date());
-	}
-
-	private get logLevel(): TsServerLogLevel {
-		return TsServerLogLevel.fromString(vscode.workspace.getConfiguration().get(LogLevelMonitor.logLevelConfigKey, 'off'));
-	}
-
-	/**
-	 * Last date change if it exists and can be parsed as a date,
-	 * otherwise undefined.
-	 */
-	private get lastLogLevelChange(): Date | undefined {
-		const lastChange = this.context.globalState.get(LogLevelMonitor.logLevelChangedStorageKey);
-
-		if (lastChange) {
-			const date = new Date(lastChange);
-			if (date instanceof Date && !isNaN(date.valueOf())) {
-				return date;
-			}
-		}
-		return undefined;
-	}
-
-	private get doNotPrompt(): boolean {
-		return this.context.globalState.get(LogLevelMonitor.doNotPromptLogLevelStorageKey) || false;
-	}
-
-	private shouldNotifyExtendedLogging(): boolean {
-		const lastChangeMilliseconds = this.lastLogLevelChange ? new Date(this.lastLogLevelChange).valueOf() : 0;
-		const lastChangePlusOneWeek = new Date(lastChangeMilliseconds + /* 7 days in milliseconds */ 86400000 * 7);
-
-		if (!this.doNotPrompt && this.logLevel !== TsServerLogLevel.Off && lastChangePlusOneWeek.valueOf() < Date.now()) {
-			return true;
-		}
-		return false;
-	}
-
-	private notifyExtendedLogging() {
-		const enum Choice {
-			DisableLogging = 0,
-			DoNotShowAgain = 1
-		}
-		interface Item extends vscode.MessageItem {
-			readonly choice: Choice;
-		}
-
-		vscode.window.showInformationMessage(
-			vscode.l10n.t("TS Server logging is currently enabled which may impact performance."),
-			{
-				title: vscode.l10n.t("Disable logging"),
-				choice: Choice.DisableLogging
-			},
-			{
-				title: vscode.l10n.t("Don't show again"),
-				choice: Choice.DoNotShowAgain
-			})
-			.then(selection => {
-				if (!selection) {
-					return;
-				}
-				if (selection.choice === Choice.DisableLogging) {
-					return vscode.workspace.getConfiguration().update(LogLevelMonitor.logLevelConfigKey, 'off', true);
-				} else if (selection.choice === Choice.DoNotShowAgain) {
-					return this.context.globalState.update(LogLevelMonitor.doNotPromptLogLevelStorageKey, true);
-				}
-				return;
-			});
-	}
+    private static readonly logLevelConfigKey = 'typescript.tsserver.log';
+    private static readonly logLevelChangedStorageKey = 'typescript.tsserver.logLevelChanged';
+    private static readonly doNotPromptLogLevelStorageKey = 'typescript.tsserver.doNotPromptLogLevel';
+    constructor(private readonly context: vscode.ExtensionContext) {
+        super();
+        this._register(vscode.workspace.onDidChangeConfiguration(this.onConfigurationChange, this, this._disposables));
+        if (this.shouldNotifyExtendedLogging()) {
+            this.notifyExtendedLogging();
+        }
+    }
+    private onConfigurationChange(event: vscode.ConfigurationChangeEvent) {
+        const logLevelChanged = event.affectsConfiguration(LogLevelMonitor.logLevelConfigKey);
+        if (!logLevelChanged) {
+            return;
+        }
+        this.context.globalState.update(LogLevelMonitor.logLevelChangedStorageKey, new Date());
+    }
+    private get logLevel(): TsServerLogLevel {
+        return TsServerLogLevel.fromString(vscode.workspace.getConfiguration().get(LogLevelMonitor.logLevelConfigKey, 'off'));
+    }
+    /**
+     * Last date change if it exists and can be parsed as a date,
+     * otherwise undefined.
+     */
+    private get lastLogLevelChange(): Date | undefined {
+        const lastChange = this.context.globalState.get(LogLevelMonitor.logLevelChangedStorageKey);
+        if (lastChange) {
+            const date = new Date(lastChange);
+            if (date instanceof Date && !isNaN(date.valueOf())) {
+                return date;
+            }
+        }
+        return undefined;
+    }
+    private get doNotPrompt(): boolean {
+        return this.context.globalState.get(LogLevelMonitor.doNotPromptLogLevelStorageKey) || false;
+    }
+    private shouldNotifyExtendedLogging(): boolean {
+        const lastChangeMilliseconds = this.lastLogLevelChange ? new Date(this.lastLogLevelChange).valueOf() : 0;
+        const lastChangePlusOneWeek = new Date(lastChangeMilliseconds + /* 7 days in milliseconds */ 86400000 * 7);
+        if (!this.doNotPrompt && this.logLevel !== TsServerLogLevel.Off && lastChangePlusOneWeek.valueOf() < Date.now()) {
+            return true;
+        }
+        return false;
+    }
+    private notifyExtendedLogging() {
+        const enum Choice {
+            DisableLogging = 0,
+            DoNotShowAgain = 1
+        }
+        interface Item extends vscode.MessageItem {
+            readonly choice: Choice;
+        }
+        vscode.window.showInformationMessage(vscode.l10n.t("TS Server logging is currently enabled which may impact performance."), {
+            title: vscode.l10n.t("Disable logging"),
+            choice: Choice.DisableLogging
+        }, {
+            title: vscode.l10n.t("Don't show again"),
+            choice: Choice.DoNotShowAgain
+        })
+            .then(selection => {
+            if (!selection) {
+                return;
+            }
+            if (selection.choice === Choice.DisableLogging) {
+                return vscode.workspace.getConfiguration().update(LogLevelMonitor.logLevelConfigKey, 'off', true);
+            }
+            else if (selection.choice === Choice.DoNotShowAgain) {
+                return this.context.globalState.update(LogLevelMonitor.doNotPromptLogLevelStorageKey, true);
+            }
+            return;
+        });
+    }
 }
diff --git a/extensions/typescript-language-features/Source/logging/logger.ts b/extensions/typescript-language-features/Source/logging/logger.ts
index 33139b1553701..cfb37c44b03fd 100644
--- a/extensions/typescript-language-features/Source/logging/logger.ts
+++ b/extensions/typescript-language-features/Source/logging/logger.ts
@@ -2,34 +2,27 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { memoize } from '../utils/memoize';
-
 export class Logger {
-
-	@memoize
-	private get output(): vscode.LogOutputChannel {
-		return vscode.window.createOutputChannel('TypeScript', { log: true });
-	}
-
-	public get logLevel(): vscode.LogLevel {
-		return this.output.logLevel;
-	}
-
-	public info(message: string, ...args: any[]): void {
-		this.output.info(message, ...args);
-	}
-
-	public trace(message: string, ...args: any[]): void {
-		this.output.trace(message, ...args);
-	}
-
-	public error(message: string, data?: any): void {
-		// See https://github.com/microsoft/TypeScript/issues/10496
-		if (data && data.message === 'No content available.') {
-			return;
-		}
-		this.output.error(message, ...(data ? [data] : []));
-	}
+    @memoize
+    private get output(): vscode.LogOutputChannel {
+        return vscode.window.createOutputChannel('TypeScript', { log: true });
+    }
+    public get logLevel(): vscode.LogLevel {
+        return this.output.logLevel;
+    }
+    public info(message: string, ...args: any[]): void {
+        this.output.info(message, ...args);
+    }
+    public trace(message: string, ...args: any[]): void {
+        this.output.trace(message, ...args);
+    }
+    public error(message: string, data?: any): void {
+        // See https://github.com/microsoft/TypeScript/issues/10496
+        if (data && data.message === 'No content available.') {
+            return;
+        }
+        this.output.error(message, ...(data ? [data] : []));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/logging/telemetry.ts b/extensions/typescript-language-features/Source/logging/telemetry.ts
index 4b2d3867f6d4d..6362e7fc63511 100644
--- a/extensions/typescript-language-features/Source/logging/telemetry.ts
+++ b/extensions/typescript-language-features/Source/logging/telemetry.ts
@@ -2,36 +2,28 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { IExperimentationTelemetryReporter } from '../experimentTelemetryReporter';
-
 export interface TelemetryProperties {
-	readonly [prop: string]: string | number | boolean | undefined;
+    readonly [prop: string]: string | number | boolean | undefined;
 }
-
 export interface TelemetryReporter {
-	logTelemetry(eventName: string, properties?: TelemetryProperties): void;
+    logTelemetry(eventName: string, properties?: TelemetryProperties): void;
 }
-
 export class VSCodeTelemetryReporter implements TelemetryReporter {
-	constructor(
-		private readonly reporter: IExperimentationTelemetryReporter | undefined,
-		private readonly clientVersionDelegate: () => string
-	) { }
-
-	public logTelemetry(eventName: string, properties: { [prop: string]: string } = {}) {
-		const reporter = this.reporter;
-		if (!reporter) {
-			return;
-		}
-
-		/* __GDPR__FRAGMENT__
-			"TypeScriptCommonProperties" : {
-				"version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-			}
-		*/
-		properties['version'] = this.clientVersionDelegate();
-
-		reporter.postEventObj(eventName, properties);
-	}
+    constructor(private readonly reporter: IExperimentationTelemetryReporter | undefined, private readonly clientVersionDelegate: () => string) { }
+    public logTelemetry(eventName: string, properties: {
+        [prop: string]: string;
+    } = {}) {
+        const reporter = this.reporter;
+        if (!reporter) {
+            return;
+        }
+        /* __GDPR__FRAGMENT__
+            "TypeScriptCommonProperties" : {
+                "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+            }
+        */
+        properties['version'] = this.clientVersionDelegate();
+        reporter.postEventObj(eventName, properties);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/logging/tracer.ts b/extensions/typescript-language-features/Source/logging/tracer.ts
index e273181075da9..6a00fff7b5f56 100644
--- a/extensions/typescript-language-features/Source/logging/tracer.ts
+++ b/extensions/typescript-language-features/Source/logging/tracer.ts
@@ -2,49 +2,38 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import type * as Proto from '../tsServer/protocol/protocol';
 import { Disposable } from '../utils/dispose';
 import { Logger } from './logger';
-
 interface RequestExecutionMetadata {
-	readonly queuingStartTime: number;
+    readonly queuingStartTime: number;
 }
-
 export default class Tracer extends Disposable {
-
-	constructor(
-		private readonly logger: Logger
-	) {
-		super();
-	}
-
-	public traceRequest(serverId: string, request: Proto.Request, responseExpected: boolean, queueLength: number): void {
-		if (this.logger.logLevel === vscode.LogLevel.Trace) {
-			this.trace(serverId, `Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, request.arguments);
-		}
-	}
-
-	public traceResponse(serverId: string, response: Proto.Response, meta: RequestExecutionMetadata): void {
-		if (this.logger.logLevel === vscode.LogLevel.Trace) {
-			this.trace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, response.body);
-		}
-	}
-
-	public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): any {
-		if (this.logger.logLevel === vscode.LogLevel.Trace) {
-			this.trace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms.`);
-		}
-	}
-
-	public traceEvent(serverId: string, event: Proto.Event): void {
-		if (this.logger.logLevel === vscode.LogLevel.Trace) {
-			this.trace(serverId, `Event received: ${event.event} (${event.seq}).`, event.body);
-		}
-	}
-
-	public trace(serverId: string, message: string, data?: unknown): void {
-		this.logger.trace(`<${serverId}> ${message}`, ...(data ? [JSON.stringify(data, null, 4)] : []));
-	}
+    constructor(private readonly logger: Logger) {
+        super();
+    }
+    public traceRequest(serverId: string, request: Proto.Request, responseExpected: boolean, queueLength: number): void {
+        if (this.logger.logLevel === vscode.LogLevel.Trace) {
+            this.trace(serverId, `Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, request.arguments);
+        }
+    }
+    public traceResponse(serverId: string, response: Proto.Response, meta: RequestExecutionMetadata): void {
+        if (this.logger.logLevel === vscode.LogLevel.Trace) {
+            this.trace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, response.body);
+        }
+    }
+    public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): any {
+        if (this.logger.logLevel === vscode.LogLevel.Trace) {
+            this.trace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms.`);
+        }
+    }
+    public traceEvent(serverId: string, event: Proto.Event): void {
+        if (this.logger.logLevel === vscode.LogLevel.Trace) {
+            this.trace(serverId, `Event received: ${event.event} (${event.seq}).`, event.body);
+        }
+    }
+    public trace(serverId: string, message: string, data?: unknown): void {
+        this.logger.trace(`<${serverId}> ${message}`, ...(data ? [JSON.stringify(data, null, 4)] : []));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/remoteRepositories.browser.ts b/extensions/typescript-language-features/Source/remoteRepositories.browser.ts
index b9b0d05a22cd3..b20cc5ebb40e7 100644
--- a/extensions/typescript-language-features/Source/remoteRepositories.browser.ts
+++ b/extensions/typescript-language-features/Source/remoteRepositories.browser.ts
@@ -2,41 +2,30 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { Extension, extensions, Uri } from 'vscode';
-
 export interface RemoteHubApi {
-	getProviderUri(uri: Uri): Uri;
-	getProviderRootUri(uri: Uri): Uri;
-
-	getVirtualUri(uri: Uri): Uri;
-	getVirtualWorkspaceUri(uri: Uri): Uri | undefined;
-
-	loadWorkspaceContents?(workspaceUri: Uri): Promise;
+    getProviderUri(uri: Uri): Uri;
+    getProviderRootUri(uri: Uri): Uri;
+    getVirtualUri(uri: Uri): Uri;
+    getVirtualWorkspaceUri(uri: Uri): Uri | undefined;
+    loadWorkspaceContents?(workspaceUri: Uri): Promise;
 }
-
 namespace RemoteRepositories {
-
-	let remoteHub: Extension | undefined;
-
-	function getRemoteExtension(): Extension {
-		if (remoteHub !== undefined) {
-			return remoteHub;
-		}
-
-		remoteHub = extensions.getExtension('ms-vscode.remote-repositories')
-			?? extensions.getExtension('GitHub.remoteHub')
-			?? extensions.getExtension('GitHub.remoteHub-insiders');
-
-		if (remoteHub === undefined) {
-			throw new Error(`No Remote repository extension found.`);
-		}
-		return remoteHub;
-	}
-
-	export function getApi(): Thenable {
-		return getRemoteExtension().activate();
-	}
+    let remoteHub: Extension | undefined;
+    function getRemoteExtension(): Extension {
+        if (remoteHub !== undefined) {
+            return remoteHub;
+        }
+        remoteHub = extensions.getExtension('ms-vscode.remote-repositories')
+            ?? extensions.getExtension('GitHub.remoteHub')
+            ?? extensions.getExtension('GitHub.remoteHub-insiders');
+        if (remoteHub === undefined) {
+            throw new Error(`No Remote repository extension found.`);
+        }
+        return remoteHub;
+    }
+    export function getApi(): Thenable {
+        return getRemoteExtension().activate();
+    }
 }
-
 export default RemoteRepositories;
diff --git a/extensions/typescript-language-features/Source/task/taskProvider.ts b/extensions/typescript-language-features/Source/task/taskProvider.ts
index 3cf0e3328fa09..cc7c9dc236ffc 100644
--- a/extensions/typescript-language-features/Source/task/taskProvider.ts
+++ b/extensions/typescript-language-features/Source/task/taskProvider.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as jsonc from 'jsonc-parser';
 import * as path from 'path';
 import * as vscode from 'vscode';
@@ -15,287 +14,225 @@ import { isTsConfigFileName } from '../configuration/languageDescription';
 import { Lazy } from '../utils/lazy';
 import { isImplicitProjectConfigFile } from '../tsconfig';
 import { TSConfig, TsConfigProvider } from './tsconfigProvider';
-
-
 enum AutoDetect {
-	on = 'on',
-	off = 'off',
-	build = 'build',
-	watch = 'watch'
+    on = 'on',
+    off = 'off',
+    build = 'build',
+    watch = 'watch'
 }
-
-
 interface TypeScriptTaskDefinition extends vscode.TaskDefinition {
-	tsconfig: string;
-	option?: string;
+    tsconfig: string;
+    option?: string;
 }
-
 /**
  * Provides tasks for building `tsconfig.json` files in a project.
  */
 class TscTaskProvider extends Disposable implements vscode.TaskProvider {
-
-	private readonly projectInfoRequestTimeout = 2000;
-	private readonly findConfigFilesTimeout = 5000;
-
-	private autoDetect = AutoDetect.on;
-	private readonly tsconfigProvider: TsConfigProvider;
-
-	public constructor(
-		private readonly client: Lazy
-	) {
-		super();
-		this.tsconfigProvider = new TsConfigProvider();
-
-		this._register(vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this));
-		this.onConfigurationChanged();
-	}
-
-	public async provideTasks(token: vscode.CancellationToken): Promise {
-		const folders = vscode.workspace.workspaceFolders;
-		if ((this.autoDetect === AutoDetect.off) || !folders?.length) {
-			return [];
-		}
-
-		const configPaths: Set = new Set();
-		const tasks: vscode.Task[] = [];
-		for (const project of await this.getAllTsConfigs(token)) {
-			if (!configPaths.has(project.fsPath)) {
-				configPaths.add(project.fsPath);
-				tasks.push(...(await this.getTasksForProject(project)));
-			}
-		}
-		return tasks;
-	}
-
-	public async resolveTask(task: vscode.Task): Promise {
-		const definition = task.definition;
-		if (/\\tsconfig.*\.json/.test(definition.tsconfig)) {
-			// Warn that the task has the wrong slash type
-			vscode.window.showWarningMessage(vscode.l10n.t("TypeScript Task in tasks.json contains \"\\\\\". TypeScript tasks tsconfig must use \"/\""));
-			return undefined;
-		}
-
-		const tsconfigPath = definition.tsconfig;
-		if (!tsconfigPath) {
-			return undefined;
-		}
-
-		if (task.scope === undefined || task.scope === vscode.TaskScope.Global || task.scope === vscode.TaskScope.Workspace) {
-			// scope is required to be a WorkspaceFolder for resolveTask
-			return undefined;
-		}
-		const tsconfigUri = task.scope.uri.with({ path: task.scope.uri.path + '/' + tsconfigPath });
-		const tsconfig: TSConfig = {
-			uri: tsconfigUri,
-			fsPath: tsconfigUri.fsPath,
-			posixPath: tsconfigUri.path,
-			workspaceFolder: task.scope
-		};
-		return this.getTasksForProjectAndDefinition(tsconfig, definition);
-	}
-
-	private async getAllTsConfigs(token: vscode.CancellationToken): Promise {
-		const configs = (await Promise.all([
-			this.getTsConfigForActiveFile(token),
-			this.getTsConfigsInWorkspace(token),
-		])).flat();
-
-		return Promise.all(
-			configs.map(async config => await exists(config.uri) ? config : undefined),
-		).then(coalesce);
-	}
-
-	private async getTsConfigForActiveFile(token: vscode.CancellationToken): Promise {
-		const editor = vscode.window.activeTextEditor;
-		if (editor) {
-			if (isTsConfigFileName(editor.document.fileName)) {
-				const uri = editor.document.uri;
-				return [{
-					uri,
-					fsPath: uri.fsPath,
-					posixPath: uri.path,
-					workspaceFolder: vscode.workspace.getWorkspaceFolder(uri)
-				}];
-			}
-		}
-
-		const file = this.getActiveTypeScriptFile();
-		if (!file) {
-			return [];
-		}
-
-		const response = await Promise.race([
-			this.client.value.execute(
-				'projectInfo',
-				{ file, needFileNameList: false },
-				token),
-			new Promise(resolve => setTimeout(() => resolve(ServerResponse.NoContent), this.projectInfoRequestTimeout))
-		]);
-		if (response.type !== 'response' || !response.body) {
-			return [];
-		}
-
-		const { configFileName } = response.body;
-		if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
-			const normalizedConfigPath = path.normalize(configFileName);
-			const uri = vscode.Uri.file(normalizedConfigPath);
-			const folder = vscode.workspace.getWorkspaceFolder(uri);
-			return [{
-				uri,
-				fsPath: normalizedConfigPath,
-				posixPath: uri.path,
-				workspaceFolder: folder
-			}];
-		}
-
-		return [];
-	}
-
-	private async getTsConfigsInWorkspace(token: vscode.CancellationToken): Promise {
-		const getConfigsTimeout = new vscode.CancellationTokenSource();
-		token.onCancellationRequested(() => getConfigsTimeout.cancel());
-
-		return Promise.race([
-			this.tsconfigProvider.getConfigsForWorkspace(getConfigsTimeout.token).then(x => Array.from(x)),
-			wait(this.findConfigFilesTimeout).then(() => {
-				getConfigsTimeout.cancel();
-				return [];
-			}),
-		]);
-	}
-
-	private static async getCommand(project: TSConfig): Promise {
-		if (project.workspaceFolder) {
-			const localTsc = await TscTaskProvider.getLocalTscAtPath(path.dirname(project.fsPath));
-			if (localTsc) {
-				return localTsc;
-			}
-
-			const workspaceTsc = await TscTaskProvider.getLocalTscAtPath(project.workspaceFolder.uri.fsPath);
-			if (workspaceTsc) {
-				return workspaceTsc;
-			}
-		}
-
-		// Use global tsc version
-		return 'tsc';
-	}
-
-	private static async getLocalTscAtPath(folderPath: string): Promise {
-		const platform = process.platform;
-		const bin = path.join(folderPath, 'node_modules', '.bin');
-		if (platform === 'win32' && await exists(vscode.Uri.file(path.join(bin, 'tsc.cmd')))) {
-			return path.join(bin, 'tsc.cmd');
-		} else if ((platform === 'linux' || platform === 'darwin') && await exists(vscode.Uri.file(path.join(bin, 'tsc')))) {
-			return path.join(bin, 'tsc');
-		}
-		return undefined;
-	}
-
-	private getActiveTypeScriptFile(): string | undefined {
-		const editor = vscode.window.activeTextEditor;
-		if (editor) {
-			const document = editor.document;
-			if (document && (document.languageId === 'typescript' || document.languageId === 'typescriptreact')) {
-				return this.client.value.toTsFilePath(document.uri);
-			}
-		}
-		return undefined;
-	}
-
-	private getBuildTask(workspaceFolder: vscode.WorkspaceFolder | undefined, label: string, command: string, args: string[], buildTaskidentifier: TypeScriptTaskDefinition): vscode.Task {
-		const buildTask = new vscode.Task(
-			buildTaskidentifier,
-			workspaceFolder || vscode.TaskScope.Workspace,
-			vscode.l10n.t("build - {0}", label),
-			'tsc',
-			new vscode.ShellExecution(command, args),
-			'$tsc');
-		buildTask.group = vscode.TaskGroup.Build;
-		buildTask.isBackground = false;
-		return buildTask;
-	}
-
-	private getWatchTask(workspaceFolder: vscode.WorkspaceFolder | undefined, label: string, command: string, args: string[], watchTaskidentifier: TypeScriptTaskDefinition) {
-		const watchTask = new vscode.Task(
-			watchTaskidentifier,
-			workspaceFolder || vscode.TaskScope.Workspace,
-			vscode.l10n.t("watch - {0}", label),
-			'tsc',
-			new vscode.ShellExecution(command, [...args, '--watch']),
-			'$tsc-watch');
-		watchTask.group = vscode.TaskGroup.Build;
-		watchTask.isBackground = true;
-		return watchTask;
-	}
-
-	private async getTasksForProject(project: TSConfig): Promise {
-		const command = await TscTaskProvider.getCommand(project);
-		const args = await this.getBuildShellArgs(project);
-		const label = this.getLabelForTasks(project);
-
-		const tasks: vscode.Task[] = [];
-
-		if (this.autoDetect === AutoDetect.build || this.autoDetect === AutoDetect.on) {
-			tasks.push(this.getBuildTask(project.workspaceFolder, label, command, args, { type: 'typescript', tsconfig: label }));
-		}
-
-		if (this.autoDetect === AutoDetect.watch || this.autoDetect === AutoDetect.on) {
-			tasks.push(this.getWatchTask(project.workspaceFolder, label, command, args, { type: 'typescript', tsconfig: label, option: 'watch' }));
-		}
-
-		return tasks;
-	}
-
-	private async getTasksForProjectAndDefinition(project: TSConfig, definition: TypeScriptTaskDefinition): Promise {
-		const command = await TscTaskProvider.getCommand(project);
-		const args = await this.getBuildShellArgs(project);
-		const label = this.getLabelForTasks(project);
-
-		let task: vscode.Task | undefined;
-
-		if (definition.option === undefined) {
-			task = this.getBuildTask(project.workspaceFolder, label, command, args, definition);
-		} else if (definition.option === 'watch') {
-			task = this.getWatchTask(project.workspaceFolder, label, command, args, definition);
-		}
-
-		return task;
-	}
-
-	private async getBuildShellArgs(project: TSConfig): Promise> {
-		const defaultArgs = ['-p', project.fsPath];
-		try {
-			const bytes = await vscode.workspace.fs.readFile(project.uri);
-			const text = Buffer.from(bytes).toString('utf-8');
-			const tsconfig = jsonc.parse(text);
-			if (tsconfig?.references) {
-				return ['-b', project.fsPath];
-			}
-		} catch {
-			// noops
-		}
-		return defaultArgs;
-	}
-
-	private getLabelForTasks(project: TSConfig): string {
-		if (project.workspaceFolder) {
-			const workspaceNormalizedUri = vscode.Uri.file(path.normalize(project.workspaceFolder.uri.fsPath)); // Make sure the drive letter is lowercase
-			return path.posix.relative(workspaceNormalizedUri.path, project.posixPath);
-		}
-
-		return project.posixPath;
-	}
-
-	private onConfigurationChanged(): void {
-		const type = vscode.workspace.getConfiguration('typescript.tsc').get('autoDetect');
-		this.autoDetect = typeof type === 'undefined' ? AutoDetect.on : type;
-	}
+    private readonly projectInfoRequestTimeout = 2000;
+    private readonly findConfigFilesTimeout = 5000;
+    private autoDetect = AutoDetect.on;
+    private readonly tsconfigProvider: TsConfigProvider;
+    public constructor(private readonly client: Lazy) {
+        super();
+        this.tsconfigProvider = new TsConfigProvider();
+        this._register(vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this));
+        this.onConfigurationChanged();
+    }
+    public async provideTasks(token: vscode.CancellationToken): Promise {
+        const folders = vscode.workspace.workspaceFolders;
+        if ((this.autoDetect === AutoDetect.off) || !folders?.length) {
+            return [];
+        }
+        const configPaths: Set = new Set();
+        const tasks: vscode.Task[] = [];
+        for (const project of await this.getAllTsConfigs(token)) {
+            if (!configPaths.has(project.fsPath)) {
+                configPaths.add(project.fsPath);
+                tasks.push(...(await this.getTasksForProject(project)));
+            }
+        }
+        return tasks;
+    }
+    public async resolveTask(task: vscode.Task): Promise {
+        const definition = task.definition;
+        if (/\\tsconfig.*\.json/.test(definition.tsconfig)) {
+            // Warn that the task has the wrong slash type
+            vscode.window.showWarningMessage(vscode.l10n.t("TypeScript Task in tasks.json contains \"\\\\\". TypeScript tasks tsconfig must use \"/\""));
+            return undefined;
+        }
+        const tsconfigPath = definition.tsconfig;
+        if (!tsconfigPath) {
+            return undefined;
+        }
+        if (task.scope === undefined || task.scope === vscode.TaskScope.Global || task.scope === vscode.TaskScope.Workspace) {
+            // scope is required to be a WorkspaceFolder for resolveTask
+            return undefined;
+        }
+        const tsconfigUri = task.scope.uri.with({ path: task.scope.uri.path + '/' + tsconfigPath });
+        const tsconfig: TSConfig = {
+            uri: tsconfigUri,
+            fsPath: tsconfigUri.fsPath,
+            posixPath: tsconfigUri.path,
+            workspaceFolder: task.scope
+        };
+        return this.getTasksForProjectAndDefinition(tsconfig, definition);
+    }
+    private async getAllTsConfigs(token: vscode.CancellationToken): Promise {
+        const configs = (await Promise.all([
+            this.getTsConfigForActiveFile(token),
+            this.getTsConfigsInWorkspace(token),
+        ])).flat();
+        return Promise.all(configs.map(async (config) => await exists(config.uri) ? config : undefined)).then(coalesce);
+    }
+    private async getTsConfigForActiveFile(token: vscode.CancellationToken): Promise {
+        const editor = vscode.window.activeTextEditor;
+        if (editor) {
+            if (isTsConfigFileName(editor.document.fileName)) {
+                const uri = editor.document.uri;
+                return [{
+                        uri,
+                        fsPath: uri.fsPath,
+                        posixPath: uri.path,
+                        workspaceFolder: vscode.workspace.getWorkspaceFolder(uri)
+                    }];
+            }
+        }
+        const file = this.getActiveTypeScriptFile();
+        if (!file) {
+            return [];
+        }
+        const response = await Promise.race([
+            this.client.value.execute('projectInfo', { file, needFileNameList: false }, token),
+            new Promise(resolve => setTimeout(() => resolve(ServerResponse.NoContent), this.projectInfoRequestTimeout))
+        ]);
+        if (response.type !== 'response' || !response.body) {
+            return [];
+        }
+        const { configFileName } = response.body;
+        if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
+            const normalizedConfigPath = path.normalize(configFileName);
+            const uri = vscode.Uri.file(normalizedConfigPath);
+            const folder = vscode.workspace.getWorkspaceFolder(uri);
+            return [{
+                    uri,
+                    fsPath: normalizedConfigPath,
+                    posixPath: uri.path,
+                    workspaceFolder: folder
+                }];
+        }
+        return [];
+    }
+    private async getTsConfigsInWorkspace(token: vscode.CancellationToken): Promise {
+        const getConfigsTimeout = new vscode.CancellationTokenSource();
+        token.onCancellationRequested(() => getConfigsTimeout.cancel());
+        return Promise.race([
+            this.tsconfigProvider.getConfigsForWorkspace(getConfigsTimeout.token).then(x => Array.from(x)),
+            wait(this.findConfigFilesTimeout).then(() => {
+                getConfigsTimeout.cancel();
+                return [];
+            }),
+        ]);
+    }
+    private static async getCommand(project: TSConfig): Promise {
+        if (project.workspaceFolder) {
+            const localTsc = await TscTaskProvider.getLocalTscAtPath(path.dirname(project.fsPath));
+            if (localTsc) {
+                return localTsc;
+            }
+            const workspaceTsc = await TscTaskProvider.getLocalTscAtPath(project.workspaceFolder.uri.fsPath);
+            if (workspaceTsc) {
+                return workspaceTsc;
+            }
+        }
+        // Use global tsc version
+        return 'tsc';
+    }
+    private static async getLocalTscAtPath(folderPath: string): Promise {
+        const platform = process.platform;
+        const bin = path.join(folderPath, 'node_modules', '.bin');
+        if (platform === 'win32' && await exists(vscode.Uri.file(path.join(bin, 'tsc.cmd')))) {
+            return path.join(bin, 'tsc.cmd');
+        }
+        else if ((platform === 'linux' || platform === 'darwin') && await exists(vscode.Uri.file(path.join(bin, 'tsc')))) {
+            return path.join(bin, 'tsc');
+        }
+        return undefined;
+    }
+    private getActiveTypeScriptFile(): string | undefined {
+        const editor = vscode.window.activeTextEditor;
+        if (editor) {
+            const document = editor.document;
+            if (document && (document.languageId === 'typescript' || document.languageId === 'typescriptreact')) {
+                return this.client.value.toTsFilePath(document.uri);
+            }
+        }
+        return undefined;
+    }
+    private getBuildTask(workspaceFolder: vscode.WorkspaceFolder | undefined, label: string, command: string, args: string[], buildTaskidentifier: TypeScriptTaskDefinition): vscode.Task {
+        const buildTask = new vscode.Task(buildTaskidentifier, workspaceFolder || vscode.TaskScope.Workspace, vscode.l10n.t("build - {0}", label), 'tsc', new vscode.ShellExecution(command, args), '$tsc');
+        buildTask.group = vscode.TaskGroup.Build;
+        buildTask.isBackground = false;
+        return buildTask;
+    }
+    private getWatchTask(workspaceFolder: vscode.WorkspaceFolder | undefined, label: string, command: string, args: string[], watchTaskidentifier: TypeScriptTaskDefinition) {
+        const watchTask = new vscode.Task(watchTaskidentifier, workspaceFolder || vscode.TaskScope.Workspace, vscode.l10n.t("watch - {0}", label), 'tsc', new vscode.ShellExecution(command, [...args, '--watch']), '$tsc-watch');
+        watchTask.group = vscode.TaskGroup.Build;
+        watchTask.isBackground = true;
+        return watchTask;
+    }
+    private async getTasksForProject(project: TSConfig): Promise {
+        const command = await TscTaskProvider.getCommand(project);
+        const args = await this.getBuildShellArgs(project);
+        const label = this.getLabelForTasks(project);
+        const tasks: vscode.Task[] = [];
+        if (this.autoDetect === AutoDetect.build || this.autoDetect === AutoDetect.on) {
+            tasks.push(this.getBuildTask(project.workspaceFolder, label, command, args, { type: 'typescript', tsconfig: label }));
+        }
+        if (this.autoDetect === AutoDetect.watch || this.autoDetect === AutoDetect.on) {
+            tasks.push(this.getWatchTask(project.workspaceFolder, label, command, args, { type: 'typescript', tsconfig: label, option: 'watch' }));
+        }
+        return tasks;
+    }
+    private async getTasksForProjectAndDefinition(project: TSConfig, definition: TypeScriptTaskDefinition): Promise {
+        const command = await TscTaskProvider.getCommand(project);
+        const args = await this.getBuildShellArgs(project);
+        const label = this.getLabelForTasks(project);
+        let task: vscode.Task | undefined;
+        if (definition.option === undefined) {
+            task = this.getBuildTask(project.workspaceFolder, label, command, args, definition);
+        }
+        else if (definition.option === 'watch') {
+            task = this.getWatchTask(project.workspaceFolder, label, command, args, definition);
+        }
+        return task;
+    }
+    private async getBuildShellArgs(project: TSConfig): Promise> {
+        const defaultArgs = ['-p', project.fsPath];
+        try {
+            const bytes = await vscode.workspace.fs.readFile(project.uri);
+            const text = Buffer.from(bytes).toString('utf-8');
+            const tsconfig = jsonc.parse(text);
+            if (tsconfig?.references) {
+                return ['-b', project.fsPath];
+            }
+        }
+        catch {
+            // noops
+        }
+        return defaultArgs;
+    }
+    private getLabelForTasks(project: TSConfig): string {
+        if (project.workspaceFolder) {
+            const workspaceNormalizedUri = vscode.Uri.file(path.normalize(project.workspaceFolder.uri.fsPath)); // Make sure the drive letter is lowercase
+            return path.posix.relative(workspaceNormalizedUri.path, project.posixPath);
+        }
+        return project.posixPath;
+    }
+    private onConfigurationChanged(): void {
+        const type = vscode.workspace.getConfiguration('typescript.tsc').get('autoDetect');
+        this.autoDetect = typeof type === 'undefined' ? AutoDetect.on : type;
+    }
 }
-
-export function register(
-	lazyClient: Lazy,
-) {
-	return vscode.tasks.registerTaskProvider('typescript', new TscTaskProvider(lazyClient));
+export function register(lazyClient: Lazy) {
+    return vscode.tasks.registerTaskProvider('typescript', new TscTaskProvider(lazyClient));
 }
diff --git a/extensions/typescript-language-features/Source/task/tsconfigProvider.ts b/extensions/typescript-language-features/Source/task/tsconfigProvider.ts
index 16dca92cc62c4..fc2290ce17a6d 100644
--- a/extensions/typescript-language-features/Source/task/tsconfigProvider.ts
+++ b/extensions/typescript-language-features/Source/task/tsconfigProvider.ts
@@ -2,38 +2,33 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export interface TSConfig {
-	readonly uri: vscode.Uri;
-	readonly fsPath: string;
-	readonly posixPath: string;
-	readonly workspaceFolder?: vscode.WorkspaceFolder;
+    readonly uri: vscode.Uri;
+    readonly fsPath: string;
+    readonly posixPath: string;
+    readonly workspaceFolder?: vscode.WorkspaceFolder;
 }
-
 export class TsConfigProvider {
-	public async getConfigsForWorkspace(token: vscode.CancellationToken): Promise> {
-		if (!vscode.workspace.workspaceFolders) {
-			return [];
-		}
-
-		const configs = new Map();
-		for (const config of await this.findConfigFiles(token)) {
-			const root = vscode.workspace.getWorkspaceFolder(config);
-			if (root) {
-				configs.set(config.fsPath, {
-					uri: config,
-					fsPath: config.fsPath,
-					posixPath: config.path,
-					workspaceFolder: root
-				});
-			}
-		}
-		return configs.values();
-	}
-
-	private async findConfigFiles(token: vscode.CancellationToken): Promise {
-		return await vscode.workspace.findFiles('**/tsconfig*.json', '**/{node_modules,.*}/**', undefined, token);
-	}
+    public async getConfigsForWorkspace(token: vscode.CancellationToken): Promise> {
+        if (!vscode.workspace.workspaceFolders) {
+            return [];
+        }
+        const configs = new Map();
+        for (const config of await this.findConfigFiles(token)) {
+            const root = vscode.workspace.getWorkspaceFolder(config);
+            if (root) {
+                configs.set(config.fsPath, {
+                    uri: config,
+                    fsPath: config.fsPath,
+                    posixPath: config.path,
+                    workspaceFolder: root
+                });
+            }
+        }
+        return configs.values();
+    }
+    private async findConfigFiles(token: vscode.CancellationToken): Promise {
+        return await vscode.workspace.findFiles('**/tsconfig*.json', '**/{node_modules,.*}/**', undefined, token);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/test-all.ts b/extensions/typescript-language-features/Source/test-all.ts
index 71e88e4a60d9a..81ff127b7df11 100644
--- a/extensions/typescript-language-features/Source/test-all.ts
+++ b/extensions/typescript-language-features/Source/test-all.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 //
 // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
 //
@@ -14,15 +13,12 @@
 // host can call to run the tests. The test runner is expected to use console.log
 // to report the results back to the caller. When the tests are finished, return
 // a possible error to the callback or null if none.
-
 const testRunner = require('../../../test/integration/electron/testrunner');
-
 // You can directly control Mocha options by uncommenting the following lines
 // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
 testRunner.configure({
-	ui: 'tdd', 		// the TDD UI is being used in extension.test.ts (suite, test, etc.)
-	color: true,
-	timeout: 60000,
+    ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
+    color: true,
+    timeout: 60000,
 });
-
 export = testRunner;
diff --git a/extensions/typescript-language-features/Source/tsServer/api.ts b/extensions/typescript-language-features/Source/tsServer/api.ts
index 4ddc29944f0a4..6ec6742da67a3 100644
--- a/extensions/typescript-language-features/Source/tsServer/api.ts
+++ b/extensions/typescript-language-features/Source/tsServer/api.ts
@@ -2,79 +2,65 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as semver from 'semver';
 import * as vscode from 'vscode';
-
-
 export class API {
-	public static fromSimpleString(value: string): API {
-		return new API(value, value, value);
-	}
-
-	public static readonly defaultVersion = API.fromSimpleString('1.0.0');
-	public static readonly v380 = API.fromSimpleString('3.8.0');
-	public static readonly v390 = API.fromSimpleString('3.9.0');
-	public static readonly v400 = API.fromSimpleString('4.0.0');
-	public static readonly v401 = API.fromSimpleString('4.0.1');
-	public static readonly v420 = API.fromSimpleString('4.2.0');
-	public static readonly v430 = API.fromSimpleString('4.3.0');
-	public static readonly v440 = API.fromSimpleString('4.4.0');
-	public static readonly v460 = API.fromSimpleString('4.6.0');
-	public static readonly v470 = API.fromSimpleString('4.7.0');
-	public static readonly v490 = API.fromSimpleString('4.9.0');
-	public static readonly v500 = API.fromSimpleString('5.0.0');
-	public static readonly v510 = API.fromSimpleString('5.1.0');
-	public static readonly v520 = API.fromSimpleString('5.2.0');
-	public static readonly v544 = API.fromSimpleString('5.4.4');
-	public static readonly v540 = API.fromSimpleString('5.4.0');
-	public static readonly v560 = API.fromSimpleString('5.6.0');
-	public static readonly v570 = API.fromSimpleString('5.7.0');
-
-	public static fromVersionString(versionString: string): API {
-		let version = semver.valid(versionString);
-		if (!version) {
-			return new API(vscode.l10n.t("invalid version"), '1.0.0', '1.0.0');
-		}
-
-		// Cut off any prerelease tag since we sometimes consume those on purpose.
-		const index = versionString.indexOf('-');
-		if (index >= 0) {
-			version = version.substr(0, index);
-		}
-		return new API(versionString, version, versionString);
-	}
-
-	private constructor(
-		/**
-		 * Human readable string for the current version. Displayed in the UI
-		 */
-		public readonly displayName: string,
-
-		/**
-		 * Semver version, e.g. '3.9.0'
-		 */
-		public readonly version: string,
-
-		/**
-		 * Full version string including pre-release tags, e.g. '3.9.0-beta'
-		 */
-		public readonly fullVersionString: string,
-	) { }
-
-	public eq(other: API): boolean {
-		return semver.eq(this.version, other.version);
-	}
-
-	public gte(other: API): boolean {
-		return semver.gte(this.version, other.version);
-	}
-
-	public lt(other: API): boolean {
-		return !this.gte(other);
-	}
-
-	public isYarnPnp(): boolean {
-		return this.fullVersionString.includes('-sdk');
-	}
+    public static fromSimpleString(value: string): API {
+        return new API(value, value, value);
+    }
+    public static readonly defaultVersion = API.fromSimpleString('1.0.0');
+    public static readonly v380 = API.fromSimpleString('3.8.0');
+    public static readonly v390 = API.fromSimpleString('3.9.0');
+    public static readonly v400 = API.fromSimpleString('4.0.0');
+    public static readonly v401 = API.fromSimpleString('4.0.1');
+    public static readonly v420 = API.fromSimpleString('4.2.0');
+    public static readonly v430 = API.fromSimpleString('4.3.0');
+    public static readonly v440 = API.fromSimpleString('4.4.0');
+    public static readonly v460 = API.fromSimpleString('4.6.0');
+    public static readonly v470 = API.fromSimpleString('4.7.0');
+    public static readonly v490 = API.fromSimpleString('4.9.0');
+    public static readonly v500 = API.fromSimpleString('5.0.0');
+    public static readonly v510 = API.fromSimpleString('5.1.0');
+    public static readonly v520 = API.fromSimpleString('5.2.0');
+    public static readonly v544 = API.fromSimpleString('5.4.4');
+    public static readonly v540 = API.fromSimpleString('5.4.0');
+    public static readonly v560 = API.fromSimpleString('5.6.0');
+    public static readonly v570 = API.fromSimpleString('5.7.0');
+    public static fromVersionString(versionString: string): API {
+        let version = semver.valid(versionString);
+        if (!version) {
+            return new API(vscode.l10n.t("invalid version"), '1.0.0', '1.0.0');
+        }
+        // Cut off any prerelease tag since we sometimes consume those on purpose.
+        const index = versionString.indexOf('-');
+        if (index >= 0) {
+            version = version.substr(0, index);
+        }
+        return new API(versionString, version, versionString);
+    }
+    private constructor(
+    /**
+     * Human readable string for the current version. Displayed in the UI
+     */
+    public readonly displayName: string, 
+    /**
+     * Semver version, e.g. '3.9.0'
+     */
+    public readonly version: string, 
+    /**
+     * Full version string including pre-release tags, e.g. '3.9.0-beta'
+     */
+    public readonly fullVersionString: string) { }
+    public eq(other: API): boolean {
+        return semver.eq(this.version, other.version);
+    }
+    public gte(other: API): boolean {
+        return semver.gte(this.version, other.version);
+    }
+    public lt(other: API): boolean {
+        return !this.gte(other);
+    }
+    public isYarnPnp(): boolean {
+        return this.fullVersionString.includes('-sdk');
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/Source/tsServer/bufferSyncSupport.ts
index 56d2896fa339f..00745764d3923 100644
--- a/extensions/typescript-language-features/Source/tsServer/bufferSyncSupport.ts
+++ b/extensions/typescript-language-features/Source/tsServer/bufferSyncSupport.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as fileSchemes from '../configuration/fileSchemes';
 import * as languageModeIds from '../configuration/languageIds';
@@ -16,759 +15,620 @@ import { Disposable } from '../utils/dispose';
 import { ResourceMap } from '../utils/resourceMap';
 import { API } from './api';
 import type * as Proto from './protocol/protocol';
-
 type ScriptKind = 'TS' | 'TSX' | 'JS' | 'JSX';
-
 function mode2ScriptKind(mode: string): ScriptKind | undefined {
-	switch (mode) {
-		case languageModeIds.typescript: return 'TS';
-		case languageModeIds.typescriptreact: return 'TSX';
-		case languageModeIds.javascript: return 'JS';
-		case languageModeIds.javascriptreact: return 'JSX';
-	}
-	return undefined;
+    switch (mode) {
+        case languageModeIds.typescript: return 'TS';
+        case languageModeIds.typescriptreact: return 'TSX';
+        case languageModeIds.javascript: return 'JS';
+        case languageModeIds.javascriptreact: return 'JSX';
+    }
+    return undefined;
+}
+const enum BufferState {
+    Initial,
+    Open,
+    Closed
+}
+const enum BufferOperationType {
+    Close,
+    Open,
+    Change
 }
-
-const enum BufferState { Initial, Open, Closed }
-
-const enum BufferOperationType { Close, Open, Change }
-
 class CloseOperation {
-	readonly type = BufferOperationType.Close;
-	constructor(
-		public readonly args: string,
-		public readonly scriptKind: ScriptKind | undefined,
-	) { }
+    readonly type = BufferOperationType.Close;
+    constructor(public readonly args: string, public readonly scriptKind: ScriptKind | undefined) { }
 }
-
 class OpenOperation {
-	readonly type = BufferOperationType.Open;
-	constructor(
-		public readonly args: Proto.OpenRequestArgs,
-		public readonly scriptKind: ScriptKind | undefined,
-	) { }
+    readonly type = BufferOperationType.Open;
+    constructor(public readonly args: Proto.OpenRequestArgs, public readonly scriptKind: ScriptKind | undefined) { }
 }
-
 class ChangeOperation {
-	readonly type = BufferOperationType.Change;
-	constructor(
-		public readonly args: Proto.FileCodeEdits
-	) { }
+    readonly type = BufferOperationType.Change;
+    constructor(public readonly args: Proto.FileCodeEdits) { }
 }
-
 type BufferOperation = CloseOperation | OpenOperation | ChangeOperation;
-
 /**
  * Manages synchronization of buffers with the TS server.
  *
  * If supported, batches together file changes. This allows the TS server to more efficiently process changes.
  */
 class BufferSynchronizer {
-
-	private readonly _pending: ResourceMap;
-
-	constructor(
-		private readonly client: ITypeScriptServiceClient,
-		pathNormalizer: (path: vscode.Uri) => string | undefined,
-		onCaseInsensitiveFileSystem: boolean
-	) {
-		this._pending = new ResourceMap(pathNormalizer, {
-			onCaseInsensitiveFileSystem
-		});
-	}
-
-	public open(resource: vscode.Uri, args: Proto.OpenRequestArgs) {
-		this.updatePending(resource, new OpenOperation(args, args.scriptKindName));
-	}
-
-	/**
-	 * @return Was the buffer open?
-	 */
-	public close(resource: vscode.Uri, filepath: string, scriptKind: ScriptKind | undefined): boolean {
-		return this.updatePending(resource, new CloseOperation(filepath, scriptKind));
-	}
-
-	public change(resource: vscode.Uri, filepath: string, events: readonly vscode.TextDocumentContentChangeEvent[]) {
-		if (!events.length) {
-			return;
-		}
-
-		this.updatePending(resource, new ChangeOperation({
-			fileName: filepath,
-			textChanges: events.map((change): Proto.CodeEdit => ({
-				newText: change.text,
-				start: typeConverters.Position.toLocation(change.range.start),
-				end: typeConverters.Position.toLocation(change.range.end),
-			})).reverse(), // Send the edits end-of-document to start-of-document order
-		}));
-	}
-
-	public reset(): void {
-		this._pending.clear();
-	}
-
-	public beforeCommand(command: string): void {
-		if (command === 'updateOpen') {
-			return;
-		}
-
-		this.flush();
-	}
-
-	private flush() {
-		if (this._pending.size > 0) {
-			const closedFiles: string[] = [];
-			const openFiles: Proto.OpenRequestArgs[] = [];
-			const changedFiles: Proto.FileCodeEdits[] = [];
-			for (const change of this._pending.values()) {
-				switch (change.type) {
-					case BufferOperationType.Change: changedFiles.push(change.args); break;
-					case BufferOperationType.Open: openFiles.push(change.args); break;
-					case BufferOperationType.Close: closedFiles.push(change.args); break;
-				}
-			}
-			this.client.execute('updateOpen', { changedFiles, closedFiles, openFiles }, nulToken, { nonRecoverable: true });
-			this._pending.clear();
-		}
-	}
-
-	private updatePending(resource: vscode.Uri, op: BufferOperation): boolean {
-		switch (op.type) {
-			case BufferOperationType.Close: {
-				const existing = this._pending.get(resource);
-				switch (existing?.type) {
-					case BufferOperationType.Open:
-						if (existing.scriptKind === op.scriptKind) {
-							this._pending.delete(resource);
-							return false; // Open then close. No need to do anything
-						}
-				}
-				break;
-			}
-		}
-
-		if (this._pending.has(resource)) {
-			// we saw this file before, make sure we flush before working with it again
-			this.flush();
-		}
-		this._pending.set(resource, op);
-		return true;
-	}
+    private readonly _pending: ResourceMap;
+    constructor(private readonly client: ITypeScriptServiceClient, pathNormalizer: (path: vscode.Uri) => string | undefined, onCaseInsensitiveFileSystem: boolean) {
+        this._pending = new ResourceMap(pathNormalizer, {
+            onCaseInsensitiveFileSystem
+        });
+    }
+    public open(resource: vscode.Uri, args: Proto.OpenRequestArgs) {
+        this.updatePending(resource, new OpenOperation(args, args.scriptKindName));
+    }
+    /**
+     * @return Was the buffer open?
+     */
+    public close(resource: vscode.Uri, filepath: string, scriptKind: ScriptKind | undefined): boolean {
+        return this.updatePending(resource, new CloseOperation(filepath, scriptKind));
+    }
+    public change(resource: vscode.Uri, filepath: string, events: readonly vscode.TextDocumentContentChangeEvent[]) {
+        if (!events.length) {
+            return;
+        }
+        this.updatePending(resource, new ChangeOperation({
+            fileName: filepath,
+            textChanges: events.map((change): Proto.CodeEdit => ({
+                newText: change.text,
+                start: typeConverters.Position.toLocation(change.range.start),
+                end: typeConverters.Position.toLocation(change.range.end),
+            })).reverse(), // Send the edits end-of-document to start-of-document order
+        }));
+    }
+    public reset(): void {
+        this._pending.clear();
+    }
+    public beforeCommand(command: string): void {
+        if (command === 'updateOpen') {
+            return;
+        }
+        this.flush();
+    }
+    private flush() {
+        if (this._pending.size > 0) {
+            const closedFiles: string[] = [];
+            const openFiles: Proto.OpenRequestArgs[] = [];
+            const changedFiles: Proto.FileCodeEdits[] = [];
+            for (const change of this._pending.values()) {
+                switch (change.type) {
+                    case BufferOperationType.Change:
+                        changedFiles.push(change.args);
+                        break;
+                    case BufferOperationType.Open:
+                        openFiles.push(change.args);
+                        break;
+                    case BufferOperationType.Close:
+                        closedFiles.push(change.args);
+                        break;
+                }
+            }
+            this.client.execute('updateOpen', { changedFiles, closedFiles, openFiles }, nulToken, { nonRecoverable: true });
+            this._pending.clear();
+        }
+    }
+    private updatePending(resource: vscode.Uri, op: BufferOperation): boolean {
+        switch (op.type) {
+            case BufferOperationType.Close: {
+                const existing = this._pending.get(resource);
+                switch (existing?.type) {
+                    case BufferOperationType.Open:
+                        if (existing.scriptKind === op.scriptKind) {
+                            this._pending.delete(resource);
+                            return false; // Open then close. No need to do anything
+                        }
+                }
+                break;
+            }
+        }
+        if (this._pending.has(resource)) {
+            // we saw this file before, make sure we flush before working with it again
+            this.flush();
+        }
+        this._pending.set(resource, op);
+        return true;
+    }
 }
-
 class SyncedBuffer {
-
-	private state = BufferState.Initial;
-
-	constructor(
-		private readonly document: vscode.TextDocument,
-		public readonly filepath: string,
-		private readonly client: ITypeScriptServiceClient,
-		private readonly synchronizer: BufferSynchronizer,
-	) { }
-
-	public open(): void {
-		const args: Proto.OpenRequestArgs = {
-			file: this.filepath,
-			fileContent: this.document.getText(),
-			projectRootPath: this.getProjectRootPath(this.document.uri),
-		};
-
-		const scriptKind = mode2ScriptKind(this.document.languageId);
-		if (scriptKind) {
-			args.scriptKindName = scriptKind;
-		}
-
-		const tsPluginsForDocument = this.client.pluginManager.plugins
-			.filter(x => x.languages.indexOf(this.document.languageId) >= 0);
-
-		if (tsPluginsForDocument.length) {
-			(args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name);
-		}
-
-		this.synchronizer.open(this.resource, args);
-		this.state = BufferState.Open;
-	}
-
-	private getProjectRootPath(resource: vscode.Uri): string | undefined {
-		const workspaceRoot = this.client.getWorkspaceRootForResource(resource);
-		if (workspaceRoot) {
-			const tsRoot = this.client.toTsFilePath(workspaceRoot);
-			return tsRoot?.startsWith(inMemoryResourcePrefix) ? undefined : tsRoot;
-		}
-
-		return fileSchemes.isOfScheme(resource, fileSchemes.officeScript, fileSchemes.chatCodeBlock) ? '/' : undefined;
-	}
-
-	public get resource(): vscode.Uri {
-		return this.document.uri;
-	}
-
-	public get lineCount(): number {
-		return this.document.lineCount;
-	}
-
-	public get languageId(): string {
-		return this.document.languageId;
-	}
-
-	/**
-	 * @return Was the buffer open?
-	 */
-	public close(): boolean {
-		if (this.state !== BufferState.Open) {
-			this.state = BufferState.Closed;
-			return false;
-		}
-		this.state = BufferState.Closed;
-		return this.synchronizer.close(this.resource, this.filepath, mode2ScriptKind(this.document.languageId));
-	}
-
-	public onContentChanged(events: readonly vscode.TextDocumentContentChangeEvent[]): void {
-		if (this.state !== BufferState.Open) {
-			console.error(`Unexpected buffer state: ${this.state}`);
-		}
-
-		this.synchronizer.change(this.resource, this.filepath, events);
-	}
+    private state = BufferState.Initial;
+    constructor(private readonly document: vscode.TextDocument, public readonly filepath: string, private readonly client: ITypeScriptServiceClient, private readonly synchronizer: BufferSynchronizer) { }
+    public open(): void {
+        const args: Proto.OpenRequestArgs = {
+            file: this.filepath,
+            fileContent: this.document.getText(),
+            projectRootPath: this.getProjectRootPath(this.document.uri),
+        };
+        const scriptKind = mode2ScriptKind(this.document.languageId);
+        if (scriptKind) {
+            args.scriptKindName = scriptKind;
+        }
+        const tsPluginsForDocument = this.client.pluginManager.plugins
+            .filter(x => x.languages.indexOf(this.document.languageId) >= 0);
+        if (tsPluginsForDocument.length) {
+            (args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name);
+        }
+        this.synchronizer.open(this.resource, args);
+        this.state = BufferState.Open;
+    }
+    private getProjectRootPath(resource: vscode.Uri): string | undefined {
+        const workspaceRoot = this.client.getWorkspaceRootForResource(resource);
+        if (workspaceRoot) {
+            const tsRoot = this.client.toTsFilePath(workspaceRoot);
+            return tsRoot?.startsWith(inMemoryResourcePrefix) ? undefined : tsRoot;
+        }
+        return fileSchemes.isOfScheme(resource, fileSchemes.officeScript, fileSchemes.chatCodeBlock) ? '/' : undefined;
+    }
+    public get resource(): vscode.Uri {
+        return this.document.uri;
+    }
+    public get lineCount(): number {
+        return this.document.lineCount;
+    }
+    public get languageId(): string {
+        return this.document.languageId;
+    }
+    /**
+     * @return Was the buffer open?
+     */
+    public close(): boolean {
+        if (this.state !== BufferState.Open) {
+            this.state = BufferState.Closed;
+            return false;
+        }
+        this.state = BufferState.Closed;
+        return this.synchronizer.close(this.resource, this.filepath, mode2ScriptKind(this.document.languageId));
+    }
+    public onContentChanged(events: readonly vscode.TextDocumentContentChangeEvent[]): void {
+        if (this.state !== BufferState.Open) {
+            console.error(`Unexpected buffer state: ${this.state}`);
+        }
+        this.synchronizer.change(this.resource, this.filepath, events);
+    }
 }
-
 class SyncedBufferMap extends ResourceMap {
-
-	public getForPath(filePath: string): SyncedBuffer | undefined {
-		return this.get(vscode.Uri.file(filePath));
-	}
-
-	public get allBuffers(): Iterable {
-		return this.values();
-	}
+    public getForPath(filePath: string): SyncedBuffer | undefined {
+        return this.get(vscode.Uri.file(filePath));
+    }
+    public get allBuffers(): Iterable {
+        return this.values();
+    }
 }
-
 class PendingDiagnostics extends ResourceMap {
-	public getOrderedFileSet(): ResourceMap {
-		const orderedResources = Array.from(this.entries())
-			.sort((a, b) => a.value - b.value)
-			.map(entry => entry.resource);
-
-		const map = new ResourceMap(this._normalizePath, this.config);
-		for (const resource of orderedResources) {
-			map.set(resource, undefined);
-		}
-		return map;
-	}
+    public getOrderedFileSet(): ResourceMap {
+        const orderedResources = Array.from(this.entries())
+            .sort((a, b) => a.value - b.value)
+            .map(entry => entry.resource);
+        const map = new ResourceMap(this._normalizePath, this.config);
+        for (const resource of orderedResources) {
+            map.set(resource, undefined);
+        }
+        return map;
+    }
 }
-
 class GetErrRequest {
-
-	public static executeGetErrRequest(
-		client: ITypeScriptServiceClient,
-		files: ResourceMap,
-		onDone: () => void
-	) {
-		return new GetErrRequest(client, files, onDone);
-	}
-
-	private _done: boolean = false;
-	private readonly _token: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
-
-	private constructor(
-		private readonly client: ITypeScriptServiceClient,
-		public readonly files: ResourceMap,
-		onDone: () => void
-	) {
-		if (!this.isErrorReportingEnabled()) {
-			this._done = true;
-			setImmediate(onDone);
-			return;
-		}
-
-		const supportsSyntaxGetErr = this.client.apiVersion.gte(API.v440);
-		const fileEntries = Array.from(files.entries()).filter(entry => supportsSyntaxGetErr || client.hasCapabilityForResource(entry.resource, ClientCapability.Semantic));
-		const allFiles = coalesce(fileEntries
-			.map(entry => client.toTsFilePath(entry.resource)));
-
-		if (!allFiles.length) {
-			this._done = true;
-			setImmediate(onDone);
-		} else {
-			let request;
-			if (this.areProjectDiagnosticsEnabled()) {
-				// Note that geterrForProject is almost certainly not the api we want here as it ends up computing far
-				// too many diagnostics
-				request = client.executeAsync('geterrForProject', { delay: 0, file: allFiles[0] }, this._token.token);
-			}
-			else {
-				let requestFiles;
-				if (this.areRegionDiagnosticsEnabled()) {
-					requestFiles = coalesce(fileEntries
-						.map(entry => {
-							const file = client.toTsFilePath(entry.resource);
-							const ranges = entry.value;
-							if (file && ranges) {
-								return typeConverters.Range.toFileRangesRequestArgs(file, ranges);
-							}
-
-							return file;
-						}));
-				}
-				else {
-					requestFiles = allFiles;
-				}
-				request = client.executeAsync('geterr', { delay: 0, files: requestFiles }, this._token.token);
-			}
-
-			request.finally(() => {
-				if (this._done) {
-					return;
-				}
-				this._done = true;
-				onDone();
-			});
-		}
-	}
-
-	private isErrorReportingEnabled() {
-		if (this.client.apiVersion.gte(API.v440)) {
-			return true;
-		} else {
-			// Older TS versions only support `getErr` on semantic server
-			return this.client.capabilities.has(ClientCapability.Semantic);
-		}
-	}
-
-	private areProjectDiagnosticsEnabled() {
-		return this.client.configuration.enableProjectDiagnostics && this.client.capabilities.has(ClientCapability.Semantic);
-	}
-
-	private areRegionDiagnosticsEnabled() {
-		return this.client.configuration.enableRegionDiagnostics && this.client.apiVersion.gte(API.v560);
-	}
-
-	public cancel(): any {
-		if (!this._done) {
-			this._token.cancel();
-		}
-
-		this._token.dispose();
-	}
+    public static executeGetErrRequest(client: ITypeScriptServiceClient, files: ResourceMap, onDone: () => void) {
+        return new GetErrRequest(client, files, onDone);
+    }
+    private _done: boolean = false;
+    private readonly _token: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
+    private constructor(private readonly client: ITypeScriptServiceClient, public readonly files: ResourceMap, onDone: () => void) {
+        if (!this.isErrorReportingEnabled()) {
+            this._done = true;
+            setImmediate(onDone);
+            return;
+        }
+        const supportsSyntaxGetErr = this.client.apiVersion.gte(API.v440);
+        const fileEntries = Array.from(files.entries()).filter(entry => supportsSyntaxGetErr || client.hasCapabilityForResource(entry.resource, ClientCapability.Semantic));
+        const allFiles = coalesce(fileEntries
+            .map(entry => client.toTsFilePath(entry.resource)));
+        if (!allFiles.length) {
+            this._done = true;
+            setImmediate(onDone);
+        }
+        else {
+            let request;
+            if (this.areProjectDiagnosticsEnabled()) {
+                // Note that geterrForProject is almost certainly not the api we want here as it ends up computing far
+                // too many diagnostics
+                request = client.executeAsync('geterrForProject', { delay: 0, file: allFiles[0] }, this._token.token);
+            }
+            else {
+                let requestFiles;
+                if (this.areRegionDiagnosticsEnabled()) {
+                    requestFiles = coalesce(fileEntries
+                        .map(entry => {
+                        const file = client.toTsFilePath(entry.resource);
+                        const ranges = entry.value;
+                        if (file && ranges) {
+                            return typeConverters.Range.toFileRangesRequestArgs(file, ranges);
+                        }
+                        return file;
+                    }));
+                }
+                else {
+                    requestFiles = allFiles;
+                }
+                request = client.executeAsync('geterr', { delay: 0, files: requestFiles }, this._token.token);
+            }
+            request.finally(() => {
+                if (this._done) {
+                    return;
+                }
+                this._done = true;
+                onDone();
+            });
+        }
+    }
+    private isErrorReportingEnabled() {
+        if (this.client.apiVersion.gte(API.v440)) {
+            return true;
+        }
+        else {
+            // Older TS versions only support `getErr` on semantic server
+            return this.client.capabilities.has(ClientCapability.Semantic);
+        }
+    }
+    private areProjectDiagnosticsEnabled() {
+        return this.client.configuration.enableProjectDiagnostics && this.client.capabilities.has(ClientCapability.Semantic);
+    }
+    private areRegionDiagnosticsEnabled() {
+        return this.client.configuration.enableRegionDiagnostics && this.client.apiVersion.gte(API.v560);
+    }
+    public cancel(): any {
+        if (!this._done) {
+            this._token.cancel();
+        }
+        this._token.dispose();
+    }
 }
-
 class TabResourceTracker extends Disposable {
-
-	private readonly _onDidChange = this._register(new vscode.EventEmitter<{
-		readonly closed: Iterable;
-		readonly opened: Iterable;
-	}>());
-	public readonly onDidChange = this._onDidChange.event;
-
-	private readonly _tabResources: ResourceMap<{ readonly tabs: Set }>;
-
-	constructor(
-		normalizePath: (resource: vscode.Uri) => string | undefined,
-		config: {
-			readonly onCaseInsensitiveFileSystem: boolean;
-		},
-	) {
-		super();
-
-		this._tabResources = new ResourceMap<{ readonly tabs: Set }>(normalizePath, config);
-
-		for (const tabGroup of vscode.window.tabGroups.all) {
-			for (const tab of tabGroup.tabs) {
-				this.add(tab);
-			}
-		}
-
-		this._register(vscode.window.tabGroups.onDidChangeTabs(e => {
-			const closed = e.closed.flatMap(tab => this.delete(tab));
-			const opened = e.opened.flatMap(tab => this.add(tab));
-			if (closed.length || opened.length) {
-				this._onDidChange.fire({ closed, opened });
-			}
-		}));
-	}
-
-	public has(resource: vscode.Uri): boolean {
-		if (resource.scheme === fileSchemes.vscodeNotebookCell) {
-			const notebook = vscode.workspace.notebookDocuments.find(doc =>
-				doc.getCells().some(cell => cell.document.uri.toString() === resource.toString()));
-
-			return !!notebook && this.has(notebook.uri);
-		}
-
-		const entry = this._tabResources.get(resource);
-		return !!entry && entry.tabs.size > 0;
-	}
-
-	private add(tab: vscode.Tab): vscode.Uri[] {
-		const addedResources: vscode.Uri[] = [];
-		for (const uri of this.getResourcesForTab(tab)) {
-			const entry = this._tabResources.get(uri);
-			if (entry) {
-				entry.tabs.add(tab);
-			} else {
-				this._tabResources.set(uri, { tabs: new Set([tab]) });
-				addedResources.push(uri);
-			}
-		}
-		return addedResources;
-	}
-
-	private delete(tab: vscode.Tab): vscode.Uri[] {
-		const closedResources: vscode.Uri[] = [];
-		for (const uri of this.getResourcesForTab(tab)) {
-			const entry = this._tabResources.get(uri);
-			if (!entry) {
-				continue;
-			}
-
-			entry.tabs.delete(tab);
-			if (entry.tabs.size === 0) {
-				this._tabResources.delete(uri);
-				closedResources.push(uri);
-			}
-		}
-		return closedResources;
-	}
-
-	private getResourcesForTab(tab: vscode.Tab): vscode.Uri[] {
-		if (tab.input instanceof vscode.TabInputText) {
-			return [tab.input.uri];
-		} else if (tab.input instanceof vscode.TabInputTextDiff) {
-			return [tab.input.original, tab.input.modified];
-		} else if (tab.input instanceof vscode.TabInputNotebook) {
-			return [tab.input.uri];
-		} else {
-			return [];
-		}
-	}
+    private readonly _onDidChange = this._register(new vscode.EventEmitter<{
+        readonly closed: Iterable;
+        readonly opened: Iterable;
+    }>());
+    public readonly onDidChange = this._onDidChange.event;
+    private readonly _tabResources: ResourceMap<{
+        readonly tabs: Set;
+    }>;
+    constructor(normalizePath: (resource: vscode.Uri) => string | undefined, config: {
+        readonly onCaseInsensitiveFileSystem: boolean;
+    }) {
+        super();
+        this._tabResources = new ResourceMap<{
+            readonly tabs: Set;
+        }>(normalizePath, config);
+        for (const tabGroup of vscode.window.tabGroups.all) {
+            for (const tab of tabGroup.tabs) {
+                this.add(tab);
+            }
+        }
+        this._register(vscode.window.tabGroups.onDidChangeTabs(e => {
+            const closed = e.closed.flatMap(tab => this.delete(tab));
+            const opened = e.opened.flatMap(tab => this.add(tab));
+            if (closed.length || opened.length) {
+                this._onDidChange.fire({ closed, opened });
+            }
+        }));
+    }
+    public has(resource: vscode.Uri): boolean {
+        if (resource.scheme === fileSchemes.vscodeNotebookCell) {
+            const notebook = vscode.workspace.notebookDocuments.find(doc => doc.getCells().some(cell => cell.document.uri.toString() === resource.toString()));
+            return !!notebook && this.has(notebook.uri);
+        }
+        const entry = this._tabResources.get(resource);
+        return !!entry && entry.tabs.size > 0;
+    }
+    private add(tab: vscode.Tab): vscode.Uri[] {
+        const addedResources: vscode.Uri[] = [];
+        for (const uri of this.getResourcesForTab(tab)) {
+            const entry = this._tabResources.get(uri);
+            if (entry) {
+                entry.tabs.add(tab);
+            }
+            else {
+                this._tabResources.set(uri, { tabs: new Set([tab]) });
+                addedResources.push(uri);
+            }
+        }
+        return addedResources;
+    }
+    private delete(tab: vscode.Tab): vscode.Uri[] {
+        const closedResources: vscode.Uri[] = [];
+        for (const uri of this.getResourcesForTab(tab)) {
+            const entry = this._tabResources.get(uri);
+            if (!entry) {
+                continue;
+            }
+            entry.tabs.delete(tab);
+            if (entry.tabs.size === 0) {
+                this._tabResources.delete(uri);
+                closedResources.push(uri);
+            }
+        }
+        return closedResources;
+    }
+    private getResourcesForTab(tab: vscode.Tab): vscode.Uri[] {
+        if (tab.input instanceof vscode.TabInputText) {
+            return [tab.input.uri];
+        }
+        else if (tab.input instanceof vscode.TabInputTextDiff) {
+            return [tab.input.original, tab.input.modified];
+        }
+        else if (tab.input instanceof vscode.TabInputNotebook) {
+            return [tab.input.uri];
+        }
+        else {
+            return [];
+        }
+    }
 }
-
-
 export default class BufferSyncSupport extends Disposable {
-
-	private readonly client: ITypeScriptServiceClient;
-
-	private _validateJavaScript = true;
-	private _validateTypeScript = true;
-
-	private readonly modeIds: Set;
-	private readonly syncedBuffers: SyncedBufferMap;
-	private readonly pendingDiagnostics: PendingDiagnostics;
-	private readonly diagnosticDelayer: Delayer;
-	private pendingGetErr: GetErrRequest | undefined;
-	private listening: boolean = false;
-	private readonly synchronizer: BufferSynchronizer;
-
-	private readonly _tabResources: TabResourceTracker;
-
-	constructor(
-		client: ITypeScriptServiceClient,
-		modeIds: readonly string[],
-		onCaseInsensitiveFileSystem: boolean
-	) {
-		super();
-		this.client = client;
-		this.modeIds = new Set(modeIds);
-
-		this.diagnosticDelayer = new Delayer(300);
-
-		const pathNormalizer = (path: vscode.Uri) => this.client.toTsFilePath(path);
-		this.syncedBuffers = new SyncedBufferMap(pathNormalizer, { onCaseInsensitiveFileSystem });
-		this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer, { onCaseInsensitiveFileSystem });
-		this.synchronizer = new BufferSynchronizer(client, pathNormalizer, onCaseInsensitiveFileSystem);
-
-		this._tabResources = this._register(new TabResourceTracker(pathNormalizer, { onCaseInsensitiveFileSystem }));
-		this._register(this._tabResources.onDidChange(e => {
-			if (this.client.configuration.enableProjectDiagnostics) {
-				return;
-			}
-
-			for (const closed of e.closed) {
-				const syncedBuffer = this.syncedBuffers.get(closed);
-				if (syncedBuffer) {
-					this.pendingDiagnostics.delete(closed);
-					this.pendingGetErr?.files.delete(closed);
-				}
-			}
-
-			for (const opened of e.opened) {
-				const syncedBuffer = this.syncedBuffers.get(opened);
-				if (syncedBuffer) {
-					this.requestDiagnostic(syncedBuffer);
-				}
-			}
-		}));
-
-		this.updateConfiguration();
-		vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this, this._disposables);
-	}
-
-	private readonly _onDelete = this._register(new vscode.EventEmitter());
-	public readonly onDelete = this._onDelete.event;
-
-	private readonly _onWillChange = this._register(new vscode.EventEmitter());
-	public readonly onWillChange = this._onWillChange.event;
-
-	public listen(): void {
-		if (this.listening) {
-			return;
-		}
-		this.listening = true;
-		vscode.workspace.onDidOpenTextDocument(this.openTextDocument, this, this._disposables);
-		vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, this._disposables);
-		vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, this._disposables);
-		vscode.window.onDidChangeVisibleTextEditors(e => {
-			for (const { document } of e) {
-				const syncedBuffer = this.syncedBuffers.get(document.uri);
-				if (syncedBuffer) {
-					this.requestDiagnostic(syncedBuffer);
-				}
-			}
-		}, this, this._disposables);
-		vscode.workspace.textDocuments.forEach(this.openTextDocument, this);
-	}
-
-	public handles(resource: vscode.Uri): boolean {
-		return this.syncedBuffers.has(resource);
-	}
-
-	public ensureHasBuffer(resource: vscode.Uri): boolean {
-		if (this.syncedBuffers.has(resource)) {
-			return true;
-		}
-
-		const existingDocument = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString());
-		if (existingDocument) {
-			return this.openTextDocument(existingDocument);
-		}
-
-		return false;
-	}
-
-	public toVsCodeResource(resource: vscode.Uri): vscode.Uri {
-		const filepath = this.client.toTsFilePath(resource);
-		for (const buffer of this.syncedBuffers.allBuffers) {
-			if (buffer.filepath === filepath) {
-				return buffer.resource;
-			}
-		}
-		return resource;
-	}
-
-	public toResource(filePath: string): vscode.Uri {
-		const buffer = this.syncedBuffers.getForPath(filePath);
-		if (buffer) {
-			return buffer.resource;
-		}
-		return vscode.Uri.file(filePath);
-	}
-
-	public reset(): void {
-		this.pendingGetErr?.cancel();
-		this.pendingDiagnostics.clear();
-		this.synchronizer.reset();
-	}
-
-	public reinitialize(): void {
-		this.reset();
-		for (const buffer of this.syncedBuffers.allBuffers) {
-			buffer.open();
-		}
-	}
-
-	public openTextDocument(document: vscode.TextDocument): boolean {
-		if (!this.modeIds.has(document.languageId)) {
-			return false;
-		}
-		const resource = document.uri;
-		const filepath = this.client.toTsFilePath(resource);
-		if (!filepath) {
-			return false;
-		}
-
-		if (this.syncedBuffers.has(resource)) {
-			return true;
-		}
-
-		const syncedBuffer = new SyncedBuffer(document, filepath, this.client, this.synchronizer);
-		this.syncedBuffers.set(resource, syncedBuffer);
-		syncedBuffer.open();
-		this.requestDiagnostic(syncedBuffer);
-		return true;
-	}
-
-	public closeResource(resource: vscode.Uri): void {
-		const syncedBuffer = this.syncedBuffers.get(resource);
-		if (!syncedBuffer) {
-			return;
-		}
-
-		this.pendingDiagnostics.delete(resource);
-		this.pendingGetErr?.files.delete(resource);
-		this.syncedBuffers.delete(resource);
-		const wasBufferOpen = syncedBuffer.close();
-		this._onDelete.fire(resource);
-		if (wasBufferOpen) {
-			this.requestAllDiagnostics();
-		}
-	}
-
-	public interruptGetErr(f: () => R): R {
-		if (!this.pendingGetErr
-			|| this.client.configuration.enableProjectDiagnostics // `geterr` happens on separate server so no need to cancel it.
-		) {
-			return f();
-		}
-
-		this.pendingGetErr.cancel();
-		this.pendingGetErr = undefined;
-		const result = f();
-		this.triggerDiagnostics();
-		return result;
-	}
-
-	public beforeCommand(command: string): void {
-		this.synchronizer.beforeCommand(command);
-	}
-
-	public lineCount(resource: vscode.Uri): number | undefined {
-		return this.syncedBuffers.get(resource)?.lineCount;
-	}
-
-	private onDidCloseTextDocument(document: vscode.TextDocument): void {
-		this.closeResource(document.uri);
-	}
-
-	private onDidChangeTextDocument(e: vscode.TextDocumentChangeEvent): void {
-		const syncedBuffer = this.syncedBuffers.get(e.document.uri);
-		if (!syncedBuffer) {
-			return;
-		}
-
-		this._onWillChange.fire(syncedBuffer.resource);
-
-		syncedBuffer.onContentChanged(e.contentChanges);
-		const didTrigger = this.requestDiagnostic(syncedBuffer);
-
-		if (!didTrigger && this.pendingGetErr) {
-			// In this case we always want to re-trigger all diagnostics
-			this.pendingGetErr.cancel();
-			this.pendingGetErr = undefined;
-			this.triggerDiagnostics();
-		}
-	}
-
-	public requestAllDiagnostics() {
-		for (const buffer of this.syncedBuffers.allBuffers) {
-			if (this.shouldValidate(buffer)) {
-				this.pendingDiagnostics.set(buffer.resource, Date.now());
-			}
-		}
-		this.triggerDiagnostics();
-	}
-
-	public getErr(resources: readonly vscode.Uri[]): any {
-		const handledResources = resources.filter(resource => this.handles(resource));
-		if (!handledResources.length) {
-			return;
-		}
-
-		for (const resource of handledResources) {
-			this.pendingDiagnostics.set(resource, Date.now());
-		}
-
-		this.triggerDiagnostics();
-	}
-
-	private triggerDiagnostics(delay: number = 200) {
-		this.diagnosticDelayer.trigger(() => {
-			this.sendPendingDiagnostics();
-		}, delay);
-	}
-
-	private requestDiagnostic(buffer: SyncedBuffer): boolean {
-		if (!this.shouldValidate(buffer)) {
-			return false;
-		}
-
-		this.pendingDiagnostics.set(buffer.resource, Date.now());
-
-		const delay = Math.min(Math.max(Math.ceil(buffer.lineCount / 20), 300), 800);
-		this.triggerDiagnostics(delay);
-		return true;
-	}
-
-	public hasPendingDiagnostics(resource: vscode.Uri): boolean {
-		return this.pendingDiagnostics.has(resource);
-	}
-
-	private sendPendingDiagnostics(): void {
-		const orderedFileSet = this.pendingDiagnostics.getOrderedFileSet();
-
-		if (this.pendingGetErr) {
-			this.pendingGetErr.cancel();
-
-			for (const { resource } of this.pendingGetErr.files.entries()) {
-				if (this.syncedBuffers.get(resource)) {
-					orderedFileSet.set(resource, undefined);
-				}
-			}
-
-			this.pendingGetErr = undefined;
-		}
-
-		// Add all open TS buffers to the geterr request. They might be visible
-		for (const buffer of this.syncedBuffers.values()) {
-			const editors = vscode.window.visibleTextEditors.filter(editor => editor.document.uri.toString() === buffer.resource.toString());
-			const visibleRanges = editors.flatMap(editor => editor.visibleRanges);
-			orderedFileSet.set(buffer.resource, visibleRanges.length ? visibleRanges : undefined);
-		}
-
-		for (const { resource } of orderedFileSet.entries()) {
-			const buffer = this.syncedBuffers.get(resource);
-			if (buffer && !this.shouldValidate(buffer)) {
-				orderedFileSet.delete(resource);
-			}
-		}
-
-		if (orderedFileSet.size) {
-			const getErr = this.pendingGetErr = GetErrRequest.executeGetErrRequest(this.client, orderedFileSet, () => {
-				if (this.pendingGetErr === getErr) {
-					this.pendingGetErr = undefined;
-				}
-			});
-		}
-
-		this.pendingDiagnostics.clear();
-	}
-
-	private updateConfiguration() {
-		const jsConfig = vscode.workspace.getConfiguration('javascript', null);
-		const tsConfig = vscode.workspace.getConfiguration('typescript', null);
-
-		this._validateJavaScript = jsConfig.get('validate.enable', true);
-		this._validateTypeScript = tsConfig.get('validate.enable', true);
-	}
-
-	private shouldValidate(buffer: SyncedBuffer): boolean {
-		if (fileSchemes.isOfScheme(buffer.resource, fileSchemes.chatCodeBlock)) {
-			return false;
-		}
-
-		if (!this.client.configuration.enableProjectDiagnostics && !this._tabResources.has(buffer.resource)) { // Only validate resources that are showing to the user
-			return false;
-		}
-
-		switch (buffer.languageId) {
-			case languageModeIds.javascript:
-			case languageModeIds.javascriptreact:
-				return this._validateJavaScript;
-
-			case languageModeIds.typescript:
-			case languageModeIds.typescriptreact:
-			default:
-				return this._validateTypeScript;
-		}
-	}
+    private readonly client: ITypeScriptServiceClient;
+    private _validateJavaScript = true;
+    private _validateTypeScript = true;
+    private readonly modeIds: Set;
+    private readonly syncedBuffers: SyncedBufferMap;
+    private readonly pendingDiagnostics: PendingDiagnostics;
+    private readonly diagnosticDelayer: Delayer;
+    private pendingGetErr: GetErrRequest | undefined;
+    private listening: boolean = false;
+    private readonly synchronizer: BufferSynchronizer;
+    private readonly _tabResources: TabResourceTracker;
+    constructor(client: ITypeScriptServiceClient, modeIds: readonly string[], onCaseInsensitiveFileSystem: boolean) {
+        super();
+        this.client = client;
+        this.modeIds = new Set(modeIds);
+        this.diagnosticDelayer = new Delayer(300);
+        const pathNormalizer = (path: vscode.Uri) => this.client.toTsFilePath(path);
+        this.syncedBuffers = new SyncedBufferMap(pathNormalizer, { onCaseInsensitiveFileSystem });
+        this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer, { onCaseInsensitiveFileSystem });
+        this.synchronizer = new BufferSynchronizer(client, pathNormalizer, onCaseInsensitiveFileSystem);
+        this._tabResources = this._register(new TabResourceTracker(pathNormalizer, { onCaseInsensitiveFileSystem }));
+        this._register(this._tabResources.onDidChange(e => {
+            if (this.client.configuration.enableProjectDiagnostics) {
+                return;
+            }
+            for (const closed of e.closed) {
+                const syncedBuffer = this.syncedBuffers.get(closed);
+                if (syncedBuffer) {
+                    this.pendingDiagnostics.delete(closed);
+                    this.pendingGetErr?.files.delete(closed);
+                }
+            }
+            for (const opened of e.opened) {
+                const syncedBuffer = this.syncedBuffers.get(opened);
+                if (syncedBuffer) {
+                    this.requestDiagnostic(syncedBuffer);
+                }
+            }
+        }));
+        this.updateConfiguration();
+        vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this, this._disposables);
+    }
+    private readonly _onDelete = this._register(new vscode.EventEmitter());
+    public readonly onDelete = this._onDelete.event;
+    private readonly _onWillChange = this._register(new vscode.EventEmitter());
+    public readonly onWillChange = this._onWillChange.event;
+    public listen(): void {
+        if (this.listening) {
+            return;
+        }
+        this.listening = true;
+        vscode.workspace.onDidOpenTextDocument(this.openTextDocument, this, this._disposables);
+        vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, this._disposables);
+        vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, this._disposables);
+        vscode.window.onDidChangeVisibleTextEditors(e => {
+            for (const { document } of e) {
+                const syncedBuffer = this.syncedBuffers.get(document.uri);
+                if (syncedBuffer) {
+                    this.requestDiagnostic(syncedBuffer);
+                }
+            }
+        }, this, this._disposables);
+        vscode.workspace.textDocuments.forEach(this.openTextDocument, this);
+    }
+    public handles(resource: vscode.Uri): boolean {
+        return this.syncedBuffers.has(resource);
+    }
+    public ensureHasBuffer(resource: vscode.Uri): boolean {
+        if (this.syncedBuffers.has(resource)) {
+            return true;
+        }
+        const existingDocument = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString());
+        if (existingDocument) {
+            return this.openTextDocument(existingDocument);
+        }
+        return false;
+    }
+    public toVsCodeResource(resource: vscode.Uri): vscode.Uri {
+        const filepath = this.client.toTsFilePath(resource);
+        for (const buffer of this.syncedBuffers.allBuffers) {
+            if (buffer.filepath === filepath) {
+                return buffer.resource;
+            }
+        }
+        return resource;
+    }
+    public toResource(filePath: string): vscode.Uri {
+        const buffer = this.syncedBuffers.getForPath(filePath);
+        if (buffer) {
+            return buffer.resource;
+        }
+        return vscode.Uri.file(filePath);
+    }
+    public reset(): void {
+        this.pendingGetErr?.cancel();
+        this.pendingDiagnostics.clear();
+        this.synchronizer.reset();
+    }
+    public reinitialize(): void {
+        this.reset();
+        for (const buffer of this.syncedBuffers.allBuffers) {
+            buffer.open();
+        }
+    }
+    public openTextDocument(document: vscode.TextDocument): boolean {
+        if (!this.modeIds.has(document.languageId)) {
+            return false;
+        }
+        const resource = document.uri;
+        const filepath = this.client.toTsFilePath(resource);
+        if (!filepath) {
+            return false;
+        }
+        if (this.syncedBuffers.has(resource)) {
+            return true;
+        }
+        const syncedBuffer = new SyncedBuffer(document, filepath, this.client, this.synchronizer);
+        this.syncedBuffers.set(resource, syncedBuffer);
+        syncedBuffer.open();
+        this.requestDiagnostic(syncedBuffer);
+        return true;
+    }
+    public closeResource(resource: vscode.Uri): void {
+        const syncedBuffer = this.syncedBuffers.get(resource);
+        if (!syncedBuffer) {
+            return;
+        }
+        this.pendingDiagnostics.delete(resource);
+        this.pendingGetErr?.files.delete(resource);
+        this.syncedBuffers.delete(resource);
+        const wasBufferOpen = syncedBuffer.close();
+        this._onDelete.fire(resource);
+        if (wasBufferOpen) {
+            this.requestAllDiagnostics();
+        }
+    }
+    public interruptGetErr(f: () => R): R {
+        if (!this.pendingGetErr
+            || this.client.configuration.enableProjectDiagnostics // `geterr` happens on separate server so no need to cancel it.
+        ) {
+            return f();
+        }
+        this.pendingGetErr.cancel();
+        this.pendingGetErr = undefined;
+        const result = f();
+        this.triggerDiagnostics();
+        return result;
+    }
+    public beforeCommand(command: string): void {
+        this.synchronizer.beforeCommand(command);
+    }
+    public lineCount(resource: vscode.Uri): number | undefined {
+        return this.syncedBuffers.get(resource)?.lineCount;
+    }
+    private onDidCloseTextDocument(document: vscode.TextDocument): void {
+        this.closeResource(document.uri);
+    }
+    private onDidChangeTextDocument(e: vscode.TextDocumentChangeEvent): void {
+        const syncedBuffer = this.syncedBuffers.get(e.document.uri);
+        if (!syncedBuffer) {
+            return;
+        }
+        this._onWillChange.fire(syncedBuffer.resource);
+        syncedBuffer.onContentChanged(e.contentChanges);
+        const didTrigger = this.requestDiagnostic(syncedBuffer);
+        if (!didTrigger && this.pendingGetErr) {
+            // In this case we always want to re-trigger all diagnostics
+            this.pendingGetErr.cancel();
+            this.pendingGetErr = undefined;
+            this.triggerDiagnostics();
+        }
+    }
+    public requestAllDiagnostics() {
+        for (const buffer of this.syncedBuffers.allBuffers) {
+            if (this.shouldValidate(buffer)) {
+                this.pendingDiagnostics.set(buffer.resource, Date.now());
+            }
+        }
+        this.triggerDiagnostics();
+    }
+    public getErr(resources: readonly vscode.Uri[]): any {
+        const handledResources = resources.filter(resource => this.handles(resource));
+        if (!handledResources.length) {
+            return;
+        }
+        for (const resource of handledResources) {
+            this.pendingDiagnostics.set(resource, Date.now());
+        }
+        this.triggerDiagnostics();
+    }
+    private triggerDiagnostics(delay: number = 200) {
+        this.diagnosticDelayer.trigger(() => {
+            this.sendPendingDiagnostics();
+        }, delay);
+    }
+    private requestDiagnostic(buffer: SyncedBuffer): boolean {
+        if (!this.shouldValidate(buffer)) {
+            return false;
+        }
+        this.pendingDiagnostics.set(buffer.resource, Date.now());
+        const delay = Math.min(Math.max(Math.ceil(buffer.lineCount / 20), 300), 800);
+        this.triggerDiagnostics(delay);
+        return true;
+    }
+    public hasPendingDiagnostics(resource: vscode.Uri): boolean {
+        return this.pendingDiagnostics.has(resource);
+    }
+    private sendPendingDiagnostics(): void {
+        const orderedFileSet = this.pendingDiagnostics.getOrderedFileSet();
+        if (this.pendingGetErr) {
+            this.pendingGetErr.cancel();
+            for (const { resource } of this.pendingGetErr.files.entries()) {
+                if (this.syncedBuffers.get(resource)) {
+                    orderedFileSet.set(resource, undefined);
+                }
+            }
+            this.pendingGetErr = undefined;
+        }
+        // Add all open TS buffers to the geterr request. They might be visible
+        for (const buffer of this.syncedBuffers.values()) {
+            const editors = vscode.window.visibleTextEditors.filter(editor => editor.document.uri.toString() === buffer.resource.toString());
+            const visibleRanges = editors.flatMap(editor => editor.visibleRanges);
+            orderedFileSet.set(buffer.resource, visibleRanges.length ? visibleRanges : undefined);
+        }
+        for (const { resource } of orderedFileSet.entries()) {
+            const buffer = this.syncedBuffers.get(resource);
+            if (buffer && !this.shouldValidate(buffer)) {
+                orderedFileSet.delete(resource);
+            }
+        }
+        if (orderedFileSet.size) {
+            const getErr = this.pendingGetErr = GetErrRequest.executeGetErrRequest(this.client, orderedFileSet, () => {
+                if (this.pendingGetErr === getErr) {
+                    this.pendingGetErr = undefined;
+                }
+            });
+        }
+        this.pendingDiagnostics.clear();
+    }
+    private updateConfiguration() {
+        const jsConfig = vscode.workspace.getConfiguration('javascript', null);
+        const tsConfig = vscode.workspace.getConfiguration('typescript', null);
+        this._validateJavaScript = jsConfig.get('validate.enable', true);
+        this._validateTypeScript = tsConfig.get('validate.enable', true);
+    }
+    private shouldValidate(buffer: SyncedBuffer): boolean {
+        if (fileSchemes.isOfScheme(buffer.resource, fileSchemes.chatCodeBlock)) {
+            return false;
+        }
+        if (!this.client.configuration.enableProjectDiagnostics && !this._tabResources.has(buffer.resource)) { // Only validate resources that are showing to the user
+            return false;
+        }
+        switch (buffer.languageId) {
+            case languageModeIds.javascript:
+            case languageModeIds.javascriptreact:
+                return this._validateJavaScript;
+            case languageModeIds.typescript:
+            case languageModeIds.typescriptreact:
+            default:
+                return this._validateTypeScript;
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/cachedResponse.ts b/extensions/typescript-language-features/Source/tsServer/cachedResponse.ts
index cedc580761f18..f1c8158f4197e 100644
--- a/extensions/typescript-language-features/Source/tsServer/cachedResponse.ts
+++ b/extensions/typescript-language-features/Source/tsServer/cachedResponse.ts
@@ -2,47 +2,35 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { ServerResponse } from '../typescriptService';
 import type * as Proto from './protocol/protocol';
-
 type Resolve = () => Promise>;
-
 /**
  * Caches a class of TS Server request based on document.
  */
 export class CachedResponse {
-	private response?: Promise>;
-	private version: number = -1;
-	private document: string = '';
-
-	/**
-	 * Execute a request. May return cached value or resolve the new value
-	 *
-	 * Caller must ensure that all input `resolve` functions return equivilent results (keyed only off of document).
-	 */
-	public execute(
-		document: vscode.TextDocument,
-		resolve: Resolve
-	): Promise> {
-		if (this.response && this.matches(document)) {
-			// Chain so that on cancellation we fall back to the next resolve
-			return this.response = this.response.then(result => result.type === 'cancelled' ? resolve() : result);
-		}
-		return this.reset(document, resolve);
-	}
-
-	private matches(document: vscode.TextDocument): boolean {
-		return this.version === document.version && this.document === document.uri.toString();
-	}
-
-	private async reset(
-		document: vscode.TextDocument,
-		resolve: Resolve
-	): Promise> {
-		this.version = document.version;
-		this.document = document.uri.toString();
-		return this.response = resolve();
-	}
+    private response?: Promise>;
+    private version: number = -1;
+    private document: string = '';
+    /**
+     * Execute a request. May return cached value or resolve the new value
+     *
+     * Caller must ensure that all input `resolve` functions return equivilent results (keyed only off of document).
+     */
+    public execute(document: vscode.TextDocument, resolve: Resolve): Promise> {
+        if (this.response && this.matches(document)) {
+            // Chain so that on cancellation we fall back to the next resolve
+            return this.response = this.response.then(result => result.type === 'cancelled' ? resolve() : result);
+        }
+        return this.reset(document, resolve);
+    }
+    private matches(document: vscode.TextDocument): boolean {
+        return this.version === document.version && this.document === document.uri.toString();
+    }
+    private async reset(document: vscode.TextDocument, resolve: Resolve): Promise> {
+        this.version = document.version;
+        this.document = document.uri.toString();
+        return this.response = resolve();
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/callbackMap.ts b/extensions/typescript-language-features/Source/tsServer/callbackMap.ts
index 57a80051e6db0..a9e052e2ab828 100644
--- a/extensions/typescript-language-features/Source/tsServer/callbackMap.ts
+++ b/extensions/typescript-language-features/Source/tsServer/callbackMap.ts
@@ -2,50 +2,44 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { ServerResponse } from '../typescriptService';
 import type * as Proto from './protocol/protocol';
-
 export interface CallbackItem {
-	readonly onSuccess: (value: R) => void;
-	readonly onError: (err: Error) => void;
-	readonly queuingStartTime: number;
-	readonly isAsync: boolean;
+    readonly onSuccess: (value: R) => void;
+    readonly onError: (err: Error) => void;
+    readonly queuingStartTime: number;
+    readonly isAsync: boolean;
 }
-
 export class CallbackMap {
-	private readonly _callbacks = new Map | undefined>>();
-	private readonly _asyncCallbacks = new Map | undefined>>();
-
-	public destroy(cause: string): void {
-		const cancellation = new ServerResponse.Cancelled(cause);
-		for (const callback of this._callbacks.values()) {
-			callback.onSuccess(cancellation);
-		}
-		this._callbacks.clear();
-		for (const callback of this._asyncCallbacks.values()) {
-			callback.onSuccess(cancellation);
-		}
-		this._asyncCallbacks.clear();
-	}
-
-	public add(seq: number, callback: CallbackItem | undefined>, isAsync: boolean) {
-		if (isAsync) {
-			this._asyncCallbacks.set(seq, callback);
-		} else {
-			this._callbacks.set(seq, callback);
-		}
-	}
-
-	public fetch(seq: number): CallbackItem | undefined> | undefined {
-		const callback = this._callbacks.get(seq) || this._asyncCallbacks.get(seq);
-		this.delete(seq);
-		return callback;
-	}
-
-	private delete(seq: number) {
-		if (!this._callbacks.delete(seq)) {
-			this._asyncCallbacks.delete(seq);
-		}
-	}
+    private readonly _callbacks = new Map | undefined>>();
+    private readonly _asyncCallbacks = new Map | undefined>>();
+    public destroy(cause: string): void {
+        const cancellation = new ServerResponse.Cancelled(cause);
+        for (const callback of this._callbacks.values()) {
+            callback.onSuccess(cancellation);
+        }
+        this._callbacks.clear();
+        for (const callback of this._asyncCallbacks.values()) {
+            callback.onSuccess(cancellation);
+        }
+        this._asyncCallbacks.clear();
+    }
+    public add(seq: number, callback: CallbackItem | undefined>, isAsync: boolean) {
+        if (isAsync) {
+            this._asyncCallbacks.set(seq, callback);
+        }
+        else {
+            this._callbacks.set(seq, callback);
+        }
+    }
+    public fetch(seq: number): CallbackItem | undefined> | undefined {
+        const callback = this._callbacks.get(seq) || this._asyncCallbacks.get(seq);
+        this.delete(seq);
+        return callback;
+    }
+    private delete(seq: number) {
+        if (!this._callbacks.delete(seq)) {
+            this._asyncCallbacks.delete(seq);
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/cancellation.electron.ts b/extensions/typescript-language-features/Source/tsServer/cancellation.electron.ts
index 3e1ad0a0c5cd5..37b9088a0a413 100644
--- a/extensions/typescript-language-features/Source/tsServer/cancellation.electron.ts
+++ b/extensions/typescript-language-features/Source/tsServer/cancellation.electron.ts
@@ -2,39 +2,31 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as fs from 'fs';
 import Tracer from '../logging/tracer';
 import { getTempFile } from '../utils/temp.electron';
 import { OngoingRequestCanceller, OngoingRequestCancellerFactory } from './cancellation';
-
 export class NodeRequestCanceller implements OngoingRequestCanceller {
-	public readonly cancellationPipeName: string;
-
-	public constructor(
-		private readonly _serverId: string,
-		private readonly _tracer: Tracer,
-	) {
-		this.cancellationPipeName = getTempFile('tscancellation');
-	}
-
-	public tryCancelOngoingRequest(seq: number): boolean {
-		if (!this.cancellationPipeName) {
-			return false;
-		}
-		this._tracer.trace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`);
-		try {
-			fs.writeFileSync(this.cancellationPipeName + seq, '');
-		} catch {
-			// noop
-		}
-		return true;
-	}
+    public readonly cancellationPipeName: string;
+    public constructor(private readonly _serverId: string, private readonly _tracer: Tracer) {
+        this.cancellationPipeName = getTempFile('tscancellation');
+    }
+    public tryCancelOngoingRequest(seq: number): boolean {
+        if (!this.cancellationPipeName) {
+            return false;
+        }
+        this._tracer.trace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`);
+        try {
+            fs.writeFileSync(this.cancellationPipeName + seq, '');
+        }
+        catch {
+            // noop
+        }
+        return true;
+    }
 }
-
-
 export const nodeRequestCancellerFactory = new class implements OngoingRequestCancellerFactory {
-	create(serverId: string, tracer: Tracer): OngoingRequestCanceller {
-		return new NodeRequestCanceller(serverId, tracer);
-	}
+    create(serverId: string, tracer: Tracer): OngoingRequestCanceller {
+        return new NodeRequestCanceller(serverId, tracer);
+    }
 };
diff --git a/extensions/typescript-language-features/Source/tsServer/cancellation.ts b/extensions/typescript-language-features/Source/tsServer/cancellation.ts
index 051708a03d1e3..da319b39627c0 100644
--- a/extensions/typescript-language-features/Source/tsServer/cancellation.ts
+++ b/extensions/typescript-language-features/Source/tsServer/cancellation.ts
@@ -2,28 +2,22 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import Tracer from '../logging/tracer';
-
 export interface OngoingRequestCanceller {
-	readonly cancellationPipeName: string | undefined;
-	tryCancelOngoingRequest(seq: number): boolean;
+    readonly cancellationPipeName: string | undefined;
+    tryCancelOngoingRequest(seq: number): boolean;
 }
-
 export interface OngoingRequestCancellerFactory {
-	create(serverId: string, tracer: Tracer): OngoingRequestCanceller;
+    create(serverId: string, tracer: Tracer): OngoingRequestCanceller;
 }
-
 const noopRequestCanceller = new class implements OngoingRequestCanceller {
-	public readonly cancellationPipeName = undefined;
-
-	public tryCancelOngoingRequest(_seq: number): boolean {
-		return false;
-	}
+    public readonly cancellationPipeName = undefined;
+    public tryCancelOngoingRequest(_seq: number): boolean {
+        return false;
+    }
 };
-
 export const noopRequestCancellerFactory = new class implements OngoingRequestCancellerFactory {
-	create(_serverId: string, _tracer: Tracer): OngoingRequestCanceller {
-		return noopRequestCanceller;
-	}
+    create(_serverId: string, _tracer: Tracer): OngoingRequestCanceller {
+        return noopRequestCanceller;
+    }
 };
diff --git a/extensions/typescript-language-features/Source/tsServer/fileWatchingManager.ts b/extensions/typescript-language-features/Source/tsServer/fileWatchingManager.ts
index 847584a300cb8..675f9caab44f7 100644
--- a/extensions/typescript-language-features/Source/tsServer/fileWatchingManager.ts
+++ b/extensions/typescript-language-features/Source/tsServer/fileWatchingManager.ts
@@ -2,128 +2,113 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { Utils } from 'vscode-uri';
 import { Schemes } from '../configuration/schemes';
 import { Logger } from '../logging/logger';
 import { disposeAll, IDisposable } from '../utils/dispose';
 import { ResourceMap } from '../utils/resourceMap';
-
 interface DirWatcherEntry {
-	readonly uri: vscode.Uri;
-	readonly disposables: readonly IDisposable[];
+    readonly uri: vscode.Uri;
+    readonly disposables: readonly IDisposable[];
 }
-
-
 export class FileWatcherManager implements IDisposable {
-
-	private readonly _fileWatchers = new Map();
-
-	private readonly _dirWatchers = new ResourceMap<{
-		readonly uri: vscode.Uri;
-		readonly watcher: vscode.FileSystemWatcher;
-		refCount: number;
-	}>(uri => uri.toString(), { onCaseInsensitiveFileSystem: false });
-
-	constructor(
-		private readonly logger: Logger,
-	) { }
-
-	dispose(): void {
-		for (const entry of this._fileWatchers.values()) {
-			entry.watcher.dispose();
-		}
-		this._fileWatchers.clear();
-
-		for (const entry of this._dirWatchers.values()) {
-			entry.watcher.dispose();
-		}
-		this._dirWatchers.clear();
-	}
-
-	create(id: number, uri: vscode.Uri, watchParentDirs: boolean, isRecursive: boolean, listeners: { create?: (uri: vscode.Uri) => void; change?: (uri: vscode.Uri) => void; delete?: (uri: vscode.Uri) => void }): void {
-		this.logger.trace(`Creating file watcher for ${uri.toString()}`);
-
-		// Non-writable file systems do not support file watching
-		if (!vscode.workspace.fs.isWritableFileSystem(uri.scheme)) {
-			return;
-		}
-
-		const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, isRecursive ? '**' : '*'), !listeners.create, !listeners.change, !listeners.delete);
-		const parentDirWatchers: DirWatcherEntry[] = [];
-		this._fileWatchers.set(id, { uri, watcher, dirWatchers: parentDirWatchers });
-
-		if (listeners.create) { watcher.onDidCreate(listeners.create); }
-		if (listeners.change) { watcher.onDidChange(listeners.change); }
-		if (listeners.delete) { watcher.onDidDelete(listeners.delete); }
-
-		if (watchParentDirs && uri.scheme !== Schemes.untitled) {
-			// We need to watch the parent directories too for when these are deleted / created
-			for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) {
-				const disposables: IDisposable[] = [];
-
-				let parentDirWatcher = this._dirWatchers.get(dirUri);
-				if (!parentDirWatcher) {
-					this.logger.trace(`Creating parent dir watcher for ${dirUri.toString()}`);
-					const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri));
-					const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete);
-					parentDirWatcher = { uri: dirUri, refCount: 0, watcher: parentWatcher };
-					this._dirWatchers.set(dirUri, parentDirWatcher);
-				}
-				parentDirWatcher.refCount++;
-
-				if (listeners.create) {
-					disposables.push(parentDirWatcher.watcher.onDidCreate(async () => {
-						// Just because the parent dir was created doesn't mean our file was created
-						try {
-							const stat = await vscode.workspace.fs.stat(uri);
-							if (stat.type === vscode.FileType.File) {
-								listeners.create!(uri);
-							}
-						} catch {
-							// Noop
-						}
-					}));
-				}
-
-				if (listeners.delete) {
-					// When the parent dir is deleted, consider our file deleted too
-					// TODO: this fires if the file previously did not exist and then the parent is deleted
-					disposables.push(parentDirWatcher.watcher.onDidDelete(listeners.delete));
-				}
-
-				parentDirWatchers.push({ uri: dirUri, disposables });
-			}
-		}
-	}
-
-
-	delete(id: number): void {
-		const entry = this._fileWatchers.get(id);
-		if (entry) {
-			this.logger.trace(`Deleting file watcher for ${entry.uri}`);
-
-			for (const dirWatcher of entry.dirWatchers) {
-				disposeAll(dirWatcher.disposables);
-
-				const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri);
-				if (dirWatcherEntry) {
-					if (--dirWatcherEntry.refCount <= 0) {
-						this.logger.trace(`Deleting parent dir ${dirWatcherEntry.uri}`);
-						dirWatcherEntry.watcher.dispose();
-						this._dirWatchers.delete(dirWatcher.uri);
-					}
-				}
-			}
-
-			entry.watcher.dispose();
-		}
-
-		this._fileWatchers.delete(id);
-	}
+    private readonly _fileWatchers = new Map();
+    private readonly _dirWatchers = new ResourceMap<{
+        readonly uri: vscode.Uri;
+        readonly watcher: vscode.FileSystemWatcher;
+        refCount: number;
+    }>(uri => uri.toString(), { onCaseInsensitiveFileSystem: false });
+    constructor(private readonly logger: Logger) { }
+    dispose(): void {
+        for (const entry of this._fileWatchers.values()) {
+            entry.watcher.dispose();
+        }
+        this._fileWatchers.clear();
+        for (const entry of this._dirWatchers.values()) {
+            entry.watcher.dispose();
+        }
+        this._dirWatchers.clear();
+    }
+    create(id: number, uri: vscode.Uri, watchParentDirs: boolean, isRecursive: boolean, listeners: {
+        create?: (uri: vscode.Uri) => void;
+        change?: (uri: vscode.Uri) => void;
+        delete?: (uri: vscode.Uri) => void;
+    }): void {
+        this.logger.trace(`Creating file watcher for ${uri.toString()}`);
+        // Non-writable file systems do not support file watching
+        if (!vscode.workspace.fs.isWritableFileSystem(uri.scheme)) {
+            return;
+        }
+        const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, isRecursive ? '**' : '*'), !listeners.create, !listeners.change, !listeners.delete);
+        const parentDirWatchers: DirWatcherEntry[] = [];
+        this._fileWatchers.set(id, { uri, watcher, dirWatchers: parentDirWatchers });
+        if (listeners.create) {
+            watcher.onDidCreate(listeners.create);
+        }
+        if (listeners.change) {
+            watcher.onDidChange(listeners.change);
+        }
+        if (listeners.delete) {
+            watcher.onDidDelete(listeners.delete);
+        }
+        if (watchParentDirs && uri.scheme !== Schemes.untitled) {
+            // We need to watch the parent directories too for when these are deleted / created
+            for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) {
+                const disposables: IDisposable[] = [];
+                let parentDirWatcher = this._dirWatchers.get(dirUri);
+                if (!parentDirWatcher) {
+                    this.logger.trace(`Creating parent dir watcher for ${dirUri.toString()}`);
+                    const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri));
+                    const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete);
+                    parentDirWatcher = { uri: dirUri, refCount: 0, watcher: parentWatcher };
+                    this._dirWatchers.set(dirUri, parentDirWatcher);
+                }
+                parentDirWatcher.refCount++;
+                if (listeners.create) {
+                    disposables.push(parentDirWatcher.watcher.onDidCreate(async () => {
+                        // Just because the parent dir was created doesn't mean our file was created
+                        try {
+                            const stat = await vscode.workspace.fs.stat(uri);
+                            if (stat.type === vscode.FileType.File) {
+                                listeners.create!(uri);
+                            }
+                        }
+                        catch {
+                            // Noop
+                        }
+                    }));
+                }
+                if (listeners.delete) {
+                    // When the parent dir is deleted, consider our file deleted too
+                    // TODO: this fires if the file previously did not exist and then the parent is deleted
+                    disposables.push(parentDirWatcher.watcher.onDidDelete(listeners.delete));
+                }
+                parentDirWatchers.push({ uri: dirUri, disposables });
+            }
+        }
+    }
+    delete(id: number): void {
+        const entry = this._fileWatchers.get(id);
+        if (entry) {
+            this.logger.trace(`Deleting file watcher for ${entry.uri}`);
+            for (const dirWatcher of entry.dirWatchers) {
+                disposeAll(dirWatcher.disposables);
+                const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri);
+                if (dirWatcherEntry) {
+                    if (--dirWatcherEntry.refCount <= 0) {
+                        this.logger.trace(`Deleting parent dir ${dirWatcherEntry.uri}`);
+                        dirWatcherEntry.watcher.dispose();
+                        this._dirWatchers.delete(dirWatcher.uri);
+                    }
+                }
+            }
+            entry.watcher.dispose();
+        }
+        this._fileWatchers.delete(id);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.electron.ts b/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.electron.ts
index 69f9515211cfe..2711c495a70f9 100644
--- a/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.electron.ts
+++ b/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.electron.ts
@@ -2,40 +2,36 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as fs from 'fs';
 import * as path from 'path';
 import * as vscode from 'vscode';
 import { memoize } from '../utils/memoize';
 import { ILogDirectoryProvider } from './logDirectoryProvider';
-
 export class NodeLogDirectoryProvider implements ILogDirectoryProvider {
-	public constructor(
-		private readonly context: vscode.ExtensionContext
-	) { }
-
-	public getNewLogDirectory(): vscode.Uri | undefined {
-		const root = this.logDirectory();
-		if (root) {
-			try {
-				return vscode.Uri.file(fs.mkdtempSync(path.join(root, `tsserver-log-`)));
-			} catch (e) {
-				return undefined;
-			}
-		}
-		return undefined;
-	}
-
-	@memoize
-	private logDirectory(): string | undefined {
-		try {
-			const path = this.context.logPath;
-			if (!fs.existsSync(path)) {
-				fs.mkdirSync(path);
-			}
-			return this.context.logPath;
-		} catch {
-			return undefined;
-		}
-	}
+    public constructor(private readonly context: vscode.ExtensionContext) { }
+    public getNewLogDirectory(): vscode.Uri | undefined {
+        const root = this.logDirectory();
+        if (root) {
+            try {
+                return vscode.Uri.file(fs.mkdtempSync(path.join(root, `tsserver-log-`)));
+            }
+            catch (e) {
+                return undefined;
+            }
+        }
+        return undefined;
+    }
+    @memoize
+    private logDirectory(): string | undefined {
+        try {
+            const path = this.context.logPath;
+            if (!fs.existsSync(path)) {
+                fs.mkdirSync(path);
+            }
+            return this.context.logPath;
+        }
+        catch {
+            return undefined;
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.ts b/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.ts
index a35a1789baf86..81fa722035f38 100644
--- a/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.ts
+++ b/extensions/typescript-language-features/Source/tsServer/logDirectoryProvider.ts
@@ -2,15 +2,12 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export interface ILogDirectoryProvider {
-	getNewLogDirectory(): vscode.Uri | undefined;
+    getNewLogDirectory(): vscode.Uri | undefined;
 }
-
 export const noopLogDirectoryProvider = new class implements ILogDirectoryProvider {
-	public getNewLogDirectory(): undefined {
-		return undefined;
-	}
+    public getNewLogDirectory(): undefined {
+        return undefined;
+    }
 };
diff --git a/extensions/typescript-language-features/Source/tsServer/nodeManager.ts b/extensions/typescript-language-features/Source/tsServer/nodeManager.ts
index 037fc1898e864..37f3061529f8d 100644
--- a/extensions/typescript-language-features/Source/tsServer/nodeManager.ts
+++ b/extensions/typescript-language-features/Source/tsServer/nodeManager.ts
@@ -2,148 +2,123 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TypeScriptServiceConfiguration } from '../configuration/configuration';
 import { setImmediate } from '../utils/async';
 import { Disposable } from '../utils/dispose';
-
-
 const useWorkspaceNodeStorageKey = 'typescript.useWorkspaceNode';
 const lastKnownWorkspaceNodeStorageKey = 'typescript.lastKnownWorkspaceNode';
 type UseWorkspaceNodeState = undefined | boolean;
 type LastKnownWorkspaceNodeState = undefined | string;
-
 export class NodeVersionManager extends Disposable {
-	private _currentVersion: string | undefined;
-
-	public constructor(
-		private configuration: TypeScriptServiceConfiguration,
-		private readonly workspaceState: vscode.Memento
-	) {
-		super();
-
-		this._currentVersion = this.configuration.globalNodePath || undefined;
-		if (vscode.workspace.isTrusted) {
-			const workspaceVersion = this.configuration.localNodePath;
-			if (workspaceVersion) {
-				const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
-				if (useWorkspaceNode === undefined) {
-					setImmediate(() => {
-						this.promptAndSetWorkspaceNode();
-					});
-				}
-				else if (useWorkspaceNode) {
-					this._currentVersion = workspaceVersion;
-				}
-			}
-		}
-		else {
-			this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => {
-				const workspaceVersion = this.configuration.localNodePath;
-				if (workspaceVersion) {
-					const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
-					if (useWorkspaceNode === undefined) {
-						setImmediate(() => {
-							this.promptAndSetWorkspaceNode();
-						});
-					}
-					else if (useWorkspaceNode) {
-						this.updateActiveVersion(workspaceVersion);
-					}
-				}
-			}));
-		}
-	}
-
-	private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter());
-	public readonly onDidPickNewVersion = this._onDidPickNewVersion.event;
-
-	public get currentVersion(): string | undefined {
-		return this._currentVersion;
-	}
-
-	public async updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) {
-		const oldConfiguration = this.configuration;
-		this.configuration = nextConfiguration;
-		if (oldConfiguration.globalNodePath !== nextConfiguration.globalNodePath
-			|| oldConfiguration.localNodePath !== nextConfiguration.localNodePath) {
-			await this.computeNewVersion();
-		}
-	}
-
-	private async computeNewVersion() {
-		let version = this.configuration.globalNodePath || undefined;
-		const workspaceVersion = this.configuration.localNodePath;
-		if (vscode.workspace.isTrusted && workspaceVersion) {
-			const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
-			if (useWorkspaceNode === undefined) {
-				version = await this.promptUseWorkspaceNode() || version;
-			}
-			else if (useWorkspaceNode) {
-				version = workspaceVersion;
-			}
-		}
-		this.updateActiveVersion(version);
-	}
-
-	private async promptUseWorkspaceNode(): Promise {
-		const workspaceVersion = this.configuration.localNodePath;
-		if (workspaceVersion === null) {
-			throw new Error('Could not prompt to use workspace Node installation because no workspace Node installation is specified');
-		}
-
-		const allow = vscode.l10n.t("Yes");
-		const disallow = vscode.l10n.t("No");
-		const dismiss = vscode.l10n.t("Not now");
-
-		const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace wants to use the Node installation at '{0}' to run TS Server. Would you like to use it?", workspaceVersion),
-			allow,
-			disallow,
-			dismiss,
-		);
-
-		let version = undefined;
-		switch (result) {
-			case allow:
-				await this.setUseWorkspaceNodeState(true, workspaceVersion);
-				version = workspaceVersion;
-				break;
-			case disallow:
-				await this.setUseWorkspaceNodeState(false, workspaceVersion);
-				break;
-			case dismiss:
-				await this.setUseWorkspaceNodeState(undefined, workspaceVersion);
-				break;
-		}
-		return version;
-	}
-
-	private async promptAndSetWorkspaceNode(): Promise {
-		const version = await this.promptUseWorkspaceNode();
-		if (version !== undefined) {
-			this.updateActiveVersion(version);
-		}
-	}
-
-	private updateActiveVersion(pickedVersion: string | undefined): void {
-		const oldVersion = this.currentVersion;
-		this._currentVersion = pickedVersion;
-		if (oldVersion !== pickedVersion) {
-			this._onDidPickNewVersion.fire();
-		}
-	}
-
-	private canUseWorkspaceNode(nodeVersion: string): boolean | undefined {
-		const lastKnownWorkspaceNode = this.workspaceState.get(lastKnownWorkspaceNodeStorageKey);
-		if (lastKnownWorkspaceNode === nodeVersion) {
-			return this.workspaceState.get(useWorkspaceNodeStorageKey);
-		}
-		return undefined;
-	}
-
-	private async setUseWorkspaceNodeState(allow: boolean | undefined, nodeVersion: string) {
-		await this.workspaceState.update(lastKnownWorkspaceNodeStorageKey, nodeVersion);
-		await this.workspaceState.update(useWorkspaceNodeStorageKey, allow);
-	}
+    private _currentVersion: string | undefined;
+    public constructor(private configuration: TypeScriptServiceConfiguration, private readonly workspaceState: vscode.Memento) {
+        super();
+        this._currentVersion = this.configuration.globalNodePath || undefined;
+        if (vscode.workspace.isTrusted) {
+            const workspaceVersion = this.configuration.localNodePath;
+            if (workspaceVersion) {
+                const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
+                if (useWorkspaceNode === undefined) {
+                    setImmediate(() => {
+                        this.promptAndSetWorkspaceNode();
+                    });
+                }
+                else if (useWorkspaceNode) {
+                    this._currentVersion = workspaceVersion;
+                }
+            }
+        }
+        else {
+            this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => {
+                const workspaceVersion = this.configuration.localNodePath;
+                if (workspaceVersion) {
+                    const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
+                    if (useWorkspaceNode === undefined) {
+                        setImmediate(() => {
+                            this.promptAndSetWorkspaceNode();
+                        });
+                    }
+                    else if (useWorkspaceNode) {
+                        this.updateActiveVersion(workspaceVersion);
+                    }
+                }
+            }));
+        }
+    }
+    private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter());
+    public readonly onDidPickNewVersion = this._onDidPickNewVersion.event;
+    public get currentVersion(): string | undefined {
+        return this._currentVersion;
+    }
+    public async updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) {
+        const oldConfiguration = this.configuration;
+        this.configuration = nextConfiguration;
+        if (oldConfiguration.globalNodePath !== nextConfiguration.globalNodePath
+            || oldConfiguration.localNodePath !== nextConfiguration.localNodePath) {
+            await this.computeNewVersion();
+        }
+    }
+    private async computeNewVersion() {
+        let version = this.configuration.globalNodePath || undefined;
+        const workspaceVersion = this.configuration.localNodePath;
+        if (vscode.workspace.isTrusted && workspaceVersion) {
+            const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
+            if (useWorkspaceNode === undefined) {
+                version = await this.promptUseWorkspaceNode() || version;
+            }
+            else if (useWorkspaceNode) {
+                version = workspaceVersion;
+            }
+        }
+        this.updateActiveVersion(version);
+    }
+    private async promptUseWorkspaceNode(): Promise {
+        const workspaceVersion = this.configuration.localNodePath;
+        if (workspaceVersion === null) {
+            throw new Error('Could not prompt to use workspace Node installation because no workspace Node installation is specified');
+        }
+        const allow = vscode.l10n.t("Yes");
+        const disallow = vscode.l10n.t("No");
+        const dismiss = vscode.l10n.t("Not now");
+        const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace wants to use the Node installation at '{0}' to run TS Server. Would you like to use it?", workspaceVersion), allow, disallow, dismiss);
+        let version = undefined;
+        switch (result) {
+            case allow:
+                await this.setUseWorkspaceNodeState(true, workspaceVersion);
+                version = workspaceVersion;
+                break;
+            case disallow:
+                await this.setUseWorkspaceNodeState(false, workspaceVersion);
+                break;
+            case dismiss:
+                await this.setUseWorkspaceNodeState(undefined, workspaceVersion);
+                break;
+        }
+        return version;
+    }
+    private async promptAndSetWorkspaceNode(): Promise {
+        const version = await this.promptUseWorkspaceNode();
+        if (version !== undefined) {
+            this.updateActiveVersion(version);
+        }
+    }
+    private updateActiveVersion(pickedVersion: string | undefined): void {
+        const oldVersion = this.currentVersion;
+        this._currentVersion = pickedVersion;
+        if (oldVersion !== pickedVersion) {
+            this._onDidPickNewVersion.fire();
+        }
+    }
+    private canUseWorkspaceNode(nodeVersion: string): boolean | undefined {
+        const lastKnownWorkspaceNode = this.workspaceState.get(lastKnownWorkspaceNodeStorageKey);
+        if (lastKnownWorkspaceNode === nodeVersion) {
+            return this.workspaceState.get(useWorkspaceNodeStorageKey);
+        }
+        return undefined;
+    }
+    private async setUseWorkspaceNodeState(allow: boolean | undefined, nodeVersion: string) {
+        await this.workspaceState.update(lastKnownWorkspaceNodeStorageKey, nodeVersion);
+        await this.workspaceState.update(useWorkspaceNodeStorageKey, allow);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/pluginPathsProvider.ts b/extensions/typescript-language-features/Source/tsServer/pluginPathsProvider.ts
index a56076d3944ef..735f57c1d0476 100644
--- a/extensions/typescript-language-features/Source/tsServer/pluginPathsProvider.ts
+++ b/extensions/typescript-language-features/Source/tsServer/pluginPathsProvider.ts
@@ -6,37 +6,27 @@ import * as path from 'path';
 import * as vscode from 'vscode';
 import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver';
 import { TypeScriptServiceConfiguration } from '../configuration/configuration';
-
-
 export class TypeScriptPluginPathsProvider {
-
-	public constructor(
-		private configuration: TypeScriptServiceConfiguration
-	) { }
-
-	public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
-		this.configuration = configuration;
-	}
-
-	public getPluginPaths(): string[] {
-		const pluginPaths = [];
-		for (const pluginPath of this.configuration.tsServerPluginPaths) {
-			pluginPaths.push(...this.resolvePluginPath(pluginPath));
-		}
-		return pluginPaths;
-	}
-
-	private resolvePluginPath(pluginPath: string): string[] {
-		if (path.isAbsolute(pluginPath)) {
-			return [pluginPath];
-		}
-
-		const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(pluginPath);
-		if (workspacePath !== undefined) {
-			return [workspacePath];
-		}
-
-		return (vscode.workspace.workspaceFolders || [])
-			.map(workspaceFolder => path.join(workspaceFolder.uri.fsPath, pluginPath));
-	}
+    public constructor(private configuration: TypeScriptServiceConfiguration) { }
+    public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
+        this.configuration = configuration;
+    }
+    public getPluginPaths(): string[] {
+        const pluginPaths = [];
+        for (const pluginPath of this.configuration.tsServerPluginPaths) {
+            pluginPaths.push(...this.resolvePluginPath(pluginPath));
+        }
+        return pluginPaths;
+    }
+    private resolvePluginPath(pluginPath: string): string[] {
+        if (path.isAbsolute(pluginPath)) {
+            return [pluginPath];
+        }
+        const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(pluginPath);
+        if (workspacePath !== undefined) {
+            return [workspacePath];
+        }
+        return (vscode.workspace.workspaceFolders || [])
+            .map(workspaceFolder => path.join(workspaceFolder.uri.fsPath, pluginPath));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/plugins.ts b/extensions/typescript-language-features/Source/tsServer/plugins.ts
index 6036e4c968b02..e986ad32011fe 100644
--- a/extensions/typescript-language-features/Source/tsServer/plugins.ts
+++ b/extensions/typescript-language-features/Source/tsServer/plugins.ts
@@ -2,91 +2,83 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as arrays from '../utils/arrays';
 import { Disposable } from '../utils/dispose';
-
 export interface TypeScriptServerPlugin {
-	readonly extension: vscode.Extension;
-	readonly uri: vscode.Uri;
-	readonly name: string;
-	readonly enableForWorkspaceTypeScriptVersions: boolean;
-	readonly languages: ReadonlyArray;
-	readonly configNamespace?: string;
+    readonly extension: vscode.Extension;
+    readonly uri: vscode.Uri;
+    readonly name: string;
+    readonly enableForWorkspaceTypeScriptVersions: boolean;
+    readonly languages: ReadonlyArray;
+    readonly configNamespace?: string;
 }
-
 namespace TypeScriptServerPlugin {
-	export function equals(a: TypeScriptServerPlugin, b: TypeScriptServerPlugin): boolean {
-		return a.uri.toString() === b.uri.toString()
-			&& a.name === b.name
-			&& a.enableForWorkspaceTypeScriptVersions === b.enableForWorkspaceTypeScriptVersions
-			&& arrays.equals(a.languages, b.languages);
-	}
+    export function equals(a: TypeScriptServerPlugin, b: TypeScriptServerPlugin): boolean {
+        return a.uri.toString() === b.uri.toString()
+            && a.name === b.name
+            && a.enableForWorkspaceTypeScriptVersions === b.enableForWorkspaceTypeScriptVersions
+            && arrays.equals(a.languages, b.languages);
+    }
 }
-
 export class PluginManager extends Disposable {
-	private readonly _pluginConfigurations = new Map();
-
-	private _plugins?: Map>;
-
-	constructor() {
-		super();
-
-		vscode.extensions.onDidChange(() => {
-			if (!this._plugins) {
-				return;
-			}
-
-			const newPlugins = this.readPlugins();
-			if (!arrays.equals(Array.from(this._plugins.values()).flat(), Array.from(newPlugins.values()).flat(), TypeScriptServerPlugin.equals)) {
-				this._plugins = newPlugins;
-				this._onDidUpdatePlugins.fire(this);
-			}
-		}, undefined, this._disposables);
-	}
-
-	public get plugins(): ReadonlyArray {
-		this._plugins ??= this.readPlugins();
-		return Array.from(this._plugins.values()).flat();
-	}
-
-	private readonly _onDidUpdatePlugins = this._register(new vscode.EventEmitter());
-	public readonly onDidChangePlugins = this._onDidUpdatePlugins.event;
-
-	private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{ pluginId: string; config: {} }>());
-	public readonly onDidUpdateConfig = this._onDidUpdateConfig.event;
-
-	public setConfiguration(pluginId: string, config: {}) {
-		this._pluginConfigurations.set(pluginId, config);
-		this._onDidUpdateConfig.fire({ pluginId, config });
-	}
-
-	public configurations(): IterableIterator<[string, {}]> {
-		return this._pluginConfigurations.entries();
-	}
-
-	private readPlugins() {
-		const pluginMap = new Map>();
-		for (const extension of vscode.extensions.all) {
-			const pack = extension.packageJSON;
-			if (pack.contributes && Array.isArray(pack.contributes.typescriptServerPlugins)) {
-				const plugins: TypeScriptServerPlugin[] = [];
-				for (const plugin of pack.contributes.typescriptServerPlugins) {
-					plugins.push({
-						extension,
-						name: plugin.name,
-						enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions,
-						uri: extension.extensionUri,
-						languages: Array.isArray(plugin.languages) ? plugin.languages : [],
-						configNamespace: plugin.configNamespace,
-					});
-				}
-				if (plugins.length) {
-					pluginMap.set(extension.id, plugins);
-				}
-			}
-		}
-		return pluginMap;
-	}
+    private readonly _pluginConfigurations = new Map();
+    private _plugins?: Map>;
+    constructor() {
+        super();
+        vscode.extensions.onDidChange(() => {
+            if (!this._plugins) {
+                return;
+            }
+            const newPlugins = this.readPlugins();
+            if (!arrays.equals(Array.from(this._plugins.values()).flat(), Array.from(newPlugins.values()).flat(), TypeScriptServerPlugin.equals)) {
+                this._plugins = newPlugins;
+                this._onDidUpdatePlugins.fire(this);
+            }
+        }, undefined, this._disposables);
+    }
+    public get plugins(): ReadonlyArray {
+        this._plugins ??= this.readPlugins();
+        return Array.from(this._plugins.values()).flat();
+    }
+    private readonly _onDidUpdatePlugins = this._register(new vscode.EventEmitter());
+    public readonly onDidChangePlugins = this._onDidUpdatePlugins.event;
+    private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{
+        pluginId: string;
+        config: {};
+    }>());
+    public readonly onDidUpdateConfig = this._onDidUpdateConfig.event;
+    public setConfiguration(pluginId: string, config: {}) {
+        this._pluginConfigurations.set(pluginId, config);
+        this._onDidUpdateConfig.fire({ pluginId, config });
+    }
+    public configurations(): IterableIterator<[
+        string,
+        {}
+    ]> {
+        return this._pluginConfigurations.entries();
+    }
+    private readPlugins() {
+        const pluginMap = new Map>();
+        for (const extension of vscode.extensions.all) {
+            const pack = extension.packageJSON;
+            if (pack.contributes && Array.isArray(pack.contributes.typescriptServerPlugins)) {
+                const plugins: TypeScriptServerPlugin[] = [];
+                for (const plugin of pack.contributes.typescriptServerPlugins) {
+                    plugins.push({
+                        extension,
+                        name: plugin.name,
+                        enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions,
+                        uri: extension.extensionUri,
+                        languages: Array.isArray(plugin.languages) ? plugin.languages : [],
+                        configNamespace: plugin.configNamespace,
+                    });
+                }
+                if (plugins.length) {
+                    pluginMap.set(extension.id, plugins);
+                }
+            }
+        }
+        return pluginMap;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/protocol/errorCodes.ts b/extensions/typescript-language-features/Source/tsServer/protocol/errorCodes.ts
index 2f554e9658468..e6d98c22f9660 100644
--- a/extensions/typescript-language-features/Source/tsServer/protocol/errorCodes.ts
+++ b/extensions/typescript-language-features/Source/tsServer/protocol/errorCodes.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export const variableDeclaredButNeverUsed = new Set([6196, 6133]);
 export const propertyDeclaretedButNeverUsed = new Set([6138]);
 export const allImportsAreUnused = new Set([6192]);
diff --git a/extensions/typescript-language-features/Source/tsServer/protocol/fixNames.ts b/extensions/typescript-language-features/Source/tsServer/protocol/fixNames.ts
index c4234010302cf..adf7d23541037 100644
--- a/extensions/typescript-language-features/Source/tsServer/protocol/fixNames.ts
+++ b/extensions/typescript-language-features/Source/tsServer/protocol/fixNames.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export const addMissingAwait = 'addMissingAwait';
 export const addMissingNewOperator = 'addMissingNewOperator';
 export const addMissingOverride = 'fixOverrideModifier';
diff --git a/extensions/typescript-language-features/Source/tsServer/protocol/modifiers.ts b/extensions/typescript-language-features/Source/tsServer/protocol/modifiers.ts
index 589b5da3d52c9..6acc4366baf45 100644
--- a/extensions/typescript-language-features/Source/tsServer/protocol/modifiers.ts
+++ b/extensions/typescript-language-features/Source/tsServer/protocol/modifiers.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function parseKindModifier(kindModifiers: string): Set {
-	return new Set(kindModifiers.split(/,|\s+/g));
+    return new Set(kindModifiers.split(/,|\s+/g));
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/protocol/protocol.const.ts b/extensions/typescript-language-features/Source/tsServer/protocol/protocol.const.ts
index ed4806e6fddab..b2139f315b8eb 100644
--- a/extensions/typescript-language-features/Source/tsServer/protocol/protocol.const.ts
+++ b/extensions/typescript-language-features/Source/tsServer/protocol/protocol.const.ts
@@ -2,101 +2,92 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export class Kind {
-	public static readonly alias = 'alias';
-	public static readonly callSignature = 'call';
-	public static readonly class = 'class';
-	public static readonly const = 'const';
-	public static readonly constructorImplementation = 'constructor';
-	public static readonly constructSignature = 'construct';
-	public static readonly directory = 'directory';
-	public static readonly enum = 'enum';
-	public static readonly enumMember = 'enum member';
-	public static readonly externalModuleName = 'external module name';
-	public static readonly function = 'function';
-	public static readonly indexSignature = 'index';
-	public static readonly interface = 'interface';
-	public static readonly keyword = 'keyword';
-	public static readonly let = 'let';
-	public static readonly localFunction = 'local function';
-	public static readonly localVariable = 'local var';
-	public static readonly method = 'method';
-	public static readonly memberGetAccessor = 'getter';
-	public static readonly memberSetAccessor = 'setter';
-	public static readonly memberVariable = 'property';
-	public static readonly module = 'module';
-	public static readonly primitiveType = 'primitive type';
-	public static readonly script = 'script';
-	public static readonly type = 'type';
-	public static readonly variable = 'var';
-	public static readonly warning = 'warning';
-	public static readonly string = 'string';
-	public static readonly parameter = 'parameter';
-	public static readonly typeParameter = 'type parameter';
+    public static readonly alias = 'alias';
+    public static readonly callSignature = 'call';
+    public static readonly class = 'class';
+    public static readonly const = 'const';
+    public static readonly constructorImplementation = 'constructor';
+    public static readonly constructSignature = 'construct';
+    public static readonly directory = 'directory';
+    public static readonly enum = 'enum';
+    public static readonly enumMember = 'enum member';
+    public static readonly externalModuleName = 'external module name';
+    public static readonly function = 'function';
+    public static readonly indexSignature = 'index';
+    public static readonly interface = 'interface';
+    public static readonly keyword = 'keyword';
+    public static readonly let = 'let';
+    public static readonly localFunction = 'local function';
+    public static readonly localVariable = 'local var';
+    public static readonly method = 'method';
+    public static readonly memberGetAccessor = 'getter';
+    public static readonly memberSetAccessor = 'setter';
+    public static readonly memberVariable = 'property';
+    public static readonly module = 'module';
+    public static readonly primitiveType = 'primitive type';
+    public static readonly script = 'script';
+    public static readonly type = 'type';
+    public static readonly variable = 'var';
+    public static readonly warning = 'warning';
+    public static readonly string = 'string';
+    public static readonly parameter = 'parameter';
+    public static readonly typeParameter = 'type parameter';
 }
-
-
 export class DiagnosticCategory {
-	public static readonly error = 'error';
-	public static readonly warning = 'warning';
-	public static readonly suggestion = 'suggestion';
+    public static readonly error = 'error';
+    public static readonly warning = 'warning';
+    public static readonly suggestion = 'suggestion';
 }
-
 export class KindModifiers {
-	public static readonly optional = 'optional';
-	public static readonly deprecated = 'deprecated';
-	public static readonly color = 'color';
-
-	public static readonly dtsFile = '.d.ts';
-	public static readonly tsFile = '.ts';
-	public static readonly tsxFile = '.tsx';
-	public static readonly jsFile = '.js';
-	public static readonly jsxFile = '.jsx';
-	public static readonly jsonFile = '.json';
-
-	public static readonly fileExtensionKindModifiers = [
-		KindModifiers.dtsFile,
-		KindModifiers.tsFile,
-		KindModifiers.tsxFile,
-		KindModifiers.jsFile,
-		KindModifiers.jsxFile,
-		KindModifiers.jsonFile,
-	];
+    public static readonly optional = 'optional';
+    public static readonly deprecated = 'deprecated';
+    public static readonly color = 'color';
+    public static readonly dtsFile = '.d.ts';
+    public static readonly tsFile = '.ts';
+    public static readonly tsxFile = '.tsx';
+    public static readonly jsFile = '.js';
+    public static readonly jsxFile = '.jsx';
+    public static readonly jsonFile = '.json';
+    public static readonly fileExtensionKindModifiers = [
+        KindModifiers.dtsFile,
+        KindModifiers.tsFile,
+        KindModifiers.tsxFile,
+        KindModifiers.jsFile,
+        KindModifiers.jsxFile,
+        KindModifiers.jsonFile,
+    ];
 }
-
 export class DisplayPartKind {
-	public static readonly functionName = 'functionName';
-	public static readonly methodName = 'methodName';
-	public static readonly parameterName = 'parameterName';
-	public static readonly propertyName = 'propertyName';
-	public static readonly punctuation = 'punctuation';
-	public static readonly text = 'text';
+    public static readonly functionName = 'functionName';
+    public static readonly methodName = 'methodName';
+    public static readonly parameterName = 'parameterName';
+    public static readonly propertyName = 'propertyName';
+    public static readonly punctuation = 'punctuation';
+    public static readonly text = 'text';
 }
-
 export enum EventName {
-	syntaxDiag = 'syntaxDiag',
-	semanticDiag = 'semanticDiag',
-	suggestionDiag = 'suggestionDiag',
-	regionSemanticDiag = 'regionSemanticDiag',
-	configFileDiag = 'configFileDiag',
-	telemetry = 'telemetry',
-	projectLanguageServiceState = 'projectLanguageServiceState',
-	projectsUpdatedInBackground = 'projectsUpdatedInBackground',
-	beginInstallTypes = 'beginInstallTypes',
-	endInstallTypes = 'endInstallTypes',
-	typesInstallerInitializationFailed = 'typesInstallerInitializationFailed',
-	surveyReady = 'surveyReady',
-	projectLoadingStart = 'projectLoadingStart',
-	projectLoadingFinish = 'projectLoadingFinish',
-	createFileWatcher = 'createFileWatcher',
-	createDirectoryWatcher = 'createDirectoryWatcher',
-	closeFileWatcher = 'closeFileWatcher',
-	requestCompleted = 'requestCompleted',
+    syntaxDiag = 'syntaxDiag',
+    semanticDiag = 'semanticDiag',
+    suggestionDiag = 'suggestionDiag',
+    regionSemanticDiag = 'regionSemanticDiag',
+    configFileDiag = 'configFileDiag',
+    telemetry = 'telemetry',
+    projectLanguageServiceState = 'projectLanguageServiceState',
+    projectsUpdatedInBackground = 'projectsUpdatedInBackground',
+    beginInstallTypes = 'beginInstallTypes',
+    endInstallTypes = 'endInstallTypes',
+    typesInstallerInitializationFailed = 'typesInstallerInitializationFailed',
+    surveyReady = 'surveyReady',
+    projectLoadingStart = 'projectLoadingStart',
+    projectLoadingFinish = 'projectLoadingFinish',
+    createFileWatcher = 'createFileWatcher',
+    createDirectoryWatcher = 'createDirectoryWatcher',
+    closeFileWatcher = 'closeFileWatcher',
+    requestCompleted = 'requestCompleted'
 }
-
 export enum OrganizeImportsMode {
-	All = 'All',
-	SortAndCombine = 'SortAndCombine',
-	RemoveUnused = 'RemoveUnused',
+    All = 'All',
+    SortAndCombine = 'SortAndCombine',
+    RemoveUnused = 'RemoveUnused'
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/requestQueue.ts b/extensions/typescript-language-features/Source/tsServer/requestQueue.ts
index e81da742fd56e..317840b6d2081 100644
--- a/extensions/typescript-language-features/Source/tsServer/requestQueue.ts
+++ b/extensions/typescript-language-features/Source/tsServer/requestQueue.ts
@@ -2,80 +2,70 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import type * as Proto from './protocol/protocol';
-
 export enum RequestQueueingType {
-	/**
-	 * Normal request that is executed in order.
-	 */
-	Normal = 1,
-
-	/**
-	 * Request that normal requests jump in front of in the queue.
-	 */
-	LowPriority = 2,
-
-	/**
-	 * A fence that blocks request reordering.
-	 *
-	 * Fences are not reordered. Unlike a normal request, a fence will never jump in front of a low priority request
-	 * in the request queue.
-	 */
-	Fence = 3,
+    /**
+     * Normal request that is executed in order.
+     */
+    Normal = 1,
+    /**
+     * Request that normal requests jump in front of in the queue.
+     */
+    LowPriority = 2,
+    /**
+     * A fence that blocks request reordering.
+     *
+     * Fences are not reordered. Unlike a normal request, a fence will never jump in front of a low priority request
+     * in the request queue.
+     */
+    Fence = 3
 }
-
 export interface RequestItem {
-	readonly request: Proto.Request;
-	readonly expectsResponse: boolean;
-	readonly isAsync: boolean;
-	readonly queueingType: RequestQueueingType;
+    readonly request: Proto.Request;
+    readonly expectsResponse: boolean;
+    readonly isAsync: boolean;
+    readonly queueingType: RequestQueueingType;
 }
-
 export class RequestQueue {
-	private readonly queue: RequestItem[] = [];
-	private sequenceNumber: number = 0;
-
-	public get length(): number {
-		return this.queue.length;
-	}
-
-	public enqueue(item: RequestItem): void {
-		if (item.queueingType === RequestQueueingType.Normal) {
-			let index = this.queue.length - 1;
-			while (index >= 0) {
-				if (this.queue[index].queueingType !== RequestQueueingType.LowPriority) {
-					break;
-				}
-				--index;
-			}
-			this.queue.splice(index + 1, 0, item);
-		} else {
-			// Only normal priority requests can be reordered. All other requests just go to the end.
-			this.queue.push(item);
-		}
-	}
-
-	public dequeue(): RequestItem | undefined {
-		return this.queue.shift();
-	}
-
-	public tryDeletePendingRequest(seq: number): boolean {
-		for (let i = 0; i < this.queue.length; i++) {
-			if (this.queue[i].request.seq === seq) {
-				this.queue.splice(i, 1);
-				return true;
-			}
-		}
-		return false;
-	}
-
-	public createRequest(command: string, args: any): Proto.Request {
-		return {
-			seq: this.sequenceNumber++,
-			type: 'request',
-			command: command,
-			arguments: args
-		};
-	}
+    private readonly queue: RequestItem[] = [];
+    private sequenceNumber: number = 0;
+    public get length(): number {
+        return this.queue.length;
+    }
+    public enqueue(item: RequestItem): void {
+        if (item.queueingType === RequestQueueingType.Normal) {
+            let index = this.queue.length - 1;
+            while (index >= 0) {
+                if (this.queue[index].queueingType !== RequestQueueingType.LowPriority) {
+                    break;
+                }
+                --index;
+            }
+            this.queue.splice(index + 1, 0, item);
+        }
+        else {
+            // Only normal priority requests can be reordered. All other requests just go to the end.
+            this.queue.push(item);
+        }
+    }
+    public dequeue(): RequestItem | undefined {
+        return this.queue.shift();
+    }
+    public tryDeletePendingRequest(seq: number): boolean {
+        for (let i = 0; i < this.queue.length; i++) {
+            if (this.queue[i].request.seq === seq) {
+                this.queue.splice(i, 1);
+                return true;
+            }
+        }
+        return false;
+    }
+    public createRequest(command: string, args: any): Proto.Request {
+        return {
+            seq: this.sequenceNumber++,
+            type: 'request',
+            command: command,
+            arguments: args
+        };
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/server.ts b/extensions/typescript-language-features/Source/tsServer/server.ts
index 4e41f7aa79aac..33151a60b506d 100644
--- a/extensions/typescript-language-features/Source/tsServer/server.ts
+++ b/extensions/typescript-language-features/Source/tsServer/server.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { Cancellation } from '@vscode/sync-api-common/lib/common/messageCancellation';
 import * as vscode from 'vscode';
 import { TypeScriptServiceConfiguration } from '../configuration/configuration';
@@ -20,627 +19,527 @@ import { EventName } from './protocol/protocol.const';
 import { TypeScriptVersionManager } from './versionManager';
 import { TypeScriptVersion } from './versionProvider';
 import { NodeVersionManager } from './nodeManager';
-
 export enum ExecutionTarget {
-	Semantic,
-	Syntax
+    Semantic,
+    Syntax
 }
-
 export interface TypeScriptServerExitEvent {
-	readonly code: number | null;
-	readonly signal: string | null;
+    readonly code: number | null;
+    readonly signal: string | null;
 }
-
-export type TsServerLog =
-	{ readonly type: 'file'; readonly uri: vscode.Uri } |
-	{ readonly type: 'output'; readonly output: vscode.OutputChannel };
-
+export type TsServerLog = {
+    readonly type: 'file';
+    readonly uri: vscode.Uri;
+} | {
+    readonly type: 'output';
+    readonly output: vscode.OutputChannel;
+};
 export interface ITypeScriptServer {
-	readonly onEvent: vscode.Event;
-	readonly onExit: vscode.Event;
-	readonly onError: vscode.Event;
-
-	readonly tsServerLog: TsServerLog | undefined;
-
-	kill(): void;
-
-	/**
-	 * @return A list of all execute requests. If there are multiple entries, the first item is the primary
-	 * request while the rest are secondary ones.
-	 */
-	executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined>;
-
-	dispose(): void;
+    readonly onEvent: vscode.Event;
+    readonly onExit: vscode.Event;
+    readonly onError: vscode.Event;
+    readonly tsServerLog: TsServerLog | undefined;
+    kill(): void;
+    /**
+     * @return A list of all execute requests. If there are multiple entries, the first item is the primary
+     * request while the rest are secondary ones.
+     */
+    executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: {
+        isAsync: boolean;
+        token?: vscode.CancellationToken;
+        expectsResult: boolean;
+        lowPriority?: boolean;
+        executionTarget?: ExecutionTarget;
+    }): Array> | undefined>;
+    dispose(): void;
 }
-
 export interface TsServerDelegate {
-	onFatalError(command: string, error: Error): void;
+    onFatalError(command: string, error: Error): void;
 }
-
 export const enum TsServerProcessKind {
-	Main = 'main',
-	Syntax = 'syntax',
-	Semantic = 'semantic',
-	Diagnostics = 'diagnostics'
+    Main = 'main',
+    Syntax = 'syntax',
+    Semantic = 'semantic',
+    Diagnostics = 'diagnostics'
 }
-
 export interface TsServerProcessFactory {
-	fork(
-		version: TypeScriptVersion,
-		args: readonly string[],
-		kind: TsServerProcessKind,
-		configuration: TypeScriptServiceConfiguration,
-		versionManager: TypeScriptVersionManager,
-		nodeVersionManager: NodeVersionManager,
-		tsServerLog: TsServerLog | undefined,
-	): TsServerProcess;
+    fork(version: TypeScriptVersion, args: readonly string[], kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, versionManager: TypeScriptVersionManager, nodeVersionManager: NodeVersionManager, tsServerLog: TsServerLog | undefined): TsServerProcess;
 }
-
 export interface TsServerProcess {
-	write(serverRequest: Proto.Request): void;
-
-	onData(handler: (data: Proto.Response) => void): void;
-	onExit(handler: (code: number | null, signal: string | null) => void): void;
-	onError(handler: (error: Error) => void): void;
-
-	kill(): void;
+    write(serverRequest: Proto.Request): void;
+    onData(handler: (data: Proto.Response) => void): void;
+    onExit(handler: (code: number | null, signal: string | null) => void): void;
+    onError(handler: (error: Error) => void): void;
+    kill(): void;
 }
-
 export class SingleTsServer extends Disposable implements ITypeScriptServer {
-	private readonly _requestQueue = new RequestQueue();
-	private readonly _callbacks = new CallbackMap();
-	private readonly _pendingResponses = new Set();
-
-	constructor(
-		private readonly _serverId: string,
-		private readonly _serverSource: ServerType,
-		private readonly _process: TsServerProcess,
-		private readonly _tsServerLog: TsServerLog | undefined,
-		private readonly _requestCanceller: OngoingRequestCanceller,
-		private readonly _version: TypeScriptVersion,
-		private readonly _telemetryReporter: TelemetryReporter,
-		private readonly _tracer: Tracer,
-	) {
-		super();
-
-		this._process.onData(msg => {
-			this.dispatchMessage(msg);
-		});
-
-		this._process.onExit((code, signal) => {
-			this._onExit.fire({ code, signal });
-			this._callbacks.destroy('server exited');
-		});
-
-		this._process.onError(error => {
-			this._onError.fire(error);
-			this._callbacks.destroy('server errored');
-		});
-	}
-
-	private readonly _onEvent = this._register(new vscode.EventEmitter());
-	public readonly onEvent = this._onEvent.event;
-
-	private readonly _onExit = this._register(new vscode.EventEmitter());
-	public readonly onExit = this._onExit.event;
-
-	private readonly _onError = this._register(new vscode.EventEmitter());
-	public readonly onError = this._onError.event;
-
-	public get tsServerLog() { return this._tsServerLog; }
-
-	private write(serverRequest: Proto.Request) {
-		this._process.write(serverRequest);
-	}
-
-	public override dispose() {
-		super.dispose();
-		this._callbacks.destroy('server disposed');
-		this._pendingResponses.clear();
-	}
-
-	public kill() {
-		this._process.kill();
-	}
-
-	private dispatchMessage(message: Proto.Message) {
-		try {
-			switch (message.type) {
-				case 'response':
-					if (this._serverSource) {
-						this.dispatchResponse({
-							...(message as Proto.Response),
-							_serverType: this._serverSource
-						});
-					} else {
-						this.dispatchResponse(message as Proto.Response);
-					}
-					break;
-
-				case 'event': {
-					const event = message as Proto.Event;
-					if (event.event === 'requestCompleted') {
-						const seq = (event as Proto.RequestCompletedEvent).body.request_seq;
-						const callback = this._callbacks.fetch(seq);
-						if (callback) {
-							this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
-							callback.onSuccess(undefined);
-						}
-						if ((event as Proto.RequestCompletedEvent).body.performanceData) {
-							this._onEvent.fire(event);
-						}
-					} else {
-						this._tracer.traceEvent(this._serverId, event);
-						this._onEvent.fire(event);
-					}
-					break;
-				}
-				default:
-					throw new Error(`Unknown message type ${message.type} received`);
-			}
-		} finally {
-			this.sendNextRequests();
-		}
-	}
-
-	private tryCancelRequest(request: Proto.Request, command: string): boolean {
-		const seq = request.seq;
-		try {
-			if (this._requestQueue.tryDeletePendingRequest(seq)) {
-				this.logTrace(`Canceled request with sequence number ${seq}`);
-				return true;
-			}
-			if (this._requestCanceller.tryCancelOngoingRequest(seq)) {
-				return true;
-			}
-			this.logTrace(`Tried to cancel request with sequence number ${seq}. But request got already delivered.`);
-			return false;
-		} finally {
-			const callback = this.fetchCallback(seq);
-			callback?.onSuccess(new ServerResponse.Cancelled(`Cancelled request ${seq} - ${command}`));
-		}
-	}
-
-	private dispatchResponse(response: Proto.Response) {
-		const callback = this.fetchCallback(response.request_seq);
-		if (!callback) {
-			return;
-		}
-
-		this._tracer.traceResponse(this._serverId, response, callback);
-		if (response.success) {
-			callback.onSuccess(response);
-		} else if (response.message === 'No content available.') {
-			// Special case where response itself is successful but there is not any data to return.
-			callback.onSuccess(ServerResponse.NoContent);
-		} else {
-			callback.onError(TypeScriptServerError.create(this._serverId, this._version, response));
-		}
-	}
-
-	public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> {
-		const request = this._requestQueue.createRequest(command, args);
-		const requestInfo: RequestItem = {
-			request,
-			expectsResponse: executeInfo.expectsResult,
-			isAsync: executeInfo.isAsync,
-			queueingType: SingleTsServer.getQueueingType(command, executeInfo.lowPriority)
-		};
-		let result: Promise> | undefined;
-		if (executeInfo.expectsResult) {
-			result = new Promise>((resolve, reject) => {
-				this._callbacks.add(request.seq, { onSuccess: resolve as () => ServerResponse.Response | undefined, onError: reject, queuingStartTime: Date.now(), isAsync: executeInfo.isAsync }, executeInfo.isAsync);
-
-				if (executeInfo.token) {
-
-					const cancelViaSAB = isWebAndHasSharedArrayBuffers()
-						? Cancellation.addData(request)
-						: undefined;
-
-					executeInfo.token.onCancellationRequested(() => {
-						cancelViaSAB?.();
-						this.tryCancelRequest(request, command);
-					});
-				}
-			}).catch((err: Error) => {
-				if (err instanceof TypeScriptServerError) {
-					if (!executeInfo.token?.isCancellationRequested) {
-						/* __GDPR__
-							"languageServiceErrorResponse" : {
-								"owner": "mjbvz",
-								"${include}": [
-									"${TypeScriptCommonProperties}",
-									"${TypeScriptRequestErrorProperties}"
-								]
-							}
-						*/
-						this._telemetryReporter.logTelemetry('languageServiceErrorResponse', err.telemetry);
-					}
-				}
-
-				throw err;
-			});
-		}
-
-		this._requestQueue.enqueue(requestInfo);
-		this.sendNextRequests();
-
-		return [result];
-	}
-
-	private sendNextRequests(): void {
-		while (this._pendingResponses.size === 0 && this._requestQueue.length > 0) {
-			const item = this._requestQueue.dequeue();
-			if (item) {
-				this.sendRequest(item);
-			}
-		}
-	}
-
-	private sendRequest(requestItem: RequestItem): void {
-		const serverRequest = requestItem.request;
-		this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length);
-
-		if (requestItem.expectsResponse && !requestItem.isAsync) {
-			this._pendingResponses.add(requestItem.request.seq);
-		}
-
-		try {
-			this.write(serverRequest);
-		} catch (err) {
-			const callback = this.fetchCallback(serverRequest.seq);
-			callback?.onError(err);
-		}
-	}
-
-	private fetchCallback(seq: number) {
-		const callback = this._callbacks.fetch(seq);
-		if (!callback) {
-			return undefined;
-		}
-
-		this._pendingResponses.delete(seq);
-		return callback;
-	}
-
-	private logTrace(message: string) {
-		this._tracer.trace(this._serverId, message);
-	}
-
-	private static readonly fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']);
-
-	private static getQueueingType(
-		command: string,
-		lowPriority?: boolean
-	): RequestQueueingType {
-		if (SingleTsServer.fenceCommands.has(command)) {
-			return RequestQueueingType.Fence;
-		}
-		return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal;
-	}
+    private readonly _requestQueue = new RequestQueue();
+    private readonly _callbacks = new CallbackMap();
+    private readonly _pendingResponses = new Set();
+    constructor(private readonly _serverId: string, private readonly _serverSource: ServerType, private readonly _process: TsServerProcess, private readonly _tsServerLog: TsServerLog | undefined, private readonly _requestCanceller: OngoingRequestCanceller, private readonly _version: TypeScriptVersion, private readonly _telemetryReporter: TelemetryReporter, private readonly _tracer: Tracer) {
+        super();
+        this._process.onData(msg => {
+            this.dispatchMessage(msg);
+        });
+        this._process.onExit((code, signal) => {
+            this._onExit.fire({ code, signal });
+            this._callbacks.destroy('server exited');
+        });
+        this._process.onError(error => {
+            this._onError.fire(error);
+            this._callbacks.destroy('server errored');
+        });
+    }
+    private readonly _onEvent = this._register(new vscode.EventEmitter());
+    public readonly onEvent = this._onEvent.event;
+    private readonly _onExit = this._register(new vscode.EventEmitter());
+    public readonly onExit = this._onExit.event;
+    private readonly _onError = this._register(new vscode.EventEmitter());
+    public readonly onError = this._onError.event;
+    public get tsServerLog() { return this._tsServerLog; }
+    private write(serverRequest: Proto.Request) {
+        this._process.write(serverRequest);
+    }
+    public override dispose() {
+        super.dispose();
+        this._callbacks.destroy('server disposed');
+        this._pendingResponses.clear();
+    }
+    public kill() {
+        this._process.kill();
+    }
+    private dispatchMessage(message: Proto.Message) {
+        try {
+            switch (message.type) {
+                case 'response':
+                    if (this._serverSource) {
+                        this.dispatchResponse({
+                            ...(message as Proto.Response),
+                            _serverType: this._serverSource
+                        });
+                    }
+                    else {
+                        this.dispatchResponse(message as Proto.Response);
+                    }
+                    break;
+                case 'event': {
+                    const event = message as Proto.Event;
+                    if (event.event === 'requestCompleted') {
+                        const seq = (event as Proto.RequestCompletedEvent).body.request_seq;
+                        const callback = this._callbacks.fetch(seq);
+                        if (callback) {
+                            this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
+                            callback.onSuccess(undefined);
+                        }
+                        if ((event as Proto.RequestCompletedEvent).body.performanceData) {
+                            this._onEvent.fire(event);
+                        }
+                    }
+                    else {
+                        this._tracer.traceEvent(this._serverId, event);
+                        this._onEvent.fire(event);
+                    }
+                    break;
+                }
+                default:
+                    throw new Error(`Unknown message type ${message.type} received`);
+            }
+        }
+        finally {
+            this.sendNextRequests();
+        }
+    }
+    private tryCancelRequest(request: Proto.Request, command: string): boolean {
+        const seq = request.seq;
+        try {
+            if (this._requestQueue.tryDeletePendingRequest(seq)) {
+                this.logTrace(`Canceled request with sequence number ${seq}`);
+                return true;
+            }
+            if (this._requestCanceller.tryCancelOngoingRequest(seq)) {
+                return true;
+            }
+            this.logTrace(`Tried to cancel request with sequence number ${seq}. But request got already delivered.`);
+            return false;
+        }
+        finally {
+            const callback = this.fetchCallback(seq);
+            callback?.onSuccess(new ServerResponse.Cancelled(`Cancelled request ${seq} - ${command}`));
+        }
+    }
+    private dispatchResponse(response: Proto.Response) {
+        const callback = this.fetchCallback(response.request_seq);
+        if (!callback) {
+            return;
+        }
+        this._tracer.traceResponse(this._serverId, response, callback);
+        if (response.success) {
+            callback.onSuccess(response);
+        }
+        else if (response.message === 'No content available.') {
+            // Special case where response itself is successful but there is not any data to return.
+            callback.onSuccess(ServerResponse.NoContent);
+        }
+        else {
+            callback.onError(TypeScriptServerError.create(this._serverId, this._version, response));
+        }
+    }
+    public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: {
+        isAsync: boolean;
+        token?: vscode.CancellationToken;
+        expectsResult: boolean;
+        lowPriority?: boolean;
+        executionTarget?: ExecutionTarget;
+    }): Array> | undefined> {
+        const request = this._requestQueue.createRequest(command, args);
+        const requestInfo: RequestItem = {
+            request,
+            expectsResponse: executeInfo.expectsResult,
+            isAsync: executeInfo.isAsync,
+            queueingType: SingleTsServer.getQueueingType(command, executeInfo.lowPriority)
+        };
+        let result: Promise> | undefined;
+        if (executeInfo.expectsResult) {
+            result = new Promise>((resolve, reject) => {
+                this._callbacks.add(request.seq, { onSuccess: resolve as () => ServerResponse.Response | undefined, onError: reject, queuingStartTime: Date.now(), isAsync: executeInfo.isAsync }, executeInfo.isAsync);
+                if (executeInfo.token) {
+                    const cancelViaSAB = isWebAndHasSharedArrayBuffers()
+                        ? Cancellation.addData(request)
+                        : undefined;
+                    executeInfo.token.onCancellationRequested(() => {
+                        cancelViaSAB?.();
+                        this.tryCancelRequest(request, command);
+                    });
+                }
+            }).catch((err: Error) => {
+                if (err instanceof TypeScriptServerError) {
+                    if (!executeInfo.token?.isCancellationRequested) {
+                        /* __GDPR__
+                            "languageServiceErrorResponse" : {
+                                "owner": "mjbvz",
+                                "${include}": [
+                                    "${TypeScriptCommonProperties}",
+                                    "${TypeScriptRequestErrorProperties}"
+                                ]
+                            }
+                        */
+                        this._telemetryReporter.logTelemetry('languageServiceErrorResponse', err.telemetry);
+                    }
+                }
+                throw err;
+            });
+        }
+        this._requestQueue.enqueue(requestInfo);
+        this.sendNextRequests();
+        return [result];
+    }
+    private sendNextRequests(): void {
+        while (this._pendingResponses.size === 0 && this._requestQueue.length > 0) {
+            const item = this._requestQueue.dequeue();
+            if (item) {
+                this.sendRequest(item);
+            }
+        }
+    }
+    private sendRequest(requestItem: RequestItem): void {
+        const serverRequest = requestItem.request;
+        this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length);
+        if (requestItem.expectsResponse && !requestItem.isAsync) {
+            this._pendingResponses.add(requestItem.request.seq);
+        }
+        try {
+            this.write(serverRequest);
+        }
+        catch (err) {
+            const callback = this.fetchCallback(serverRequest.seq);
+            callback?.onError(err);
+        }
+    }
+    private fetchCallback(seq: number) {
+        const callback = this._callbacks.fetch(seq);
+        if (!callback) {
+            return undefined;
+        }
+        this._pendingResponses.delete(seq);
+        return callback;
+    }
+    private logTrace(message: string) {
+        this._tracer.trace(this._serverId, message);
+    }
+    private static readonly fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']);
+    private static getQueueingType(command: string, lowPriority?: boolean): RequestQueueingType {
+        if (SingleTsServer.fenceCommands.has(command)) {
+            return RequestQueueingType.Fence;
+        }
+        return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal;
+    }
 }
-
-
 interface ExecuteInfo {
-	readonly isAsync: boolean;
-	readonly token?: vscode.CancellationToken;
-	readonly expectsResult: boolean;
-	readonly lowPriority?: boolean;
-	readonly executionTarget?: ExecutionTarget;
+    readonly isAsync: boolean;
+    readonly token?: vscode.CancellationToken;
+    readonly expectsResult: boolean;
+    readonly lowPriority?: boolean;
+    readonly executionTarget?: ExecutionTarget;
 }
-
 class RequestRouter {
-
-	private static readonly sharedCommands = new Set([
-		'change',
-		'close',
-		'open',
-		'updateOpen',
-		'configure',
-	]);
-
-	constructor(
-		private readonly servers: ReadonlyArray<{
-			readonly server: ITypeScriptServer;
-			canRun?(command: keyof TypeScriptRequests, executeInfo: ExecuteInfo): boolean;
-		}>,
-		private readonly delegate: TsServerDelegate,
-	) { }
-
-	public execute(
-		command: keyof TypeScriptRequests,
-		args: any,
-		executeInfo: ExecuteInfo,
-	): Array> | undefined> {
-		if (RequestRouter.sharedCommands.has(command) && typeof executeInfo.executionTarget === 'undefined') {
-			// Dispatch shared commands to all servers but use first one as the primary response
-
-			const requestStates: RequestState.State[] = this.servers.map(() => RequestState.Unresolved);
-
-			// Also make sure we never cancel requests to just one server
-			let token: vscode.CancellationToken | undefined = undefined;
-			if (executeInfo.token) {
-				const source = new vscode.CancellationTokenSource();
-				executeInfo.token.onCancellationRequested(() => {
-					if (requestStates.some(state => state === RequestState.Resolved)) {
-						// Don't cancel.
-						// One of the servers completed this request so we don't want to leave the other
-						// in a different state.
-						return;
-					}
-					source.cancel();
-				});
-				token = source.token;
-			}
-
-			const allRequests: Array> | undefined> = [];
-
-			for (let serverIndex = 0; serverIndex < this.servers.length; ++serverIndex) {
-				const server = this.servers[serverIndex].server;
-
-				const request = server.executeImpl(command, args, { ...executeInfo, token })[0];
-				allRequests.push(request);
-				if (request) {
-					request
-						.then(result => {
-							requestStates[serverIndex] = RequestState.Resolved;
-							const erroredRequest = requestStates.find(state => state.type === RequestState.Type.Errored) as RequestState.Errored | undefined;
-							if (erroredRequest) {
-								// We've gone out of sync
-								this.delegate.onFatalError(command, erroredRequest.err);
-							}
-							return result;
-						}, err => {
-							requestStates[serverIndex] = new RequestState.Errored(err);
-							if (requestStates.some(state => state === RequestState.Resolved)) {
-								// We've gone out of sync
-								this.delegate.onFatalError(command, err);
-							}
-							throw err;
-						});
-				}
-			}
-
-			return allRequests;
-		}
-
-		for (const { canRun, server } of this.servers) {
-			if (!canRun || canRun(command, executeInfo)) {
-				return server.executeImpl(command, args, executeInfo);
-			}
-		}
-
-		throw new Error(`Could not find server for command: '${command}'`);
-	}
+    private static readonly sharedCommands = new Set([
+        'change',
+        'close',
+        'open',
+        'updateOpen',
+        'configure',
+    ]);
+    constructor(private readonly servers: ReadonlyArray<{
+        readonly server: ITypeScriptServer;
+        canRun?(command: keyof TypeScriptRequests, executeInfo: ExecuteInfo): boolean;
+    }>, private readonly delegate: TsServerDelegate) { }
+    public execute(command: keyof TypeScriptRequests, args: any, executeInfo: ExecuteInfo): Array> | undefined> {
+        if (RequestRouter.sharedCommands.has(command) && typeof executeInfo.executionTarget === 'undefined') {
+            // Dispatch shared commands to all servers but use first one as the primary response
+            const requestStates: RequestState.State[] = this.servers.map(() => RequestState.Unresolved);
+            // Also make sure we never cancel requests to just one server
+            let token: vscode.CancellationToken | undefined = undefined;
+            if (executeInfo.token) {
+                const source = new vscode.CancellationTokenSource();
+                executeInfo.token.onCancellationRequested(() => {
+                    if (requestStates.some(state => state === RequestState.Resolved)) {
+                        // Don't cancel.
+                        // One of the servers completed this request so we don't want to leave the other
+                        // in a different state.
+                        return;
+                    }
+                    source.cancel();
+                });
+                token = source.token;
+            }
+            const allRequests: Array> | undefined> = [];
+            for (let serverIndex = 0; serverIndex < this.servers.length; ++serverIndex) {
+                const server = this.servers[serverIndex].server;
+                const request = server.executeImpl(command, args, { ...executeInfo, token })[0];
+                allRequests.push(request);
+                if (request) {
+                    request
+                        .then(result => {
+                        requestStates[serverIndex] = RequestState.Resolved;
+                        const erroredRequest = requestStates.find(state => state.type === RequestState.Type.Errored) as RequestState.Errored | undefined;
+                        if (erroredRequest) {
+                            // We've gone out of sync
+                            this.delegate.onFatalError(command, erroredRequest.err);
+                        }
+                        return result;
+                    }, err => {
+                        requestStates[serverIndex] = new RequestState.Errored(err);
+                        if (requestStates.some(state => state === RequestState.Resolved)) {
+                            // We've gone out of sync
+                            this.delegate.onFatalError(command, err);
+                        }
+                        throw err;
+                    });
+                }
+            }
+            return allRequests;
+        }
+        for (const { canRun, server } of this.servers) {
+            if (!canRun || canRun(command, executeInfo)) {
+                return server.executeImpl(command, args, executeInfo);
+            }
+        }
+        throw new Error(`Could not find server for command: '${command}'`);
+    }
 }
-
 export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServer {
-
-	private static readonly diagnosticEvents = new Set([
-		EventName.configFileDiag,
-		EventName.syntaxDiag,
-		EventName.semanticDiag,
-		EventName.suggestionDiag
-	]);
-
-	private readonly getErrServer: ITypeScriptServer;
-	private readonly mainServer: ITypeScriptServer;
-	private readonly router: RequestRouter;
-
-	public constructor(
-		servers: { getErr: ITypeScriptServer; primary: ITypeScriptServer },
-		delegate: TsServerDelegate,
-	) {
-		super();
-
-		this.getErrServer = servers.getErr;
-		this.mainServer = servers.primary;
-
-		this.router = new RequestRouter(
-			[
-				{ server: this.getErrServer, canRun: (command) => ['geterr', 'geterrForProject'].includes(command) },
-				{ server: this.mainServer, canRun: undefined /* gets all other commands */ }
-			],
-			delegate);
-
-		this._register(this.getErrServer.onEvent(e => {
-			if (GetErrRoutingTsServer.diagnosticEvents.has(e.event)) {
-				this._onEvent.fire(e);
-			}
-			// Ignore all other events
-		}));
-		this._register(this.mainServer.onEvent(e => {
-			if (!GetErrRoutingTsServer.diagnosticEvents.has(e.event)) {
-				this._onEvent.fire(e);
-			}
-			// Ignore all other events
-		}));
-
-		this._register(this.getErrServer.onError(e => this._onError.fire(e)));
-		this._register(this.mainServer.onError(e => this._onError.fire(e)));
-
-		this._register(this.mainServer.onExit(e => {
-			this._onExit.fire(e);
-			this.getErrServer.kill();
-		}));
-	}
-
-	private readonly _onEvent = this._register(new vscode.EventEmitter());
-	public readonly onEvent = this._onEvent.event;
-
-	private readonly _onExit = this._register(new vscode.EventEmitter());
-	public readonly onExit = this._onExit.event;
-
-	private readonly _onError = this._register(new vscode.EventEmitter());
-	public readonly onError = this._onError.event;
-
-	public get tsServerLog() { return this.mainServer.tsServerLog; }
-
-	public kill(): void {
-		this.getErrServer.kill();
-		this.mainServer.kill();
-	}
-
-	public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> {
-		return this.router.execute(command, args, executeInfo);
-	}
+    private static readonly diagnosticEvents = new Set([
+        EventName.configFileDiag,
+        EventName.syntaxDiag,
+        EventName.semanticDiag,
+        EventName.suggestionDiag
+    ]);
+    private readonly getErrServer: ITypeScriptServer;
+    private readonly mainServer: ITypeScriptServer;
+    private readonly router: RequestRouter;
+    public constructor(servers: {
+        getErr: ITypeScriptServer;
+        primary: ITypeScriptServer;
+    }, delegate: TsServerDelegate) {
+        super();
+        this.getErrServer = servers.getErr;
+        this.mainServer = servers.primary;
+        this.router = new RequestRouter([
+            { server: this.getErrServer, canRun: (command) => ['geterr', 'geterrForProject'].includes(command) },
+            { server: this.mainServer, canRun: undefined /* gets all other commands */ }
+        ], delegate);
+        this._register(this.getErrServer.onEvent(e => {
+            if (GetErrRoutingTsServer.diagnosticEvents.has(e.event)) {
+                this._onEvent.fire(e);
+            }
+            // Ignore all other events
+        }));
+        this._register(this.mainServer.onEvent(e => {
+            if (!GetErrRoutingTsServer.diagnosticEvents.has(e.event)) {
+                this._onEvent.fire(e);
+            }
+            // Ignore all other events
+        }));
+        this._register(this.getErrServer.onError(e => this._onError.fire(e)));
+        this._register(this.mainServer.onError(e => this._onError.fire(e)));
+        this._register(this.mainServer.onExit(e => {
+            this._onExit.fire(e);
+            this.getErrServer.kill();
+        }));
+    }
+    private readonly _onEvent = this._register(new vscode.EventEmitter());
+    public readonly onEvent = this._onEvent.event;
+    private readonly _onExit = this._register(new vscode.EventEmitter());
+    public readonly onExit = this._onExit.event;
+    private readonly _onError = this._register(new vscode.EventEmitter());
+    public readonly onError = this._onError.event;
+    public get tsServerLog() { return this.mainServer.tsServerLog; }
+    public kill(): void {
+        this.getErrServer.kill();
+        this.mainServer.kill();
+    }
+    public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: {
+        isAsync: boolean;
+        token?: vscode.CancellationToken;
+        expectsResult: boolean;
+        lowPriority?: boolean;
+        executionTarget?: ExecutionTarget;
+    }): Array> | undefined> {
+        return this.router.execute(command, args, executeInfo);
+    }
 }
-
-
 export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServer {
-
-	/**
-	 * Commands that should always be run on the syntax server.
-	 */
-	private static readonly syntaxAlwaysCommands = new Set([
-		'navtree',
-		'getOutliningSpans',
-		'jsxClosingTag',
-		'selectionRange',
-		'format',
-		'formatonkey',
-		'docCommentTemplate',
-		'linkedEditingRange'
-	]);
-
-	/**
-	 * Commands that should always be run on the semantic server.
-	 */
-	private static readonly semanticCommands = new Set([
-		'geterr',
-		'geterrForProject',
-		'projectInfo',
-		'configurePlugin',
-	]);
-
-	/**
-	 * Commands that can be run on the syntax server but would benefit from being upgraded to the semantic server.
-	 */
-	private static readonly syntaxAllowedCommands = new Set([
-		'completions',
-		'completionEntryDetails',
-		'completionInfo',
-		'definition',
-		'definitionAndBoundSpan',
-		'documentHighlights',
-		'implementation',
-		'navto',
-		'quickinfo',
-		'references',
-		'rename',
-		'signatureHelp',
-	]);
-
-	private readonly syntaxServer: ITypeScriptServer;
-	private readonly semanticServer: ITypeScriptServer;
-	private readonly router: RequestRouter;
-
-	private _projectLoading = true;
-
-	public constructor(
-		servers: { syntax: ITypeScriptServer; semantic: ITypeScriptServer },
-		delegate: TsServerDelegate,
-		enableDynamicRouting: boolean,
-	) {
-		super();
-
-		this.syntaxServer = servers.syntax;
-		this.semanticServer = servers.semantic;
-
-		this.router = new RequestRouter(
-			[
-				{
-					server: this.syntaxServer,
-					canRun: (command, execInfo) => {
-						switch (execInfo.executionTarget) {
-							case ExecutionTarget.Semantic: return false;
-							case ExecutionTarget.Syntax: return true;
-						}
-
-						if (SyntaxRoutingTsServer.syntaxAlwaysCommands.has(command)) {
-							return true;
-						}
-						if (SyntaxRoutingTsServer.semanticCommands.has(command)) {
-							return false;
-						}
-						if (enableDynamicRouting && this.projectLoading && SyntaxRoutingTsServer.syntaxAllowedCommands.has(command)) {
-							return true;
-						}
-						return false;
-					}
-				}, {
-					server: this.semanticServer,
-					canRun: undefined /* gets all other commands */
-				}
-			],
-			delegate);
-
-		this._register(this.syntaxServer.onEvent(e => {
-			return this._onEvent.fire(e);
-		}));
-
-		this._register(this.semanticServer.onEvent(e => {
-			switch (e.event) {
-				case EventName.projectLoadingStart:
-					this._projectLoading = true;
-					break;
-
-				case EventName.projectLoadingFinish:
-				case EventName.semanticDiag:
-				case EventName.syntaxDiag:
-				case EventName.suggestionDiag:
-				case EventName.configFileDiag:
-					this._projectLoading = false;
-					break;
-			}
-			return this._onEvent.fire(e);
-		}));
-
-		this._register(this.semanticServer.onExit(e => {
-			this._onExit.fire(e);
-			this.syntaxServer.kill();
-		}));
-
-		this._register(this.semanticServer.onError(e => this._onError.fire(e)));
-	}
-
-	private get projectLoading() { return this._projectLoading; }
-
-	private readonly _onEvent = this._register(new vscode.EventEmitter());
-	public readonly onEvent = this._onEvent.event;
-
-	private readonly _onExit = this._register(new vscode.EventEmitter());
-	public readonly onExit = this._onExit.event;
-
-	private readonly _onError = this._register(new vscode.EventEmitter());
-	public readonly onError = this._onError.event;
-
-	public get tsServerLog() { return this.semanticServer.tsServerLog; }
-
-	public kill(): void {
-		this.syntaxServer.kill();
-		this.semanticServer.kill();
-	}
-
-	public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> {
-		return this.router.execute(command, args, executeInfo);
-	}
+    /**
+     * Commands that should always be run on the syntax server.
+     */
+    private static readonly syntaxAlwaysCommands = new Set([
+        'navtree',
+        'getOutliningSpans',
+        'jsxClosingTag',
+        'selectionRange',
+        'format',
+        'formatonkey',
+        'docCommentTemplate',
+        'linkedEditingRange'
+    ]);
+    /**
+     * Commands that should always be run on the semantic server.
+     */
+    private static readonly semanticCommands = new Set([
+        'geterr',
+        'geterrForProject',
+        'projectInfo',
+        'configurePlugin',
+    ]);
+    /**
+     * Commands that can be run on the syntax server but would benefit from being upgraded to the semantic server.
+     */
+    private static readonly syntaxAllowedCommands = new Set([
+        'completions',
+        'completionEntryDetails',
+        'completionInfo',
+        'definition',
+        'definitionAndBoundSpan',
+        'documentHighlights',
+        'implementation',
+        'navto',
+        'quickinfo',
+        'references',
+        'rename',
+        'signatureHelp',
+    ]);
+    private readonly syntaxServer: ITypeScriptServer;
+    private readonly semanticServer: ITypeScriptServer;
+    private readonly router: RequestRouter;
+    private _projectLoading = true;
+    public constructor(servers: {
+        syntax: ITypeScriptServer;
+        semantic: ITypeScriptServer;
+    }, delegate: TsServerDelegate, enableDynamicRouting: boolean) {
+        super();
+        this.syntaxServer = servers.syntax;
+        this.semanticServer = servers.semantic;
+        this.router = new RequestRouter([
+            {
+                server: this.syntaxServer,
+                canRun: (command, execInfo) => {
+                    switch (execInfo.executionTarget) {
+                        case ExecutionTarget.Semantic: return false;
+                        case ExecutionTarget.Syntax: return true;
+                    }
+                    if (SyntaxRoutingTsServer.syntaxAlwaysCommands.has(command)) {
+                        return true;
+                    }
+                    if (SyntaxRoutingTsServer.semanticCommands.has(command)) {
+                        return false;
+                    }
+                    if (enableDynamicRouting && this.projectLoading && SyntaxRoutingTsServer.syntaxAllowedCommands.has(command)) {
+                        return true;
+                    }
+                    return false;
+                }
+            }, {
+                server: this.semanticServer,
+                canRun: undefined /* gets all other commands */
+            }
+        ], delegate);
+        this._register(this.syntaxServer.onEvent(e => {
+            return this._onEvent.fire(e);
+        }));
+        this._register(this.semanticServer.onEvent(e => {
+            switch (e.event) {
+                case EventName.projectLoadingStart:
+                    this._projectLoading = true;
+                    break;
+                case EventName.projectLoadingFinish:
+                case EventName.semanticDiag:
+                case EventName.syntaxDiag:
+                case EventName.suggestionDiag:
+                case EventName.configFileDiag:
+                    this._projectLoading = false;
+                    break;
+            }
+            return this._onEvent.fire(e);
+        }));
+        this._register(this.semanticServer.onExit(e => {
+            this._onExit.fire(e);
+            this.syntaxServer.kill();
+        }));
+        this._register(this.semanticServer.onError(e => this._onError.fire(e)));
+    }
+    private get projectLoading() { return this._projectLoading; }
+    private readonly _onEvent = this._register(new vscode.EventEmitter());
+    public readonly onEvent = this._onEvent.event;
+    private readonly _onExit = this._register(new vscode.EventEmitter());
+    public readonly onExit = this._onExit.event;
+    private readonly _onError = this._register(new vscode.EventEmitter());
+    public readonly onError = this._onError.event;
+    public get tsServerLog() { return this.semanticServer.tsServerLog; }
+    public kill(): void {
+        this.syntaxServer.kill();
+        this.semanticServer.kill();
+    }
+    public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: {
+        isAsync: boolean;
+        token?: vscode.CancellationToken;
+        expectsResult: boolean;
+        lowPriority?: boolean;
+        executionTarget?: ExecutionTarget;
+    }): Array> | undefined> {
+        return this.router.execute(command, args, executeInfo);
+    }
 }
-
 namespace RequestState {
-	export const enum Type { Unresolved, Resolved, Errored }
-
-	export const Unresolved = { type: Type.Unresolved } as const;
-
-	export const Resolved = { type: Type.Resolved } as const;
-
-	export class Errored {
-		readonly type = Type.Errored;
-
-		constructor(
-			public readonly err: Error
-		) { }
-	}
-
-	export type State = typeof Unresolved | typeof Resolved | Errored;
+    export const enum Type {
+        Unresolved,
+        Resolved,
+        Errored
+    }
+    export const Unresolved = { type: Type.Unresolved } as const;
+    export const Resolved = { type: Type.Resolved } as const;
+    export class Errored {
+        readonly type = Type.Errored;
+        constructor(public readonly err: Error) { }
+    }
+    export type State = typeof Unresolved | typeof Resolved | Errored;
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/serverError.ts b/extensions/typescript-language-features/Source/tsServer/serverError.ts
index 7467c87f216d1..ee8afeee50771 100644
--- a/extensions/typescript-language-features/Source/tsServer/serverError.ts
+++ b/extensions/typescript-language-features/Source/tsServer/serverError.ts
@@ -2,98 +2,78 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import type * as Proto from './protocol/protocol';
 import { TypeScriptVersion } from './versionProvider';
-
-
 export class TypeScriptServerError extends Error {
-	public static create(
-		serverId: string,
-		version: TypeScriptVersion,
-		response: Proto.Response
-	): TypeScriptServerError {
-		const parsedResult = TypeScriptServerError.parseErrorText(response);
-		return new TypeScriptServerError(serverId, version, response, parsedResult?.message, parsedResult?.stack, parsedResult?.sanitizedStack);
-	}
-
-	private constructor(
-		public readonly serverId: string,
-		public readonly version: TypeScriptVersion,
-		private readonly response: Proto.Response,
-		public readonly serverMessage: string | undefined,
-		public readonly serverStack: string | undefined,
-		private readonly sanitizedStack: string | undefined
-	) {
-		super(`<${serverId}> TypeScript Server Error (${version.displayName})\n${serverMessage}\n${serverStack}`);
-	}
-
-	public get serverErrorText() { return this.response.message; }
-
-	public get serverCommand() { return this.response.command; }
-
-	public get telemetry() {
-		// The "sanitizedstack" has been purged of error messages, paths, and file names (other than tsserver)
-		// and, thus, can be classified as SystemMetaData, rather than CallstackOrException.
-		/* __GDPR__FRAGMENT__
-			"TypeScriptRequestErrorProperties" : {
-				"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"serverid" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
-				"sanitizedstack" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
-				"badclient" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
-			}
-		*/
-		return {
-			command: this.serverCommand,
-			serverid: this.serverId,
-			sanitizedstack: this.sanitizedStack || '',
-			badclient: /\bBADCLIENT\b/.test(this.stack || ''),
-		} as const;
-	}
-
-	/**
-	 * Given a `errorText` from a tsserver request indicating failure in handling a request,
-	 * prepares a payload for telemetry-logging.
-	 */
-	private static parseErrorText(response: Proto.Response) {
-		const errorText = response.message;
-		if (errorText) {
-			const errorPrefix = 'Error processing request. ';
-			if (errorText.startsWith(errorPrefix)) {
-				const prefixFreeErrorText = errorText.substr(errorPrefix.length);
-				const newlineIndex = prefixFreeErrorText.indexOf('\n');
-				if (newlineIndex >= 0) {
-					// Newline expected between message and stack.
-					const stack = prefixFreeErrorText.substring(newlineIndex + 1);
-					return {
-						message: prefixFreeErrorText.substring(0, newlineIndex),
-						stack,
-						sanitizedStack: TypeScriptServerError.sanitizeStack(stack)
-					};
-				}
-			}
-		}
-		return undefined;
-	}
-
-	/**
-	 * Drop everything but ".js" and line/column numbers (though retain "tsserver" if that's the filename).
-	 */
-	private static sanitizeStack(message: string | undefined) {
-		if (!message) {
-			return '';
-		}
-		const regex = /(\btsserver)?(\.(?:ts|tsx|js|jsx)(?::\d+(?::\d+)?)?)\)?$/igm;
-		let serverStack = '';
-		while (true) {
-			const match = regex.exec(message);
-			if (!match) {
-				break;
-			}
-			// [1] is 'tsserver' or undefined
-			// [2] is '.js:{line_number}:{column_number}'
-			serverStack += `${match[1] || 'suppressed'}${match[2]}\n`;
-		}
-		return serverStack;
-	}
+    public static create(serverId: string, version: TypeScriptVersion, response: Proto.Response): TypeScriptServerError {
+        const parsedResult = TypeScriptServerError.parseErrorText(response);
+        return new TypeScriptServerError(serverId, version, response, parsedResult?.message, parsedResult?.stack, parsedResult?.sanitizedStack);
+    }
+    private constructor(public readonly serverId: string, public readonly version: TypeScriptVersion, private readonly response: Proto.Response, public readonly serverMessage: string | undefined, public readonly serverStack: string | undefined, private readonly sanitizedStack: string | undefined) {
+        super(`<${serverId}> TypeScript Server Error (${version.displayName})\n${serverMessage}\n${serverStack}`);
+    }
+    public get serverErrorText() { return this.response.message; }
+    public get serverCommand() { return this.response.command; }
+    public get telemetry() {
+        // The "sanitizedstack" has been purged of error messages, paths, and file names (other than tsserver)
+        // and, thus, can be classified as SystemMetaData, rather than CallstackOrException.
+        /* __GDPR__FRAGMENT__
+            "TypeScriptRequestErrorProperties" : {
+                "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "serverid" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
+                "sanitizedstack" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
+                "badclient" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
+            }
+        */
+        return {
+            command: this.serverCommand,
+            serverid: this.serverId,
+            sanitizedstack: this.sanitizedStack || '',
+            badclient: /\bBADCLIENT\b/.test(this.stack || ''),
+        } as const;
+    }
+    /**
+     * Given a `errorText` from a tsserver request indicating failure in handling a request,
+     * prepares a payload for telemetry-logging.
+     */
+    private static parseErrorText(response: Proto.Response) {
+        const errorText = response.message;
+        if (errorText) {
+            const errorPrefix = 'Error processing request. ';
+            if (errorText.startsWith(errorPrefix)) {
+                const prefixFreeErrorText = errorText.substr(errorPrefix.length);
+                const newlineIndex = prefixFreeErrorText.indexOf('\n');
+                if (newlineIndex >= 0) {
+                    // Newline expected between message and stack.
+                    const stack = prefixFreeErrorText.substring(newlineIndex + 1);
+                    return {
+                        message: prefixFreeErrorText.substring(0, newlineIndex),
+                        stack,
+                        sanitizedStack: TypeScriptServerError.sanitizeStack(stack)
+                    };
+                }
+            }
+        }
+        return undefined;
+    }
+    /**
+     * Drop everything but ".js" and line/column numbers (though retain "tsserver" if that's the filename).
+     */
+    private static sanitizeStack(message: string | undefined) {
+        if (!message) {
+            return '';
+        }
+        const regex = /(\btsserver)?(\.(?:ts|tsx|js|jsx)(?::\d+(?::\d+)?)?)\)?$/igm;
+        let serverStack = '';
+        while (true) {
+            const match = regex.exec(message);
+            if (!match) {
+                break;
+            }
+            // [1] is 'tsserver' or undefined
+            // [2] is '.js:{line_number}:{column_number}'
+            serverStack += `${match[1] || 'suppressed'}${match[2]}\n`;
+        }
+        return serverStack;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/Source/tsServer/serverProcess.browser.ts
index 5adf186611256..9ae3834816b5f 100644
--- a/extensions/typescript-language-features/Source/tsServer/serverProcess.browser.ts
+++ b/extensions/typescript-language-features/Source/tsServer/serverProcess.browser.ts
@@ -15,174 +15,127 @@ import type * as Proto from './protocol/protocol';
 import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
 import { TypeScriptVersionManager } from './versionManager';
 import { TypeScriptVersion } from './versionProvider';
-
 type BrowserWatchEvent = {
-	type: 'watchDirectory' | 'watchFile';
-	recursive?: boolean;
-	uri: {
-		scheme: string;
-		authority: string;
-		path: string;
-	};
-	id: number;
+    type: 'watchDirectory' | 'watchFile';
+    recursive?: boolean;
+    uri: {
+        scheme: string;
+        authority: string;
+        path: string;
+    };
+    id: number;
 } | {
-	type: 'dispose';
-	id: number;
+    type: 'dispose';
+    id: number;
 };
-
 export class WorkerServerProcessFactory implements TsServerProcessFactory {
-	constructor(
-		private readonly _extensionUri: vscode.Uri,
-		private readonly _logger: Logger,
-	) { }
-
-	public fork(
-		version: TypeScriptVersion,
-		args: readonly string[],
-		kind: TsServerProcessKind,
-		configuration: TypeScriptServiceConfiguration,
-		_versionManager: TypeScriptVersionManager,
-		_nodeVersionManager: NodeVersionManager,
-		tsServerLog: TsServerLog | undefined,
-	) {
-		const tsServerPath = version.tsServerPath;
-		const launchArgs = [
-			...args,
-			// Explicitly give TS Server its path so it can load local resources
-			'--executingFilePath', tsServerPath,
-			// Enable/disable web type acquisition
-			(configuration.webTypeAcquisitionEnabled && supportsReadableByteStreams() ? '--experimentalTypeAcquisition' : '--disableAutomaticTypingAcquisition'),
-		];
-
-		return new WorkerServerProcess(kind, tsServerPath, this._extensionUri, launchArgs, tsServerLog, this._logger);
-	}
+    constructor(private readonly _extensionUri: vscode.Uri, private readonly _logger: Logger) { }
+    public fork(version: TypeScriptVersion, args: readonly string[], kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, _versionManager: TypeScriptVersionManager, _nodeVersionManager: NodeVersionManager, tsServerLog: TsServerLog | undefined) {
+        const tsServerPath = version.tsServerPath;
+        const launchArgs = [
+            ...args,
+            // Explicitly give TS Server its path so it can load local resources
+            '--executingFilePath', tsServerPath,
+            // Enable/disable web type acquisition
+            (configuration.webTypeAcquisitionEnabled && supportsReadableByteStreams() ? '--experimentalTypeAcquisition' : '--disableAutomaticTypingAcquisition'),
+        ];
+        return new WorkerServerProcess(kind, tsServerPath, this._extensionUri, launchArgs, tsServerLog, this._logger);
+    }
 }
-
 class WorkerServerProcess implements TsServerProcess {
-
-	private static idPool = 0;
-
-	private readonly id = WorkerServerProcess.idPool++;
-
-	private readonly _onDataHandlers = new Set<(data: Proto.Response) => void>();
-	private readonly _onErrorHandlers = new Set<(err: Error) => void>();
-	private readonly _onExitHandlers = new Set<(code: number | null, signal: string | null) => void>();
-
-	private readonly _worker: Worker;
-	private readonly _watches: FileWatcherManager;
-
-	/** For communicating with TS server synchronously */
-	private readonly _tsserver: MessagePort;
-	/** For communicating watches asynchronously */
-	private readonly _watcher: MessagePort;
-	/** For communicating with filesystem synchronously */
-	private readonly _syncFs: MessagePort;
-
-	public constructor(
-		private readonly kind: TsServerProcessKind,
-		tsServerPath: string,
-		extensionUri: vscode.Uri,
-		args: readonly string[],
-		private readonly tsServerLog: TsServerLog | undefined,
-		logger: Logger,
-	) {
-		this._worker = new Worker(tsServerPath, { name: `TS ${kind} server #${this.id}` });
-
-		this._watches = new FileWatcherManager(logger);
-
-		const tsserverChannel = new MessageChannel();
-		const watcherChannel = new MessageChannel();
-		const syncChannel = new MessageChannel();
-		this._tsserver = tsserverChannel.port2;
-		this._watcher = watcherChannel.port2;
-		this._syncFs = syncChannel.port2;
-
-		this._tsserver.onmessage = (event) => {
-			if (event.data.type === 'log') {
-				console.error(`unexpected log message on tsserver channel: ${JSON.stringify(event)}`);
-				return;
-			}
-			for (const handler of this._onDataHandlers) {
-				handler(event.data);
-			}
-		};
-
-		this._watcher.onmessage = (event: MessageEvent) => {
-			switch (event.data.type) {
-				case 'dispose': {
-					this._watches.delete(event.data.id);
-					break;
-				}
-				case 'watchDirectory':
-				case 'watchFile': {
-					this._watches.create(event.data.id, vscode.Uri.from(event.data.uri), /*watchParentDirs*/ true, !!event.data.recursive, {
-						change: uri => this._watcher.postMessage({ type: 'watch', event: 'change', uri }),
-						create: uri => this._watcher.postMessage({ type: 'watch', event: 'create', uri }),
-						delete: uri => this._watcher.postMessage({ type: 'watch', event: 'delete', uri }),
-					});
-					break;
-				}
-				default:
-					console.error(`unexpected message on watcher channel: ${JSON.stringify(event)}`);
-			}
-		};
-
-		this._worker.onmessage = (msg: any) => {
-			// for logging only
-			if (msg.data.type === 'log') {
-				this.appendLog(msg.data.body);
-				return;
-			}
-			console.error(`unexpected message on main channel: ${JSON.stringify(msg)}`);
-		};
-
-		this._worker.onerror = (err: ErrorEvent) => {
-			console.error('error! ' + JSON.stringify(err));
-			for (const handler of this._onErrorHandlers) {
-				// TODO: The ErrorEvent type might be wrong; previously this was typed as Error and didn't have the property access.
-				handler(err.error);
-			}
-		};
-
-		this._worker.postMessage(
-			{ args, extensionUri },
-			[syncChannel.port1, tsserverChannel.port1, watcherChannel.port1]
-		);
-
-		const connection = new ServiceConnection(syncChannel.port2);
-		new ApiService('vscode-wasm-typescript', connection);
-		connection.signalReady();
-	}
-
-	write(serverRequest: Proto.Request): void {
-		this._tsserver.postMessage(serverRequest);
-	}
-
-	onData(handler: (response: Proto.Response) => void): void {
-		this._onDataHandlers.add(handler);
-	}
-
-	onError(handler: (err: Error) => void): void {
-		this._onErrorHandlers.add(handler);
-	}
-
-	onExit(handler: (code: number | null, signal: string | null) => void): void {
-		this._onExitHandlers.add(handler);
-		// Todo: not implemented
-	}
-
-	kill(): void {
-		this._worker.terminate();
-		this._tsserver.close();
-		this._watcher.close();
-		this._syncFs.close();
-		this._watches.dispose();
-	}
-
-	private appendLog(msg: string) {
-		if (this.tsServerLog?.type === 'output') {
-			this.tsServerLog.output.appendLine(`(${this.id} - ${this.kind}) ${msg}`);
-		}
-	}
+    private static idPool = 0;
+    private readonly id = WorkerServerProcess.idPool++;
+    private readonly _onDataHandlers = new Set<(data: Proto.Response) => void>();
+    private readonly _onErrorHandlers = new Set<(err: Error) => void>();
+    private readonly _onExitHandlers = new Set<(code: number | null, signal: string | null) => void>();
+    private readonly _worker: Worker;
+    private readonly _watches: FileWatcherManager;
+    /** For communicating with TS server synchronously */
+    private readonly _tsserver: MessagePort;
+    /** For communicating watches asynchronously */
+    private readonly _watcher: MessagePort;
+    /** For communicating with filesystem synchronously */
+    private readonly _syncFs: MessagePort;
+    public constructor(private readonly kind: TsServerProcessKind, tsServerPath: string, extensionUri: vscode.Uri, args: readonly string[], private readonly tsServerLog: TsServerLog | undefined, logger: Logger) {
+        this._worker = new Worker(tsServerPath, { name: `TS ${kind} server #${this.id}` });
+        this._watches = new FileWatcherManager(logger);
+        const tsserverChannel = new MessageChannel();
+        const watcherChannel = new MessageChannel();
+        const syncChannel = new MessageChannel();
+        this._tsserver = tsserverChannel.port2;
+        this._watcher = watcherChannel.port2;
+        this._syncFs = syncChannel.port2;
+        this._tsserver.onmessage = (event) => {
+            if (event.data.type === 'log') {
+                console.error(`unexpected log message on tsserver channel: ${JSON.stringify(event)}`);
+                return;
+            }
+            for (const handler of this._onDataHandlers) {
+                handler(event.data);
+            }
+        };
+        this._watcher.onmessage = (event: MessageEvent) => {
+            switch (event.data.type) {
+                case 'dispose': {
+                    this._watches.delete(event.data.id);
+                    break;
+                }
+                case 'watchDirectory':
+                case 'watchFile': {
+                    this._watches.create(event.data.id, vscode.Uri.from(event.data.uri), /*watchParentDirs*/ true, !!event.data.recursive, {
+                        change: uri => this._watcher.postMessage({ type: 'watch', event: 'change', uri }),
+                        create: uri => this._watcher.postMessage({ type: 'watch', event: 'create', uri }),
+                        delete: uri => this._watcher.postMessage({ type: 'watch', event: 'delete', uri }),
+                    });
+                    break;
+                }
+                default:
+                    console.error(`unexpected message on watcher channel: ${JSON.stringify(event)}`);
+            }
+        };
+        this._worker.onmessage = (msg: any) => {
+            // for logging only
+            if (msg.data.type === 'log') {
+                this.appendLog(msg.data.body);
+                return;
+            }
+            console.error(`unexpected message on main channel: ${JSON.stringify(msg)}`);
+        };
+        this._worker.onerror = (err: ErrorEvent) => {
+            console.error('error! ' + JSON.stringify(err));
+            for (const handler of this._onErrorHandlers) {
+                // TODO: The ErrorEvent type might be wrong; previously this was typed as Error and didn't have the property access.
+                handler(err.error);
+            }
+        };
+        this._worker.postMessage({ args, extensionUri }, [syncChannel.port1, tsserverChannel.port1, watcherChannel.port1]);
+        const connection = new ServiceConnection(syncChannel.port2);
+        new ApiService('vscode-wasm-typescript', connection);
+        connection.signalReady();
+    }
+    write(serverRequest: Proto.Request): void {
+        this._tsserver.postMessage(serverRequest);
+    }
+    onData(handler: (response: Proto.Response) => void): void {
+        this._onDataHandlers.add(handler);
+    }
+    onError(handler: (err: Error) => void): void {
+        this._onErrorHandlers.add(handler);
+    }
+    onExit(handler: (code: number | null, signal: string | null) => void): void {
+        this._onExitHandlers.add(handler);
+        // Todo: not implemented
+    }
+    kill(): void {
+        this._worker.terminate();
+        this._tsserver.close();
+        this._watcher.close();
+        this._syncFs.close();
+        this._watches.dispose();
+    }
+    private appendLog(msg: string) {
+        if (this.tsServerLog?.type === 'output') {
+            this.tsServerLog.output.appendLine(`(${this.id} - ${this.kind}) ${msg}`);
+        }
+    }
 }
-
diff --git a/extensions/typescript-language-features/Source/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/Source/tsServer/serverProcess.electron.ts
index 7dbde90f7924c..bd2df7012628d 100644
--- a/extensions/typescript-language-features/Source/tsServer/serverProcess.electron.ts
+++ b/extensions/typescript-language-features/Source/tsServer/serverProcess.electron.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as child_process from 'child_process';
 import * as fs from 'fs';
 import * as path from 'path';
@@ -16,281 +15,229 @@ import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKi
 import { TypeScriptVersionManager } from './versionManager';
 import { TypeScriptVersion } from './versionProvider';
 import { NodeVersionManager } from './nodeManager';
-
-
 const defaultSize: number = 8192;
 const contentLength: string = 'Content-Length: ';
 const contentLengthSize: number = Buffer.byteLength(contentLength, 'utf8');
 const blank: number = Buffer.from(' ', 'utf8')[0];
 const backslashR: number = Buffer.from('\r', 'utf8')[0];
 const backslashN: number = Buffer.from('\n', 'utf8')[0];
-
 class ProtocolBuffer {
-
-	private index: number = 0;
-	private buffer: Buffer = Buffer.allocUnsafe(defaultSize);
-
-	public append(data: string | Buffer): void {
-		let toAppend: Buffer | null = null;
-		if (Buffer.isBuffer(data)) {
-			toAppend = data;
-		} else {
-			toAppend = Buffer.from(data, 'utf8');
-		}
-		if (this.buffer.length - this.index >= toAppend.length) {
-			toAppend.copy(this.buffer, this.index, 0, toAppend.length);
-		} else {
-			const newSize = (Math.ceil((this.index + toAppend.length) / defaultSize) + 1) * defaultSize;
-			if (this.index === 0) {
-				this.buffer = Buffer.allocUnsafe(newSize);
-				toAppend.copy(this.buffer, 0, 0, toAppend.length);
-			} else {
-				this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
-			}
-		}
-		this.index += toAppend.length;
-	}
-
-	public tryReadContentLength(): number {
-		let result = -1;
-		let current = 0;
-		// we are utf8 encoding...
-		while (current < this.index && (this.buffer[current] === blank || this.buffer[current] === backslashR || this.buffer[current] === backslashN)) {
-			current++;
-		}
-		if (this.index < current + contentLengthSize) {
-			return result;
-		}
-		current += contentLengthSize;
-		const start = current;
-		while (current < this.index && this.buffer[current] !== backslashR) {
-			current++;
-		}
-		if (current + 3 >= this.index || this.buffer[current + 1] !== backslashN || this.buffer[current + 2] !== backslashR || this.buffer[current + 3] !== backslashN) {
-			return result;
-		}
-		const data = this.buffer.toString('utf8', start, current);
-		result = parseInt(data);
-		this.buffer = this.buffer.slice(current + 4);
-		this.index = this.index - (current + 4);
-		return result;
-	}
-
-	public tryReadContent(length: number): string | null {
-		if (this.index < length) {
-			return null;
-		}
-		const result = this.buffer.toString('utf8', 0, length);
-		let sourceStart = length;
-		while (sourceStart < this.index && (this.buffer[sourceStart] === backslashR || this.buffer[sourceStart] === backslashN)) {
-			sourceStart++;
-		}
-		this.buffer.copy(this.buffer, 0, sourceStart);
-		this.index = this.index - sourceStart;
-		return result;
-	}
+    private index: number = 0;
+    private buffer: Buffer = Buffer.allocUnsafe(defaultSize);
+    public append(data: string | Buffer): void {
+        let toAppend: Buffer | null = null;
+        if (Buffer.isBuffer(data)) {
+            toAppend = data;
+        }
+        else {
+            toAppend = Buffer.from(data, 'utf8');
+        }
+        if (this.buffer.length - this.index >= toAppend.length) {
+            toAppend.copy(this.buffer, this.index, 0, toAppend.length);
+        }
+        else {
+            const newSize = (Math.ceil((this.index + toAppend.length) / defaultSize) + 1) * defaultSize;
+            if (this.index === 0) {
+                this.buffer = Buffer.allocUnsafe(newSize);
+                toAppend.copy(this.buffer, 0, 0, toAppend.length);
+            }
+            else {
+                this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
+            }
+        }
+        this.index += toAppend.length;
+    }
+    public tryReadContentLength(): number {
+        let result = -1;
+        let current = 0;
+        // we are utf8 encoding...
+        while (current < this.index && (this.buffer[current] === blank || this.buffer[current] === backslashR || this.buffer[current] === backslashN)) {
+            current++;
+        }
+        if (this.index < current + contentLengthSize) {
+            return result;
+        }
+        current += contentLengthSize;
+        const start = current;
+        while (current < this.index && this.buffer[current] !== backslashR) {
+            current++;
+        }
+        if (current + 3 >= this.index || this.buffer[current + 1] !== backslashN || this.buffer[current + 2] !== backslashR || this.buffer[current + 3] !== backslashN) {
+            return result;
+        }
+        const data = this.buffer.toString('utf8', start, current);
+        result = parseInt(data);
+        this.buffer = this.buffer.slice(current + 4);
+        this.index = this.index - (current + 4);
+        return result;
+    }
+    public tryReadContent(length: number): string | null {
+        if (this.index < length) {
+            return null;
+        }
+        const result = this.buffer.toString('utf8', 0, length);
+        let sourceStart = length;
+        while (sourceStart < this.index && (this.buffer[sourceStart] === backslashR || this.buffer[sourceStart] === backslashN)) {
+            sourceStart++;
+        }
+        this.buffer.copy(this.buffer, 0, sourceStart);
+        this.index = this.index - sourceStart;
+        return result;
+    }
 }
-
 class Reader extends Disposable {
-
-	private readonly buffer: ProtocolBuffer = new ProtocolBuffer();
-	private nextMessageLength: number = -1;
-
-	public constructor(readable: Readable) {
-		super();
-		readable.on('data', data => this.onLengthData(data));
-	}
-
-	private readonly _onError = this._register(new vscode.EventEmitter());
-	public readonly onError = this._onError.event;
-
-	private readonly _onData = this._register(new vscode.EventEmitter());
-	public readonly onData = this._onData.event;
-
-	private onLengthData(data: Buffer | string): void {
-		if (this.isDisposed) {
-			return;
-		}
-
-		try {
-			this.buffer.append(data);
-			while (true) {
-				if (this.nextMessageLength === -1) {
-					this.nextMessageLength = this.buffer.tryReadContentLength();
-					if (this.nextMessageLength === -1) {
-						return;
-					}
-				}
-				const msg = this.buffer.tryReadContent(this.nextMessageLength);
-				if (msg === null) {
-					return;
-				}
-				this.nextMessageLength = -1;
-				const json = JSON.parse(msg);
-				this._onData.fire(json);
-			}
-		} catch (e) {
-			this._onError.fire(e);
-		}
-	}
+    private readonly buffer: ProtocolBuffer = new ProtocolBuffer();
+    private nextMessageLength: number = -1;
+    public constructor(readable: Readable) {
+        super();
+        readable.on('data', data => this.onLengthData(data));
+    }
+    private readonly _onError = this._register(new vscode.EventEmitter());
+    public readonly onError = this._onError.event;
+    private readonly _onData = this._register(new vscode.EventEmitter());
+    public readonly onData = this._onData.event;
+    private onLengthData(data: Buffer | string): void {
+        if (this.isDisposed) {
+            return;
+        }
+        try {
+            this.buffer.append(data);
+            while (true) {
+                if (this.nextMessageLength === -1) {
+                    this.nextMessageLength = this.buffer.tryReadContentLength();
+                    if (this.nextMessageLength === -1) {
+                        return;
+                    }
+                }
+                const msg = this.buffer.tryReadContent(this.nextMessageLength);
+                if (msg === null) {
+                    return;
+                }
+                this.nextMessageLength = -1;
+                const json = JSON.parse(msg);
+                this._onData.fire(json);
+            }
+        }
+        catch (e) {
+            this._onError.fire(e);
+        }
+    }
 }
-
 function generatePatchedEnv(env: any, modulePath: string, hasExecPath: boolean): any {
-	const newEnv = Object.assign({}, env);
-
-	if (!hasExecPath) {
-		newEnv['ELECTRON_RUN_AS_NODE'] = '1';
-	}
-	newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
-
-	// Ensure we always have a PATH set
-	newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
-
-	return newEnv;
+    const newEnv = Object.assign({}, env);
+    if (!hasExecPath) {
+        newEnv['ELECTRON_RUN_AS_NODE'] = '1';
+    }
+    newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
+    // Ensure we always have a PATH set
+    newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
+    return newEnv;
 }
-
 function getExecArgv(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration): string[] {
-	const args: string[] = [];
-
-	const debugPort = getDebugPort(kind);
-	if (debugPort) {
-		const inspectFlag = getTssDebugBrk() ? '--inspect-brk' : '--inspect';
-		args.push(`${inspectFlag}=${debugPort}`);
-	}
-
-	if (configuration.maxTsServerMemory) {
-		args.push(`--max-old-space-size=${configuration.maxTsServerMemory}`);
-	}
-
-	return args;
+    const args: string[] = [];
+    const debugPort = getDebugPort(kind);
+    if (debugPort) {
+        const inspectFlag = getTssDebugBrk() ? '--inspect-brk' : '--inspect';
+        args.push(`${inspectFlag}=${debugPort}`);
+    }
+    if (configuration.maxTsServerMemory) {
+        args.push(`--max-old-space-size=${configuration.maxTsServerMemory}`);
+    }
+    return args;
 }
-
 function getDebugPort(kind: TsServerProcessKind): number | undefined {
-	if (kind === TsServerProcessKind.Syntax) {
-		// We typically only want to debug the main semantic server
-		return undefined;
-	}
-	const value = getTssDebugBrk() || getTssDebug();
-	if (value) {
-		const port = parseInt(value);
-		if (!isNaN(port)) {
-			return port;
-		}
-	}
-	return undefined;
+    if (kind === TsServerProcessKind.Syntax) {
+        // We typically only want to debug the main semantic server
+        return undefined;
+    }
+    const value = getTssDebugBrk() || getTssDebug();
+    if (value) {
+        const port = parseInt(value);
+        if (!isNaN(port)) {
+            return port;
+        }
+    }
+    return undefined;
 }
-
 function getTssDebug(): string | undefined {
-	return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG' : 'TSS_DEBUG'];
+    return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG' : 'TSS_DEBUG'];
 }
-
 function getTssDebugBrk(): string | undefined {
-	return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'];
+    return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'];
 }
-
 class IpcChildServerProcess extends Disposable implements TsServerProcess {
-	constructor(
-		private readonly _process: child_process.ChildProcess,
-	) {
-		super();
-	}
-
-	write(serverRequest: Proto.Request): void {
-		this._process.send(serverRequest);
-	}
-
-	onData(handler: (data: Proto.Response) => void): void {
-		this._process.on('message', handler);
-	}
-
-	onExit(handler: (code: number | null, signal: string | null) => void): void {
-		this._process.on('exit', handler);
-	}
-
-	onError(handler: (err: Error) => void): void {
-		this._process.on('error', handler);
-	}
-
-	kill(): void {
-		this._process.kill();
-	}
+    constructor(private readonly _process: child_process.ChildProcess) {
+        super();
+    }
+    write(serverRequest: Proto.Request): void {
+        this._process.send(serverRequest);
+    }
+    onData(handler: (data: Proto.Response) => void): void {
+        this._process.on('message', handler);
+    }
+    onExit(handler: (code: number | null, signal: string | null) => void): void {
+        this._process.on('exit', handler);
+    }
+    onError(handler: (err: Error) => void): void {
+        this._process.on('error', handler);
+    }
+    kill(): void {
+        this._process.kill();
+    }
 }
-
 class StdioChildServerProcess extends Disposable implements TsServerProcess {
-	private readonly _reader: Reader;
-
-	constructor(
-		private readonly _process: child_process.ChildProcess,
-	) {
-		super();
-		this._reader = this._register(new Reader(this._process.stdout!));
-	}
-
-	write(serverRequest: Proto.Request): void {
-		this._process.stdin!.write(JSON.stringify(serverRequest) + '\r\n', 'utf8');
-	}
-
-	onData(handler: (data: Proto.Response) => void): void {
-		this._reader.onData(handler);
-	}
-
-	onExit(handler: (code: number | null, signal: string | null) => void): void {
-		this._process.on('exit', handler);
-	}
-
-	onError(handler: (err: Error) => void): void {
-		this._process.on('error', handler);
-		this._reader.onError(handler);
-	}
-
-	kill(): void {
-		this._process.kill();
-		this._reader.dispose();
-	}
+    private readonly _reader: Reader;
+    constructor(private readonly _process: child_process.ChildProcess) {
+        super();
+        this._reader = this._register(new Reader(this._process.stdout!));
+    }
+    write(serverRequest: Proto.Request): void {
+        this._process.stdin!.write(JSON.stringify(serverRequest) + '\r\n', 'utf8');
+    }
+    onData(handler: (data: Proto.Response) => void): void {
+        this._reader.onData(handler);
+    }
+    onExit(handler: (code: number | null, signal: string | null) => void): void {
+        this._process.on('exit', handler);
+    }
+    onError(handler: (err: Error) => void): void {
+        this._process.on('error', handler);
+        this._reader.onError(handler);
+    }
+    kill(): void {
+        this._process.kill();
+        this._reader.dispose();
+    }
 }
-
 export class ElectronServiceProcessFactory implements TsServerProcessFactory {
-	fork(
-		version: TypeScriptVersion,
-		args: readonly string[],
-		kind: TsServerProcessKind,
-		configuration: TypeScriptServiceConfiguration,
-		versionManager: TypeScriptVersionManager,
-		nodeVersionManager: NodeVersionManager,
-		_tsserverLog: TsServerLog | undefined,
-	): TsServerProcess {
-		let tsServerPath = version.tsServerPath;
-
-		if (!fs.existsSync(tsServerPath)) {
-			vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.", tsServerPath));
-			versionManager.reset();
-			tsServerPath = versionManager.currentVersion.tsServerPath;
-		}
-
-		const execPath = nodeVersionManager.currentVersion;
-
-		const env = generatePatchedEnv(process.env, tsServerPath, !!execPath);
-		const runtimeArgs = [...args];
-		const execArgv = getExecArgv(kind, configuration);
-		const useIpc = !execPath && version.apiVersion?.gte(API.v460);
-		if (useIpc) {
-			runtimeArgs.push('--useNodeIpc');
-		}
-
-		const childProcess = execPath ?
-			child_process.spawn(execPath, [...execArgv, tsServerPath, ...runtimeArgs], {
-				windowsHide: true,
-				cwd: undefined,
-				env,
-			}) :
-			child_process.fork(tsServerPath, runtimeArgs, {
-				silent: true,
-				cwd: undefined,
-				env,
-				execArgv,
-				stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
-			});
-
-		return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess);
-	}
+    fork(version: TypeScriptVersion, args: readonly string[], kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, versionManager: TypeScriptVersionManager, nodeVersionManager: NodeVersionManager, _tsserverLog: TsServerLog | undefined): TsServerProcess {
+        let tsServerPath = version.tsServerPath;
+        if (!fs.existsSync(tsServerPath)) {
+            vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.", tsServerPath));
+            versionManager.reset();
+            tsServerPath = versionManager.currentVersion.tsServerPath;
+        }
+        const execPath = nodeVersionManager.currentVersion;
+        const env = generatePatchedEnv(process.env, tsServerPath, !!execPath);
+        const runtimeArgs = [...args];
+        const execArgv = getExecArgv(kind, configuration);
+        const useIpc = !execPath && version.apiVersion?.gte(API.v460);
+        if (useIpc) {
+            runtimeArgs.push('--useNodeIpc');
+        }
+        const childProcess = execPath ?
+            child_process.spawn(execPath, [...execArgv, tsServerPath, ...runtimeArgs], {
+                windowsHide: true,
+                cwd: undefined,
+                env,
+            }) :
+            child_process.fork(tsServerPath, runtimeArgs, {
+                silent: true,
+                cwd: undefined,
+                env,
+                execArgv,
+                stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
+            });
+        return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/spawner.ts b/extensions/typescript-language-features/Source/tsServer/spawner.ts
index aac3186631e28..159c06528fb68 100644
--- a/extensions/typescript-language-features/Source/tsServer/spawner.ts
+++ b/extensions/typescript-language-features/Source/tsServer/spawner.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration } from '../configuration/configuration';
 import { Logger } from '../logging/logger';
@@ -20,287 +19,206 @@ import { GetErrRoutingTsServer, ITypeScriptServer, SingleTsServer, SyntaxRouting
 import { TypeScriptVersionManager } from './versionManager';
 import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
 import { NodeVersionManager } from './nodeManager';
-
 const enum CompositeServerType {
-	/** Run a single server that handles all commands  */
-	Single,
-
-	/** Run a separate server for syntax commands */
-	SeparateSyntax,
-
-	/** Use a separate syntax server while the project is loading */
-	DynamicSeparateSyntax,
-
-	/** Only enable the syntax server */
-	SyntaxOnly
+    /** Run a single server that handles all commands  */
+    Single,
+    /** Run a separate server for syntax commands */
+    SeparateSyntax,
+    /** Use a separate syntax server while the project is loading */
+    DynamicSeparateSyntax,
+    /** Only enable the syntax server */
+    SyntaxOnly
 }
-
 export class TypeScriptServerSpawner {
-
-	@memoize
-	public static get tsServerLogOutputChannel(): vscode.OutputChannel {
-		return vscode.window.createOutputChannel(vscode.l10n.t("TypeScript Server Log"));
-	}
-
-	public constructor(
-		private readonly _versionProvider: ITypeScriptVersionProvider,
-		private readonly _versionManager: TypeScriptVersionManager,
-		private readonly _nodeVersionManager: NodeVersionManager,
-		private readonly _logDirectoryProvider: ILogDirectoryProvider,
-		private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider,
-		private readonly _logger: Logger,
-		private readonly _telemetryReporter: TelemetryReporter,
-		private readonly _tracer: Tracer,
-		private readonly _factory: TsServerProcessFactory,
-	) { }
-
-	public spawn(
-		version: TypeScriptVersion,
-		capabilities: ClientCapabilities,
-		configuration: TypeScriptServiceConfiguration,
-		pluginManager: PluginManager,
-		cancellerFactory: OngoingRequestCancellerFactory,
-		delegate: TsServerDelegate,
-	): ITypeScriptServer {
-		let primaryServer: ITypeScriptServer;
-		const serverType = this.getCompositeServerType(version, capabilities, configuration);
-		const shouldUseSeparateDiagnosticsServer = this.shouldUseSeparateDiagnosticsServer(configuration);
-
-		switch (serverType) {
-			case CompositeServerType.SeparateSyntax:
-			case CompositeServerType.DynamicSeparateSyntax:
-				{
-					const enableDynamicRouting = !shouldUseSeparateDiagnosticsServer && serverType === CompositeServerType.DynamicSeparateSyntax;
-					primaryServer = new SyntaxRoutingTsServer({
-						syntax: this.spawnTsServer(TsServerProcessKind.Syntax, version, configuration, pluginManager, cancellerFactory),
-						semantic: this.spawnTsServer(TsServerProcessKind.Semantic, version, configuration, pluginManager, cancellerFactory),
-					}, delegate, enableDynamicRouting);
-					break;
-				}
-			case CompositeServerType.Single:
-				{
-					primaryServer = this.spawnTsServer(TsServerProcessKind.Main, version, configuration, pluginManager, cancellerFactory);
-					break;
-				}
-			case CompositeServerType.SyntaxOnly:
-				{
-					primaryServer = this.spawnTsServer(TsServerProcessKind.Syntax, version, configuration, pluginManager, cancellerFactory);
-					break;
-				}
-		}
-
-		if (shouldUseSeparateDiagnosticsServer) {
-			return new GetErrRoutingTsServer({
-				getErr: this.spawnTsServer(TsServerProcessKind.Diagnostics, version, configuration, pluginManager, cancellerFactory),
-				primary: primaryServer,
-			}, delegate);
-		}
-
-		return primaryServer;
-	}
-
-	private getCompositeServerType(
-		version: TypeScriptVersion,
-		capabilities: ClientCapabilities,
-		configuration: TypeScriptServiceConfiguration,
-	): CompositeServerType {
-		if (!capabilities.has(ClientCapability.Semantic)) {
-			return CompositeServerType.SyntaxOnly;
-		}
-
-		switch (configuration.useSyntaxServer) {
-			case SyntaxServerConfiguration.Always:
-				return CompositeServerType.SyntaxOnly;
-
-			case SyntaxServerConfiguration.Never:
-				return CompositeServerType.Single;
-
-			case SyntaxServerConfiguration.Auto:
-				return version.apiVersion?.gte(API.v400)
-					? CompositeServerType.DynamicSeparateSyntax
-					: CompositeServerType.SeparateSyntax;
-		}
-	}
-
-	private shouldUseSeparateDiagnosticsServer(
-		configuration: TypeScriptServiceConfiguration,
-	): boolean {
-		return configuration.enableProjectDiagnostics;
-	}
-
-	private spawnTsServer(
-		kind: TsServerProcessKind,
-		version: TypeScriptVersion,
-		configuration: TypeScriptServiceConfiguration,
-		pluginManager: PluginManager,
-		cancellerFactory: OngoingRequestCancellerFactory,
-	): ITypeScriptServer {
-		const apiVersion = version.apiVersion || API.defaultVersion;
-
-		const canceller = cancellerFactory.create(kind, this._tracer);
-		const { args, tsServerLog, tsServerTraceDirectory } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName);
-
-		if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
-			if (tsServerLog?.type === 'file') {
-				this._logger.info(`<${kind}> Log file: ${tsServerLog.uri.fsPath}`);
-			} else if (tsServerLog?.type === 'output') {
-				this._logger.info(`<${kind}> Logging to output`);
-			} else {
-				this._logger.error(`<${kind}> Could not create TS Server log`);
-			}
-		}
-
-		if (configuration.enableTsServerTracing) {
-			if (tsServerTraceDirectory) {
-				this._logger.info(`<${kind}> Trace directory: ${tsServerTraceDirectory.fsPath}`);
-			} else {
-				this._logger.error(`<${kind}> Could not create trace directory`);
-			}
-		}
-
-		this._logger.info(`<${kind}> Forking...`);
-		const process = this._factory.fork(version, args, kind, configuration, this._versionManager, this._nodeVersionManager, tsServerLog);
-		this._logger.info(`<${kind}> Starting...`);
-
-		return new SingleTsServer(
-			kind,
-			this.kindToServerType(kind),
-			process!,
-			tsServerLog,
-			canceller,
-			version,
-			this._telemetryReporter,
-			this._tracer);
-	}
-
-	private kindToServerType(kind: TsServerProcessKind): ServerType {
-		switch (kind) {
-			case TsServerProcessKind.Syntax:
-				return ServerType.Syntax;
-
-			case TsServerProcessKind.Main:
-			case TsServerProcessKind.Semantic:
-			case TsServerProcessKind.Diagnostics:
-			default:
-				return ServerType.Semantic;
-		}
-	}
-
-	private getTsServerArgs(
-		kind: TsServerProcessKind,
-		configuration: TypeScriptServiceConfiguration,
-		currentVersion: TypeScriptVersion,
-		apiVersion: API,
-		pluginManager: PluginManager,
-		cancellationPipeName: string | undefined,
-	): { args: string[]; tsServerLog: TsServerLog | undefined; tsServerTraceDirectory: vscode.Uri | undefined } {
-		const args: string[] = [];
-		let tsServerLog: TsServerLog | undefined;
-		let tsServerTraceDirectory: vscode.Uri | undefined;
-
-		if (kind === TsServerProcessKind.Syntax) {
-			if (apiVersion.gte(API.v401)) {
-				args.push('--serverMode', 'partialSemantic');
-			} else {
-				args.push('--syntaxOnly');
-			}
-		}
-
-		args.push('--useInferredProjectPerProjectRoot');
-
-		if (configuration.disableAutomaticTypeAcquisition || kind === TsServerProcessKind.Syntax || kind === TsServerProcessKind.Diagnostics) {
-			args.push('--disableAutomaticTypingAcquisition');
-		}
-
-		if (kind === TsServerProcessKind.Semantic || kind === TsServerProcessKind.Main) {
-			args.push('--enableTelemetry');
-		}
-
-		if (cancellationPipeName) {
-			args.push('--cancellationPipeName', cancellationPipeName + '*');
-		}
-
-		if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
-			if (isWeb()) {
-				args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
-				tsServerLog = { type: 'output', output: TypeScriptServerSpawner.tsServerLogOutputChannel };
-			} else {
-				const logDir = this._logDirectoryProvider.getNewLogDirectory();
-				if (logDir) {
-					const logFilePath = vscode.Uri.joinPath(logDir, `tsserver.log`);
-					tsServerLog = { type: 'file', uri: logFilePath };
-
-					args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
-					args.push('--logFile', logFilePath.fsPath);
-				}
-			}
-		}
-
-		if (configuration.enableTsServerTracing && !isWeb()) {
-			tsServerTraceDirectory = this._logDirectoryProvider.getNewLogDirectory();
-			if (tsServerTraceDirectory) {
-				args.push('--traceDirectory', `"${tsServerTraceDirectory.fsPath}"`);
-			}
-		}
-
-		const pluginPaths = isWeb() ? [] : this._pluginPathsProvider.getPluginPaths();
-
-		if (pluginManager.plugins.length) {
-			args.push('--globalPlugins', pluginManager.plugins.map(x => x.name).join(','));
-
-			const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path;
-			for (const plugin of pluginManager.plugins) {
-				if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) {
-					pluginPaths.push(isWeb() ? plugin.uri.toString() : plugin.uri.fsPath);
-				}
-			}
-		}
-
-		if (pluginPaths.length !== 0) {
-			args.push('--pluginProbeLocations', pluginPaths.join(','));
-		}
-
-		if (configuration.npmLocation && !isWeb()) {
-			args.push('--npmLocation', `"${configuration.npmLocation}"`);
-		}
-
-		args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration));
-
-		args.push('--noGetErrOnBackgroundUpdate');
-
-		const configUseVsCodeWatcher = configuration.useVsCodeWatcher;
-		const isYarnPnp = apiVersion.isYarnPnp();
-		if (
-			apiVersion.gte(API.v544)
-			&& configUseVsCodeWatcher
-			&& !isYarnPnp // Disable for yarn pnp as it currently breaks with the VS Code watcher
-		) {
-			args.push('--canUseWatchEvents');
-		} else {
-			if (!configUseVsCodeWatcher) {
-				this._logger.info(`<${kind}> Falling back to legacy node.js based file watching because of user settings.`);
-			} else if (isYarnPnp) {
-				this._logger.info(`<${kind}> Falling back to legacy node.js based file watching because of Yarn PnP.`);
-			}
-		}
-
-		args.push('--validateDefaultNpmLocation');
-
-		if (isWebAndHasSharedArrayBuffers()) {
-			args.push('--enableProjectWideIntelliSenseOnWeb');
-		}
-
-		return { args, tsServerLog, tsServerTraceDirectory };
-	}
-
-	private static isLoggingEnabled(configuration: TypeScriptServiceConfiguration) {
-		return configuration.tsServerLogLevel !== TsServerLogLevel.Off;
-	}
-
-	private static getTsLocale(configuration: TypeScriptServiceConfiguration): string {
-		return configuration.locale
-			? configuration.locale
-			: vscode.env.language;
-	}
+    @memoize
+    public static get tsServerLogOutputChannel(): vscode.OutputChannel {
+        return vscode.window.createOutputChannel(vscode.l10n.t("TypeScript Server Log"));
+    }
+    public constructor(private readonly _versionProvider: ITypeScriptVersionProvider, private readonly _versionManager: TypeScriptVersionManager, private readonly _nodeVersionManager: NodeVersionManager, private readonly _logDirectoryProvider: ILogDirectoryProvider, private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider, private readonly _logger: Logger, private readonly _telemetryReporter: TelemetryReporter, private readonly _tracer: Tracer, private readonly _factory: TsServerProcessFactory) { }
+    public spawn(version: TypeScriptVersion, capabilities: ClientCapabilities, configuration: TypeScriptServiceConfiguration, pluginManager: PluginManager, cancellerFactory: OngoingRequestCancellerFactory, delegate: TsServerDelegate): ITypeScriptServer {
+        let primaryServer: ITypeScriptServer;
+        const serverType = this.getCompositeServerType(version, capabilities, configuration);
+        const shouldUseSeparateDiagnosticsServer = this.shouldUseSeparateDiagnosticsServer(configuration);
+        switch (serverType) {
+            case CompositeServerType.SeparateSyntax:
+            case CompositeServerType.DynamicSeparateSyntax:
+                {
+                    const enableDynamicRouting = !shouldUseSeparateDiagnosticsServer && serverType === CompositeServerType.DynamicSeparateSyntax;
+                    primaryServer = new SyntaxRoutingTsServer({
+                        syntax: this.spawnTsServer(TsServerProcessKind.Syntax, version, configuration, pluginManager, cancellerFactory),
+                        semantic: this.spawnTsServer(TsServerProcessKind.Semantic, version, configuration, pluginManager, cancellerFactory),
+                    }, delegate, enableDynamicRouting);
+                    break;
+                }
+            case CompositeServerType.Single:
+                {
+                    primaryServer = this.spawnTsServer(TsServerProcessKind.Main, version, configuration, pluginManager, cancellerFactory);
+                    break;
+                }
+            case CompositeServerType.SyntaxOnly:
+                {
+                    primaryServer = this.spawnTsServer(TsServerProcessKind.Syntax, version, configuration, pluginManager, cancellerFactory);
+                    break;
+                }
+        }
+        if (shouldUseSeparateDiagnosticsServer) {
+            return new GetErrRoutingTsServer({
+                getErr: this.spawnTsServer(TsServerProcessKind.Diagnostics, version, configuration, pluginManager, cancellerFactory),
+                primary: primaryServer,
+            }, delegate);
+        }
+        return primaryServer;
+    }
+    private getCompositeServerType(version: TypeScriptVersion, capabilities: ClientCapabilities, configuration: TypeScriptServiceConfiguration): CompositeServerType {
+        if (!capabilities.has(ClientCapability.Semantic)) {
+            return CompositeServerType.SyntaxOnly;
+        }
+        switch (configuration.useSyntaxServer) {
+            case SyntaxServerConfiguration.Always:
+                return CompositeServerType.SyntaxOnly;
+            case SyntaxServerConfiguration.Never:
+                return CompositeServerType.Single;
+            case SyntaxServerConfiguration.Auto:
+                return version.apiVersion?.gte(API.v400)
+                    ? CompositeServerType.DynamicSeparateSyntax
+                    : CompositeServerType.SeparateSyntax;
+        }
+    }
+    private shouldUseSeparateDiagnosticsServer(configuration: TypeScriptServiceConfiguration): boolean {
+        return configuration.enableProjectDiagnostics;
+    }
+    private spawnTsServer(kind: TsServerProcessKind, version: TypeScriptVersion, configuration: TypeScriptServiceConfiguration, pluginManager: PluginManager, cancellerFactory: OngoingRequestCancellerFactory): ITypeScriptServer {
+        const apiVersion = version.apiVersion || API.defaultVersion;
+        const canceller = cancellerFactory.create(kind, this._tracer);
+        const { args, tsServerLog, tsServerTraceDirectory } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName);
+        if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
+            if (tsServerLog?.type === 'file') {
+                this._logger.info(`<${kind}> Log file: ${tsServerLog.uri.fsPath}`);
+            }
+            else if (tsServerLog?.type === 'output') {
+                this._logger.info(`<${kind}> Logging to output`);
+            }
+            else {
+                this._logger.error(`<${kind}> Could not create TS Server log`);
+            }
+        }
+        if (configuration.enableTsServerTracing) {
+            if (tsServerTraceDirectory) {
+                this._logger.info(`<${kind}> Trace directory: ${tsServerTraceDirectory.fsPath}`);
+            }
+            else {
+                this._logger.error(`<${kind}> Could not create trace directory`);
+            }
+        }
+        this._logger.info(`<${kind}> Forking...`);
+        const process = this._factory.fork(version, args, kind, configuration, this._versionManager, this._nodeVersionManager, tsServerLog);
+        this._logger.info(`<${kind}> Starting...`);
+        return new SingleTsServer(kind, this.kindToServerType(kind), process!, tsServerLog, canceller, version, this._telemetryReporter, this._tracer);
+    }
+    private kindToServerType(kind: TsServerProcessKind): ServerType {
+        switch (kind) {
+            case TsServerProcessKind.Syntax:
+                return ServerType.Syntax;
+            case TsServerProcessKind.Main:
+            case TsServerProcessKind.Semantic:
+            case TsServerProcessKind.Diagnostics:
+            default:
+                return ServerType.Semantic;
+        }
+    }
+    private getTsServerArgs(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, currentVersion: TypeScriptVersion, apiVersion: API, pluginManager: PluginManager, cancellationPipeName: string | undefined): {
+        args: string[];
+        tsServerLog: TsServerLog | undefined;
+        tsServerTraceDirectory: vscode.Uri | undefined;
+    } {
+        const args: string[] = [];
+        let tsServerLog: TsServerLog | undefined;
+        let tsServerTraceDirectory: vscode.Uri | undefined;
+        if (kind === TsServerProcessKind.Syntax) {
+            if (apiVersion.gte(API.v401)) {
+                args.push('--serverMode', 'partialSemantic');
+            }
+            else {
+                args.push('--syntaxOnly');
+            }
+        }
+        args.push('--useInferredProjectPerProjectRoot');
+        if (configuration.disableAutomaticTypeAcquisition || kind === TsServerProcessKind.Syntax || kind === TsServerProcessKind.Diagnostics) {
+            args.push('--disableAutomaticTypingAcquisition');
+        }
+        if (kind === TsServerProcessKind.Semantic || kind === TsServerProcessKind.Main) {
+            args.push('--enableTelemetry');
+        }
+        if (cancellationPipeName) {
+            args.push('--cancellationPipeName', cancellationPipeName + '*');
+        }
+        if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
+            if (isWeb()) {
+                args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
+                tsServerLog = { type: 'output', output: TypeScriptServerSpawner.tsServerLogOutputChannel };
+            }
+            else {
+                const logDir = this._logDirectoryProvider.getNewLogDirectory();
+                if (logDir) {
+                    const logFilePath = vscode.Uri.joinPath(logDir, `tsserver.log`);
+                    tsServerLog = { type: 'file', uri: logFilePath };
+                    args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
+                    args.push('--logFile', logFilePath.fsPath);
+                }
+            }
+        }
+        if (configuration.enableTsServerTracing && !isWeb()) {
+            tsServerTraceDirectory = this._logDirectoryProvider.getNewLogDirectory();
+            if (tsServerTraceDirectory) {
+                args.push('--traceDirectory', `"${tsServerTraceDirectory.fsPath}"`);
+            }
+        }
+        const pluginPaths = isWeb() ? [] : this._pluginPathsProvider.getPluginPaths();
+        if (pluginManager.plugins.length) {
+            args.push('--globalPlugins', pluginManager.plugins.map(x => x.name).join(','));
+            const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path;
+            for (const plugin of pluginManager.plugins) {
+                if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) {
+                    pluginPaths.push(isWeb() ? plugin.uri.toString() : plugin.uri.fsPath);
+                }
+            }
+        }
+        if (pluginPaths.length !== 0) {
+            args.push('--pluginProbeLocations', pluginPaths.join(','));
+        }
+        if (configuration.npmLocation && !isWeb()) {
+            args.push('--npmLocation', `"${configuration.npmLocation}"`);
+        }
+        args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration));
+        args.push('--noGetErrOnBackgroundUpdate');
+        const configUseVsCodeWatcher = configuration.useVsCodeWatcher;
+        const isYarnPnp = apiVersion.isYarnPnp();
+        if (apiVersion.gte(API.v544)
+            && configUseVsCodeWatcher
+            && !isYarnPnp // Disable for yarn pnp as it currently breaks with the VS Code watcher
+        ) {
+            args.push('--canUseWatchEvents');
+        }
+        else {
+            if (!configUseVsCodeWatcher) {
+                this._logger.info(`<${kind}> Falling back to legacy node.js based file watching because of user settings.`);
+            }
+            else if (isYarnPnp) {
+                this._logger.info(`<${kind}> Falling back to legacy node.js based file watching because of Yarn PnP.`);
+            }
+        }
+        args.push('--validateDefaultNpmLocation');
+        if (isWebAndHasSharedArrayBuffers()) {
+            args.push('--enableProjectWideIntelliSenseOnWeb');
+        }
+        return { args, tsServerLog, tsServerTraceDirectory };
+    }
+    private static isLoggingEnabled(configuration: TypeScriptServiceConfiguration) {
+        return configuration.tsServerLogLevel !== TsServerLogLevel.Off;
+    }
+    private static getTsLocale(configuration: TypeScriptServiceConfiguration): string {
+        return configuration.locale
+            ? configuration.locale
+            : vscode.env.language;
+    }
 }
-
diff --git a/extensions/typescript-language-features/Source/tsServer/versionManager.ts b/extensions/typescript-language-features/Source/tsServer/versionManager.ts
index 43a2413e38374..5007f208eebf6 100644
--- a/extensions/typescript-language-features/Source/tsServer/versionManager.ts
+++ b/extensions/typescript-language-features/Source/tsServer/versionManager.ts
@@ -2,188 +2,148 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TypeScriptServiceConfiguration } from '../configuration/configuration';
 import { setImmediate } from '../utils/async';
 import { Disposable } from '../utils/dispose';
 import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
-
-
 const useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk';
 const suppressPromptWorkspaceTsdkStorageKey = 'typescript.suppressPromptWorkspaceTsdk';
-
 interface QuickPickItem extends vscode.QuickPickItem {
-	run(): void;
+    run(): void;
 }
-
 export class TypeScriptVersionManager extends Disposable {
-
-	private _currentVersion: TypeScriptVersion;
-
-	public constructor(
-		private configuration: TypeScriptServiceConfiguration,
-		private readonly versionProvider: ITypeScriptVersionProvider,
-		private readonly workspaceState: vscode.Memento
-	) {
-		super();
-
-		this._currentVersion = this.versionProvider.defaultVersion;
-
-		if (this.useWorkspaceTsdkSetting) {
-			if (vscode.workspace.isTrusted) {
-				const localVersion = this.versionProvider.localVersion;
-				if (localVersion) {
-					this._currentVersion = localVersion;
-				}
-			} else {
-				this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => {
-					if (this.versionProvider.localVersion) {
-						this.updateActiveVersion(this.versionProvider.localVersion);
-					}
-				}));
-			}
-		}
-
-		if (this.isInPromptWorkspaceTsdkState(configuration)) {
-			setImmediate(() => {
-				this.promptUseWorkspaceTsdk();
-			});
-		}
-
-	}
-
-	private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter());
-	public readonly onDidPickNewVersion = this._onDidPickNewVersion.event;
-
-	public updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) {
-		const lastConfiguration = this.configuration;
-		this.configuration = nextConfiguration;
-
-		if (
-			!this.isInPromptWorkspaceTsdkState(lastConfiguration)
-			&& this.isInPromptWorkspaceTsdkState(nextConfiguration)
-		) {
-			this.promptUseWorkspaceTsdk();
-		}
-	}
-
-	public get currentVersion(): TypeScriptVersion {
-		return this._currentVersion;
-	}
-
-	public reset(): void {
-		this._currentVersion = this.versionProvider.bundledVersion;
-	}
-
-	public async promptUserForVersion(): Promise {
-		const selected = await vscode.window.showQuickPick([
-			this.getBundledPickItem(),
-			...this.getLocalPickItems(),
-			{
-				kind: vscode.QuickPickItemKind.Separator,
-				label: '',
-				run: () => { /* noop */ },
-			},
-			LearnMorePickItem,
-		], {
-			placeHolder: vscode.l10n.t("Select the TypeScript version used for JavaScript and TypeScript language features"),
-		});
-
-		return selected?.run();
-	}
-
-	private getBundledPickItem(): QuickPickItem {
-		const bundledVersion = this.versionProvider.defaultVersion;
-		return {
-			label: (!this.useWorkspaceTsdkSetting || !vscode.workspace.isTrusted
-				? '• '
-				: '') + vscode.l10n.t("Use VS Code's Version"),
-			description: bundledVersion.displayName,
-			detail: bundledVersion.pathLabel,
-			run: async () => {
-				await this.workspaceState.update(useWorkspaceTsdkStorageKey, false);
-				this.updateActiveVersion(bundledVersion);
-			},
-		};
-	}
-
-	private getLocalPickItems(): QuickPickItem[] {
-		return this.versionProvider.localVersions.map(version => {
-			return {
-				label: (this.useWorkspaceTsdkSetting && vscode.workspace.isTrusted && this.currentVersion.eq(version)
-					? '• '
-					: '') + vscode.l10n.t("Use Workspace Version"),
-				description: version.displayName,
-				detail: version.pathLabel,
-				run: async () => {
-					const trusted = await vscode.workspace.requestWorkspaceTrust();
-					if (trusted) {
-						await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
-						const tsConfig = vscode.workspace.getConfiguration('typescript');
-						await tsConfig.update('tsdk', version.pathLabel, false);
-						this.updateActiveVersion(version);
-					}
-				},
-			};
-		});
-	}
-
-	private async promptUseWorkspaceTsdk(): Promise {
-		const workspaceVersion = this.versionProvider.localVersion;
-
-		if (workspaceVersion === undefined) {
-			throw new Error('Could not prompt to use workspace TypeScript version because no workspace version is specified');
-		}
-
-		const allowIt = vscode.l10n.t("Allow");
-		const dismissPrompt = vscode.l10n.t("Dismiss");
-		const suppressPrompt = vscode.l10n.t("Never in this Workspace");
-
-		const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace contains a TypeScript version. Would you like to use the workspace TypeScript version for TypeScript and JavaScript language features?"),
-			allowIt,
-			dismissPrompt,
-			suppressPrompt
-		);
-
-		if (result === allowIt) {
-			await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
-			this.updateActiveVersion(workspaceVersion);
-		} else if (result === suppressPrompt) {
-			await this.workspaceState.update(suppressPromptWorkspaceTsdkStorageKey, true);
-		}
-	}
-
-	private updateActiveVersion(pickedVersion: TypeScriptVersion) {
-		const oldVersion = this.currentVersion;
-		this._currentVersion = pickedVersion;
-		if (!oldVersion.eq(pickedVersion)) {
-			this._onDidPickNewVersion.fire();
-		}
-	}
-
-	private get useWorkspaceTsdkSetting(): boolean {
-		return this.workspaceState.get(useWorkspaceTsdkStorageKey, false);
-	}
-
-	private get suppressPromptWorkspaceTsdkSetting(): boolean {
-		return this.workspaceState.get(suppressPromptWorkspaceTsdkStorageKey, false);
-	}
-
-	private isInPromptWorkspaceTsdkState(configuration: TypeScriptServiceConfiguration) {
-		return (
-			configuration.localTsdk !== null
-			&& configuration.enablePromptUseWorkspaceTsdk === true
-			&& this.suppressPromptWorkspaceTsdkSetting === false
-			&& this.useWorkspaceTsdkSetting === false
-		);
-	}
+    private _currentVersion: TypeScriptVersion;
+    public constructor(private configuration: TypeScriptServiceConfiguration, private readonly versionProvider: ITypeScriptVersionProvider, private readonly workspaceState: vscode.Memento) {
+        super();
+        this._currentVersion = this.versionProvider.defaultVersion;
+        if (this.useWorkspaceTsdkSetting) {
+            if (vscode.workspace.isTrusted) {
+                const localVersion = this.versionProvider.localVersion;
+                if (localVersion) {
+                    this._currentVersion = localVersion;
+                }
+            }
+            else {
+                this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => {
+                    if (this.versionProvider.localVersion) {
+                        this.updateActiveVersion(this.versionProvider.localVersion);
+                    }
+                }));
+            }
+        }
+        if (this.isInPromptWorkspaceTsdkState(configuration)) {
+            setImmediate(() => {
+                this.promptUseWorkspaceTsdk();
+            });
+        }
+    }
+    private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter());
+    public readonly onDidPickNewVersion = this._onDidPickNewVersion.event;
+    public updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) {
+        const lastConfiguration = this.configuration;
+        this.configuration = nextConfiguration;
+        if (!this.isInPromptWorkspaceTsdkState(lastConfiguration)
+            && this.isInPromptWorkspaceTsdkState(nextConfiguration)) {
+            this.promptUseWorkspaceTsdk();
+        }
+    }
+    public get currentVersion(): TypeScriptVersion {
+        return this._currentVersion;
+    }
+    public reset(): void {
+        this._currentVersion = this.versionProvider.bundledVersion;
+    }
+    public async promptUserForVersion(): Promise {
+        const selected = await vscode.window.showQuickPick([
+            this.getBundledPickItem(),
+            ...this.getLocalPickItems(),
+            {
+                kind: vscode.QuickPickItemKind.Separator,
+                label: '',
+                run: () => { },
+            },
+            LearnMorePickItem,
+        ], {
+            placeHolder: vscode.l10n.t("Select the TypeScript version used for JavaScript and TypeScript language features"),
+        });
+        return selected?.run();
+    }
+    private getBundledPickItem(): QuickPickItem {
+        const bundledVersion = this.versionProvider.defaultVersion;
+        return {
+            label: (!this.useWorkspaceTsdkSetting || !vscode.workspace.isTrusted
+                ? '• '
+                : '') + vscode.l10n.t("Use VS Code's Version"),
+            description: bundledVersion.displayName,
+            detail: bundledVersion.pathLabel,
+            run: async () => {
+                await this.workspaceState.update(useWorkspaceTsdkStorageKey, false);
+                this.updateActiveVersion(bundledVersion);
+            },
+        };
+    }
+    private getLocalPickItems(): QuickPickItem[] {
+        return this.versionProvider.localVersions.map(version => {
+            return {
+                label: (this.useWorkspaceTsdkSetting && vscode.workspace.isTrusted && this.currentVersion.eq(version)
+                    ? '• '
+                    : '') + vscode.l10n.t("Use Workspace Version"),
+                description: version.displayName,
+                detail: version.pathLabel,
+                run: async () => {
+                    const trusted = await vscode.workspace.requestWorkspaceTrust();
+                    if (trusted) {
+                        await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
+                        const tsConfig = vscode.workspace.getConfiguration('typescript');
+                        await tsConfig.update('tsdk', version.pathLabel, false);
+                        this.updateActiveVersion(version);
+                    }
+                },
+            };
+        });
+    }
+    private async promptUseWorkspaceTsdk(): Promise {
+        const workspaceVersion = this.versionProvider.localVersion;
+        if (workspaceVersion === undefined) {
+            throw new Error('Could not prompt to use workspace TypeScript version because no workspace version is specified');
+        }
+        const allowIt = vscode.l10n.t("Allow");
+        const dismissPrompt = vscode.l10n.t("Dismiss");
+        const suppressPrompt = vscode.l10n.t("Never in this Workspace");
+        const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace contains a TypeScript version. Would you like to use the workspace TypeScript version for TypeScript and JavaScript language features?"), allowIt, dismissPrompt, suppressPrompt);
+        if (result === allowIt) {
+            await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
+            this.updateActiveVersion(workspaceVersion);
+        }
+        else if (result === suppressPrompt) {
+            await this.workspaceState.update(suppressPromptWorkspaceTsdkStorageKey, true);
+        }
+    }
+    private updateActiveVersion(pickedVersion: TypeScriptVersion) {
+        const oldVersion = this.currentVersion;
+        this._currentVersion = pickedVersion;
+        if (!oldVersion.eq(pickedVersion)) {
+            this._onDidPickNewVersion.fire();
+        }
+    }
+    private get useWorkspaceTsdkSetting(): boolean {
+        return this.workspaceState.get(useWorkspaceTsdkStorageKey, false);
+    }
+    private get suppressPromptWorkspaceTsdkSetting(): boolean {
+        return this.workspaceState.get(suppressPromptWorkspaceTsdkStorageKey, false);
+    }
+    private isInPromptWorkspaceTsdkState(configuration: TypeScriptServiceConfiguration) {
+        return (configuration.localTsdk !== null
+            && configuration.enablePromptUseWorkspaceTsdk === true
+            && this.suppressPromptWorkspaceTsdkSetting === false
+            && this.useWorkspaceTsdkSetting === false);
+    }
 }
-
 const LearnMorePickItem: QuickPickItem = {
-	label: vscode.l10n.t("Learn more about managing TypeScript versions"),
-	description: '',
-	run: () => {
-		vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
-	}
+    label: vscode.l10n.t("Learn more about managing TypeScript versions"),
+    description: '',
+    run: () => {
+        vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
+    }
 };
diff --git a/extensions/typescript-language-features/Source/tsServer/versionProvider.electron.ts b/extensions/typescript-language-features/Source/tsServer/versionProvider.electron.ts
index 239519e6f6a07..4eb9d9b60500d 100644
--- a/extensions/typescript-language-features/Source/tsServer/versionProvider.electron.ts
+++ b/extensions/typescript-language-features/Source/tsServer/versionProvider.electron.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as fs from 'fs';
 import * as path from 'path';
 import * as vscode from 'vscode';
@@ -10,188 +9,154 @@ import { TypeScriptServiceConfiguration } from '../configuration/configuration';
 import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver';
 import { API } from './api';
 import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './versionProvider';
-
-
 export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider {
-
-	public constructor(
-		private configuration?: TypeScriptServiceConfiguration
-	) { }
-
-	public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
-		this.configuration = configuration;
-	}
-
-	public get defaultVersion(): TypeScriptVersion {
-		return this.globalVersion || this.bundledVersion;
-	}
-
-	public get globalVersion(): TypeScriptVersion | undefined {
-		if (this.configuration?.globalTsdk) {
-			const globals = this.loadVersionsFromSetting(TypeScriptVersionSource.UserSetting, this.configuration.globalTsdk);
-			if (globals?.length) {
-				return globals[0];
-			}
-		}
-		return this.contributedTsNextVersion;
-	}
-
-	public get localVersion(): TypeScriptVersion | undefined {
-		const tsdkVersions = this.localTsdkVersions;
-		if (tsdkVersions?.length) {
-			return tsdkVersions[0];
-		}
-
-		const nodeVersions = this.localNodeModulesVersions;
-		if (nodeVersions && nodeVersions.length === 1) {
-			return nodeVersions[0];
-		}
-		return undefined;
-	}
-
-
-	public get localVersions(): TypeScriptVersion[] {
-		const allVersions = this.localTsdkVersions.concat(this.localNodeModulesVersions);
-		const paths = new Set();
-		return allVersions.filter(x => {
-			if (paths.has(x.path)) {
-				return false;
-			}
-			paths.add(x.path);
-			return true;
-		});
-	}
-
-	public get bundledVersion(): TypeScriptVersion {
-		const version = this.getContributedVersion(TypeScriptVersionSource.Bundled, 'vscode.typescript-language-features', ['..', 'node_modules']);
-		if (version) {
-			return version;
-		}
-
-		vscode.window.showErrorMessage(vscode.l10n.t("VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code."));
-		throw new Error('Could not find bundled tsserver.js');
-	}
-
-	private get contributedTsNextVersion(): TypeScriptVersion | undefined {
-		return this.getContributedVersion(TypeScriptVersionSource.TsNightlyExtension, 'ms-vscode.vscode-typescript-next', ['node_modules']);
-	}
-
-	private getContributedVersion(source: TypeScriptVersionSource, extensionId: string, pathToTs: readonly string[]): TypeScriptVersion | undefined {
-		try {
-			const extension = vscode.extensions.getExtension(extensionId);
-			if (extension) {
-				const serverPath = path.join(extension.extensionPath, ...pathToTs, 'typescript', 'lib', 'tsserver.js');
-				const bundledVersion = new TypeScriptVersion(source, serverPath, DiskTypeScriptVersionProvider.getApiVersion(serverPath), '');
-				if (bundledVersion.isValid) {
-					return bundledVersion;
-				}
-			}
-		} catch {
-			// noop
-		}
-		return undefined;
-	}
-
-	private get localTsdkVersions(): TypeScriptVersion[] {
-		const localTsdk = this.configuration?.localTsdk;
-		return localTsdk ? this.loadVersionsFromSetting(TypeScriptVersionSource.WorkspaceSetting, localTsdk) : [];
-	}
-
-	private loadVersionsFromSetting(source: TypeScriptVersionSource, tsdkPathSetting: string): TypeScriptVersion[] {
-		if (path.isAbsolute(tsdkPathSetting)) {
-			const serverPath = path.join(tsdkPathSetting, 'tsserver.js');
-			return [
-				new TypeScriptVersion(source,
-					serverPath,
-					DiskTypeScriptVersionProvider.getApiVersion(serverPath),
-					tsdkPathSetting)
-			];
-		}
-
-		const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting);
-		if (workspacePath !== undefined) {
-			const serverPath = path.join(workspacePath, 'tsserver.js');
-			return [
-				new TypeScriptVersion(source,
-					serverPath,
-					DiskTypeScriptVersionProvider.getApiVersion(serverPath),
-					tsdkPathSetting)
-			];
-		}
-
-		return this.loadTypeScriptVersionsFromPath(source, tsdkPathSetting);
-	}
-
-	private get localNodeModulesVersions(): TypeScriptVersion[] {
-		return this.loadTypeScriptVersionsFromPath(TypeScriptVersionSource.NodeModules, path.join('node_modules', 'typescript', 'lib'))
-			.filter(x => x.isValid);
-	}
-
-	private loadTypeScriptVersionsFromPath(source: TypeScriptVersionSource, relativePath: string): TypeScriptVersion[] {
-		if (!vscode.workspace.workspaceFolders) {
-			return [];
-		}
-
-		const versions: TypeScriptVersion[] = [];
-		for (const root of vscode.workspace.workspaceFolders) {
-			let label: string = relativePath;
-			if (vscode.workspace.workspaceFolders.length > 1) {
-				label = path.join(root.name, relativePath);
-			}
-
-			const serverPath = path.join(root.uri.fsPath, relativePath, 'tsserver.js');
-			versions.push(new TypeScriptVersion(source, serverPath, DiskTypeScriptVersionProvider.getApiVersion(serverPath), label));
-		}
-		return versions;
-	}
-
-	private static getApiVersion(serverPath: string): API | undefined {
-		const version = DiskTypeScriptVersionProvider.getTypeScriptVersion(serverPath);
-		if (version) {
-			return version;
-		}
-
-		// Allow TS developers to provide custom version
-		const tsdkVersion = vscode.workspace.getConfiguration().get('typescript.tsdk_version', undefined);
-		if (tsdkVersion) {
-			return API.fromVersionString(tsdkVersion);
-		}
-
-		return undefined;
-	}
-
-	private static getTypeScriptVersion(serverPath: string): API | undefined {
-		if (!fs.existsSync(serverPath)) {
-			return undefined;
-		}
-
-		const p = serverPath.split(path.sep);
-		if (p.length <= 2) {
-			return undefined;
-		}
-		const p2 = p.slice(0, -2);
-		const modulePath = p2.join(path.sep);
-		let fileName = path.join(modulePath, 'package.json');
-		if (!fs.existsSync(fileName)) {
-			// Special case for ts dev versions
-			if (path.basename(modulePath) === 'built') {
-				fileName = path.join(modulePath, '..', 'package.json');
-			}
-		}
-		if (!fs.existsSync(fileName)) {
-			return undefined;
-		}
-
-		const contents = fs.readFileSync(fileName).toString();
-		let desc: any = null;
-		try {
-			desc = JSON.parse(contents);
-		} catch (err) {
-			return undefined;
-		}
-		if (!desc?.version) {
-			return undefined;
-		}
-		return desc.version ? API.fromVersionString(desc.version) : undefined;
-	}
+    public constructor(private configuration?: TypeScriptServiceConfiguration) { }
+    public updateConfiguration(configuration: TypeScriptServiceConfiguration): void {
+        this.configuration = configuration;
+    }
+    public get defaultVersion(): TypeScriptVersion {
+        return this.globalVersion || this.bundledVersion;
+    }
+    public get globalVersion(): TypeScriptVersion | undefined {
+        if (this.configuration?.globalTsdk) {
+            const globals = this.loadVersionsFromSetting(TypeScriptVersionSource.UserSetting, this.configuration.globalTsdk);
+            if (globals?.length) {
+                return globals[0];
+            }
+        }
+        return this.contributedTsNextVersion;
+    }
+    public get localVersion(): TypeScriptVersion | undefined {
+        const tsdkVersions = this.localTsdkVersions;
+        if (tsdkVersions?.length) {
+            return tsdkVersions[0];
+        }
+        const nodeVersions = this.localNodeModulesVersions;
+        if (nodeVersions && nodeVersions.length === 1) {
+            return nodeVersions[0];
+        }
+        return undefined;
+    }
+    public get localVersions(): TypeScriptVersion[] {
+        const allVersions = this.localTsdkVersions.concat(this.localNodeModulesVersions);
+        const paths = new Set();
+        return allVersions.filter(x => {
+            if (paths.has(x.path)) {
+                return false;
+            }
+            paths.add(x.path);
+            return true;
+        });
+    }
+    public get bundledVersion(): TypeScriptVersion {
+        const version = this.getContributedVersion(TypeScriptVersionSource.Bundled, 'vscode.typescript-language-features', ['..', 'node_modules']);
+        if (version) {
+            return version;
+        }
+        vscode.window.showErrorMessage(vscode.l10n.t("VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code."));
+        throw new Error('Could not find bundled tsserver.js');
+    }
+    private get contributedTsNextVersion(): TypeScriptVersion | undefined {
+        return this.getContributedVersion(TypeScriptVersionSource.TsNightlyExtension, 'ms-vscode.vscode-typescript-next', ['node_modules']);
+    }
+    private getContributedVersion(source: TypeScriptVersionSource, extensionId: string, pathToTs: readonly string[]): TypeScriptVersion | undefined {
+        try {
+            const extension = vscode.extensions.getExtension(extensionId);
+            if (extension) {
+                const serverPath = path.join(extension.extensionPath, ...pathToTs, 'typescript', 'lib', 'tsserver.js');
+                const bundledVersion = new TypeScriptVersion(source, serverPath, DiskTypeScriptVersionProvider.getApiVersion(serverPath), '');
+                if (bundledVersion.isValid) {
+                    return bundledVersion;
+                }
+            }
+        }
+        catch {
+            // noop
+        }
+        return undefined;
+    }
+    private get localTsdkVersions(): TypeScriptVersion[] {
+        const localTsdk = this.configuration?.localTsdk;
+        return localTsdk ? this.loadVersionsFromSetting(TypeScriptVersionSource.WorkspaceSetting, localTsdk) : [];
+    }
+    private loadVersionsFromSetting(source: TypeScriptVersionSource, tsdkPathSetting: string): TypeScriptVersion[] {
+        if (path.isAbsolute(tsdkPathSetting)) {
+            const serverPath = path.join(tsdkPathSetting, 'tsserver.js');
+            return [
+                new TypeScriptVersion(source, serverPath, DiskTypeScriptVersionProvider.getApiVersion(serverPath), tsdkPathSetting)
+            ];
+        }
+        const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting);
+        if (workspacePath !== undefined) {
+            const serverPath = path.join(workspacePath, 'tsserver.js');
+            return [
+                new TypeScriptVersion(source, serverPath, DiskTypeScriptVersionProvider.getApiVersion(serverPath), tsdkPathSetting)
+            ];
+        }
+        return this.loadTypeScriptVersionsFromPath(source, tsdkPathSetting);
+    }
+    private get localNodeModulesVersions(): TypeScriptVersion[] {
+        return this.loadTypeScriptVersionsFromPath(TypeScriptVersionSource.NodeModules, path.join('node_modules', 'typescript', 'lib'))
+            .filter(x => x.isValid);
+    }
+    private loadTypeScriptVersionsFromPath(source: TypeScriptVersionSource, relativePath: string): TypeScriptVersion[] {
+        if (!vscode.workspace.workspaceFolders) {
+            return [];
+        }
+        const versions: TypeScriptVersion[] = [];
+        for (const root of vscode.workspace.workspaceFolders) {
+            let label: string = relativePath;
+            if (vscode.workspace.workspaceFolders.length > 1) {
+                label = path.join(root.name, relativePath);
+            }
+            const serverPath = path.join(root.uri.fsPath, relativePath, 'tsserver.js');
+            versions.push(new TypeScriptVersion(source, serverPath, DiskTypeScriptVersionProvider.getApiVersion(serverPath), label));
+        }
+        return versions;
+    }
+    private static getApiVersion(serverPath: string): API | undefined {
+        const version = DiskTypeScriptVersionProvider.getTypeScriptVersion(serverPath);
+        if (version) {
+            return version;
+        }
+        // Allow TS developers to provide custom version
+        const tsdkVersion = vscode.workspace.getConfiguration().get('typescript.tsdk_version', undefined);
+        if (tsdkVersion) {
+            return API.fromVersionString(tsdkVersion);
+        }
+        return undefined;
+    }
+    private static getTypeScriptVersion(serverPath: string): API | undefined {
+        if (!fs.existsSync(serverPath)) {
+            return undefined;
+        }
+        const p = serverPath.split(path.sep);
+        if (p.length <= 2) {
+            return undefined;
+        }
+        const p2 = p.slice(0, -2);
+        const modulePath = p2.join(path.sep);
+        let fileName = path.join(modulePath, 'package.json');
+        if (!fs.existsSync(fileName)) {
+            // Special case for ts dev versions
+            if (path.basename(modulePath) === 'built') {
+                fileName = path.join(modulePath, '..', 'package.json');
+            }
+        }
+        if (!fs.existsSync(fileName)) {
+            return undefined;
+        }
+        const contents = fs.readFileSync(fileName).toString();
+        let desc: any = null;
+        try {
+            desc = JSON.parse(contents);
+        }
+        catch (err) {
+            return undefined;
+        }
+        if (!desc?.version) {
+            return undefined;
+        }
+        return desc.version ? API.fromVersionString(desc.version) : undefined;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/tsServer/versionProvider.ts b/extensions/typescript-language-features/Source/tsServer/versionProvider.ts
index 2eaa0670551f0..f1cdc91d39ed9 100644
--- a/extensions/typescript-language-features/Source/tsServer/versionProvider.ts
+++ b/extensions/typescript-language-features/Source/tsServer/versionProvider.ts
@@ -2,67 +2,49 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TypeScriptServiceConfiguration } from '../configuration/configuration';
 import { API } from './api';
-
-
 export const enum TypeScriptVersionSource {
-	Bundled = 'bundled',
-	TsNightlyExtension = 'ts-nightly-extension',
-	NodeModules = 'node-modules',
-	UserSetting = 'user-setting',
-	WorkspaceSetting = 'workspace-setting',
+    Bundled = 'bundled',
+    TsNightlyExtension = 'ts-nightly-extension',
+    NodeModules = 'node-modules',
+    UserSetting = 'user-setting',
+    WorkspaceSetting = 'workspace-setting'
 }
-
 export class TypeScriptVersion {
-
-	constructor(
-		public readonly source: TypeScriptVersionSource,
-		public readonly path: string,
-		public readonly apiVersion: API | undefined,
-		private readonly _pathLabel?: string,
-	) { }
-
-	public get tsServerPath(): string {
-		return this.path;
-	}
-
-	public get pathLabel(): string {
-		return this._pathLabel ?? this.path;
-	}
-
-	public get isValid(): boolean {
-		return this.apiVersion !== undefined;
-	}
-
-	public eq(other: TypeScriptVersion): boolean {
-		if (this.path !== other.path) {
-			return false;
-		}
-
-		if (this.apiVersion === other.apiVersion) {
-			return true;
-		}
-		if (!this.apiVersion || !other.apiVersion) {
-			return false;
-		}
-		return this.apiVersion.eq(other.apiVersion);
-	}
-
-	public get displayName(): string {
-		const version = this.apiVersion;
-		return version ? version.displayName : vscode.l10n.t("Could not load the TypeScript version at this path");
-	}
+    constructor(public readonly source: TypeScriptVersionSource, public readonly path: string, public readonly apiVersion: API | undefined, private readonly _pathLabel?: string) { }
+    public get tsServerPath(): string {
+        return this.path;
+    }
+    public get pathLabel(): string {
+        return this._pathLabel ?? this.path;
+    }
+    public get isValid(): boolean {
+        return this.apiVersion !== undefined;
+    }
+    public eq(other: TypeScriptVersion): boolean {
+        if (this.path !== other.path) {
+            return false;
+        }
+        if (this.apiVersion === other.apiVersion) {
+            return true;
+        }
+        if (!this.apiVersion || !other.apiVersion) {
+            return false;
+        }
+        return this.apiVersion.eq(other.apiVersion);
+    }
+    public get displayName(): string {
+        const version = this.apiVersion;
+        return version ? version.displayName : vscode.l10n.t("Could not load the TypeScript version at this path");
+    }
 }
-
 export interface ITypeScriptVersionProvider {
-	updateConfiguration(configuration: TypeScriptServiceConfiguration): void;
-
-	readonly defaultVersion: TypeScriptVersion;
-	readonly globalVersion: TypeScriptVersion | undefined;
-	readonly localVersion: TypeScriptVersion | undefined;
-	readonly localVersions: readonly TypeScriptVersion[];
-	readonly bundledVersion: TypeScriptVersion;
+    updateConfiguration(configuration: TypeScriptServiceConfiguration): void;
+    readonly defaultVersion: TypeScriptVersion;
+    readonly globalVersion: TypeScriptVersion | undefined;
+    readonly localVersion: TypeScriptVersion | undefined;
+    readonly localVersions: readonly TypeScriptVersion[];
+    readonly bundledVersion: TypeScriptVersion;
 }
diff --git a/extensions/typescript-language-features/Source/tsconfig.ts b/extensions/typescript-language-features/Source/tsconfig.ts
index e85c715e8757b..ae06cb76b5a91 100644
--- a/extensions/typescript-language-features/Source/tsconfig.ts
+++ b/extensions/typescript-language-features/Source/tsconfig.ts
@@ -2,86 +2,62 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TypeScriptServiceConfiguration } from './configuration/configuration';
 import { API } from './tsServer/api';
 import type * as Proto from './tsServer/protocol/protocol';
 import { ITypeScriptServiceClient, ServerResponse } from './typescriptService';
 import { nulToken } from './utils/cancellation';
-
-
 export const enum ProjectType {
-	TypeScript,
-	JavaScript,
+    TypeScript,
+    JavaScript
 }
-
 export function isImplicitProjectConfigFile(configFileName: string) {
-	return configFileName.startsWith('/dev/null/');
+    return configFileName.startsWith('/dev/null/');
 }
-
-export function inferredProjectCompilerOptions(
-	version: API,
-	projectType: ProjectType,
-	serviceConfig: TypeScriptServiceConfiguration,
-): Proto.ExternalProjectCompilerOptions {
-	const projectConfig: Proto.ExternalProjectCompilerOptions = {
-		module: (version.gte(API.v540) ? 'Preserve' : 'ESNext') as Proto.ModuleKind,
-		moduleResolution: (version.gte(API.v540) ? 'Bundler' : 'Node') as Proto.ModuleResolutionKind,
-		target: 'ES2022' as Proto.ScriptTarget,
-		jsx: 'react' as Proto.JsxEmit,
-	};
-
-	if (version.gte(API.v500)) {
-		projectConfig.allowImportingTsExtensions = true;
-	}
-
-	if (serviceConfig.implicitProjectConfiguration.checkJs) {
-		projectConfig.checkJs = true;
-		if (projectType === ProjectType.TypeScript) {
-			projectConfig.allowJs = true;
-		}
-	}
-
-	if (serviceConfig.implicitProjectConfiguration.experimentalDecorators) {
-		projectConfig.experimentalDecorators = true;
-	}
-
-	if (serviceConfig.implicitProjectConfiguration.strictNullChecks) {
-		projectConfig.strictNullChecks = true;
-	}
-
-	if (serviceConfig.implicitProjectConfiguration.strictFunctionTypes) {
-		projectConfig.strictFunctionTypes = true;
-	}
-
-	if (serviceConfig.implicitProjectConfiguration.module) {
-		projectConfig.module = serviceConfig.implicitProjectConfiguration.module as Proto.ModuleKind;
-	}
-
-	if (serviceConfig.implicitProjectConfiguration.target) {
-		projectConfig.target = serviceConfig.implicitProjectConfiguration.target as Proto.ScriptTarget;
-	}
-
-	if (projectType === ProjectType.TypeScript) {
-		projectConfig.sourceMap = true;
-	}
-
-	return projectConfig;
+export function inferredProjectCompilerOptions(version: API, projectType: ProjectType, serviceConfig: TypeScriptServiceConfiguration): Proto.ExternalProjectCompilerOptions {
+    const projectConfig: Proto.ExternalProjectCompilerOptions = {
+        module: (version.gte(API.v540) ? 'Preserve' : 'ESNext') as Proto.ModuleKind,
+        moduleResolution: (version.gte(API.v540) ? 'Bundler' : 'Node') as Proto.ModuleResolutionKind,
+        target: 'ES2022' as Proto.ScriptTarget,
+        jsx: 'react' as Proto.JsxEmit,
+    };
+    if (version.gte(API.v500)) {
+        projectConfig.allowImportingTsExtensions = true;
+    }
+    if (serviceConfig.implicitProjectConfiguration.checkJs) {
+        projectConfig.checkJs = true;
+        if (projectType === ProjectType.TypeScript) {
+            projectConfig.allowJs = true;
+        }
+    }
+    if (serviceConfig.implicitProjectConfiguration.experimentalDecorators) {
+        projectConfig.experimentalDecorators = true;
+    }
+    if (serviceConfig.implicitProjectConfiguration.strictNullChecks) {
+        projectConfig.strictNullChecks = true;
+    }
+    if (serviceConfig.implicitProjectConfiguration.strictFunctionTypes) {
+        projectConfig.strictFunctionTypes = true;
+    }
+    if (serviceConfig.implicitProjectConfiguration.module) {
+        projectConfig.module = serviceConfig.implicitProjectConfiguration.module as Proto.ModuleKind;
+    }
+    if (serviceConfig.implicitProjectConfiguration.target) {
+        projectConfig.target = serviceConfig.implicitProjectConfiguration.target as Proto.ScriptTarget;
+    }
+    if (projectType === ProjectType.TypeScript) {
+        projectConfig.sourceMap = true;
+    }
+    return projectConfig;
 }
-
-function inferredProjectConfigSnippet(
-	version: API,
-	projectType: ProjectType,
-	config: TypeScriptServiceConfiguration
-) {
-	const baseConfig = inferredProjectCompilerOptions(version, projectType, config);
-	if (projectType === ProjectType.TypeScript) {
-		delete baseConfig.allowImportingTsExtensions;
-	}
-
-	const compilerOptions = Object.keys(baseConfig).map(key => `"${key}": ${JSON.stringify(baseConfig[key])}`);
-	return new vscode.SnippetString(`{
+function inferredProjectConfigSnippet(version: API, projectType: ProjectType, config: TypeScriptServiceConfiguration) {
+    const baseConfig = inferredProjectCompilerOptions(version, projectType, config);
+    if (projectType === ProjectType.TypeScript) {
+        delete baseConfig.allowImportingTsExtensions;
+    }
+    const compilerOptions = Object.keys(baseConfig).map(key => `"${key}": ${JSON.stringify(baseConfig[key])}`);
+    return new vscode.SnippetString(`{
 	"compilerOptions": {
 		${compilerOptions.join(',\n\t\t')}$0
 	},
@@ -91,91 +67,64 @@ function inferredProjectConfigSnippet(
 	]
 }`);
 }
-
-export async function openOrCreateConfig(
-	version: API,
-	projectType: ProjectType,
-	rootPath: vscode.Uri,
-	configuration: TypeScriptServiceConfiguration,
-): Promise {
-	const configFile = vscode.Uri.joinPath(rootPath, projectType === ProjectType.TypeScript ? 'tsconfig.json' : 'jsconfig.json');
-	const col = vscode.window.activeTextEditor?.viewColumn;
-	try {
-		const doc = await vscode.workspace.openTextDocument(configFile);
-		return vscode.window.showTextDocument(doc, col);
-	} catch {
-		const doc = await vscode.workspace.openTextDocument(configFile.with({ scheme: 'untitled' }));
-		const editor = await vscode.window.showTextDocument(doc, col);
-		if (editor.document.getText().length === 0) {
-			await editor.insertSnippet(inferredProjectConfigSnippet(version, projectType, configuration));
-		}
-		return editor;
-	}
+export async function openOrCreateConfig(version: API, projectType: ProjectType, rootPath: vscode.Uri, configuration: TypeScriptServiceConfiguration): Promise {
+    const configFile = vscode.Uri.joinPath(rootPath, projectType === ProjectType.TypeScript ? 'tsconfig.json' : 'jsconfig.json');
+    const col = vscode.window.activeTextEditor?.viewColumn;
+    try {
+        const doc = await vscode.workspace.openTextDocument(configFile);
+        return vscode.window.showTextDocument(doc, col);
+    }
+    catch {
+        const doc = await vscode.workspace.openTextDocument(configFile.with({ scheme: 'untitled' }));
+        const editor = await vscode.window.showTextDocument(doc, col);
+        if (editor.document.getText().length === 0) {
+            await editor.insertSnippet(inferredProjectConfigSnippet(version, projectType, configuration));
+        }
+        return editor;
+    }
 }
-
-export async function openProjectConfigOrPromptToCreate(
-	projectType: ProjectType,
-	client: ITypeScriptServiceClient,
-	rootPath: vscode.Uri,
-	configFilePath: string,
-): Promise {
-	if (!isImplicitProjectConfigFile(configFilePath)) {
-		const doc = await vscode.workspace.openTextDocument(client.toResource(configFilePath));
-		vscode.window.showTextDocument(doc, vscode.window.activeTextEditor?.viewColumn);
-		return;
-	}
-
-	const CreateConfigItem: vscode.MessageItem = {
-		title: projectType === ProjectType.TypeScript
-			? vscode.l10n.t("Configure tsconfig.json")
-			: vscode.l10n.t("Configure jsconfig.json"),
-	};
-
-	const selected = await vscode.window.showInformationMessage(
-		(projectType === ProjectType.TypeScript
-			? vscode.l10n.t("File is not part of a TypeScript project. View the [tsconfig.json documentation]({0}) to learn more.", 'https://go.microsoft.com/fwlink/?linkid=841896')
-			: vscode.l10n.t("File is not part of a JavaScript project. View the [jsconfig.json documentation]({0}) to learn more.", 'https://go.microsoft.com/fwlink/?linkid=759670')
-		),
-		CreateConfigItem);
-
-	switch (selected) {
-		case CreateConfigItem:
-			openOrCreateConfig(client.apiVersion, projectType, rootPath, client.configuration);
-			return;
-	}
+export async function openProjectConfigOrPromptToCreate(projectType: ProjectType, client: ITypeScriptServiceClient, rootPath: vscode.Uri, configFilePath: string): Promise {
+    if (!isImplicitProjectConfigFile(configFilePath)) {
+        const doc = await vscode.workspace.openTextDocument(client.toResource(configFilePath));
+        vscode.window.showTextDocument(doc, vscode.window.activeTextEditor?.viewColumn);
+        return;
+    }
+    const CreateConfigItem: vscode.MessageItem = {
+        title: projectType === ProjectType.TypeScript
+            ? vscode.l10n.t("Configure tsconfig.json")
+            : vscode.l10n.t("Configure jsconfig.json"),
+    };
+    const selected = await vscode.window.showInformationMessage((projectType === ProjectType.TypeScript
+        ? vscode.l10n.t("File is not part of a TypeScript project. View the [tsconfig.json documentation]({0}) to learn more.", 'https://go.microsoft.com/fwlink/?linkid=841896')
+        : vscode.l10n.t("File is not part of a JavaScript project. View the [jsconfig.json documentation]({0}) to learn more.", 'https://go.microsoft.com/fwlink/?linkid=759670')), CreateConfigItem);
+    switch (selected) {
+        case CreateConfigItem:
+            openOrCreateConfig(client.apiVersion, projectType, rootPath, client.configuration);
+            return;
+    }
 }
-
-export async function openProjectConfigForFile(
-	projectType: ProjectType,
-	client: ITypeScriptServiceClient,
-	resource: vscode.Uri,
-): Promise {
-	const rootPath = client.getWorkspaceRootForResource(resource);
-	if (!rootPath) {
-		vscode.window.showInformationMessage(
-			vscode.l10n.t("Please open a folder in VS Code to use a TypeScript or JavaScript project"));
-		return;
-	}
-
-	const file = client.toTsFilePath(resource);
-	// TSServer errors when 'projectInfo' is invoked on a non js/ts file
-	if (!file || !client.toTsFilePath(resource)) {
-		vscode.window.showWarningMessage(
-			vscode.l10n.t("Could not determine TypeScript or JavaScript project. Unsupported file type"));
-		return;
-	}
-
-	let res: ServerResponse.Response | undefined;
-	try {
-		res = await client.execute('projectInfo', { file, needFileNameList: false }, nulToken);
-	} catch {
-		// noop
-	}
-
-	if (res?.type !== 'response' || !res.body) {
-		vscode.window.showWarningMessage(vscode.l10n.t("Could not determine TypeScript or JavaScript project"));
-		return;
-	}
-	return openProjectConfigOrPromptToCreate(projectType, client, rootPath, res.body.configFileName);
+export async function openProjectConfigForFile(projectType: ProjectType, client: ITypeScriptServiceClient, resource: vscode.Uri): Promise {
+    const rootPath = client.getWorkspaceRootForResource(resource);
+    if (!rootPath) {
+        vscode.window.showInformationMessage(vscode.l10n.t("Please open a folder in VS Code to use a TypeScript or JavaScript project"));
+        return;
+    }
+    const file = client.toTsFilePath(resource);
+    // TSServer errors when 'projectInfo' is invoked on a non js/ts file
+    if (!file || !client.toTsFilePath(resource)) {
+        vscode.window.showWarningMessage(vscode.l10n.t("Could not determine TypeScript or JavaScript project. Unsupported file type"));
+        return;
+    }
+    let res: ServerResponse.Response | undefined;
+    try {
+        res = await client.execute('projectInfo', { file, needFileNameList: false }, nulToken);
+    }
+    catch {
+        // noop
+    }
+    if (res?.type !== 'response' || !res.body) {
+        vscode.window.showWarningMessage(vscode.l10n.t("Could not determine TypeScript or JavaScript project"));
+        return;
+    }
+    return openProjectConfigOrPromptToCreate(projectType, client, rootPath, res.body.configFileName);
 }
-
diff --git a/extensions/typescript-language-features/Source/typeConverters.ts b/extensions/typescript-language-features/Source/typeConverters.ts
index a860251bb5977..ffec6ccaa0830 100644
--- a/extensions/typescript-language-features/Source/typeConverters.ts
+++ b/extensions/typescript-language-features/Source/typeConverters.ts
@@ -2,156 +2,118 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 /**
  * Helpers for converting FROM vscode types TO ts types
  */
-
 import * as vscode from 'vscode';
 import type * as Proto from './tsServer/protocol/protocol';
 import * as PConst from './tsServer/protocol/protocol.const';
 import { ITypeScriptServiceClient } from './typescriptService';
-
 export namespace Range {
-	export const fromTextSpan = (span: Proto.TextSpan): vscode.Range =>
-		fromLocations(span.start, span.end);
-
-	export const toTextSpan = (range: vscode.Range): Proto.TextSpan => ({
-		start: Position.toLocation(range.start),
-		end: Position.toLocation(range.end)
-	});
-
-	export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range =>
-		new vscode.Range(
-			Math.max(0, start.line - 1), Math.max(start.offset - 1, 0),
-			Math.max(0, end.line - 1), Math.max(0, end.offset - 1));
-
-	export const toFileRange = (range: vscode.Range): Proto.FileRange => ({
-		startLine: range.start.line + 1,
-		startOffset: range.start.character + 1,
-		endLine: range.end.line + 1,
-		endOffset: range.end.character + 1
-	});
-
-	export const toFileRangeRequestArgs = (file: string, range: vscode.Range): Proto.FileRangeRequestArgs => ({
-		file,
-		...toFileRange(range)
-	});
-
-	export const toFileRangesRequestArgs = (file: string, ranges: vscode.Range[]): Proto.FileRangesRequestArgs => ({
-		file,
-		ranges: ranges.map(toFileRange)
-	});
-
-	export const toFormattingRequestArgs = (file: string, range: vscode.Range): Proto.FormatRequestArgs => ({
-		file,
-		line: range.start.line + 1,
-		offset: range.start.character + 1,
-		endLine: range.end.line + 1,
-		endOffset: range.end.character + 1
-	});
+    export const fromTextSpan = (span: Proto.TextSpan): vscode.Range => fromLocations(span.start, span.end);
+    export const toTextSpan = (range: vscode.Range): Proto.TextSpan => ({
+        start: Position.toLocation(range.start),
+        end: Position.toLocation(range.end)
+    });
+    export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range => new vscode.Range(Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), Math.max(0, end.line - 1), Math.max(0, end.offset - 1));
+    export const toFileRange = (range: vscode.Range): Proto.FileRange => ({
+        startLine: range.start.line + 1,
+        startOffset: range.start.character + 1,
+        endLine: range.end.line + 1,
+        endOffset: range.end.character + 1
+    });
+    export const toFileRangeRequestArgs = (file: string, range: vscode.Range): Proto.FileRangeRequestArgs => ({
+        file,
+        ...toFileRange(range)
+    });
+    export const toFileRangesRequestArgs = (file: string, ranges: vscode.Range[]): Proto.FileRangesRequestArgs => ({
+        file,
+        ranges: ranges.map(toFileRange)
+    });
+    export const toFormattingRequestArgs = (file: string, range: vscode.Range): Proto.FormatRequestArgs => ({
+        file,
+        line: range.start.line + 1,
+        offset: range.start.character + 1,
+        endLine: range.end.line + 1,
+        endOffset: range.end.character + 1
+    });
 }
-
 export namespace Position {
-	export const fromLocation = (tslocation: Proto.Location): vscode.Position =>
-		new vscode.Position(tslocation.line - 1, tslocation.offset - 1);
-
-	export const toLocation = (vsPosition: vscode.Position): Proto.Location => ({
-		line: vsPosition.line + 1,
-		offset: vsPosition.character + 1,
-	});
-
-	export const toFileLocationRequestArgs = (file: string, position: vscode.Position): Proto.FileLocationRequestArgs => ({
-		file,
-		line: position.line + 1,
-		offset: position.character + 1,
-	});
+    export const fromLocation = (tslocation: Proto.Location): vscode.Position => new vscode.Position(tslocation.line - 1, tslocation.offset - 1);
+    export const toLocation = (vsPosition: vscode.Position): Proto.Location => ({
+        line: vsPosition.line + 1,
+        offset: vsPosition.character + 1,
+    });
+    export const toFileLocationRequestArgs = (file: string, position: vscode.Position): Proto.FileLocationRequestArgs => ({
+        file,
+        line: position.line + 1,
+        offset: position.character + 1,
+    });
 }
-
 export namespace Location {
-	export const fromTextSpan = (resource: vscode.Uri, tsTextSpan: Proto.TextSpan): vscode.Location =>
-		new vscode.Location(resource, Range.fromTextSpan(tsTextSpan));
+    export const fromTextSpan = (resource: vscode.Uri, tsTextSpan: Proto.TextSpan): vscode.Location => new vscode.Location(resource, Range.fromTextSpan(tsTextSpan));
 }
-
 export namespace TextEdit {
-	export const fromCodeEdit = (edit: Proto.CodeEdit): vscode.TextEdit =>
-		new vscode.TextEdit(
-			Range.fromTextSpan(edit),
-			edit.newText);
+    export const fromCodeEdit = (edit: Proto.CodeEdit): vscode.TextEdit => new vscode.TextEdit(Range.fromTextSpan(edit), edit.newText);
 }
-
 export namespace WorkspaceEdit {
-	export function fromFileCodeEdits(
-		client: ITypeScriptServiceClient,
-		edits: Iterable
-	): vscode.WorkspaceEdit {
-		return withFileCodeEdits(new vscode.WorkspaceEdit(), client, edits);
-	}
-
-	export function withFileCodeEdits(
-		workspaceEdit: vscode.WorkspaceEdit,
-		client: ITypeScriptServiceClient,
-		edits: Iterable
-	): vscode.WorkspaceEdit {
-		for (const edit of edits) {
-			const resource = client.toResource(edit.fileName);
-			for (const textChange of edit.textChanges) {
-				workspaceEdit.replace(resource,
-					Range.fromTextSpan(textChange),
-					textChange.newText);
-			}
-		}
-
-		return workspaceEdit;
-	}
+    export function fromFileCodeEdits(client: ITypeScriptServiceClient, edits: Iterable): vscode.WorkspaceEdit {
+        return withFileCodeEdits(new vscode.WorkspaceEdit(), client, edits);
+    }
+    export function withFileCodeEdits(workspaceEdit: vscode.WorkspaceEdit, client: ITypeScriptServiceClient, edits: Iterable): vscode.WorkspaceEdit {
+        for (const edit of edits) {
+            const resource = client.toResource(edit.fileName);
+            for (const textChange of edit.textChanges) {
+                workspaceEdit.replace(resource, Range.fromTextSpan(textChange), textChange.newText);
+            }
+        }
+        return workspaceEdit;
+    }
 }
-
 export namespace SymbolKind {
-	export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) {
-		switch (kind) {
-			case PConst.Kind.module: return vscode.SymbolKind.Module;
-			case PConst.Kind.class: return vscode.SymbolKind.Class;
-			case PConst.Kind.enum: return vscode.SymbolKind.Enum;
-			case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember;
-			case PConst.Kind.interface: return vscode.SymbolKind.Interface;
-			case PConst.Kind.indexSignature: return vscode.SymbolKind.Method;
-			case PConst.Kind.callSignature: return vscode.SymbolKind.Method;
-			case PConst.Kind.method: return vscode.SymbolKind.Method;
-			case PConst.Kind.memberVariable: return vscode.SymbolKind.Property;
-			case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property;
-			case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property;
-			case PConst.Kind.variable: return vscode.SymbolKind.Variable;
-			case PConst.Kind.let: return vscode.SymbolKind.Variable;
-			case PConst.Kind.const: return vscode.SymbolKind.Variable;
-			case PConst.Kind.localVariable: return vscode.SymbolKind.Variable;
-			case PConst.Kind.alias: return vscode.SymbolKind.Variable;
-			case PConst.Kind.function: return vscode.SymbolKind.Function;
-			case PConst.Kind.localFunction: return vscode.SymbolKind.Function;
-			case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor;
-			case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor;
-			case PConst.Kind.typeParameter: return vscode.SymbolKind.TypeParameter;
-			case PConst.Kind.string: return vscode.SymbolKind.String;
-			default: return vscode.SymbolKind.Variable;
-		}
-	}
+    export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) {
+        switch (kind) {
+            case PConst.Kind.module: return vscode.SymbolKind.Module;
+            case PConst.Kind.class: return vscode.SymbolKind.Class;
+            case PConst.Kind.enum: return vscode.SymbolKind.Enum;
+            case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember;
+            case PConst.Kind.interface: return vscode.SymbolKind.Interface;
+            case PConst.Kind.indexSignature: return vscode.SymbolKind.Method;
+            case PConst.Kind.callSignature: return vscode.SymbolKind.Method;
+            case PConst.Kind.method: return vscode.SymbolKind.Method;
+            case PConst.Kind.memberVariable: return vscode.SymbolKind.Property;
+            case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property;
+            case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property;
+            case PConst.Kind.variable: return vscode.SymbolKind.Variable;
+            case PConst.Kind.let: return vscode.SymbolKind.Variable;
+            case PConst.Kind.const: return vscode.SymbolKind.Variable;
+            case PConst.Kind.localVariable: return vscode.SymbolKind.Variable;
+            case PConst.Kind.alias: return vscode.SymbolKind.Variable;
+            case PConst.Kind.function: return vscode.SymbolKind.Function;
+            case PConst.Kind.localFunction: return vscode.SymbolKind.Function;
+            case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor;
+            case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor;
+            case PConst.Kind.typeParameter: return vscode.SymbolKind.TypeParameter;
+            case PConst.Kind.string: return vscode.SymbolKind.String;
+            default: return vscode.SymbolKind.Variable;
+        }
+    }
 }
-
 export namespace CompletionTriggerKind {
-	export function toProtocolCompletionTriggerKind(kind: vscode.CompletionTriggerKind): Proto.CompletionTriggerKind {
-		switch (kind) {
-			case vscode.CompletionTriggerKind.Invoke: return 1;
-			case vscode.CompletionTriggerKind.TriggerCharacter: return 2;
-			case vscode.CompletionTriggerKind.TriggerForIncompleteCompletions: return 3;
-		}
-	}
+    export function toProtocolCompletionTriggerKind(kind: vscode.CompletionTriggerKind): Proto.CompletionTriggerKind {
+        switch (kind) {
+            case vscode.CompletionTriggerKind.Invoke: return 1;
+            case vscode.CompletionTriggerKind.TriggerCharacter: return 2;
+            case vscode.CompletionTriggerKind.TriggerForIncompleteCompletions: return 3;
+        }
+    }
 }
-
 export namespace OrganizeImportsMode {
-	export function toProtocolOrganizeImportsMode(mode: PConst.OrganizeImportsMode): Proto.OrganizeImportsMode {
-		switch (mode) {
-			case PConst.OrganizeImportsMode.All: return 'All' as Proto.OrganizeImportsMode.All;
-			case PConst.OrganizeImportsMode.SortAndCombine: return 'SortAndCombine' as Proto.OrganizeImportsMode.SortAndCombine;
-			case PConst.OrganizeImportsMode.RemoveUnused: return 'RemoveUnused' as Proto.OrganizeImportsMode.RemoveUnused;
-		}
-	}
+    export function toProtocolOrganizeImportsMode(mode: PConst.OrganizeImportsMode): Proto.OrganizeImportsMode {
+        switch (mode) {
+            case PConst.OrganizeImportsMode.All: return 'All' as Proto.OrganizeImportsMode.All;
+            case PConst.OrganizeImportsMode.SortAndCombine: return 'SortAndCombine' as Proto.OrganizeImportsMode.SortAndCombine;
+            case PConst.OrganizeImportsMode.RemoveUnused: return 'RemoveUnused' as Proto.OrganizeImportsMode.RemoveUnused;
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/typeScriptServiceClientHost.ts b/extensions/typescript-language-features/Source/typeScriptServiceClientHost.ts
index c44bfc3ac3fd2..50a0beb030ca0 100644
--- a/extensions/typescript-language-features/Source/typeScriptServiceClientHost.ts
+++ b/extensions/typescript-language-features/Source/typeScriptServiceClientHost.ts
@@ -2,12 +2,10 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 /* --------------------------------------------------------------------------------------------
  * Includes code from typescript-sublime-plugin project, obtained from
  * https://github.com/microsoft/TypeScript-Sublime-Plugin/blob/master/TypeScript%20Indent.tmPreferences
  * ------------------------------------------------------------------------------------------ */
-
 import * as vscode from 'vscode';
 import { CommandManager } from './commands/commandManager';
 import { ServiceConfigurationProvider } from './configuration/configuration';
@@ -35,301 +33,242 @@ import TypingsStatus, { AtaProgressReporter } from './ui/typingsStatus';
 import { VersionStatus } from './ui/versionStatus';
 import { coalesce } from './utils/arrays';
 import { Disposable } from './utils/dispose';
-
 // Style check diagnostics that can be reported as warnings
 const styleCheckDiagnostics = new Set([
-	...errorCodes.variableDeclaredButNeverUsed,
-	...errorCodes.propertyDeclaretedButNeverUsed,
-	...errorCodes.allImportsAreUnused,
-	...errorCodes.unreachableCode,
-	...errorCodes.unusedLabel,
-	...errorCodes.fallThroughCaseInSwitch,
-	...errorCodes.notAllCodePathsReturnAValue,
+    ...errorCodes.variableDeclaredButNeverUsed,
+    ...errorCodes.propertyDeclaretedButNeverUsed,
+    ...errorCodes.allImportsAreUnused,
+    ...errorCodes.unreachableCode,
+    ...errorCodes.unusedLabel,
+    ...errorCodes.fallThroughCaseInSwitch,
+    ...errorCodes.notAllCodePathsReturnAValue,
 ]);
-
 export default class TypeScriptServiceClientHost extends Disposable {
-
-	private readonly client: TypeScriptServiceClient;
-	private readonly languages: LanguageProvider[] = [];
-	private readonly languagePerId = new Map();
-
-	private readonly typingsStatus: TypingsStatus;
-
-	private readonly fileConfigurationManager: FileConfigurationManager;
-
-	private reportStyleCheckAsWarnings: boolean = true;
-
-	private readonly commandManager: CommandManager;
-
-	constructor(
-		descriptions: LanguageDescription[],
-		context: vscode.ExtensionContext,
-		onCaseInsensitiveFileSystem: boolean,
-		services: {
-			pluginManager: PluginManager;
-			commandManager: CommandManager;
-			logDirectoryProvider: ILogDirectoryProvider;
-			cancellerFactory: OngoingRequestCancellerFactory;
-			versionProvider: ITypeScriptVersionProvider;
-			processFactory: TsServerProcessFactory;
-			activeJsTsEditorTracker: ActiveJsTsEditorTracker;
-			serviceConfigurationProvider: ServiceConfigurationProvider;
-			experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
-			logger: Logger;
-		},
-		onCompletionAccepted: (item: vscode.CompletionItem) => void,
-	) {
-		super();
-
-		this.commandManager = services.commandManager;
-
-		const allModeIds = this.getAllModeIds(descriptions, services.pluginManager);
-		this.client = this._register(new TypeScriptServiceClient(
-			context,
-			onCaseInsensitiveFileSystem,
-			services,
-			allModeIds));
-
-		this.client.onDiagnosticsReceived(({ kind, resource, diagnostics, spans }) => {
-			this.diagnosticsReceived(kind, resource, diagnostics, spans);
-		}, null, this._disposables);
-
-		this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this._disposables);
-		this.client.onResendModelsRequested(() => this.populateService(), null, this._disposables);
-
-		this._register(new VersionStatus(this.client));
-		this._register(new IntellisenseStatus(this.client, services.commandManager, services.activeJsTsEditorTracker));
-		this._register(new AtaProgressReporter(this.client));
-		this.typingsStatus = this._register(new TypingsStatus(this.client));
-		this._register(LargeProjectStatus.create(this.client));
-
-		this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsensitiveFileSystem));
-
-		for (const description of descriptions) {
-			const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager, onCompletionAccepted);
-			this.languages.push(manager);
-			this._register(manager);
-			this.languagePerId.set(description.id, manager);
-		}
-
-		import('./languageFeatures/updatePathsOnRename').then(module =>
-			this._register(module.register(this.client, this.fileConfigurationManager, uri => this.handles(uri))));
-
-		import('./languageFeatures/workspaceSymbols').then(module =>
-			this._register(module.register(this.client, allModeIds)));
-
-		this.client.ensureServiceStarted();
-		this.client.onReady(() => {
-			const languages = new Set();
-			for (const plugin of services.pluginManager.plugins) {
-				if (plugin.configNamespace && plugin.languages.length) {
-					this.registerExtensionLanguageProvider({
-						id: plugin.configNamespace,
-						languageIds: Array.from(plugin.languages),
-						diagnosticSource: 'ts-plugin',
-						diagnosticLanguage: DiagnosticLanguage.TypeScript,
-						diagnosticOwner: 'typescript',
-						isExternal: true,
-						standardFileExtensions: [],
-					}, onCompletionAccepted);
-				} else {
-					for (const language of plugin.languages) {
-						languages.add(language);
-					}
-				}
-			}
-
-			if (languages.size) {
-				this.registerExtensionLanguageProvider({
-					id: 'typescript-plugins',
-					languageIds: Array.from(languages.values()),
-					diagnosticSource: 'ts-plugin',
-					diagnosticLanguage: DiagnosticLanguage.TypeScript,
-					diagnosticOwner: 'typescript',
-					isExternal: true,
-					standardFileExtensions: [],
-				}, onCompletionAccepted);
-			}
-		});
-
-		this.client.onTsServerStarted(() => {
-			this.triggerAllDiagnostics();
-		});
-
-		vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
-		this.configurationChanged();
-		this._register(new LogLevelMonitor(context));
-	}
-
-	private registerExtensionLanguageProvider(description: LanguageDescription, onCompletionAccepted: (item: vscode.CompletionItem) => void) {
-		const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager, onCompletionAccepted);
-		this.languages.push(manager);
-		this._register(manager);
-		this.languagePerId.set(description.id, manager);
-	}
-
-	private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager) {
-		return [
-			...descriptions.map(x => x.languageIds),
-			...pluginManager.plugins.map(x => x.languages)
-		].flat();
-	}
-
-	public get serviceClient(): TypeScriptServiceClient {
-		return this.client;
-	}
-
-	public reloadProjects(): void {
-		this.client.executeWithoutWaitingForResponse('reloadProjects', null);
-		this.triggerAllDiagnostics();
-	}
-
-	public async handles(resource: vscode.Uri): Promise {
-		const provider = await this.findLanguage(resource);
-		if (provider) {
-			return true;
-		}
-		return this.client.bufferSyncSupport.handles(resource);
-	}
-
-	private configurationChanged(): void {
-		const typescriptConfig = vscode.workspace.getConfiguration('typescript');
-
-		this.reportStyleCheckAsWarnings = typescriptConfig.get('reportStyleChecksAsWarnings', true);
-	}
-
-	private async findLanguage(resource: vscode.Uri): Promise {
-		try {
-			// First try finding language just based on the resource.
-			// This is not strictly correct but should be in the vast majority of cases
-			// (except when someone goes and maps `.js` to `typescript` or something...)
-			for (const language of this.languages) {
-				if (language.handlesUri(resource)) {
-					return language;
-				}
-			}
-
-			// If that doesn't work, fallback to using a text document language mode.
-			// This is not ideal since we have to open the document but should always
-			// be correct
-			const doc = await vscode.workspace.openTextDocument(resource);
-			return this.languages.find(language => language.handlesDocument(doc));
-		} catch {
-			return undefined;
-		}
-	}
-
-	private triggerAllDiagnostics() {
-		for (const language of this.languagePerId.values()) {
-			language.triggerAllDiagnostics();
-		}
-	}
-
-	private populateService(): void {
-		this.fileConfigurationManager.reset();
-
-		for (const language of this.languagePerId.values()) {
-			language.reInitialize();
-		}
-	}
-
-	private async diagnosticsReceived(
-		kind: DiagnosticKind,
-		resource: vscode.Uri,
-		diagnostics: Proto.Diagnostic[],
-		spans: Proto.TextSpan[] | undefined,
-	): Promise {
-		const language = await this.findLanguage(resource);
-		if (language) {
-			language.diagnosticsReceived(
-				kind,
-				resource,
-				this.createMarkerDatas(diagnostics, language.diagnosticSource),
-				spans?.map(span => typeConverters.Range.fromTextSpan(span)));
-		}
-	}
-
-	private configFileDiagnosticsReceived(event: Proto.ConfigFileDiagnosticEvent): void {
-		// See https://github.com/microsoft/TypeScript/issues/10384
-		const body = event.body;
-		if (!body?.diagnostics || !body.configFile) {
-			return;
-		}
-
-		this.findLanguage(this.client.toResource(body.configFile)).then(language => {
-			language?.configFileDiagnosticsReceived(this.client.toResource(body.configFile), body.diagnostics.map(tsDiag => {
-				const range = tsDiag.start && tsDiag.end ? typeConverters.Range.fromTextSpan(tsDiag) : new vscode.Range(0, 0, 0, 1);
-				const diagnostic = new vscode.Diagnostic(range, tsDiag.text, this.getDiagnosticSeverity(tsDiag));
-				diagnostic.source = language.diagnosticSource;
-				return diagnostic;
-			}));
-		});
-	}
-
-	private createMarkerDatas(
-		diagnostics: Proto.Diagnostic[],
-		source: string
-	): (vscode.Diagnostic & { reportUnnecessary: any; reportDeprecated: any })[] {
-		return diagnostics.map(tsDiag => this.tsDiagnosticToVsDiagnostic(tsDiag, source));
-	}
-
-	private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string): vscode.Diagnostic & { reportUnnecessary: any; reportDeprecated: any } {
-		const { start, end, text } = diagnostic;
-		const range = new vscode.Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end));
-		const converted = new vscode.Diagnostic(range, text, this.getDiagnosticSeverity(diagnostic));
-		converted.source = diagnostic.source || source;
-		if (diagnostic.code) {
-			converted.code = diagnostic.code;
-		}
-		const relatedInformation = diagnostic.relatedInformation;
-		if (relatedInformation) {
-			converted.relatedInformation = coalesce(relatedInformation.map((info: any) => {
-				const span = info.span;
-				if (!span) {
-					return undefined;
-				}
-				return new vscode.DiagnosticRelatedInformation(typeConverters.Location.fromTextSpan(this.client.toResource(span.file), span), info.message);
-			}));
-		}
-		const tags: vscode.DiagnosticTag[] = [];
-		if (diagnostic.reportsUnnecessary) {
-			tags.push(vscode.DiagnosticTag.Unnecessary);
-		}
-		if (diagnostic.reportsDeprecated) {
-			tags.push(vscode.DiagnosticTag.Deprecated);
-		}
-		converted.tags = tags.length ? tags : undefined;
-
-		const resultConverted = converted as vscode.Diagnostic & { reportUnnecessary: any; reportDeprecated: any };
-		resultConverted.reportUnnecessary = diagnostic.reportsUnnecessary;
-		resultConverted.reportDeprecated = diagnostic.reportsDeprecated;
-		return resultConverted;
-	}
-
-	private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): vscode.DiagnosticSeverity {
-		if (this.reportStyleCheckAsWarnings
-			&& this.isStyleCheckDiagnostic(diagnostic.code)
-			&& diagnostic.category === PConst.DiagnosticCategory.error
-		) {
-			return vscode.DiagnosticSeverity.Warning;
-		}
-
-		switch (diagnostic.category) {
-			case PConst.DiagnosticCategory.error:
-				return vscode.DiagnosticSeverity.Error;
-
-			case PConst.DiagnosticCategory.warning:
-				return vscode.DiagnosticSeverity.Warning;
-
-			case PConst.DiagnosticCategory.suggestion:
-				return vscode.DiagnosticSeverity.Hint;
-
-			default:
-				return vscode.DiagnosticSeverity.Error;
-		}
-	}
-
-	private isStyleCheckDiagnostic(code: number | undefined): boolean {
-		return typeof code === 'number' && styleCheckDiagnostics.has(code);
-	}
+    private readonly client: TypeScriptServiceClient;
+    private readonly languages: LanguageProvider[] = [];
+    private readonly languagePerId = new Map();
+    private readonly typingsStatus: TypingsStatus;
+    private readonly fileConfigurationManager: FileConfigurationManager;
+    private reportStyleCheckAsWarnings: boolean = true;
+    private readonly commandManager: CommandManager;
+    constructor(descriptions: LanguageDescription[], context: vscode.ExtensionContext, onCaseInsensitiveFileSystem: boolean, services: {
+        pluginManager: PluginManager;
+        commandManager: CommandManager;
+        logDirectoryProvider: ILogDirectoryProvider;
+        cancellerFactory: OngoingRequestCancellerFactory;
+        versionProvider: ITypeScriptVersionProvider;
+        processFactory: TsServerProcessFactory;
+        activeJsTsEditorTracker: ActiveJsTsEditorTracker;
+        serviceConfigurationProvider: ServiceConfigurationProvider;
+        experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
+        logger: Logger;
+    }, onCompletionAccepted: (item: vscode.CompletionItem) => void) {
+        super();
+        this.commandManager = services.commandManager;
+        const allModeIds = this.getAllModeIds(descriptions, services.pluginManager);
+        this.client = this._register(new TypeScriptServiceClient(context, onCaseInsensitiveFileSystem, services, allModeIds));
+        this.client.onDiagnosticsReceived(({ kind, resource, diagnostics, spans }) => {
+            this.diagnosticsReceived(kind, resource, diagnostics, spans);
+        }, null, this._disposables);
+        this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this._disposables);
+        this.client.onResendModelsRequested(() => this.populateService(), null, this._disposables);
+        this._register(new VersionStatus(this.client));
+        this._register(new IntellisenseStatus(this.client, services.commandManager, services.activeJsTsEditorTracker));
+        this._register(new AtaProgressReporter(this.client));
+        this.typingsStatus = this._register(new TypingsStatus(this.client));
+        this._register(LargeProjectStatus.create(this.client));
+        this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsensitiveFileSystem));
+        for (const description of descriptions) {
+            const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager, onCompletionAccepted);
+            this.languages.push(manager);
+            this._register(manager);
+            this.languagePerId.set(description.id, manager);
+        }
+        import('./languageFeatures/updatePathsOnRename').then(module => this._register(module.register(this.client, this.fileConfigurationManager, uri => this.handles(uri))));
+        import('./languageFeatures/workspaceSymbols').then(module => this._register(module.register(this.client, allModeIds)));
+        this.client.ensureServiceStarted();
+        this.client.onReady(() => {
+            const languages = new Set();
+            for (const plugin of services.pluginManager.plugins) {
+                if (plugin.configNamespace && plugin.languages.length) {
+                    this.registerExtensionLanguageProvider({
+                        id: plugin.configNamespace,
+                        languageIds: Array.from(plugin.languages),
+                        diagnosticSource: 'ts-plugin',
+                        diagnosticLanguage: DiagnosticLanguage.TypeScript,
+                        diagnosticOwner: 'typescript',
+                        isExternal: true,
+                        standardFileExtensions: [],
+                    }, onCompletionAccepted);
+                }
+                else {
+                    for (const language of plugin.languages) {
+                        languages.add(language);
+                    }
+                }
+            }
+            if (languages.size) {
+                this.registerExtensionLanguageProvider({
+                    id: 'typescript-plugins',
+                    languageIds: Array.from(languages.values()),
+                    diagnosticSource: 'ts-plugin',
+                    diagnosticLanguage: DiagnosticLanguage.TypeScript,
+                    diagnosticOwner: 'typescript',
+                    isExternal: true,
+                    standardFileExtensions: [],
+                }, onCompletionAccepted);
+            }
+        });
+        this.client.onTsServerStarted(() => {
+            this.triggerAllDiagnostics();
+        });
+        vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
+        this.configurationChanged();
+        this._register(new LogLevelMonitor(context));
+    }
+    private registerExtensionLanguageProvider(description: LanguageDescription, onCompletionAccepted: (item: vscode.CompletionItem) => void) {
+        const manager = new LanguageProvider(this.client, description, this.commandManager, this.client.telemetryReporter, this.typingsStatus, this.fileConfigurationManager, onCompletionAccepted);
+        this.languages.push(manager);
+        this._register(manager);
+        this.languagePerId.set(description.id, manager);
+    }
+    private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager) {
+        return [
+            ...descriptions.map(x => x.languageIds),
+            ...pluginManager.plugins.map(x => x.languages)
+        ].flat();
+    }
+    public get serviceClient(): TypeScriptServiceClient {
+        return this.client;
+    }
+    public reloadProjects(): void {
+        this.client.executeWithoutWaitingForResponse('reloadProjects', null);
+        this.triggerAllDiagnostics();
+    }
+    public async handles(resource: vscode.Uri): Promise {
+        const provider = await this.findLanguage(resource);
+        if (provider) {
+            return true;
+        }
+        return this.client.bufferSyncSupport.handles(resource);
+    }
+    private configurationChanged(): void {
+        const typescriptConfig = vscode.workspace.getConfiguration('typescript');
+        this.reportStyleCheckAsWarnings = typescriptConfig.get('reportStyleChecksAsWarnings', true);
+    }
+    private async findLanguage(resource: vscode.Uri): Promise {
+        try {
+            // First try finding language just based on the resource.
+            // This is not strictly correct but should be in the vast majority of cases
+            // (except when someone goes and maps `.js` to `typescript` or something...)
+            for (const language of this.languages) {
+                if (language.handlesUri(resource)) {
+                    return language;
+                }
+            }
+            // If that doesn't work, fallback to using a text document language mode.
+            // This is not ideal since we have to open the document but should always
+            // be correct
+            const doc = await vscode.workspace.openTextDocument(resource);
+            return this.languages.find(language => language.handlesDocument(doc));
+        }
+        catch {
+            return undefined;
+        }
+    }
+    private triggerAllDiagnostics() {
+        for (const language of this.languagePerId.values()) {
+            language.triggerAllDiagnostics();
+        }
+    }
+    private populateService(): void {
+        this.fileConfigurationManager.reset();
+        for (const language of this.languagePerId.values()) {
+            language.reInitialize();
+        }
+    }
+    private async diagnosticsReceived(kind: DiagnosticKind, resource: vscode.Uri, diagnostics: Proto.Diagnostic[], spans: Proto.TextSpan[] | undefined): Promise {
+        const language = await this.findLanguage(resource);
+        if (language) {
+            language.diagnosticsReceived(kind, resource, this.createMarkerDatas(diagnostics, language.diagnosticSource), spans?.map(span => typeConverters.Range.fromTextSpan(span)));
+        }
+    }
+    private configFileDiagnosticsReceived(event: Proto.ConfigFileDiagnosticEvent): void {
+        // See https://github.com/microsoft/TypeScript/issues/10384
+        const body = event.body;
+        if (!body?.diagnostics || !body.configFile) {
+            return;
+        }
+        this.findLanguage(this.client.toResource(body.configFile)).then(language => {
+            language?.configFileDiagnosticsReceived(this.client.toResource(body.configFile), body.diagnostics.map(tsDiag => {
+                const range = tsDiag.start && tsDiag.end ? typeConverters.Range.fromTextSpan(tsDiag) : new vscode.Range(0, 0, 0, 1);
+                const diagnostic = new vscode.Diagnostic(range, tsDiag.text, this.getDiagnosticSeverity(tsDiag));
+                diagnostic.source = language.diagnosticSource;
+                return diagnostic;
+            }));
+        });
+    }
+    private createMarkerDatas(diagnostics: Proto.Diagnostic[], source: string): (vscode.Diagnostic & {
+        reportUnnecessary: any;
+        reportDeprecated: any;
+    })[] {
+        return diagnostics.map(tsDiag => this.tsDiagnosticToVsDiagnostic(tsDiag, source));
+    }
+    private tsDiagnosticToVsDiagnostic(diagnostic: Proto.Diagnostic, source: string): vscode.Diagnostic & {
+        reportUnnecessary: any;
+        reportDeprecated: any;
+    } {
+        const { start, end, text } = diagnostic;
+        const range = new vscode.Range(typeConverters.Position.fromLocation(start), typeConverters.Position.fromLocation(end));
+        const converted = new vscode.Diagnostic(range, text, this.getDiagnosticSeverity(diagnostic));
+        converted.source = diagnostic.source || source;
+        if (diagnostic.code) {
+            converted.code = diagnostic.code;
+        }
+        const relatedInformation = diagnostic.relatedInformation;
+        if (relatedInformation) {
+            converted.relatedInformation = coalesce(relatedInformation.map((info: any) => {
+                const span = info.span;
+                if (!span) {
+                    return undefined;
+                }
+                return new vscode.DiagnosticRelatedInformation(typeConverters.Location.fromTextSpan(this.client.toResource(span.file), span), info.message);
+            }));
+        }
+        const tags: vscode.DiagnosticTag[] = [];
+        if (diagnostic.reportsUnnecessary) {
+            tags.push(vscode.DiagnosticTag.Unnecessary);
+        }
+        if (diagnostic.reportsDeprecated) {
+            tags.push(vscode.DiagnosticTag.Deprecated);
+        }
+        converted.tags = tags.length ? tags : undefined;
+        const resultConverted = converted as vscode.Diagnostic & {
+            reportUnnecessary: any;
+            reportDeprecated: any;
+        };
+        resultConverted.reportUnnecessary = diagnostic.reportsUnnecessary;
+        resultConverted.reportDeprecated = diagnostic.reportsDeprecated;
+        return resultConverted;
+    }
+    private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): vscode.DiagnosticSeverity {
+        if (this.reportStyleCheckAsWarnings
+            && this.isStyleCheckDiagnostic(diagnostic.code)
+            && diagnostic.category === PConst.DiagnosticCategory.error) {
+            return vscode.DiagnosticSeverity.Warning;
+        }
+        switch (diagnostic.category) {
+            case PConst.DiagnosticCategory.error:
+                return vscode.DiagnosticSeverity.Error;
+            case PConst.DiagnosticCategory.warning:
+                return vscode.DiagnosticSeverity.Warning;
+            case PConst.DiagnosticCategory.suggestion:
+                return vscode.DiagnosticSeverity.Hint;
+            default:
+                return vscode.DiagnosticSeverity.Error;
+        }
+    }
+    private isStyleCheckDiagnostic(code: number | undefined): boolean {
+        return typeof code === 'number' && styleCheckDiagnostics.has(code);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/typescriptService.ts b/extensions/typescript-language-features/Source/typescriptService.ts
index 33d89e1df8d5b..10f1cf026824a 100644
--- a/extensions/typescript-language-features/Source/typescriptService.ts
+++ b/extensions/typescript-language-features/Source/typescriptService.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as Proto from './tsServer/protocol/protocol';
 import BufferSyncSupport from './tsServer/bufferSyncSupport';
@@ -12,198 +11,312 @@ import { API } from './tsServer/api';
 import { TypeScriptServiceConfiguration } from './configuration/configuration';
 import { PluginManager } from './tsServer/plugins';
 import { TelemetryReporter } from './logging/telemetry';
-
 export enum ServerType {
-	Syntax = 'syntax',
-	Semantic = 'semantic',
+    Syntax = 'syntax',
+    Semantic = 'semantic'
 }
-
 export namespace ServerResponse {
-
-	export class Cancelled {
-		public readonly type = 'cancelled';
-
-		constructor(
-			public readonly reason: string
-		) { }
-	}
-
-	export const NoContent = { type: 'noContent' } as const;
-
-	export const NoServer = { type: 'noServer' } as const;
-
-	export type Response = T | Cancelled | typeof NoContent | typeof NoServer;
+    export class Cancelled {
+        public readonly type = 'cancelled';
+        constructor(public readonly reason: string) { }
+    }
+    export const NoContent = { type: 'noContent' } as const;
+    export const NoServer = { type: 'noServer' } as const;
+    export type Response = T | Cancelled | typeof NoContent | typeof NoServer;
 }
-
 interface StandardTsServerRequests {
-	'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse];
-	'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse];
-	'completionInfo': [Proto.CompletionsRequestArgs, Proto.CompletionInfoResponse];
-	'completions': [Proto.CompletionsRequestArgs, Proto.CompletionsResponse];
-	'configure': [Proto.ConfigureRequestArguments, Proto.ConfigureResponse];
-	'definition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse];
-	'definitionAndBoundSpan': [Proto.FileLocationRequestArgs, Proto.DefinitionInfoAndBoundSpanResponse];
-	'docCommentTemplate': [Proto.FileLocationRequestArgs, Proto.DocCommandTemplateResponse];
-	'documentHighlights': [Proto.DocumentHighlightsRequestArgs, Proto.DocumentHighlightsResponse];
-	'format': [Proto.FormatRequestArgs, Proto.FormatResponse];
-	'formatonkey': [Proto.FormatOnKeyRequestArgs, Proto.FormatResponse];
-	'getApplicableRefactors': [Proto.GetApplicableRefactorsRequestArgs, Proto.GetApplicableRefactorsResponse];
-	'getCodeFixes': [Proto.CodeFixRequestArgs, Proto.CodeFixResponse];
-	'getCombinedCodeFix': [Proto.GetCombinedCodeFixRequestArgs, Proto.GetCombinedCodeFixResponse];
-	'getEditsForFileRename': [Proto.GetEditsForFileRenameRequestArgs, Proto.GetEditsForFileRenameResponse];
-	'getEditsForRefactor': [Proto.GetEditsForRefactorRequestArgs, Proto.GetEditsForRefactorResponse];
-	'getOutliningSpans': [Proto.FileRequestArgs, Proto.OutliningSpansResponse];
-	'getSupportedCodeFixes': [null, Proto.GetSupportedCodeFixesResponse];
-	'implementation': [Proto.FileLocationRequestArgs, Proto.ImplementationResponse];
-	'jsxClosingTag': [Proto.JsxClosingTagRequestArgs, Proto.JsxClosingTagResponse];
-	'navto': [Proto.NavtoRequestArgs, Proto.NavtoResponse];
-	'navtree': [Proto.FileRequestArgs, Proto.NavTreeResponse];
-	'organizeImports': [Proto.OrganizeImportsRequestArgs, Proto.OrganizeImportsResponse];
-	'projectInfo': [Proto.ProjectInfoRequestArgs, Proto.ProjectInfoResponse];
-	'quickinfo': [Proto.FileLocationRequestArgs, Proto.QuickInfoResponse];
-	'references': [Proto.FileLocationRequestArgs, Proto.ReferencesResponse];
-	'rename': [Proto.RenameRequestArgs, Proto.RenameResponse];
-	'selectionRange': [Proto.SelectionRangeRequestArgs, Proto.SelectionRangeResponse];
-	'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse];
-	'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse];
-	'updateOpen': [Proto.UpdateOpenRequestArgs, Proto.Response];
-	'prepareCallHierarchy': [Proto.FileLocationRequestArgs, Proto.PrepareCallHierarchyResponse];
-	'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse];
-	'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse];
-	'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse];
-	'provideInlayHints': [Proto.InlayHintsRequestArgs, Proto.InlayHintsResponse];
-	'encodedSemanticClassifications-full': [Proto.EncodedSemanticClassificationsRequestArgs, Proto.EncodedSemanticClassificationsResponse];
-	'findSourceDefinition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse];
-	'getMoveToRefactoringFileSuggestions': [Proto.GetMoveToRefactoringFileSuggestionsRequestArgs, Proto.GetMoveToRefactoringFileSuggestions];
-	'linkedEditingRange': [Proto.FileLocationRequestArgs, Proto.LinkedEditingRangeResponse];
-	'mapCode': [Proto.MapCodeRequestArgs, Proto.MapCodeResponse];
-	// @ts-expect-error until ts5.7
-	'copilotRelated': [Proto.FileRequestArgs, Proto.CopilotRelatedResponse];
-	'getPasteEdits': [Proto.GetPasteEditsRequestArgs, Proto.GetPasteEditsResponse];
-	'preparePasteEdits': [Proto.PreparePasteEditsRequestArgs, Proto.PreparePasteEditsResponse];
+    'applyCodeActionCommand': [
+        Proto.ApplyCodeActionCommandRequestArgs,
+        Proto.ApplyCodeActionCommandResponse
+    ];
+    'completionEntryDetails': [
+        Proto.CompletionDetailsRequestArgs,
+        Proto.CompletionDetailsResponse
+    ];
+    'completionInfo': [
+        Proto.CompletionsRequestArgs,
+        Proto.CompletionInfoResponse
+    ];
+    'completions': [
+        Proto.CompletionsRequestArgs,
+        Proto.CompletionsResponse
+    ];
+    'configure': [
+        Proto.ConfigureRequestArguments,
+        Proto.ConfigureResponse
+    ];
+    'definition': [
+        Proto.FileLocationRequestArgs,
+        Proto.DefinitionResponse
+    ];
+    'definitionAndBoundSpan': [
+        Proto.FileLocationRequestArgs,
+        Proto.DefinitionInfoAndBoundSpanResponse
+    ];
+    'docCommentTemplate': [
+        Proto.FileLocationRequestArgs,
+        Proto.DocCommandTemplateResponse
+    ];
+    'documentHighlights': [
+        Proto.DocumentHighlightsRequestArgs,
+        Proto.DocumentHighlightsResponse
+    ];
+    'format': [
+        Proto.FormatRequestArgs,
+        Proto.FormatResponse
+    ];
+    'formatonkey': [
+        Proto.FormatOnKeyRequestArgs,
+        Proto.FormatResponse
+    ];
+    'getApplicableRefactors': [
+        Proto.GetApplicableRefactorsRequestArgs,
+        Proto.GetApplicableRefactorsResponse
+    ];
+    'getCodeFixes': [
+        Proto.CodeFixRequestArgs,
+        Proto.CodeFixResponse
+    ];
+    'getCombinedCodeFix': [
+        Proto.GetCombinedCodeFixRequestArgs,
+        Proto.GetCombinedCodeFixResponse
+    ];
+    'getEditsForFileRename': [
+        Proto.GetEditsForFileRenameRequestArgs,
+        Proto.GetEditsForFileRenameResponse
+    ];
+    'getEditsForRefactor': [
+        Proto.GetEditsForRefactorRequestArgs,
+        Proto.GetEditsForRefactorResponse
+    ];
+    'getOutliningSpans': [
+        Proto.FileRequestArgs,
+        Proto.OutliningSpansResponse
+    ];
+    'getSupportedCodeFixes': [
+        null,
+        Proto.GetSupportedCodeFixesResponse
+    ];
+    'implementation': [
+        Proto.FileLocationRequestArgs,
+        Proto.ImplementationResponse
+    ];
+    'jsxClosingTag': [
+        Proto.JsxClosingTagRequestArgs,
+        Proto.JsxClosingTagResponse
+    ];
+    'navto': [
+        Proto.NavtoRequestArgs,
+        Proto.NavtoResponse
+    ];
+    'navtree': [
+        Proto.FileRequestArgs,
+        Proto.NavTreeResponse
+    ];
+    'organizeImports': [
+        Proto.OrganizeImportsRequestArgs,
+        Proto.OrganizeImportsResponse
+    ];
+    'projectInfo': [
+        Proto.ProjectInfoRequestArgs,
+        Proto.ProjectInfoResponse
+    ];
+    'quickinfo': [
+        Proto.FileLocationRequestArgs,
+        Proto.QuickInfoResponse
+    ];
+    'references': [
+        Proto.FileLocationRequestArgs,
+        Proto.ReferencesResponse
+    ];
+    'rename': [
+        Proto.RenameRequestArgs,
+        Proto.RenameResponse
+    ];
+    'selectionRange': [
+        Proto.SelectionRangeRequestArgs,
+        Proto.SelectionRangeResponse
+    ];
+    'signatureHelp': [
+        Proto.SignatureHelpRequestArgs,
+        Proto.SignatureHelpResponse
+    ];
+    'typeDefinition': [
+        Proto.FileLocationRequestArgs,
+        Proto.TypeDefinitionResponse
+    ];
+    'updateOpen': [
+        Proto.UpdateOpenRequestArgs,
+        Proto.Response
+    ];
+    'prepareCallHierarchy': [
+        Proto.FileLocationRequestArgs,
+        Proto.PrepareCallHierarchyResponse
+    ];
+    'provideCallHierarchyIncomingCalls': [
+        Proto.FileLocationRequestArgs,
+        Proto.ProvideCallHierarchyIncomingCallsResponse
+    ];
+    'provideCallHierarchyOutgoingCalls': [
+        Proto.FileLocationRequestArgs,
+        Proto.ProvideCallHierarchyOutgoingCallsResponse
+    ];
+    'fileReferences': [
+        Proto.FileRequestArgs,
+        Proto.FileReferencesResponse
+    ];
+    'provideInlayHints': [
+        Proto.InlayHintsRequestArgs,
+        Proto.InlayHintsResponse
+    ];
+    'encodedSemanticClassifications-full': [
+        Proto.EncodedSemanticClassificationsRequestArgs,
+        Proto.EncodedSemanticClassificationsResponse
+    ];
+    'findSourceDefinition': [
+        Proto.FileLocationRequestArgs,
+        Proto.DefinitionResponse
+    ];
+    'getMoveToRefactoringFileSuggestions': [
+        Proto.GetMoveToRefactoringFileSuggestionsRequestArgs,
+        Proto.GetMoveToRefactoringFileSuggestions
+    ];
+    'linkedEditingRange': [
+        Proto.FileLocationRequestArgs,
+        Proto.LinkedEditingRangeResponse
+    ];
+    'mapCode': [
+        Proto.MapCodeRequestArgs,
+        Proto.MapCodeResponse
+    ];
+    // @ts-expect-error until ts5.7
+    'copilotRelated': [
+        Proto.FileRequestArgs,
+        Proto.CopilotRelatedResponse
+    ];
+    'getPasteEdits': [
+        Proto.GetPasteEditsRequestArgs,
+        Proto.GetPasteEditsResponse
+    ];
+    'preparePasteEdits': [
+        Proto.PreparePasteEditsRequestArgs,
+        Proto.PreparePasteEditsResponse
+    ];
 }
-
 interface NoResponseTsServerRequests {
-	'open': [Proto.OpenRequestArgs, null];
-	'close': [Proto.FileRequestArgs, null];
-	'change': [Proto.ChangeRequestArgs, null];
-	'compilerOptionsForInferredProjects': [Proto.SetCompilerOptionsForInferredProjectsArgs, null];
-	'reloadProjects': [null, null];
-	'configurePlugin': [Proto.ConfigurePluginRequest, Proto.ConfigurePluginResponse];
-	'watchChange': [Proto.Request, null];
+    'open': [
+        Proto.OpenRequestArgs,
+        null
+    ];
+    'close': [
+        Proto.FileRequestArgs,
+        null
+    ];
+    'change': [
+        Proto.ChangeRequestArgs,
+        null
+    ];
+    'compilerOptionsForInferredProjects': [
+        Proto.SetCompilerOptionsForInferredProjectsArgs,
+        null
+    ];
+    'reloadProjects': [
+        null,
+        null
+    ];
+    'configurePlugin': [
+        Proto.ConfigurePluginRequest,
+        Proto.ConfigurePluginResponse
+    ];
+    'watchChange': [
+        Proto.Request,
+        null
+    ];
 }
-
 interface AsyncTsServerRequests {
-	'geterr': [Proto.GeterrRequestArgs, Proto.Response];
-	'geterrForProject': [Proto.GeterrForProjectRequestArgs, Proto.Response];
+    'geterr': [
+        Proto.GeterrRequestArgs,
+        Proto.Response
+    ];
+    'geterrForProject': [
+        Proto.GeterrForProjectRequestArgs,
+        Proto.Response
+    ];
 }
-
 export type TypeScriptRequests = StandardTsServerRequests & NoResponseTsServerRequests & AsyncTsServerRequests;
-
 export type ExecConfig = {
-	readonly lowPriority?: boolean;
-	readonly nonRecoverable?: boolean;
-	readonly cancelOnResourceChange?: vscode.Uri;
-	readonly executionTarget?: ExecutionTarget;
+    readonly lowPriority?: boolean;
+    readonly nonRecoverable?: boolean;
+    readonly cancelOnResourceChange?: vscode.Uri;
+    readonly executionTarget?: ExecutionTarget;
 };
-
 export enum ClientCapability {
-	/**
-	 * Basic syntax server. All clients should support this.
-	 */
-	Syntax,
-
-	/**
-	 * Advanced syntax server that can provide single file IntelliSense.
-	 */
-	EnhancedSyntax,
-
-	/**
-	 * Complete, multi-file semantic server
-	 */
-	Semantic,
+    /**
+     * Basic syntax server. All clients should support this.
+     */
+    Syntax,
+    /**
+     * Advanced syntax server that can provide single file IntelliSense.
+     */
+    EnhancedSyntax,
+    /**
+     * Complete, multi-file semantic server
+     */
+    Semantic
 }
-
 export class ClientCapabilities {
-	private readonly capabilities: ReadonlySet;
-
-	constructor(...capabilities: ClientCapability[]) {
-		this.capabilities = new Set(capabilities);
-	}
-
-	public has(capability: ClientCapability): boolean {
-		return this.capabilities.has(capability);
-	}
+    private readonly capabilities: ReadonlySet;
+    constructor(...capabilities: ClientCapability[]) {
+        this.capabilities = new Set(capabilities);
+    }
+    public has(capability: ClientCapability): boolean {
+        return this.capabilities.has(capability);
+    }
 }
-
 export interface ITypeScriptServiceClient {
-
-	/**
-	 * Convert a (VS Code) resource to a path that TypeScript server understands.
-	 */
-	toTsFilePath(resource: vscode.Uri): string | undefined;
-
-	/**
-	 * Convert a path to a resource.
-	 */
-	toResource(filepath: string): vscode.Uri;
-
-	/**
-	 * Tries to ensure that a vscode document is open on the TS server.
-	 *
-	 * @return The normalized path or `undefined` if the document is not open on the server.
-	 */
-	toOpenTsFilePath(document: vscode.TextDocument, options?: {
-		suppressAlertOnFailure?: boolean;
-	}): string | undefined;
-
-	/**
-	 * Checks if `resource` has a given capability.
-	 */
-	hasCapabilityForResource(resource: vscode.Uri, capability: ClientCapability): boolean;
-
-	getWorkspaceRootForResource(resource: vscode.Uri): vscode.Uri | undefined;
-
-	readonly onTsServerStarted: vscode.Event<{ version: TypeScriptVersion; usedApiVersion: API }>;
-	readonly onProjectLanguageServiceStateChanged: vscode.Event;
-	readonly onDidBeginInstallTypings: vscode.Event;
-	readonly onDidEndInstallTypings: vscode.Event;
-	readonly onTypesInstallerInitializationFailed: vscode.Event;
-
-	readonly capabilities: ClientCapabilities;
-	readonly onDidChangeCapabilities: vscode.Event;
-
-	onReady(f: () => void): Promise;
-
-	showVersionPicker(): void;
-
-	readonly apiVersion: API;
-
-	readonly pluginManager: PluginManager;
-	readonly configuration: TypeScriptServiceConfiguration;
-	readonly bufferSyncSupport: BufferSyncSupport;
-	readonly telemetryReporter: TelemetryReporter;
-
-	execute(
-		command: K,
-		args: StandardTsServerRequests[K][0],
-		token: vscode.CancellationToken,
-		config?: ExecConfig
-	): Promise>;
-
-	executeWithoutWaitingForResponse(
-		command: K,
-		args: NoResponseTsServerRequests[K][0]
-	): void;
-
-	executeAsync(
-		command: K,
-		args: AsyncTsServerRequests[K][0],
-		token: vscode.CancellationToken
-	): Promise>;
-
-	/**
-	 * Cancel on going geterr requests and re-queue them after `f` has been evaluated.
-	 */
-	interruptGetErr(f: () => R): R;
+    /**
+     * Convert a (VS Code) resource to a path that TypeScript server understands.
+     */
+    toTsFilePath(resource: vscode.Uri): string | undefined;
+    /**
+     * Convert a path to a resource.
+     */
+    toResource(filepath: string): vscode.Uri;
+    /**
+     * Tries to ensure that a vscode document is open on the TS server.
+     *
+     * @return The normalized path or `undefined` if the document is not open on the server.
+     */
+    toOpenTsFilePath(document: vscode.TextDocument, options?: {
+        suppressAlertOnFailure?: boolean;
+    }): string | undefined;
+    /**
+     * Checks if `resource` has a given capability.
+     */
+    hasCapabilityForResource(resource: vscode.Uri, capability: ClientCapability): boolean;
+    getWorkspaceRootForResource(resource: vscode.Uri): vscode.Uri | undefined;
+    readonly onTsServerStarted: vscode.Event<{
+        version: TypeScriptVersion;
+        usedApiVersion: API;
+    }>;
+    readonly onProjectLanguageServiceStateChanged: vscode.Event;
+    readonly onDidBeginInstallTypings: vscode.Event;
+    readonly onDidEndInstallTypings: vscode.Event;
+    readonly onTypesInstallerInitializationFailed: vscode.Event;
+    readonly capabilities: ClientCapabilities;
+    readonly onDidChangeCapabilities: vscode.Event;
+    onReady(f: () => void): Promise;
+    showVersionPicker(): void;
+    readonly apiVersion: API;
+    readonly pluginManager: PluginManager;
+    readonly configuration: TypeScriptServiceConfiguration;
+    readonly bufferSyncSupport: BufferSyncSupport;
+    readonly telemetryReporter: TelemetryReporter;
+    execute(command: K, args: StandardTsServerRequests[K][0], token: vscode.CancellationToken, config?: ExecConfig): Promise>;
+    executeWithoutWaitingForResponse(command: K, args: NoResponseTsServerRequests[K][0]): void;
+    executeAsync(command: K, args: AsyncTsServerRequests[K][0], token: vscode.CancellationToken): Promise>;
+    /**
+     * Cancel on going geterr requests and re-queue them after `f` has been evaluated.
+     */
+    interruptGetErr(f: () => R): R;
 }
diff --git a/extensions/typescript-language-features/Source/typescriptServiceClient.ts b/extensions/typescript-language-features/Source/typescriptServiceClient.ts
index b5fdb185101e9..7e43990d5b9ac 100644
--- a/extensions/typescript-language-features/Source/typescriptServiceClient.ts
+++ b/extensions/typescript-language-features/Source/typescriptServiceClient.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { homedir } from 'os';
 import * as path from 'path';
 import * as vscode from 'vscode';
@@ -33,1264 +32,1043 @@ import { ClientCapabilities, ClientCapability, ExecConfig, ITypeScriptServiceCli
 import { Disposable, DisposableStore, disposeAll } from './utils/dispose';
 import { hash } from './utils/hash';
 import { isWeb, isWebAndHasSharedArrayBuffers } from './utils/platform';
-
-
 export interface TsDiagnostics {
-	readonly kind: DiagnosticKind;
-	readonly resource: vscode.Uri;
-	readonly diagnostics: Proto.Diagnostic[];
-	readonly spans?: Proto.TextSpan[];
+    readonly kind: DiagnosticKind;
+    readonly resource: vscode.Uri;
+    readonly diagnostics: Proto.Diagnostic[];
+    readonly spans?: Proto.TextSpan[];
 }
-
 interface ToCancelOnResourceChanged {
-	readonly resource: vscode.Uri;
-	cancel(): void;
+    readonly resource: vscode.Uri;
+    cancel(): void;
 }
-
 namespace ServerState {
-	export const enum Type {
-		None,
-		Running,
-		Errored
-	}
-
-	export const None = { type: Type.None } as const;
-
-	export class Running {
-		readonly type = Type.Running;
-
-		constructor(
-			public readonly server: ITypeScriptServer,
-
-			/**
-			 * API version obtained from the version picker after checking the corresponding path exists.
-			 */
-			public readonly apiVersion: API,
-
-			/**
-			 * Version reported by currently-running tsserver.
-			 */
-			public tsserverVersion: string | undefined,
-			public languageServiceEnabled: boolean,
-		) { }
-
-		public readonly toCancelOnResourceChange = new Set();
-
-		updateTsserverVersion(tsserverVersion: string) {
-			this.tsserverVersion = tsserverVersion;
-		}
-
-		updateLanguageServiceEnabled(enabled: boolean) {
-			this.languageServiceEnabled = enabled;
-		}
-	}
-
-	export class Errored {
-		readonly type = Type.Errored;
-		constructor(
-			public readonly error: Error,
-			public readonly tsServerLog: TsServerLog | undefined,
-		) { }
-	}
-
-	export type State = typeof None | Running | Errored;
+    export const enum Type {
+        None,
+        Running,
+        Errored
+    }
+    export const None = { type: Type.None } as const;
+    export class Running {
+        readonly type = Type.Running;
+        constructor(public readonly server: ITypeScriptServer, 
+        /**
+         * API version obtained from the version picker after checking the corresponding path exists.
+         */
+        public readonly apiVersion: API, 
+        /**
+         * Version reported by currently-running tsserver.
+         */
+        public tsserverVersion: string | undefined, public languageServiceEnabled: boolean) { }
+        public readonly toCancelOnResourceChange = new Set();
+        updateTsserverVersion(tsserverVersion: string) {
+            this.tsserverVersion = tsserverVersion;
+        }
+        updateLanguageServiceEnabled(enabled: boolean) {
+            this.languageServiceEnabled = enabled;
+        }
+    }
+    export class Errored {
+        readonly type = Type.Errored;
+        constructor(public readonly error: Error, public readonly tsServerLog: TsServerLog | undefined) { }
+    }
+    export type State = typeof None | Running | Errored;
 }
-
 export const emptyAuthority = 'ts-nul-authority';
-
 export const inMemoryResourcePrefix = '^';
-
 interface WatchEvent {
-	updated?: Set;
-	created?: Set;
-	deleted?: Set;
+    updated?: Set;
+    created?: Set;
+    deleted?: Set;
 }
-
 export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
-
-
-	private readonly _onReady?: { promise: Promise; resolve: () => void; reject: () => void };
-	private _configuration: TypeScriptServiceConfiguration;
-	private readonly pluginPathsProvider: TypeScriptPluginPathsProvider;
-	private readonly _versionManager: TypeScriptVersionManager;
-	private readonly _nodeVersionManager: NodeVersionManager;
-
-	private readonly logger: Logger;
-	private readonly tracer: Tracer;
-
-	private readonly typescriptServerSpawner: TypeScriptServerSpawner;
-	private serverState: ServerState.State = ServerState.None;
-	private lastStart: number;
-	private numberRestarts: number;
-	private _isPromptingAfterCrash = false;
-	private isRestarting: boolean = false;
-	private hasServerFatallyCrashedTooManyTimes = false;
-	private readonly loadingIndicator = this._register(new ServerInitializingIndicator());
-
-	public readonly telemetryReporter: TelemetryReporter;
-	public readonly bufferSyncSupport: BufferSyncSupport;
-	public readonly diagnosticsManager: DiagnosticsManager;
-	public readonly pluginManager: PluginManager;
-
-	private readonly logDirectoryProvider: ILogDirectoryProvider;
-	private readonly cancellerFactory: OngoingRequestCancellerFactory;
-	private readonly versionProvider: ITypeScriptVersionProvider;
-	private readonly processFactory: TsServerProcessFactory;
-
-	private readonly watches = new Map();
-	private readonly watchEvents = new Map();
-	private watchChangeTimeout: NodeJS.Timeout | undefined;
-
-	constructor(
-		private readonly context: vscode.ExtensionContext,
-		onCaseInsensitiveFileSystem: boolean,
-		services: {
-			pluginManager: PluginManager;
-			logDirectoryProvider: ILogDirectoryProvider;
-			cancellerFactory: OngoingRequestCancellerFactory;
-			versionProvider: ITypeScriptVersionProvider;
-			processFactory: TsServerProcessFactory;
-			serviceConfigurationProvider: ServiceConfigurationProvider;
-			experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
-			logger: Logger;
-		},
-		allModeIds: readonly string[]
-	) {
-		super();
-
-		this.logger = services.logger;
-		this.tracer = new Tracer(this.logger);
-
-		this.pluginManager = services.pluginManager;
-		this.logDirectoryProvider = services.logDirectoryProvider;
-		this.cancellerFactory = services.cancellerFactory;
-		this.versionProvider = services.versionProvider;
-		this.processFactory = services.processFactory;
-
-		this.lastStart = Date.now();
-
-		let resolve: () => void;
-		let reject: () => void;
-		const p = new Promise((res, rej) => {
-			resolve = res;
-			reject = rej;
-		});
-		this._onReady = { promise: p, resolve: resolve!, reject: reject! };
-
-		this.numberRestarts = 0;
-
-		this._configuration = services.serviceConfigurationProvider.loadFromWorkspace();
-		this.versionProvider.updateConfiguration(this._configuration);
-
-		this.pluginPathsProvider = new TypeScriptPluginPathsProvider(this._configuration);
-		this._versionManager = this._register(new TypeScriptVersionManager(this._configuration, this.versionProvider, context.workspaceState));
-		this._register(this._versionManager.onDidPickNewVersion(() => {
-			this.restartTsServer();
-		}));
-
-		this._nodeVersionManager = this._register(new NodeVersionManager(this._configuration, context.workspaceState));
-		this._register(this._nodeVersionManager.onDidPickNewVersion(() => {
-			this.restartTsServer();
-		}));
-
-		this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsensitiveFileSystem);
-		this.onReady(() => { this.bufferSyncSupport.listen(); });
-
-		this.bufferSyncSupport.onDelete(resource => {
-			this.cancelInflightRequestsForResource(resource);
-			this.diagnosticsManager.deleteAllDiagnosticsInFile(resource);
-		}, null, this._disposables);
-
-		this.bufferSyncSupport.onWillChange(resource => {
-			this.cancelInflightRequestsForResource(resource);
-		});
-
-		vscode.workspace.onDidChangeConfiguration(() => {
-			const oldConfiguration = this._configuration;
-			this._configuration = services.serviceConfigurationProvider.loadFromWorkspace();
-
-			this.versionProvider.updateConfiguration(this._configuration);
-			this._versionManager.updateConfiguration(this._configuration);
-			this.pluginPathsProvider.updateConfiguration(this._configuration);
-			this._nodeVersionManager.updateConfiguration(this._configuration);
-
-			if (this.serverState.type === ServerState.Type.Running) {
-				if (!this._configuration.implicitProjectConfiguration.isEqualTo(oldConfiguration.implicitProjectConfiguration)) {
-					this.setCompilerOptionsForInferredProjects(this._configuration);
-				}
-
-				if (!areServiceConfigurationsEqual(this._configuration, oldConfiguration)) {
-					this.restartTsServer();
-				}
-			}
-		}, this, this._disposables);
-
-		this.telemetryReporter = new VSCodeTelemetryReporter(services.experimentTelemetryReporter, () => {
-			if (this.serverState.type === ServerState.Type.Running) {
-				if (this.serverState.tsserverVersion) {
-					return this.serverState.tsserverVersion;
-				}
-			}
-			return this.apiVersion.fullVersionString;
-		});
-
-		this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem);
-		this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
-
-		this._register(this.pluginManager.onDidUpdateConfig(update => {
-			this.configurePlugin(update.pluginId, update.config);
-		}));
-
-		this._register(this.pluginManager.onDidChangePlugins(() => {
-			this.restartTsServer();
-		}));
-	}
-
-	public get capabilities() {
-		if (this._configuration.useSyntaxServer === SyntaxServerConfiguration.Always) {
-			return new ClientCapabilities(
-				ClientCapability.Syntax,
-				ClientCapability.EnhancedSyntax);
-		}
-
-		if (isWeb()) {
-			if (this.isProjectWideIntellisenseOnWebEnabled()) {
-				return new ClientCapabilities(
-					ClientCapability.Syntax,
-					ClientCapability.EnhancedSyntax,
-					ClientCapability.Semantic);
-			} else {
-				return new ClientCapabilities(
-					ClientCapability.Syntax,
-					ClientCapability.EnhancedSyntax);
-			}
-		}
-
-		if (this.apiVersion.gte(API.v400)) {
-			return new ClientCapabilities(
-				ClientCapability.Syntax,
-				ClientCapability.EnhancedSyntax,
-				ClientCapability.Semantic);
-		}
-
-		return new ClientCapabilities(
-			ClientCapability.Syntax,
-			ClientCapability.Semantic);
-	}
-
-	private readonly _onDidChangeCapabilities = this._register(new vscode.EventEmitter());
-	readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
-
-	private isProjectWideIntellisenseOnWebEnabled(): boolean {
-		return isWebAndHasSharedArrayBuffers() && this._configuration.webProjectWideIntellisenseEnabled;
-	}
-
-	private cancelInflightRequestsForResource(resource: vscode.Uri): void {
-		if (this.serverState.type !== ServerState.Type.Running) {
-			return;
-		}
-
-		for (const request of this.serverState.toCancelOnResourceChange) {
-			if (request.resource.toString() === resource.toString()) {
-				request.cancel();
-			}
-		}
-	}
-
-	public get configuration() {
-		return this._configuration;
-	}
-
-	public override dispose() {
-		super.dispose();
-
-		this.bufferSyncSupport.dispose();
-
-		if (this.serverState.type === ServerState.Type.Running) {
-			this.serverState.server.kill();
-		}
-
-		this.loadingIndicator.reset();
-
-		this.resetWatchers();
-	}
-
-	public restartTsServer(fromUserAction = false): void {
-		if (this.serverState.type === ServerState.Type.Running) {
-			this.info('Killing TS Server');
-			this.isRestarting = true;
-			this.serverState.server.kill();
-		}
-
-		if (fromUserAction) {
-			// Reset crash trackers
-			this.hasServerFatallyCrashedTooManyTimes = false;
-			this.numberRestarts = 0;
-			this.lastStart = Date.now();
-		}
-
-		this.serverState = this.startService(true);
-	}
-
-	private readonly _onTsServerStarted = this._register(new vscode.EventEmitter<{ version: TypeScriptVersion; usedApiVersion: API }>());
-	public readonly onTsServerStarted = this._onTsServerStarted.event;
-
-	private readonly _onDiagnosticsReceived = this._register(new vscode.EventEmitter());
-	public readonly onDiagnosticsReceived = this._onDiagnosticsReceived.event;
-
-	private readonly _onConfigDiagnosticsReceived = this._register(new vscode.EventEmitter());
-	public readonly onConfigDiagnosticsReceived = this._onConfigDiagnosticsReceived.event;
-
-	private readonly _onResendModelsRequested = this._register(new vscode.EventEmitter());
-	public readonly onResendModelsRequested = this._onResendModelsRequested.event;
-
-	private readonly _onProjectLanguageServiceStateChanged = this._register(new vscode.EventEmitter());
-	public readonly onProjectLanguageServiceStateChanged = this._onProjectLanguageServiceStateChanged.event;
-
-	private readonly _onDidBeginInstallTypings = this._register(new vscode.EventEmitter());
-	public readonly onDidBeginInstallTypings = this._onDidBeginInstallTypings.event;
-
-	private readonly _onDidEndInstallTypings = this._register(new vscode.EventEmitter());
-	public readonly onDidEndInstallTypings = this._onDidEndInstallTypings.event;
-
-	private readonly _onTypesInstallerInitializationFailed = this._register(new vscode.EventEmitter());
-	public readonly onTypesInstallerInitializationFailed = this._onTypesInstallerInitializationFailed.event;
-
-	private readonly _onSurveyReady = this._register(new vscode.EventEmitter());
-	public readonly onSurveyReady = this._onSurveyReady.event;
-
-	public get apiVersion(): API {
-		if (this.serverState.type === ServerState.Type.Running) {
-			return this.serverState.apiVersion;
-		}
-		return API.defaultVersion;
-	}
-
-	public onReady(f: () => void): Promise {
-		return this._onReady!.promise.then(f);
-	}
-
-	private info(message: string, ...data: any[]): void {
-		this.logger.info(message, ...data);
-	}
-
-	private error(message: string, ...data: any[]): void {
-		this.logger.error(message, ...data);
-	}
-
-	private logTelemetry(eventName: string, properties?: TelemetryProperties) {
-		this.telemetryReporter.logTelemetry(eventName, properties);
-	}
-
-	public ensureServiceStarted() {
-		if (this.serverState.type !== ServerState.Type.Running) {
-			this.startService();
-		}
-	}
-
-	private token: number = 0;
-	private startService(resendModels: boolean = false): ServerState.State {
-		this.info(`Starting TS Server`);
-
-		if (this.isDisposed) {
-			this.info(`Not starting server: disposed`);
-			return ServerState.None;
-		}
-
-		if (this.hasServerFatallyCrashedTooManyTimes) {
-			this.info(`Not starting server: too many crashes`);
-			return ServerState.None;
-		}
-
-		let version = this._versionManager.currentVersion;
-		if (!version.isValid) {
-			vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn't point to a valid tsserver install. Falling back to bundled TypeScript version.", version.path));
-
-			this._versionManager.reset();
-			version = this._versionManager.currentVersion;
-		}
-
-		this.info(`Using tsserver from: ${version.path}`);
-		const nodePath = this._nodeVersionManager.currentVersion;
-		if (nodePath) {
-			this.info(`Using Node installation from ${nodePath} to run TS Server`);
-		}
-
-		this.resetWatchers();
-
-		const apiVersion = version.apiVersion || API.defaultVersion;
-		const mytoken = ++this.token;
-		const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, this.cancellerFactory, {
-			onFatalError: (command, err) => this.fatalError(command, err),
-		});
-		this.serverState = new ServerState.Running(handle, apiVersion, undefined, true);
-		this.lastStart = Date.now();
-
-
-		/* __GDPR__FRAGMENT__
-			"TypeScriptServerEnvCommonProperties" : {
-				"hasGlobalPlugins": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"globalPluginNameHashes": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-			}
-		*/
-		const typeScriptServerEnvCommonProperties = {
-			hasGlobalPlugins: this.pluginManager.plugins.length > 0,
-			globalPluginNameHashes: JSON.stringify(this.pluginManager.plugins.map(plugin => hash(plugin.name))),
-		};
-
-		/* __GDPR__
-			"tsserver.spawned" : {
-				"owner": "mjbvz",
-				"${include}": [
-					"${TypeScriptCommonProperties}",
-					"${TypeScriptServerEnvCommonProperties}"
-				],
-				"localTypeScriptVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
-				"typeScriptVersionSource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-			}
-		*/
-		this.logTelemetry('tsserver.spawned', {
-			...typeScriptServerEnvCommonProperties,
-			localTypeScriptVersion: this.versionProvider.localVersion ? this.versionProvider.localVersion.displayName : '',
-			typeScriptVersionSource: version.source,
-		});
-
-		handle.onError((err: Error) => {
-			if (this.token !== mytoken) {
-				// this is coming from an old process
-				return;
-			}
-
-			if (err) {
-				vscode.window.showErrorMessage(vscode.l10n.t("TypeScript language server exited with error. Error message is: {0}", err.message || err.name));
-			}
-
-			this.serverState = new ServerState.Errored(err, handle.tsServerLog);
-			this.error('TSServer errored with error.', err);
-			if (handle.tsServerLog?.type === 'file') {
-				this.error(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`);
-			}
-
-			/* __GDPR__
-				"tsserver.error" : {
-					"owner": "mjbvz",
-					"${include}": [
-						"${TypeScriptCommonProperties}",
-						"${TypeScriptServerEnvCommonProperties}"
-					]
-				}
-			*/
-			this.logTelemetry('tsserver.error', {
-				...typeScriptServerEnvCommonProperties
-			});
-			this.serviceExited(false, apiVersion);
-		});
-
-		handle.onExit((data: TypeScriptServerExitEvent) => {
-			const { code, signal } = data;
-			this.error(`TSServer exited. Code: ${code}. Signal: ${signal}`);
-
-			// In practice, the exit code is an integer with no ties to any identity,
-			// so it can be classified as SystemMetaData, rather than CallstackOrException.
-			/* __GDPR__
-				"tsserver.exitWithCode" : {
-					"owner": "mjbvz",
-					"${include}": [
-						"${TypeScriptCommonProperties}",
-						"${TypeScriptServerEnvCommonProperties}"
-					],
-					"code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
-					"signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
-				}
-			*/
-			this.logTelemetry('tsserver.exitWithCode', {
-				...typeScriptServerEnvCommonProperties,
-				code: code ?? undefined,
-				signal: signal ?? undefined,
-			});
-
-			if (this.token !== mytoken) {
-				// this is coming from an old process
-				return;
-			}
-
-			if (handle.tsServerLog?.type === 'file') {
-				this.info(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`);
-			}
-			this.serviceExited(!this.isRestarting, apiVersion);
-			this.isRestarting = false;
-		});
-
-		handle.onEvent(event => this.dispatchEvent(event));
-
-		this.serviceStarted(resendModels);
-
-		this._onReady!.resolve();
-		this._onTsServerStarted.fire({ version: version, usedApiVersion: apiVersion });
-		this._onDidChangeCapabilities.fire();
-		return this.serverState;
-	}
-
-	private resetWatchers() {
-		clearTimeout(this.watchChangeTimeout);
-		disposeAll(Array.from(this.watches.values()));
-	}
-
-	public async showVersionPicker(): Promise {
-		this._versionManager.promptUserForVersion();
-	}
-
-	public async openTsServerLogFile(): Promise {
-		if (this._configuration.tsServerLogLevel === TsServerLogLevel.Off) {
-			vscode.window.showErrorMessage(
-				vscode.l10n.t("TS Server logging is off. Please set 'typescript.tsserver.log' and restart the TS server to enable logging"),
-				{
-					title: vscode.l10n.t("Enable logging and restart TS server"),
-				})
-				.then(selection => {
-					if (selection) {
-						return vscode.workspace.getConfiguration().update('typescript.tsserver.log', 'verbose', true).then(() => {
-							this.restartTsServer();
-						});
-					}
-					return undefined;
-				});
-			return false;
-		}
-
-		if (this.serverState.type !== ServerState.Type.Running || !this.serverState.server.tsServerLog) {
-			vscode.window.showWarningMessage(vscode.l10n.t("TS Server has not started logging."));
-			return false;
-		}
-
-		switch (this.serverState.server.tsServerLog.type) {
-			case 'output': {
-				this.serverState.server.tsServerLog.output.show();
-				return true;
-			}
-			case 'file': {
-				try {
-					const doc = await vscode.workspace.openTextDocument(this.serverState.server.tsServerLog.uri);
-					await vscode.window.showTextDocument(doc);
-					return true;
-				} catch {
-					// noop
-				}
-
-				try {
-					await vscode.commands.executeCommand('revealFileInOS', this.serverState.server.tsServerLog.uri);
-					return true;
-				} catch {
-					vscode.window.showWarningMessage(vscode.l10n.t("Could not open TS Server log file"));
-					return false;
-				}
-			}
-		}
-	}
-
-	private serviceStarted(resendModels: boolean): void {
-		this.bufferSyncSupport.reset();
-
-		const watchOptions = this.apiVersion.gte(API.v380)
-			? this.configuration.watchOptions
-			: undefined;
-
-		const configureOptions: Proto.ConfigureRequestArguments = {
-			hostInfo: 'vscode',
-			preferences: {
-				providePrefixAndSuffixTextForRename: true,
-				allowRenameOfImportPath: true,
-				includePackageJsonAutoImports: this._configuration.includePackageJsonAutoImports,
-				excludeLibrarySymbolsInNavTo: this._configuration.workspaceSymbolsExcludeLibrarySymbols,
-			},
-			watchOptions
-		};
-		this.executeWithoutWaitingForResponse('configure', configureOptions);
-		this.setCompilerOptionsForInferredProjects(this._configuration);
-		if (resendModels) {
-			this._onResendModelsRequested.fire();
-			this.bufferSyncSupport.reinitialize();
-			this.bufferSyncSupport.requestAllDiagnostics();
-		}
-
-		// Reconfigure any plugins
-		for (const [pluginName, config] of this.pluginManager.configurations()) {
-			this.configurePlugin(pluginName, config);
-		}
-	}
-
-	private setCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): void {
-		const args: Proto.SetCompilerOptionsForInferredProjectsArgs = {
-			options: this.getCompilerOptionsForInferredProjects(configuration)
-		};
-		this.executeWithoutWaitingForResponse('compilerOptionsForInferredProjects', args);
-	}
-
-	private getCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): Proto.ExternalProjectCompilerOptions {
-		return {
-			...inferredProjectCompilerOptions(this.apiVersion, ProjectType.TypeScript, configuration),
-			allowJs: true,
-			allowSyntheticDefaultImports: true,
-			allowNonTsExtensions: true,
-			resolveJsonModule: true,
-		};
-	}
-
-	private serviceExited(restart: boolean, tsVersion: API): void {
-		this.resetWatchers();
-		this.loadingIndicator.reset();
-
-		this.serverState = ServerState.None;
-
-		if (restart) {
-			const diff = Date.now() - this.lastStart;
-			this.numberRestarts++;
-			let startService = true;
-
-			const pluginExtensionList = this.pluginManager.plugins.map(plugin => plugin.extension.id).join(', ');
-			const reportIssueItem: vscode.MessageItem = {
-				title: vscode.l10n.t("Report Issue"),
-			};
-			let prompt: Thenable | undefined = undefined;
-
-			if (this.numberRestarts > 5) {
-				this.numberRestarts = 0;
-				if (diff < 10 * 1000 /* 10 seconds */) {
-					this.lastStart = Date.now();
-					startService = false;
-					this.hasServerFatallyCrashedTooManyTimes = true;
-					if (this.pluginManager.plugins.length) {
-						prompt = vscode.window.showErrorMessage(
-							vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList));
-					} else {
-						prompt = vscode.window.showErrorMessage(
-							vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted."),
-							reportIssueItem);
-					}
-
-					/* __GDPR__
-						"serviceExited" : {
-							"owner": "mjbvz",
-							"${include}": [
-								"${TypeScriptCommonProperties}"
-							]
-						}
-					*/
-					this.logTelemetry('serviceExited');
-				} else if (diff < 60 * 1000 * 5 /* 5 Minutes */) {
-					this.lastStart = Date.now();
-					if (!this._isPromptingAfterCrash) {
-						if (this.pluginManager.plugins.length) {
-							prompt = vscode.window.showWarningMessage(
-								vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList));
-						} else {
-							prompt = vscode.window.showWarningMessage(
-								vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes."),
-								reportIssueItem);
-						}
-					}
-				}
-			} else if (['vscode-insiders', 'code-oss'].includes(vscode.env.uriScheme)) {
-				// Prompt after a single restart
-				this.numberRestarts = 0;
-				if (!this._isPromptingAfterCrash) {
-					if (this.pluginManager.plugins.length) {
-						prompt = vscode.window.showWarningMessage(
-							vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList));
-					} else {
-						prompt = vscode.window.showWarningMessage(
-							vscode.l10n.t("The JS/TS language service crashed."),
-							reportIssueItem);
-					}
-				}
-			}
-
-			if (prompt) {
-				this._isPromptingAfterCrash = true;
-			}
-
-			prompt?.then(async item => {
-				this._isPromptingAfterCrash = false;
-
-				if (item === reportIssueItem) {
-
-					const minModernTsVersion = this.versionProvider.bundledVersion.apiVersion;
-
-					// Don't allow reporting issues using the PnP patched version of TS Server
-					if (tsVersion.isYarnPnp()) {
-						const reportIssue: vscode.MessageItem = {
-							title: vscode.l10n.t("Report issue against Yarn PnP"),
-						};
-						const response = await vscode.window.showWarningMessage(
-							vscode.l10n.t("Please report an issue against Yarn PnP"),
-							{
-								modal: true,
-								detail: vscode.l10n.t("The workspace is using a version of the TypeScript Server that has been patched by Yarn PnP. This patching is a common source of bugs."),
-							},
-							reportIssue);
-
-						if (response === reportIssue) {
-							vscode.env.openExternal(vscode.Uri.parse('https://github.com/yarnpkg/berry/issues'));
-						}
-					}
-					// Don't allow reporting issues with old TS versions
-					else if (
-						minModernTsVersion &&
-						tsVersion.lt(minModernTsVersion)
-					) {
-						vscode.window.showWarningMessage(
-							vscode.l10n.t("Please update your TypeScript version"),
-							{
-								modal: true,
-								detail: vscode.l10n.t(
-									"The workspace is using an old version of TypeScript ({0}).\n\nBefore reporting an issue, please update the workspace to use TypeScript {1} or newer to make sure the bug has not already been fixed.",
-									tsVersion.displayName,
-									minModernTsVersion.displayName),
-							});
-					} else {
-						vscode.env.openExternal(vscode.Uri.parse('https://github.com/microsoft/vscode/wiki/TypeScript-Issues'));
-					}
-				}
-			});
-
-			if (startService) {
-				this.startService(true);
-			}
-		}
-	}
-
-	public toTsFilePath(resource: vscode.Uri): string | undefined {
-		if (fileSchemes.disabledSchemes.has(resource.scheme)) {
-			return undefined;
-		}
-
-		if (resource.scheme === fileSchemes.file && !isWeb()) {
-			return resource.fsPath;
-		}
-
-		return (this.isProjectWideIntellisenseOnWebEnabled() ? '' : inMemoryResourcePrefix)
-			+ '/' + resource.scheme
-			+ '/' + (resource.authority || emptyAuthority)
-			+ (resource.path.startsWith('/') ? resource.path : '/' + resource.path)
-			+ (resource.fragment ? '#' + resource.fragment : '');
-	}
-
-
-	public toOpenTsFilePath(document: vscode.TextDocument, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined {
-		if (!this.bufferSyncSupport.ensureHasBuffer(document.uri)) {
-			if (!options.suppressAlertOnFailure && !fileSchemes.disabledSchemes.has(document.uri.scheme)) {
-				console.error(`Unexpected resource ${document.uri}`);
-			}
-			return undefined;
-		}
-		return this.toTsFilePath(document.uri);
-	}
-
-	public hasCapabilityForResource(resource: vscode.Uri, capability: ClientCapability): boolean {
-		if (!this.capabilities.has(capability)) {
-			return false;
-		}
-
-		switch (capability) {
-			case ClientCapability.Semantic: {
-				return fileSchemes.getSemanticSupportedSchemes().includes(resource.scheme);
-			}
-			case ClientCapability.Syntax:
-			case ClientCapability.EnhancedSyntax: {
-				return true;
-			}
-		}
-	}
-
-	public toResource(filepath: string): vscode.Uri {
-		if (isWeb()) {
-			// On web, the stdlib paths that TS return look like: '/lib.es2015.collection.d.ts'
-			// TODO: Find out what extensionUri is when testing (should be http://localhost:8080/static/sources/extensions/typescript-language-features/)
-			// TODO:  make sure that this code path is getting hit
-			if (filepath.startsWith('/lib.') && filepath.endsWith('.d.ts')) {
-				return vscode.Uri.joinPath(this.context.extensionUri, 'dist', 'browser', 'typescript', filepath.slice(1));
-			}
-			const parts = filepath.match(/^\/([^\/]+)\/([^\/]*)\/(.+)$/);
-			if (parts) {
-				const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
-				return this.bufferSyncSupport.toVsCodeResource(resource);
-			}
-		}
-
-		if (filepath.startsWith(inMemoryResourcePrefix)) {
-			const parts = filepath.match(/^\^\/([^\/]+)\/([^\/]*)\/(.+)$/);
-			if (parts) {
-				const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
-				return this.bufferSyncSupport.toVsCodeResource(resource);
-			}
-		}
-		return this.bufferSyncSupport.toResource(filepath);
-	}
-
-	public getWorkspaceRootForResource(resource: vscode.Uri): vscode.Uri | undefined {
-		const roots = vscode.workspace.workspaceFolders ? Array.from(vscode.workspace.workspaceFolders) : undefined;
-		if (!roots?.length) {
-			return undefined;
-		}
-
-		// For notebook cells, we need to use the notebook document to look up the workspace
-		if (resource.scheme === Schemes.notebookCell) {
-			for (const notebook of vscode.workspace.notebookDocuments) {
-				for (const cell of notebook.getCells()) {
-					if (cell.document.uri.toString() === resource.toString()) {
-						resource = notebook.uri;
-						break;
-					}
-				}
-			}
-		}
-
-		for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) {
-			if (root.uri.scheme === resource.scheme && root.uri.authority === resource.authority) {
-				if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) {
-					return root.uri;
-				}
-			}
-		}
-
-		return vscode.workspace.getWorkspaceFolder(resource)?.uri;
-	}
-
-	public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> {
-		let executions: Array> | undefined> | undefined;
-
-		if (config?.cancelOnResourceChange) {
-			const runningServerState = this.serverState;
-			if (runningServerState.type === ServerState.Type.Running) {
-				const source = new vscode.CancellationTokenSource();
-				token.onCancellationRequested(() => source.cancel());
-
-				const inFlight: ToCancelOnResourceChanged = {
-					resource: config.cancelOnResourceChange,
-					cancel: () => source.cancel(),
-				};
-				runningServerState.toCancelOnResourceChange.add(inFlight);
-
-				executions = this.executeImpl(command, args, {
-					isAsync: false,
-					token: source.token,
-					expectsResult: true,
-					...config,
-				});
-				executions[0]!.finally(() => {
-					runningServerState.toCancelOnResourceChange.delete(inFlight);
-					source.dispose();
-				});
-			}
-		}
-
-		if (!executions) {
-			executions = this.executeImpl(command, args, {
-				isAsync: false,
-				token,
-				expectsResult: true,
-				...config,
-			});
-		}
-
-		if (config?.nonRecoverable) {
-			executions[0]!.catch(err => this.fatalError(command, err));
-		}
-
-		if (command === 'updateOpen') {
-			// If update open has completed, consider that the project has loaded
-			const updateOpenTask = Promise.all(executions).then(() => {
-				this.loadingIndicator.reset();
-			});
-
-			const updateOpenArgs = (args as Proto.UpdateOpenRequestArgs);
-			if (updateOpenArgs.openFiles?.length === 1) {
-				this.loadingIndicator.startedLoadingFile(updateOpenArgs.openFiles[0].file, updateOpenTask);
-			}
-		}
-
-		return executions[0]!;
-	}
-
-	public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: any): void {
-		this.executeImpl(command, args, {
-			isAsync: false,
-			token: undefined,
-			expectsResult: false
-		});
-	}
-
-	public executeAsync(command: keyof TypeScriptRequests, args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise> {
-		return this.executeImpl(command, args, {
-			isAsync: true,
-			token,
-			expectsResult: true
-		})[0]!;
-	}
-
-	private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; requireSemantic?: boolean }): Array> | undefined> {
-		const serverState = this.serverState;
-		if (serverState.type === ServerState.Type.Running) {
-			this.bufferSyncSupport.beforeCommand(command);
-			return serverState.server.executeImpl(command, args, executeInfo);
-		} else {
-			return [Promise.resolve(ServerResponse.NoServer)];
-		}
-	}
-
-	public interruptGetErr(f: () => R): R {
-		return this.bufferSyncSupport.interruptGetErr(f);
-	}
-
-	private fatalError(command: string, error: unknown): void {
-		/* __GDPR__
-			"fatalError" : {
-				"owner": "mjbvz",
-				"${include}": [
-					"${TypeScriptCommonProperties}",
-					"${TypeScriptRequestErrorProperties}"
-				],
-				"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
-			}
-		*/
-		this.logTelemetry('fatalError', { ...(error instanceof TypeScriptServerError ? error.telemetry : { command }) });
-		console.error(`A non-recoverable error occurred while executing tsserver command: ${command}`);
-		if (error instanceof TypeScriptServerError && error.serverErrorText) {
-			console.error(error.serverErrorText);
-		}
-
-		if (this.serverState.type === ServerState.Type.Running) {
-			this.info('Killing TS Server');
-			const logfile = this.serverState.server.tsServerLog;
-			this.serverState.server.kill();
-			if (error instanceof TypeScriptServerError) {
-				this.serverState = new ServerState.Errored(error, logfile);
-			}
-		}
-	}
-
-	private dispatchEvent(event: Proto.Event) {
-		switch (event.event) {
-			case EventName.syntaxDiag:
-			case EventName.semanticDiag:
-			case EventName.suggestionDiag:
-			case EventName.regionSemanticDiag: {
-				// This event also roughly signals that projects have been loaded successfully (since the TS server is synchronous)
-				this.loadingIndicator.reset();
-
-				const diagnosticEvent = event as Proto.DiagnosticEvent;
-				if (diagnosticEvent.body?.diagnostics) {
-					this._onDiagnosticsReceived.fire({
-						kind: getDiagnosticsKind(event),
-						resource: this.toResource(diagnosticEvent.body.file),
-						diagnostics: diagnosticEvent.body.diagnostics,
-						spans: diagnosticEvent.body.spans,
-					});
-				}
-				return;
-			}
-			case EventName.configFileDiag:
-				this._onConfigDiagnosticsReceived.fire(event as Proto.ConfigFileDiagnosticEvent);
-				return;
-
-			case EventName.telemetry: {
-				const body = (event as Proto.TelemetryEvent).body;
-				this.dispatchTelemetryEvent(body);
-				return;
-			}
-			case EventName.projectLanguageServiceState: {
-				const body = (event as Proto.ProjectLanguageServiceStateEvent).body!;
-				if (this.serverState.type === ServerState.Type.Running) {
-					this.serverState.updateLanguageServiceEnabled(body.languageServiceEnabled);
-				}
-				this._onProjectLanguageServiceStateChanged.fire(body);
-				return;
-			}
-			case EventName.projectsUpdatedInBackground: {
-				this.loadingIndicator.reset();
-
-				const body = (event as Proto.ProjectsUpdatedInBackgroundEvent).body;
-				const resources = body.openFiles.map(file => this.toResource(file));
-				this.bufferSyncSupport.getErr(resources);
-				return;
-			}
-			case EventName.beginInstallTypes:
-				this._onDidBeginInstallTypings.fire((event as Proto.BeginInstallTypesEvent).body);
-				return;
-
-			case EventName.endInstallTypes:
-				this._onDidEndInstallTypings.fire((event as Proto.EndInstallTypesEvent).body);
-				return;
-
-			case EventName.typesInstallerInitializationFailed:
-				this._onTypesInstallerInitializationFailed.fire((event as Proto.TypesInstallerInitializationFailedEvent).body);
-				return;
-
-			case EventName.surveyReady:
-				this._onSurveyReady.fire((event as Proto.SurveyReadyEvent).body);
-				return;
-
-			case EventName.projectLoadingStart:
-				this.loadingIndicator.startedLoadingProject((event as Proto.ProjectLoadingStartEvent).body.projectName);
-				return;
-
-			case EventName.projectLoadingFinish:
-				this.loadingIndicator.finishedLoadingProject((event as Proto.ProjectLoadingFinishEvent).body.projectName);
-				return;
-
-			case EventName.createDirectoryWatcher: {
-				const fpath = (event.body as Proto.CreateDirectoryWatcherEventBody).path;
-				if (fpath.startsWith(inMemoryResourcePrefix)) {
-					return;
-				}
-				if (process.platform === 'darwin' && fpath === path.join(homedir(), 'Library')) {
-					// ignore directory watch requests on ~/Library
-					// until microsoft/TypeScript#59831 is resolved
-					return;
-				}
-
-				this.createFileSystemWatcher(
-					(event.body as Proto.CreateDirectoryWatcherEventBody).id,
-					new vscode.RelativePattern(
-						vscode.Uri.file(fpath),
-						(event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*'
-					),
-					(event.body as Proto.CreateDirectoryWatcherEventBody).ignoreUpdate
-				);
-				return;
-			}
-			case EventName.createFileWatcher: {
-				const path = (event.body as Proto.CreateFileWatcherEventBody).path;
-				if (path.startsWith(inMemoryResourcePrefix)) {
-					return;
-				}
-
-				this.createFileSystemWatcher(
-					(event.body as Proto.CreateFileWatcherEventBody).id,
-					new vscode.RelativePattern(
-						vscode.Uri.file(path),
-						'*'
-					)
-				);
-				return;
-			}
-			case EventName.closeFileWatcher:
-				this.closeFileSystemWatcher(event.body.id);
-				return;
-
-			case EventName.requestCompleted: {
-				const diagnosticsDuration = (event.body as Proto.RequestCompletedEventBody).performanceData?.diagnosticsDuration;
-				if (diagnosticsDuration) {
-					this.diagnosticsManager.logDiagnosticsPerformanceTelemetry(
-						diagnosticsDuration.map(fileData => {
-							const resource = this.toResource(fileData.file);
-							return {
-								...fileData,
-								fileLineCount: this.bufferSyncSupport.lineCount(resource),
-							};
-						})
-					);
-				}
-				return;
-			}
-		}
-	}
-
-	private scheduleExecuteWatchChangeRequest() {
-		if (!this.watchChangeTimeout) {
-			this.watchChangeTimeout = setTimeout(() => {
-				this.watchChangeTimeout = undefined;
-				const allEvents = Array.from(this.watchEvents, ([id, event]) => ({
-					id,
-					updated: event.updated && Array.from(event.updated),
-					created: event.created && Array.from(event.created),
-					deleted: event.deleted && Array.from(event.deleted)
-				}));
-				this.watchEvents.clear();
-				this.executeWithoutWaitingForResponse('watchChange', allEvents);
-			}, 100); /* aggregate events over 100ms to reduce client<->server IPC overhead */
-		}
-	}
-
-	private addWatchEvent(id: number, eventType: keyof WatchEvent, path: string) {
-		let event = this.watchEvents.get(id);
-		const removeEvent = (typeOfEventToRemove: keyof WatchEvent) => {
-			if (event?.[typeOfEventToRemove]?.delete(path) && event[typeOfEventToRemove].size === 0) {
-				event[typeOfEventToRemove] = undefined;
-			}
-		};
-		const aggregateEvent = () => {
-			if (!event) {
-				this.watchEvents.set(id, event = {});
-			}
-			(event[eventType] ??= new Set()).add(path);
-		};
-		switch (eventType) {
-			case 'created':
-				removeEvent('deleted');
-				removeEvent('updated');
-				aggregateEvent();
-				break;
-			case 'deleted':
-				removeEvent('created');
-				removeEvent('updated');
-				aggregateEvent();
-				break;
-			case 'updated':
-				if (event?.created?.has(path)) {
-					return;
-				}
-				removeEvent('deleted');
-				aggregateEvent();
-				break;
-		}
-		this.scheduleExecuteWatchChangeRequest();
-	}
-
-	private createFileSystemWatcher(
-		id: number,
-		pattern: vscode.RelativePattern,
-		ignoreChangeEvents?: boolean,
-	) {
-		const disposable = new DisposableStore();
-		const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, undefined, ignoreChangeEvents));
-		disposable.add(watcher.onDidChange(changeFile =>
-			this.addWatchEvent(id, 'updated', changeFile.fsPath)
-		));
-		disposable.add(watcher.onDidCreate(createFile =>
-			this.addWatchEvent(id, 'created', createFile.fsPath)
-		));
-		disposable.add(watcher.onDidDelete(deletedFile =>
-			this.addWatchEvent(id, 'deleted', deletedFile.fsPath)
-		));
-		disposable.add({
-			dispose: () => {
-				this.watchEvents.delete(id);
-				this.watches.delete(id);
-			}
-		});
-
-		if (this.watches.has(id)) {
-			this.closeFileSystemWatcher(id);
-		}
-		this.watches.set(id, disposable);
-	}
-
-	private closeFileSystemWatcher(id: number) {
-		const existing = this.watches.get(id);
-		existing?.dispose();
-	}
-
-	private dispatchTelemetryEvent(telemetryData: Proto.TelemetryEventBody): void {
-		const properties: { [key: string]: string } = Object.create(null);
-		switch (telemetryData.telemetryEventName) {
-			case 'typingsInstalled': {
-				const typingsInstalledPayload: Proto.TypingsInstalledTelemetryEventPayload = (telemetryData.payload as Proto.TypingsInstalledTelemetryEventPayload);
-				properties['installedPackages'] = typingsInstalledPayload.installedPackages;
-
-				if (typeof typingsInstalledPayload.installSuccess === 'boolean') {
-					properties['installSuccess'] = typingsInstalledPayload.installSuccess.toString();
-				}
-				if (typeof typingsInstalledPayload.typingsInstallerVersion === 'string') {
-					properties['typingsInstallerVersion'] = typingsInstalledPayload.typingsInstallerVersion;
-				}
-				break;
-			}
-			default: {
-				const payload = telemetryData.payload;
-				if (payload) {
-					Object.keys(payload).forEach((key) => {
-						try {
-							if (payload.hasOwnProperty(key)) {
-								properties[key] = typeof payload[key] === 'string' ? payload[key] : JSON.stringify(payload[key]);
-							}
-						} catch (e) {
-							// noop
-						}
-					});
-				}
-				break;
-			}
-		}
-
-		// Add plugin data here
-		if (telemetryData.telemetryEventName === 'projectInfo') {
-			if (this.serverState.type === ServerState.Type.Running) {
-				this.serverState.updateTsserverVersion(properties['version']);
-			}
-		}
-
-		/* __GDPR__
-			"typingsInstalled" : {
-				"owner": "mjbvz",
-				"installedPackages" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
-				"installSuccess": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
-				"typingsInstallerVersion": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		// __GDPR__COMMENT__: Other events are defined by TypeScript.
-		this.logTelemetry(telemetryData.telemetryEventName, properties);
-	}
-
-	private configurePlugin(pluginName: string, configuration: {}): any {
-		this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration });
-	}
+    private readonly _onReady?: {
+        promise: Promise;
+        resolve: () => void;
+        reject: () => void;
+    };
+    private _configuration: TypeScriptServiceConfiguration;
+    private readonly pluginPathsProvider: TypeScriptPluginPathsProvider;
+    private readonly _versionManager: TypeScriptVersionManager;
+    private readonly _nodeVersionManager: NodeVersionManager;
+    private readonly logger: Logger;
+    private readonly tracer: Tracer;
+    private readonly typescriptServerSpawner: TypeScriptServerSpawner;
+    private serverState: ServerState.State = ServerState.None;
+    private lastStart: number;
+    private numberRestarts: number;
+    private _isPromptingAfterCrash = false;
+    private isRestarting: boolean = false;
+    private hasServerFatallyCrashedTooManyTimes = false;
+    private readonly loadingIndicator = this._register(new ServerInitializingIndicator());
+    public readonly telemetryReporter: TelemetryReporter;
+    public readonly bufferSyncSupport: BufferSyncSupport;
+    public readonly diagnosticsManager: DiagnosticsManager;
+    public readonly pluginManager: PluginManager;
+    private readonly logDirectoryProvider: ILogDirectoryProvider;
+    private readonly cancellerFactory: OngoingRequestCancellerFactory;
+    private readonly versionProvider: ITypeScriptVersionProvider;
+    private readonly processFactory: TsServerProcessFactory;
+    private readonly watches = new Map();
+    private readonly watchEvents = new Map();
+    private watchChangeTimeout: NodeJS.Timeout | undefined;
+    constructor(private readonly context: vscode.ExtensionContext, onCaseInsensitiveFileSystem: boolean, services: {
+        pluginManager: PluginManager;
+        logDirectoryProvider: ILogDirectoryProvider;
+        cancellerFactory: OngoingRequestCancellerFactory;
+        versionProvider: ITypeScriptVersionProvider;
+        processFactory: TsServerProcessFactory;
+        serviceConfigurationProvider: ServiceConfigurationProvider;
+        experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined;
+        logger: Logger;
+    }, allModeIds: readonly string[]) {
+        super();
+        this.logger = services.logger;
+        this.tracer = new Tracer(this.logger);
+        this.pluginManager = services.pluginManager;
+        this.logDirectoryProvider = services.logDirectoryProvider;
+        this.cancellerFactory = services.cancellerFactory;
+        this.versionProvider = services.versionProvider;
+        this.processFactory = services.processFactory;
+        this.lastStart = Date.now();
+        let resolve: () => void;
+        let reject: () => void;
+        const p = new Promise((res, rej) => {
+            resolve = res;
+            reject = rej;
+        });
+        this._onReady = { promise: p, resolve: resolve!, reject: reject! };
+        this.numberRestarts = 0;
+        this._configuration = services.serviceConfigurationProvider.loadFromWorkspace();
+        this.versionProvider.updateConfiguration(this._configuration);
+        this.pluginPathsProvider = new TypeScriptPluginPathsProvider(this._configuration);
+        this._versionManager = this._register(new TypeScriptVersionManager(this._configuration, this.versionProvider, context.workspaceState));
+        this._register(this._versionManager.onDidPickNewVersion(() => {
+            this.restartTsServer();
+        }));
+        this._nodeVersionManager = this._register(new NodeVersionManager(this._configuration, context.workspaceState));
+        this._register(this._nodeVersionManager.onDidPickNewVersion(() => {
+            this.restartTsServer();
+        }));
+        this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsensitiveFileSystem);
+        this.onReady(() => { this.bufferSyncSupport.listen(); });
+        this.bufferSyncSupport.onDelete(resource => {
+            this.cancelInflightRequestsForResource(resource);
+            this.diagnosticsManager.deleteAllDiagnosticsInFile(resource);
+        }, null, this._disposables);
+        this.bufferSyncSupport.onWillChange(resource => {
+            this.cancelInflightRequestsForResource(resource);
+        });
+        vscode.workspace.onDidChangeConfiguration(() => {
+            const oldConfiguration = this._configuration;
+            this._configuration = services.serviceConfigurationProvider.loadFromWorkspace();
+            this.versionProvider.updateConfiguration(this._configuration);
+            this._versionManager.updateConfiguration(this._configuration);
+            this.pluginPathsProvider.updateConfiguration(this._configuration);
+            this._nodeVersionManager.updateConfiguration(this._configuration);
+            if (this.serverState.type === ServerState.Type.Running) {
+                if (!this._configuration.implicitProjectConfiguration.isEqualTo(oldConfiguration.implicitProjectConfiguration)) {
+                    this.setCompilerOptionsForInferredProjects(this._configuration);
+                }
+                if (!areServiceConfigurationsEqual(this._configuration, oldConfiguration)) {
+                    this.restartTsServer();
+                }
+            }
+        }, this, this._disposables);
+        this.telemetryReporter = new VSCodeTelemetryReporter(services.experimentTelemetryReporter, () => {
+            if (this.serverState.type === ServerState.Type.Running) {
+                if (this.serverState.tsserverVersion) {
+                    return this.serverState.tsserverVersion;
+                }
+            }
+            return this.apiVersion.fullVersionString;
+        });
+        this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem);
+        this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
+        this._register(this.pluginManager.onDidUpdateConfig(update => {
+            this.configurePlugin(update.pluginId, update.config);
+        }));
+        this._register(this.pluginManager.onDidChangePlugins(() => {
+            this.restartTsServer();
+        }));
+    }
+    public get capabilities() {
+        if (this._configuration.useSyntaxServer === SyntaxServerConfiguration.Always) {
+            return new ClientCapabilities(ClientCapability.Syntax, ClientCapability.EnhancedSyntax);
+        }
+        if (isWeb()) {
+            if (this.isProjectWideIntellisenseOnWebEnabled()) {
+                return new ClientCapabilities(ClientCapability.Syntax, ClientCapability.EnhancedSyntax, ClientCapability.Semantic);
+            }
+            else {
+                return new ClientCapabilities(ClientCapability.Syntax, ClientCapability.EnhancedSyntax);
+            }
+        }
+        if (this.apiVersion.gte(API.v400)) {
+            return new ClientCapabilities(ClientCapability.Syntax, ClientCapability.EnhancedSyntax, ClientCapability.Semantic);
+        }
+        return new ClientCapabilities(ClientCapability.Syntax, ClientCapability.Semantic);
+    }
+    private readonly _onDidChangeCapabilities = this._register(new vscode.EventEmitter());
+    readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
+    private isProjectWideIntellisenseOnWebEnabled(): boolean {
+        return isWebAndHasSharedArrayBuffers() && this._configuration.webProjectWideIntellisenseEnabled;
+    }
+    private cancelInflightRequestsForResource(resource: vscode.Uri): void {
+        if (this.serverState.type !== ServerState.Type.Running) {
+            return;
+        }
+        for (const request of this.serverState.toCancelOnResourceChange) {
+            if (request.resource.toString() === resource.toString()) {
+                request.cancel();
+            }
+        }
+    }
+    public get configuration() {
+        return this._configuration;
+    }
+    public override dispose() {
+        super.dispose();
+        this.bufferSyncSupport.dispose();
+        if (this.serverState.type === ServerState.Type.Running) {
+            this.serverState.server.kill();
+        }
+        this.loadingIndicator.reset();
+        this.resetWatchers();
+    }
+    public restartTsServer(fromUserAction = false): void {
+        if (this.serverState.type === ServerState.Type.Running) {
+            this.info('Killing TS Server');
+            this.isRestarting = true;
+            this.serverState.server.kill();
+        }
+        if (fromUserAction) {
+            // Reset crash trackers
+            this.hasServerFatallyCrashedTooManyTimes = false;
+            this.numberRestarts = 0;
+            this.lastStart = Date.now();
+        }
+        this.serverState = this.startService(true);
+    }
+    private readonly _onTsServerStarted = this._register(new vscode.EventEmitter<{
+        version: TypeScriptVersion;
+        usedApiVersion: API;
+    }>());
+    public readonly onTsServerStarted = this._onTsServerStarted.event;
+    private readonly _onDiagnosticsReceived = this._register(new vscode.EventEmitter());
+    public readonly onDiagnosticsReceived = this._onDiagnosticsReceived.event;
+    private readonly _onConfigDiagnosticsReceived = this._register(new vscode.EventEmitter());
+    public readonly onConfigDiagnosticsReceived = this._onConfigDiagnosticsReceived.event;
+    private readonly _onResendModelsRequested = this._register(new vscode.EventEmitter());
+    public readonly onResendModelsRequested = this._onResendModelsRequested.event;
+    private readonly _onProjectLanguageServiceStateChanged = this._register(new vscode.EventEmitter());
+    public readonly onProjectLanguageServiceStateChanged = this._onProjectLanguageServiceStateChanged.event;
+    private readonly _onDidBeginInstallTypings = this._register(new vscode.EventEmitter());
+    public readonly onDidBeginInstallTypings = this._onDidBeginInstallTypings.event;
+    private readonly _onDidEndInstallTypings = this._register(new vscode.EventEmitter());
+    public readonly onDidEndInstallTypings = this._onDidEndInstallTypings.event;
+    private readonly _onTypesInstallerInitializationFailed = this._register(new vscode.EventEmitter());
+    public readonly onTypesInstallerInitializationFailed = this._onTypesInstallerInitializationFailed.event;
+    private readonly _onSurveyReady = this._register(new vscode.EventEmitter());
+    public readonly onSurveyReady = this._onSurveyReady.event;
+    public get apiVersion(): API {
+        if (this.serverState.type === ServerState.Type.Running) {
+            return this.serverState.apiVersion;
+        }
+        return API.defaultVersion;
+    }
+    public onReady(f: () => void): Promise {
+        return this._onReady!.promise.then(f);
+    }
+    private info(message: string, ...data: any[]): void {
+        this.logger.info(message, ...data);
+    }
+    private error(message: string, ...data: any[]): void {
+        this.logger.error(message, ...data);
+    }
+    private logTelemetry(eventName: string, properties?: TelemetryProperties) {
+        this.telemetryReporter.logTelemetry(eventName, properties);
+    }
+    public ensureServiceStarted() {
+        if (this.serverState.type !== ServerState.Type.Running) {
+            this.startService();
+        }
+    }
+    private token: number = 0;
+    private startService(resendModels: boolean = false): ServerState.State {
+        this.info(`Starting TS Server`);
+        if (this.isDisposed) {
+            this.info(`Not starting server: disposed`);
+            return ServerState.None;
+        }
+        if (this.hasServerFatallyCrashedTooManyTimes) {
+            this.info(`Not starting server: too many crashes`);
+            return ServerState.None;
+        }
+        let version = this._versionManager.currentVersion;
+        if (!version.isValid) {
+            vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn't point to a valid tsserver install. Falling back to bundled TypeScript version.", version.path));
+            this._versionManager.reset();
+            version = this._versionManager.currentVersion;
+        }
+        this.info(`Using tsserver from: ${version.path}`);
+        const nodePath = this._nodeVersionManager.currentVersion;
+        if (nodePath) {
+            this.info(`Using Node installation from ${nodePath} to run TS Server`);
+        }
+        this.resetWatchers();
+        const apiVersion = version.apiVersion || API.defaultVersion;
+        const mytoken = ++this.token;
+        const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, this.cancellerFactory, {
+            onFatalError: (command, err) => this.fatalError(command, err),
+        });
+        this.serverState = new ServerState.Running(handle, apiVersion, undefined, true);
+        this.lastStart = Date.now();
+        /* __GDPR__FRAGMENT__
+            "TypeScriptServerEnvCommonProperties" : {
+                "hasGlobalPlugins": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "globalPluginNameHashes": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+            }
+        */
+        const typeScriptServerEnvCommonProperties = {
+            hasGlobalPlugins: this.pluginManager.plugins.length > 0,
+            globalPluginNameHashes: JSON.stringify(this.pluginManager.plugins.map(plugin => hash(plugin.name))),
+        };
+        /* __GDPR__
+            "tsserver.spawned" : {
+                "owner": "mjbvz",
+                "${include}": [
+                    "${TypeScriptCommonProperties}",
+                    "${TypeScriptServerEnvCommonProperties}"
+                ],
+                "localTypeScriptVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+                "typeScriptVersionSource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+            }
+        */
+        this.logTelemetry('tsserver.spawned', {
+            ...typeScriptServerEnvCommonProperties,
+            localTypeScriptVersion: this.versionProvider.localVersion ? this.versionProvider.localVersion.displayName : '',
+            typeScriptVersionSource: version.source,
+        });
+        handle.onError((err: Error) => {
+            if (this.token !== mytoken) {
+                // this is coming from an old process
+                return;
+            }
+            if (err) {
+                vscode.window.showErrorMessage(vscode.l10n.t("TypeScript language server exited with error. Error message is: {0}", err.message || err.name));
+            }
+            this.serverState = new ServerState.Errored(err, handle.tsServerLog);
+            this.error('TSServer errored with error.', err);
+            if (handle.tsServerLog?.type === 'file') {
+                this.error(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`);
+            }
+            /* __GDPR__
+                "tsserver.error" : {
+                    "owner": "mjbvz",
+                    "${include}": [
+                        "${TypeScriptCommonProperties}",
+                        "${TypeScriptServerEnvCommonProperties}"
+                    ]
+                }
+            */
+            this.logTelemetry('tsserver.error', {
+                ...typeScriptServerEnvCommonProperties
+            });
+            this.serviceExited(false, apiVersion);
+        });
+        handle.onExit((data: TypeScriptServerExitEvent) => {
+            const { code, signal } = data;
+            this.error(`TSServer exited. Code: ${code}. Signal: ${signal}`);
+            // In practice, the exit code is an integer with no ties to any identity,
+            // so it can be classified as SystemMetaData, rather than CallstackOrException.
+            /* __GDPR__
+                "tsserver.exitWithCode" : {
+                    "owner": "mjbvz",
+                    "${include}": [
+                        "${TypeScriptCommonProperties}",
+                        "${TypeScriptServerEnvCommonProperties}"
+                    ],
+                    "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
+                    "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
+                }
+            */
+            this.logTelemetry('tsserver.exitWithCode', {
+                ...typeScriptServerEnvCommonProperties,
+                code: code ?? undefined,
+                signal: signal ?? undefined,
+            });
+            if (this.token !== mytoken) {
+                // this is coming from an old process
+                return;
+            }
+            if (handle.tsServerLog?.type === 'file') {
+                this.info(`TSServer log file: ${handle.tsServerLog.uri.fsPath}`);
+            }
+            this.serviceExited(!this.isRestarting, apiVersion);
+            this.isRestarting = false;
+        });
+        handle.onEvent(event => this.dispatchEvent(event));
+        this.serviceStarted(resendModels);
+        this._onReady!.resolve();
+        this._onTsServerStarted.fire({ version: version, usedApiVersion: apiVersion });
+        this._onDidChangeCapabilities.fire();
+        return this.serverState;
+    }
+    private resetWatchers() {
+        clearTimeout(this.watchChangeTimeout);
+        disposeAll(Array.from(this.watches.values()));
+    }
+    public async showVersionPicker(): Promise {
+        this._versionManager.promptUserForVersion();
+    }
+    public async openTsServerLogFile(): Promise {
+        if (this._configuration.tsServerLogLevel === TsServerLogLevel.Off) {
+            vscode.window.showErrorMessage(vscode.l10n.t("TS Server logging is off. Please set 'typescript.tsserver.log' and restart the TS server to enable logging"), {
+                title: vscode.l10n.t("Enable logging and restart TS server"),
+            })
+                .then(selection => {
+                if (selection) {
+                    return vscode.workspace.getConfiguration().update('typescript.tsserver.log', 'verbose', true).then(() => {
+                        this.restartTsServer();
+                    });
+                }
+                return undefined;
+            });
+            return false;
+        }
+        if (this.serverState.type !== ServerState.Type.Running || !this.serverState.server.tsServerLog) {
+            vscode.window.showWarningMessage(vscode.l10n.t("TS Server has not started logging."));
+            return false;
+        }
+        switch (this.serverState.server.tsServerLog.type) {
+            case 'output': {
+                this.serverState.server.tsServerLog.output.show();
+                return true;
+            }
+            case 'file': {
+                try {
+                    const doc = await vscode.workspace.openTextDocument(this.serverState.server.tsServerLog.uri);
+                    await vscode.window.showTextDocument(doc);
+                    return true;
+                }
+                catch {
+                    // noop
+                }
+                try {
+                    await vscode.commands.executeCommand('revealFileInOS', this.serverState.server.tsServerLog.uri);
+                    return true;
+                }
+                catch {
+                    vscode.window.showWarningMessage(vscode.l10n.t("Could not open TS Server log file"));
+                    return false;
+                }
+            }
+        }
+    }
+    private serviceStarted(resendModels: boolean): void {
+        this.bufferSyncSupport.reset();
+        const watchOptions = this.apiVersion.gte(API.v380)
+            ? this.configuration.watchOptions
+            : undefined;
+        const configureOptions: Proto.ConfigureRequestArguments = {
+            hostInfo: 'vscode',
+            preferences: {
+                providePrefixAndSuffixTextForRename: true,
+                allowRenameOfImportPath: true,
+                includePackageJsonAutoImports: this._configuration.includePackageJsonAutoImports,
+                excludeLibrarySymbolsInNavTo: this._configuration.workspaceSymbolsExcludeLibrarySymbols,
+            },
+            watchOptions
+        };
+        this.executeWithoutWaitingForResponse('configure', configureOptions);
+        this.setCompilerOptionsForInferredProjects(this._configuration);
+        if (resendModels) {
+            this._onResendModelsRequested.fire();
+            this.bufferSyncSupport.reinitialize();
+            this.bufferSyncSupport.requestAllDiagnostics();
+        }
+        // Reconfigure any plugins
+        for (const [pluginName, config] of this.pluginManager.configurations()) {
+            this.configurePlugin(pluginName, config);
+        }
+    }
+    private setCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): void {
+        const args: Proto.SetCompilerOptionsForInferredProjectsArgs = {
+            options: this.getCompilerOptionsForInferredProjects(configuration)
+        };
+        this.executeWithoutWaitingForResponse('compilerOptionsForInferredProjects', args);
+    }
+    private getCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): Proto.ExternalProjectCompilerOptions {
+        return {
+            ...inferredProjectCompilerOptions(this.apiVersion, ProjectType.TypeScript, configuration),
+            allowJs: true,
+            allowSyntheticDefaultImports: true,
+            allowNonTsExtensions: true,
+            resolveJsonModule: true,
+        };
+    }
+    private serviceExited(restart: boolean, tsVersion: API): void {
+        this.resetWatchers();
+        this.loadingIndicator.reset();
+        this.serverState = ServerState.None;
+        if (restart) {
+            const diff = Date.now() - this.lastStart;
+            this.numberRestarts++;
+            let startService = true;
+            const pluginExtensionList = this.pluginManager.plugins.map(plugin => plugin.extension.id).join(', ');
+            const reportIssueItem: vscode.MessageItem = {
+                title: vscode.l10n.t("Report Issue"),
+            };
+            let prompt: Thenable | undefined = undefined;
+            if (this.numberRestarts > 5) {
+                this.numberRestarts = 0;
+                if (diff < 10 * 1000 /* 10 seconds */) {
+                    this.lastStart = Date.now();
+                    startService = false;
+                    this.hasServerFatallyCrashedTooManyTimes = true;
+                    if (this.pluginManager.plugins.length) {
+                        prompt = vscode.window.showErrorMessage(vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList));
+                    }
+                    else {
+                        prompt = vscode.window.showErrorMessage(vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted."), reportIssueItem);
+                    }
+                    /* __GDPR__
+                        "serviceExited" : {
+                            "owner": "mjbvz",
+                            "${include}": [
+                                "${TypeScriptCommonProperties}"
+                            ]
+                        }
+                    */
+                    this.logTelemetry('serviceExited');
+                }
+                else if (diff < 60 * 1000 * 5 /* 5 Minutes */) {
+                    this.lastStart = Date.now();
+                    if (!this._isPromptingAfterCrash) {
+                        if (this.pluginManager.plugins.length) {
+                            prompt = vscode.window.showWarningMessage(vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList));
+                        }
+                        else {
+                            prompt = vscode.window.showWarningMessage(vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes."), reportIssueItem);
+                        }
+                    }
+                }
+            }
+            else if (['vscode-insiders', 'code-oss'].includes(vscode.env.uriScheme)) {
+                // Prompt after a single restart
+                this.numberRestarts = 0;
+                if (!this._isPromptingAfterCrash) {
+                    if (this.pluginManager.plugins.length) {
+                        prompt = vscode.window.showWarningMessage(vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList));
+                    }
+                    else {
+                        prompt = vscode.window.showWarningMessage(vscode.l10n.t("The JS/TS language service crashed."), reportIssueItem);
+                    }
+                }
+            }
+            if (prompt) {
+                this._isPromptingAfterCrash = true;
+            }
+            prompt?.then(async (item) => {
+                this._isPromptingAfterCrash = false;
+                if (item === reportIssueItem) {
+                    const minModernTsVersion = this.versionProvider.bundledVersion.apiVersion;
+                    // Don't allow reporting issues using the PnP patched version of TS Server
+                    if (tsVersion.isYarnPnp()) {
+                        const reportIssue: vscode.MessageItem = {
+                            title: vscode.l10n.t("Report issue against Yarn PnP"),
+                        };
+                        const response = await vscode.window.showWarningMessage(vscode.l10n.t("Please report an issue against Yarn PnP"), {
+                            modal: true,
+                            detail: vscode.l10n.t("The workspace is using a version of the TypeScript Server that has been patched by Yarn PnP. This patching is a common source of bugs."),
+                        }, reportIssue);
+                        if (response === reportIssue) {
+                            vscode.env.openExternal(vscode.Uri.parse('https://github.com/yarnpkg/berry/issues'));
+                        }
+                    }
+                    // Don't allow reporting issues with old TS versions
+                    else if (minModernTsVersion &&
+                        tsVersion.lt(minModernTsVersion)) {
+                        vscode.window.showWarningMessage(vscode.l10n.t("Please update your TypeScript version"), {
+                            modal: true,
+                            detail: vscode.l10n.t("The workspace is using an old version of TypeScript ({0}).\n\nBefore reporting an issue, please update the workspace to use TypeScript {1} or newer to make sure the bug has not already been fixed.", tsVersion.displayName, minModernTsVersion.displayName),
+                        });
+                    }
+                    else {
+                        vscode.env.openExternal(vscode.Uri.parse('https://github.com/microsoft/vscode/wiki/TypeScript-Issues'));
+                    }
+                }
+            });
+            if (startService) {
+                this.startService(true);
+            }
+        }
+    }
+    public toTsFilePath(resource: vscode.Uri): string | undefined {
+        if (fileSchemes.disabledSchemes.has(resource.scheme)) {
+            return undefined;
+        }
+        if (resource.scheme === fileSchemes.file && !isWeb()) {
+            return resource.fsPath;
+        }
+        return (this.isProjectWideIntellisenseOnWebEnabled() ? '' : inMemoryResourcePrefix)
+            + '/' + resource.scheme
+            + '/' + (resource.authority || emptyAuthority)
+            + (resource.path.startsWith('/') ? resource.path : '/' + resource.path)
+            + (resource.fragment ? '#' + resource.fragment : '');
+    }
+    public toOpenTsFilePath(document: vscode.TextDocument, options: {
+        suppressAlertOnFailure?: boolean;
+    } = {}): string | undefined {
+        if (!this.bufferSyncSupport.ensureHasBuffer(document.uri)) {
+            if (!options.suppressAlertOnFailure && !fileSchemes.disabledSchemes.has(document.uri.scheme)) {
+                console.error(`Unexpected resource ${document.uri}`);
+            }
+            return undefined;
+        }
+        return this.toTsFilePath(document.uri);
+    }
+    public hasCapabilityForResource(resource: vscode.Uri, capability: ClientCapability): boolean {
+        if (!this.capabilities.has(capability)) {
+            return false;
+        }
+        switch (capability) {
+            case ClientCapability.Semantic: {
+                return fileSchemes.getSemanticSupportedSchemes().includes(resource.scheme);
+            }
+            case ClientCapability.Syntax:
+            case ClientCapability.EnhancedSyntax: {
+                return true;
+            }
+        }
+    }
+    public toResource(filepath: string): vscode.Uri {
+        if (isWeb()) {
+            // On web, the stdlib paths that TS return look like: '/lib.es2015.collection.d.ts'
+            // TODO: Find out what extensionUri is when testing (should be http://localhost:8080/static/sources/extensions/typescript-language-features/)
+            // TODO:  make sure that this code path is getting hit
+            if (filepath.startsWith('/lib.') && filepath.endsWith('.d.ts')) {
+                return vscode.Uri.joinPath(this.context.extensionUri, 'dist', 'browser', 'typescript', filepath.slice(1));
+            }
+            const parts = filepath.match(/^\/([^\/]+)\/([^\/]*)\/(.+)$/);
+            if (parts) {
+                const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
+                return this.bufferSyncSupport.toVsCodeResource(resource);
+            }
+        }
+        if (filepath.startsWith(inMemoryResourcePrefix)) {
+            const parts = filepath.match(/^\^\/([^\/]+)\/([^\/]*)\/(.+)$/);
+            if (parts) {
+                const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
+                return this.bufferSyncSupport.toVsCodeResource(resource);
+            }
+        }
+        return this.bufferSyncSupport.toResource(filepath);
+    }
+    public getWorkspaceRootForResource(resource: vscode.Uri): vscode.Uri | undefined {
+        const roots = vscode.workspace.workspaceFolders ? Array.from(vscode.workspace.workspaceFolders) : undefined;
+        if (!roots?.length) {
+            return undefined;
+        }
+        // For notebook cells, we need to use the notebook document to look up the workspace
+        if (resource.scheme === Schemes.notebookCell) {
+            for (const notebook of vscode.workspace.notebookDocuments) {
+                for (const cell of notebook.getCells()) {
+                    if (cell.document.uri.toString() === resource.toString()) {
+                        resource = notebook.uri;
+                        break;
+                    }
+                }
+            }
+        }
+        for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) {
+            if (root.uri.scheme === resource.scheme && root.uri.authority === resource.authority) {
+                if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) {
+                    return root.uri;
+                }
+            }
+        }
+        return vscode.workspace.getWorkspaceFolder(resource)?.uri;
+    }
+    public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> {
+        let executions: Array> | undefined> | undefined;
+        if (config?.cancelOnResourceChange) {
+            const runningServerState = this.serverState;
+            if (runningServerState.type === ServerState.Type.Running) {
+                const source = new vscode.CancellationTokenSource();
+                token.onCancellationRequested(() => source.cancel());
+                const inFlight: ToCancelOnResourceChanged = {
+                    resource: config.cancelOnResourceChange,
+                    cancel: () => source.cancel(),
+                };
+                runningServerState.toCancelOnResourceChange.add(inFlight);
+                executions = this.executeImpl(command, args, {
+                    isAsync: false,
+                    token: source.token,
+                    expectsResult: true,
+                    ...config,
+                });
+                executions[0]!.finally(() => {
+                    runningServerState.toCancelOnResourceChange.delete(inFlight);
+                    source.dispose();
+                });
+            }
+        }
+        if (!executions) {
+            executions = this.executeImpl(command, args, {
+                isAsync: false,
+                token,
+                expectsResult: true,
+                ...config,
+            });
+        }
+        if (config?.nonRecoverable) {
+            executions[0]!.catch(err => this.fatalError(command, err));
+        }
+        if (command === 'updateOpen') {
+            // If update open has completed, consider that the project has loaded
+            const updateOpenTask = Promise.all(executions).then(() => {
+                this.loadingIndicator.reset();
+            });
+            const updateOpenArgs = (args as Proto.UpdateOpenRequestArgs);
+            if (updateOpenArgs.openFiles?.length === 1) {
+                this.loadingIndicator.startedLoadingFile(updateOpenArgs.openFiles[0].file, updateOpenTask);
+            }
+        }
+        return executions[0]!;
+    }
+    public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: any): void {
+        this.executeImpl(command, args, {
+            isAsync: false,
+            token: undefined,
+            expectsResult: false
+        });
+    }
+    public executeAsync(command: keyof TypeScriptRequests, args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise> {
+        return this.executeImpl(command, args, {
+            isAsync: true,
+            token,
+            expectsResult: true
+        })[0]!;
+    }
+    private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: {
+        isAsync: boolean;
+        token?: vscode.CancellationToken;
+        expectsResult: boolean;
+        lowPriority?: boolean;
+        requireSemantic?: boolean;
+    }): Array> | undefined> {
+        const serverState = this.serverState;
+        if (serverState.type === ServerState.Type.Running) {
+            this.bufferSyncSupport.beforeCommand(command);
+            return serverState.server.executeImpl(command, args, executeInfo);
+        }
+        else {
+            return [Promise.resolve(ServerResponse.NoServer)];
+        }
+    }
+    public interruptGetErr(f: () => R): R {
+        return this.bufferSyncSupport.interruptGetErr(f);
+    }
+    private fatalError(command: string, error: unknown): void {
+        /* __GDPR__
+            "fatalError" : {
+                "owner": "mjbvz",
+                "${include}": [
+                    "${TypeScriptCommonProperties}",
+                    "${TypeScriptRequestErrorProperties}"
+                ],
+                "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+            }
+        */
+        this.logTelemetry('fatalError', { ...(error instanceof TypeScriptServerError ? error.telemetry : { command }) });
+        console.error(`A non-recoverable error occurred while executing tsserver command: ${command}`);
+        if (error instanceof TypeScriptServerError && error.serverErrorText) {
+            console.error(error.serverErrorText);
+        }
+        if (this.serverState.type === ServerState.Type.Running) {
+            this.info('Killing TS Server');
+            const logfile = this.serverState.server.tsServerLog;
+            this.serverState.server.kill();
+            if (error instanceof TypeScriptServerError) {
+                this.serverState = new ServerState.Errored(error, logfile);
+            }
+        }
+    }
+    private dispatchEvent(event: Proto.Event) {
+        switch (event.event) {
+            case EventName.syntaxDiag:
+            case EventName.semanticDiag:
+            case EventName.suggestionDiag:
+            case EventName.regionSemanticDiag: {
+                // This event also roughly signals that projects have been loaded successfully (since the TS server is synchronous)
+                this.loadingIndicator.reset();
+                const diagnosticEvent = event as Proto.DiagnosticEvent;
+                if (diagnosticEvent.body?.diagnostics) {
+                    this._onDiagnosticsReceived.fire({
+                        kind: getDiagnosticsKind(event),
+                        resource: this.toResource(diagnosticEvent.body.file),
+                        diagnostics: diagnosticEvent.body.diagnostics,
+                        spans: diagnosticEvent.body.spans,
+                    });
+                }
+                return;
+            }
+            case EventName.configFileDiag:
+                this._onConfigDiagnosticsReceived.fire(event as Proto.ConfigFileDiagnosticEvent);
+                return;
+            case EventName.telemetry: {
+                const body = (event as Proto.TelemetryEvent).body;
+                this.dispatchTelemetryEvent(body);
+                return;
+            }
+            case EventName.projectLanguageServiceState: {
+                const body = (event as Proto.ProjectLanguageServiceStateEvent).body!;
+                if (this.serverState.type === ServerState.Type.Running) {
+                    this.serverState.updateLanguageServiceEnabled(body.languageServiceEnabled);
+                }
+                this._onProjectLanguageServiceStateChanged.fire(body);
+                return;
+            }
+            case EventName.projectsUpdatedInBackground: {
+                this.loadingIndicator.reset();
+                const body = (event as Proto.ProjectsUpdatedInBackgroundEvent).body;
+                const resources = body.openFiles.map(file => this.toResource(file));
+                this.bufferSyncSupport.getErr(resources);
+                return;
+            }
+            case EventName.beginInstallTypes:
+                this._onDidBeginInstallTypings.fire((event as Proto.BeginInstallTypesEvent).body);
+                return;
+            case EventName.endInstallTypes:
+                this._onDidEndInstallTypings.fire((event as Proto.EndInstallTypesEvent).body);
+                return;
+            case EventName.typesInstallerInitializationFailed:
+                this._onTypesInstallerInitializationFailed.fire((event as Proto.TypesInstallerInitializationFailedEvent).body);
+                return;
+            case EventName.surveyReady:
+                this._onSurveyReady.fire((event as Proto.SurveyReadyEvent).body);
+                return;
+            case EventName.projectLoadingStart:
+                this.loadingIndicator.startedLoadingProject((event as Proto.ProjectLoadingStartEvent).body.projectName);
+                return;
+            case EventName.projectLoadingFinish:
+                this.loadingIndicator.finishedLoadingProject((event as Proto.ProjectLoadingFinishEvent).body.projectName);
+                return;
+            case EventName.createDirectoryWatcher: {
+                const fpath = (event.body as Proto.CreateDirectoryWatcherEventBody).path;
+                if (fpath.startsWith(inMemoryResourcePrefix)) {
+                    return;
+                }
+                if (process.platform === 'darwin' && fpath === path.join(homedir(), 'Library')) {
+                    // ignore directory watch requests on ~/Library
+                    // until microsoft/TypeScript#59831 is resolved
+                    return;
+                }
+                this.createFileSystemWatcher((event.body as Proto.CreateDirectoryWatcherEventBody).id, new vscode.RelativePattern(vscode.Uri.file(fpath), (event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*'), (event.body as Proto.CreateDirectoryWatcherEventBody).ignoreUpdate);
+                return;
+            }
+            case EventName.createFileWatcher: {
+                const path = (event.body as Proto.CreateFileWatcherEventBody).path;
+                if (path.startsWith(inMemoryResourcePrefix)) {
+                    return;
+                }
+                this.createFileSystemWatcher((event.body as Proto.CreateFileWatcherEventBody).id, new vscode.RelativePattern(vscode.Uri.file(path), '*'));
+                return;
+            }
+            case EventName.closeFileWatcher:
+                this.closeFileSystemWatcher(event.body.id);
+                return;
+            case EventName.requestCompleted: {
+                const diagnosticsDuration = (event.body as Proto.RequestCompletedEventBody).performanceData?.diagnosticsDuration;
+                if (diagnosticsDuration) {
+                    this.diagnosticsManager.logDiagnosticsPerformanceTelemetry(diagnosticsDuration.map(fileData => {
+                        const resource = this.toResource(fileData.file);
+                        return {
+                            ...fileData,
+                            fileLineCount: this.bufferSyncSupport.lineCount(resource),
+                        };
+                    }));
+                }
+                return;
+            }
+        }
+    }
+    private scheduleExecuteWatchChangeRequest() {
+        if (!this.watchChangeTimeout) {
+            this.watchChangeTimeout = setTimeout(() => {
+                this.watchChangeTimeout = undefined;
+                const allEvents = Array.from(this.watchEvents, ([id, event]) => ({
+                    id,
+                    updated: event.updated && Array.from(event.updated),
+                    created: event.created && Array.from(event.created),
+                    deleted: event.deleted && Array.from(event.deleted)
+                }));
+                this.watchEvents.clear();
+                this.executeWithoutWaitingForResponse('watchChange', allEvents);
+            }, 100); /* aggregate events over 100ms to reduce client<->server IPC overhead */
+        }
+    }
+    private addWatchEvent(id: number, eventType: keyof WatchEvent, path: string) {
+        let event = this.watchEvents.get(id);
+        const removeEvent = (typeOfEventToRemove: keyof WatchEvent) => {
+            if (event?.[typeOfEventToRemove]?.delete(path) && event[typeOfEventToRemove].size === 0) {
+                event[typeOfEventToRemove] = undefined;
+            }
+        };
+        const aggregateEvent = () => {
+            if (!event) {
+                this.watchEvents.set(id, event = {});
+            }
+            (event[eventType] ??= new Set()).add(path);
+        };
+        switch (eventType) {
+            case 'created':
+                removeEvent('deleted');
+                removeEvent('updated');
+                aggregateEvent();
+                break;
+            case 'deleted':
+                removeEvent('created');
+                removeEvent('updated');
+                aggregateEvent();
+                break;
+            case 'updated':
+                if (event?.created?.has(path)) {
+                    return;
+                }
+                removeEvent('deleted');
+                aggregateEvent();
+                break;
+        }
+        this.scheduleExecuteWatchChangeRequest();
+    }
+    private createFileSystemWatcher(id: number, pattern: vscode.RelativePattern, ignoreChangeEvents?: boolean) {
+        const disposable = new DisposableStore();
+        const watcher = disposable.add(vscode.workspace.createFileSystemWatcher(pattern, undefined, ignoreChangeEvents));
+        disposable.add(watcher.onDidChange(changeFile => this.addWatchEvent(id, 'updated', changeFile.fsPath)));
+        disposable.add(watcher.onDidCreate(createFile => this.addWatchEvent(id, 'created', createFile.fsPath)));
+        disposable.add(watcher.onDidDelete(deletedFile => this.addWatchEvent(id, 'deleted', deletedFile.fsPath)));
+        disposable.add({
+            dispose: () => {
+                this.watchEvents.delete(id);
+                this.watches.delete(id);
+            }
+        });
+        if (this.watches.has(id)) {
+            this.closeFileSystemWatcher(id);
+        }
+        this.watches.set(id, disposable);
+    }
+    private closeFileSystemWatcher(id: number) {
+        const existing = this.watches.get(id);
+        existing?.dispose();
+    }
+    private dispatchTelemetryEvent(telemetryData: Proto.TelemetryEventBody): void {
+        const properties: {
+            [key: string]: string;
+        } = Object.create(null);
+        switch (telemetryData.telemetryEventName) {
+            case 'typingsInstalled': {
+                const typingsInstalledPayload: Proto.TypingsInstalledTelemetryEventPayload = (telemetryData.payload as Proto.TypingsInstalledTelemetryEventPayload);
+                properties['installedPackages'] = typingsInstalledPayload.installedPackages;
+                if (typeof typingsInstalledPayload.installSuccess === 'boolean') {
+                    properties['installSuccess'] = typingsInstalledPayload.installSuccess.toString();
+                }
+                if (typeof typingsInstalledPayload.typingsInstallerVersion === 'string') {
+                    properties['typingsInstallerVersion'] = typingsInstalledPayload.typingsInstallerVersion;
+                }
+                break;
+            }
+            default: {
+                const payload = telemetryData.payload;
+                if (payload) {
+                    Object.keys(payload).forEach((key) => {
+                        try {
+                            if (payload.hasOwnProperty(key)) {
+                                properties[key] = typeof payload[key] === 'string' ? payload[key] : JSON.stringify(payload[key]);
+                            }
+                        }
+                        catch (e) {
+                            // noop
+                        }
+                    });
+                }
+                break;
+            }
+        }
+        // Add plugin data here
+        if (telemetryData.telemetryEventName === 'projectInfo') {
+            if (this.serverState.type === ServerState.Type.Running) {
+                this.serverState.updateTsserverVersion(properties['version']);
+            }
+        }
+        /* __GDPR__
+            "typingsInstalled" : {
+                "owner": "mjbvz",
+                "installedPackages" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
+                "installSuccess": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
+                "typingsInstallerVersion": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        // __GDPR__COMMENT__: Other events are defined by TypeScript.
+        this.logTelemetry(telemetryData.telemetryEventName, properties);
+    }
+    private configurePlugin(pluginName: string, configuration: {}): any {
+        this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration });
+    }
 }
-
 function getDiagnosticsKind(event: Proto.Event) {
-	switch (event.event) {
-		case 'syntaxDiag': return DiagnosticKind.Syntax;
-		case 'semanticDiag': return DiagnosticKind.Semantic;
-		case 'suggestionDiag': return DiagnosticKind.Suggestion;
-		case 'regionSemanticDiag': return DiagnosticKind.RegionSemantic;
-	}
-	throw new Error('Unknown dignostics kind');
+    switch (event.event) {
+        case 'syntaxDiag': return DiagnosticKind.Syntax;
+        case 'semanticDiag': return DiagnosticKind.Semantic;
+        case 'suggestionDiag': return DiagnosticKind.Suggestion;
+        case 'regionSemanticDiag': return DiagnosticKind.RegionSemantic;
+    }
+    throw new Error('Unknown dignostics kind');
 }
-
 class ServerInitializingIndicator extends Disposable {
-
-	private _task?: { project: string; resolve: () => void };
-
-	public reset(): void {
-		if (this._task) {
-			this._task.resolve();
-			this._task = undefined;
-		}
-	}
-
-	/**
-	 * Signal that a project has started loading.
-	 */
-	public startedLoadingProject(projectName: string): void {
-		// TS projects are loaded sequentially. Cancel existing task because it should always be resolved before
-		// the incoming project loading task is.
-		this.reset();
-
-		const projectDisplayName = vscode.workspace.asRelativePath(projectName);
-		vscode.window.withProgress({
-			location: vscode.ProgressLocation.Window,
-			title: vscode.l10n.t("Initializing project '{0}'", projectDisplayName),
-		}, () => new Promise(resolve => {
-			this._task = { project: projectName, resolve };
-		}));
-	}
-
-	public startedLoadingFile(fileName: string, task: Promise): void {
-		if (!this._task) {
-			vscode.window.withProgress({
-				location: vscode.ProgressLocation.Window,
-				title: vscode.l10n.t("Analyzing '{0}' and its dependencies", path.basename(fileName)),
-			}, () => task);
-		}
-	}
-
-	public finishedLoadingProject(projectName: string): void {
-		if (this._task && this._task.project === projectName) {
-			this._task.resolve();
-			this._task = undefined;
-		}
-	}
+    private _task?: {
+        project: string;
+        resolve: () => void;
+    };
+    public reset(): void {
+        if (this._task) {
+            this._task.resolve();
+            this._task = undefined;
+        }
+    }
+    /**
+     * Signal that a project has started loading.
+     */
+    public startedLoadingProject(projectName: string): void {
+        // TS projects are loaded sequentially. Cancel existing task because it should always be resolved before
+        // the incoming project loading task is.
+        this.reset();
+        const projectDisplayName = vscode.workspace.asRelativePath(projectName);
+        vscode.window.withProgress({
+            location: vscode.ProgressLocation.Window,
+            title: vscode.l10n.t("Initializing project '{0}'", projectDisplayName),
+        }, () => new Promise(resolve => {
+            this._task = { project: projectName, resolve };
+        }));
+    }
+    public startedLoadingFile(fileName: string, task: Promise): void {
+        if (!this._task) {
+            vscode.window.withProgress({
+                location: vscode.ProgressLocation.Window,
+                title: vscode.l10n.t("Analyzing '{0}' and its dependencies", path.basename(fileName)),
+            }, () => task);
+        }
+    }
+    public finishedLoadingProject(projectName: string): void {
+        if (this._task && this._task.project === projectName) {
+            this._task.resolve();
+            this._task = undefined;
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/ui/activeJsTsEditorTracker.ts b/extensions/typescript-language-features/Source/ui/activeJsTsEditorTracker.ts
index c3cae6321ffcb..6ce07ec6e7a8c 100644
--- a/extensions/typescript-language-features/Source/ui/activeJsTsEditorTracker.ts
+++ b/extensions/typescript-language-features/Source/ui/activeJsTsEditorTracker.ts
@@ -2,13 +2,11 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { isJsConfigOrTsConfigFileName } from '../configuration/languageDescription';
 import { isSupportedLanguageMode } from '../configuration/languageIds';
 import { Disposable } from '../utils/dispose';
 import { coalesce } from '../utils/arrays';
-
 /**
  * Tracks the active JS/TS editor.
  *
@@ -17,101 +15,81 @@ import { coalesce } from '../utils/arrays';
  * instead of using `vscode.window.activeTextEditor`
  */
 export class ActiveJsTsEditorTracker extends Disposable {
-
-	private _activeJsTsEditor: vscode.TextEditor | undefined;
-
-	private readonly _onDidChangeActiveJsTsEditor = this._register(new vscode.EventEmitter());
-	public readonly onDidChangeActiveJsTsEditor = this._onDidChangeActiveJsTsEditor.event;
-
-	public constructor() {
-		super();
-
-		this._register(vscode.window.onDidChangeActiveTextEditor(_ => this.update()));
-		this._register(vscode.window.onDidChangeVisibleTextEditors(_ => this.update()));
-		this._register(vscode.window.tabGroups.onDidChangeTabGroups(_ => this.update()));
-
-		this.update();
-	}
-
-	public get activeJsTsEditor(): vscode.TextEditor | undefined {
-		return this._activeJsTsEditor;
-	}
-
-
-	private update() {
-		// Use tabs to find the active editor.
-		// This correctly handles switching to the output view / debug console, which changes the activeEditor but not
-		// the active tab.
-		const editorCandidates = this.getEditorCandidatesForActiveTab();
-		const managedEditors = editorCandidates.filter(editor => this.isManagedFile(editor));
-		const newActiveJsTsEditor = managedEditors.at(0);
-		if (this._activeJsTsEditor !== newActiveJsTsEditor) {
-			this._activeJsTsEditor = newActiveJsTsEditor;
-			this._onDidChangeActiveJsTsEditor.fire(this._activeJsTsEditor);
-		}
-	}
-
-	private getEditorCandidatesForActiveTab(): vscode.TextEditor[] {
-		const tab = vscode.window.tabGroups.activeTabGroup.activeTab;
-		if (!tab) {
-			return [];
-		}
-
-		// Basic text editor tab
-		if (tab.input instanceof vscode.TabInputText) {
-			const inputUri = tab.input.uri;
-			const editor = vscode.window.visibleTextEditors.find(editor => {
-				return editor.document.uri.toString() === inputUri.toString()
-					&& editor.viewColumn === tab.group.viewColumn;
-			});
-			return editor ? [editor] : [];
-		}
-
-		// Diff editor tab. We could be focused on either side of the editor.
-		if (tab.input instanceof vscode.TabInputTextDiff) {
-			const original = tab.input.original;
-			const modified = tab.input.modified;
-			// Check the active editor first. However if a non tab editor like the output view is focused,
-			// we still need to check the visible text editors.
-			// TODO: This may return incorrect editors incorrect as there does not seem to be a reliable way to map from an editor to the
-			// view column of its parent diff editor. See https://github.com/microsoft/vscode/issues/201845
-			return coalesce([vscode.window.activeTextEditor, ...vscode.window.visibleTextEditors]).filter(editor => {
-				return (editor.document.uri.toString() === original.toString() || editor.document.uri.toString() === modified.toString())
-					&& editor.viewColumn === undefined; // Editors in diff views have undefined view columns
-			});
-		}
-
-		// Notebook editor. Find editor for notebook cell.
-		if (tab.input instanceof vscode.TabInputNotebook) {
-			const activeEditor = vscode.window.activeTextEditor;
-			if (!activeEditor) {
-				return [];
-			}
-
-			// Notebooks cell editors have undefined view columns.
-			if (activeEditor.viewColumn !== undefined) {
-				return [];
-			}
-
-			const notebook = vscode.window.visibleNotebookEditors.find(editor =>
-				editor.notebook.uri.toString() === (tab.input as vscode.TabInputNotebook).uri.toString()
-				&& editor.viewColumn === tab.group.viewColumn);
-
-			return notebook?.notebook.getCells().some(cell => cell.document.uri.toString() === activeEditor.document.uri.toString()) ? [activeEditor] : [];
-		}
-
-		return [];
-	}
-
-	private isManagedFile(editor: vscode.TextEditor): boolean {
-		return this.isManagedScriptFile(editor) || this.isManagedConfigFile(editor);
-	}
-
-	private isManagedScriptFile(editor: vscode.TextEditor): boolean {
-		return isSupportedLanguageMode(editor.document);
-	}
-
-	private isManagedConfigFile(editor: vscode.TextEditor): boolean {
-		return isJsConfigOrTsConfigFileName(editor.document.fileName);
-	}
+    private _activeJsTsEditor: vscode.TextEditor | undefined;
+    private readonly _onDidChangeActiveJsTsEditor = this._register(new vscode.EventEmitter());
+    public readonly onDidChangeActiveJsTsEditor = this._onDidChangeActiveJsTsEditor.event;
+    public constructor() {
+        super();
+        this._register(vscode.window.onDidChangeActiveTextEditor(_ => this.update()));
+        this._register(vscode.window.onDidChangeVisibleTextEditors(_ => this.update()));
+        this._register(vscode.window.tabGroups.onDidChangeTabGroups(_ => this.update()));
+        this.update();
+    }
+    public get activeJsTsEditor(): vscode.TextEditor | undefined {
+        return this._activeJsTsEditor;
+    }
+    private update() {
+        // Use tabs to find the active editor.
+        // This correctly handles switching to the output view / debug console, which changes the activeEditor but not
+        // the active tab.
+        const editorCandidates = this.getEditorCandidatesForActiveTab();
+        const managedEditors = editorCandidates.filter(editor => this.isManagedFile(editor));
+        const newActiveJsTsEditor = managedEditors.at(0);
+        if (this._activeJsTsEditor !== newActiveJsTsEditor) {
+            this._activeJsTsEditor = newActiveJsTsEditor;
+            this._onDidChangeActiveJsTsEditor.fire(this._activeJsTsEditor);
+        }
+    }
+    private getEditorCandidatesForActiveTab(): vscode.TextEditor[] {
+        const tab = vscode.window.tabGroups.activeTabGroup.activeTab;
+        if (!tab) {
+            return [];
+        }
+        // Basic text editor tab
+        if (tab.input instanceof vscode.TabInputText) {
+            const inputUri = tab.input.uri;
+            const editor = vscode.window.visibleTextEditors.find(editor => {
+                return editor.document.uri.toString() === inputUri.toString()
+                    && editor.viewColumn === tab.group.viewColumn;
+            });
+            return editor ? [editor] : [];
+        }
+        // Diff editor tab. We could be focused on either side of the editor.
+        if (tab.input instanceof vscode.TabInputTextDiff) {
+            const original = tab.input.original;
+            const modified = tab.input.modified;
+            // Check the active editor first. However if a non tab editor like the output view is focused,
+            // we still need to check the visible text editors.
+            // TODO: This may return incorrect editors incorrect as there does not seem to be a reliable way to map from an editor to the
+            // view column of its parent diff editor. See https://github.com/microsoft/vscode/issues/201845
+            return coalesce([vscode.window.activeTextEditor, ...vscode.window.visibleTextEditors]).filter(editor => {
+                return (editor.document.uri.toString() === original.toString() || editor.document.uri.toString() === modified.toString())
+                    && editor.viewColumn === undefined; // Editors in diff views have undefined view columns
+            });
+        }
+        // Notebook editor. Find editor for notebook cell.
+        if (tab.input instanceof vscode.TabInputNotebook) {
+            const activeEditor = vscode.window.activeTextEditor;
+            if (!activeEditor) {
+                return [];
+            }
+            // Notebooks cell editors have undefined view columns.
+            if (activeEditor.viewColumn !== undefined) {
+                return [];
+            }
+            const notebook = vscode.window.visibleNotebookEditors.find(editor => editor.notebook.uri.toString() === (tab.input as vscode.TabInputNotebook).uri.toString()
+                && editor.viewColumn === tab.group.viewColumn);
+            return notebook?.notebook.getCells().some(cell => cell.document.uri.toString() === activeEditor.document.uri.toString()) ? [activeEditor] : [];
+        }
+        return [];
+    }
+    private isManagedFile(editor: vscode.TextEditor): boolean {
+        return this.isManagedScriptFile(editor) || this.isManagedConfigFile(editor);
+    }
+    private isManagedScriptFile(editor: vscode.TextEditor): boolean {
+        return isSupportedLanguageMode(editor.document);
+    }
+    private isManagedConfigFile(editor: vscode.TextEditor): boolean {
+        return isJsConfigOrTsConfigFileName(editor.document.fileName);
+    }
 }
diff --git a/extensions/typescript-language-features/Source/ui/intellisenseStatus.ts b/extensions/typescript-language-features/Source/ui/intellisenseStatus.ts
index e26e2b3719f4e..5eccdd18fdcad 100644
--- a/extensions/typescript-language-features/Source/ui/intellisenseStatus.ts
+++ b/extensions/typescript-language-features/Source/ui/intellisenseStatus.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { CommandManager } from '../commands/commandManager';
 import { isSupportedLanguageMode, isTypeScriptDocument, jsTsLanguageModes } from '../configuration/languageIds';
@@ -10,216 +9,180 @@ import { ProjectType, isImplicitProjectConfigFile, openOrCreateConfig, openProje
 import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
 import { Disposable } from '../utils/dispose';
 import { ActiveJsTsEditorTracker } from './activeJsTsEditorTracker';
-
-
 namespace IntellisenseState {
-	export const enum Type { None, Pending, Resolved, SyntaxOnly }
-
-	export const None = Object.freeze({ type: Type.None } as const);
-
-	export const SyntaxOnly = Object.freeze({ type: Type.SyntaxOnly } as const);
-
-	export class Pending {
-		public readonly type = Type.Pending;
-
-		public readonly cancellation = new vscode.CancellationTokenSource();
-
-		constructor(
-			public readonly resource: vscode.Uri,
-			public readonly projectType: ProjectType,
-		) { }
-	}
-
-	export class Resolved {
-		public readonly type = Type.Resolved;
-
-		constructor(
-			public readonly resource: vscode.Uri,
-			public readonly projectType: ProjectType,
-			public readonly configFile: string,
-		) { }
-	}
-
-	export type State = typeof None | Pending | Resolved | typeof SyntaxOnly;
+    export const enum Type {
+        None,
+        Pending,
+        Resolved,
+        SyntaxOnly
+    }
+    export const None = Object.freeze({ type: Type.None } as const);
+    export const SyntaxOnly = Object.freeze({ type: Type.SyntaxOnly } as const);
+    export class Pending {
+        public readonly type = Type.Pending;
+        public readonly cancellation = new vscode.CancellationTokenSource();
+        constructor(public readonly resource: vscode.Uri, public readonly projectType: ProjectType) { }
+    }
+    export class Resolved {
+        public readonly type = Type.Resolved;
+        constructor(public readonly resource: vscode.Uri, public readonly projectType: ProjectType, public readonly configFile: string) { }
+    }
+    export type State = typeof None | Pending | Resolved | typeof SyntaxOnly;
 }
-
-type CreateOrOpenConfigCommandArgs = [root: vscode.Uri, projectType: ProjectType];
-
+type CreateOrOpenConfigCommandArgs = [
+    root: vscode.Uri,
+    projectType: ProjectType
+];
 export class IntellisenseStatus extends Disposable {
-
-	public readonly openOpenConfigCommandId = '_typescript.openConfig';
-	public readonly createOrOpenConfigCommandId = '_typescript.createOrOpenConfig';
-
-	private _statusItem?: vscode.LanguageStatusItem;
-
-	private _ready = false;
-	private _state: IntellisenseState.State = IntellisenseState.None;
-
-	constructor(
-		private readonly _client: ITypeScriptServiceClient,
-		commandManager: CommandManager,
-		private readonly _activeTextEditorManager: ActiveJsTsEditorTracker,
-	) {
-		super();
-
-		commandManager.register({
-			id: this.openOpenConfigCommandId,
-			execute: async (...[root, projectType]: CreateOrOpenConfigCommandArgs) => {
-				if (this._state.type === IntellisenseState.Type.Resolved) {
-					await openProjectConfigOrPromptToCreate(projectType, this._client, root, this._state.configFile);
-				} else if (this._state.type === IntellisenseState.Type.Pending) {
-					await openProjectConfigForFile(projectType, this._client, this._state.resource);
-				}
-			},
-		});
-		commandManager.register({
-			id: this.createOrOpenConfigCommandId,
-			execute: async (...[root, projectType]: CreateOrOpenConfigCommandArgs) => {
-				await openOrCreateConfig(this._client.apiVersion, projectType, root, this._client.configuration);
-			},
-		});
-
-		_activeTextEditorManager.onDidChangeActiveJsTsEditor(this.updateStatus, this, this._disposables);
-
-		this._client.onReady(() => {
-			this._ready = true;
-			this.updateStatus();
-		});
-	}
-
-	override dispose() {
-		super.dispose();
-		this._statusItem?.dispose();
-	}
-
-	private async updateStatus() {
-		const doc = this._activeTextEditorManager.activeJsTsEditor?.document;
-		if (!doc || !isSupportedLanguageMode(doc)) {
-			this.updateState(IntellisenseState.None);
-			return;
-		}
-
-		if (!this._client.hasCapabilityForResource(doc.uri, ClientCapability.Semantic)) {
-			this.updateState(IntellisenseState.SyntaxOnly);
-			return;
-		}
-
-		const file = this._client.toOpenTsFilePath(doc, { suppressAlertOnFailure: true });
-		if (!file) {
-			this.updateState(IntellisenseState.None);
-			return;
-		}
-
-		if (!this._ready) {
-			return;
-		}
-
-		const projectType = isTypeScriptDocument(doc) ? ProjectType.TypeScript : ProjectType.JavaScript;
-
-		const pendingState = new IntellisenseState.Pending(doc.uri, projectType);
-		this.updateState(pendingState);
-
-		const response = await this._client.execute('projectInfo', { file, needFileNameList: false }, pendingState.cancellation.token);
-		if (response.type === 'response' && response.body) {
-			if (this._state === pendingState) {
-				this.updateState(new IntellisenseState.Resolved(doc.uri, projectType, response.body.configFileName));
-			}
-		}
-	}
-
-	private updateState(newState: IntellisenseState.State): void {
-		if (this._state === newState) {
-			return;
-		}
-
-		if (this._state.type === IntellisenseState.Type.Pending) {
-			this._state.cancellation.cancel();
-			this._state.cancellation.dispose();
-		}
-
-		this._state = newState;
-
-		switch (this._state.type) {
-			case IntellisenseState.Type.None: {
-				this._statusItem?.dispose();
-				this._statusItem = undefined;
-				break;
-			}
-			case IntellisenseState.Type.Pending: {
-				const statusItem = this.ensureStatusItem();
-				statusItem.severity = vscode.LanguageStatusSeverity.Information;
-				statusItem.text = vscode.l10n.t("Loading IntelliSense status");
-				statusItem.detail = undefined;
-				statusItem.command = undefined;
-				statusItem.busy = true;
-				break;
-			}
-			case IntellisenseState.Type.Resolved: {
-				const noConfigFileText = this._state.projectType === ProjectType.TypeScript
-					? vscode.l10n.t("No tsconfig")
-					: vscode.l10n.t("No jsconfig");
-
-				const rootPath = this._client.getWorkspaceRootForResource(this._state.resource);
-				if (!rootPath) {
-					if (this._statusItem) {
-						this._statusItem.text = noConfigFileText;
-						this._statusItem.detail = !vscode.workspace.workspaceFolders
-							? vscode.l10n.t("No opened folders")
-							: vscode.l10n.t("File is not part opened folders");
-						this._statusItem.busy = false;
-					}
-					return;
-				}
-
-				const statusItem = this.ensureStatusItem();
-				statusItem.busy = false;
-				statusItem.detail = undefined;
-
-				statusItem.severity = vscode.LanguageStatusSeverity.Information;
-				if (isImplicitProjectConfigFile(this._state.configFile)) {
-					statusItem.text = noConfigFileText;
-					statusItem.detail = undefined;
-					statusItem.command = {
-						command: this.createOrOpenConfigCommandId,
-						title: this._state.projectType === ProjectType.TypeScript
-							? vscode.l10n.t("Configure tsconfig")
-							: vscode.l10n.t("Configure jsconfig"),
-						arguments: [rootPath, this._state.projectType] satisfies CreateOrOpenConfigCommandArgs,
-					};
-				} else {
-					statusItem.text = vscode.workspace.asRelativePath(this._state.configFile);
-					statusItem.detail = undefined;
-					statusItem.command = {
-						command: this.openOpenConfigCommandId,
-						title: vscode.l10n.t("Open config file"),
-						arguments: [rootPath, this._state.projectType] satisfies CreateOrOpenConfigCommandArgs,
-					};
-				}
-				break;
-			}
-			case IntellisenseState.Type.SyntaxOnly: {
-				const statusItem = this.ensureStatusItem();
-				statusItem.severity = vscode.LanguageStatusSeverity.Warning;
-				statusItem.text = vscode.l10n.t("Partial Mode");
-				statusItem.detail = vscode.l10n.t("Project Wide IntelliSense not available");
-				statusItem.busy = false;
-				statusItem.command = {
-					title: vscode.l10n.t("Learn More"),
-					command: 'vscode.open',
-					arguments: [
-						vscode.Uri.parse('https://aka.ms/vscode/jsts/partial-mode'),
-					]
-				};
-				break;
-			}
-		}
-	}
-
-	private ensureStatusItem(): vscode.LanguageStatusItem {
-		if (!this._statusItem) {
-			this._statusItem = vscode.languages.createLanguageStatusItem('typescript.projectStatus', jsTsLanguageModes);
-			this._statusItem.name = vscode.l10n.t("JS/TS IntelliSense Status");
-		}
-		return this._statusItem;
-	}
+    public readonly openOpenConfigCommandId = '_typescript.openConfig';
+    public readonly createOrOpenConfigCommandId = '_typescript.createOrOpenConfig';
+    private _statusItem?: vscode.LanguageStatusItem;
+    private _ready = false;
+    private _state: IntellisenseState.State = IntellisenseState.None;
+    constructor(private readonly _client: ITypeScriptServiceClient, commandManager: CommandManager, private readonly _activeTextEditorManager: ActiveJsTsEditorTracker) {
+        super();
+        commandManager.register({
+            id: this.openOpenConfigCommandId,
+            execute: async (...[root, projectType]: CreateOrOpenConfigCommandArgs) => {
+                if (this._state.type === IntellisenseState.Type.Resolved) {
+                    await openProjectConfigOrPromptToCreate(projectType, this._client, root, this._state.configFile);
+                }
+                else if (this._state.type === IntellisenseState.Type.Pending) {
+                    await openProjectConfigForFile(projectType, this._client, this._state.resource);
+                }
+            },
+        });
+        commandManager.register({
+            id: this.createOrOpenConfigCommandId,
+            execute: async (...[root, projectType]: CreateOrOpenConfigCommandArgs) => {
+                await openOrCreateConfig(this._client.apiVersion, projectType, root, this._client.configuration);
+            },
+        });
+        _activeTextEditorManager.onDidChangeActiveJsTsEditor(this.updateStatus, this, this._disposables);
+        this._client.onReady(() => {
+            this._ready = true;
+            this.updateStatus();
+        });
+    }
+    override dispose() {
+        super.dispose();
+        this._statusItem?.dispose();
+    }
+    private async updateStatus() {
+        const doc = this._activeTextEditorManager.activeJsTsEditor?.document;
+        if (!doc || !isSupportedLanguageMode(doc)) {
+            this.updateState(IntellisenseState.None);
+            return;
+        }
+        if (!this._client.hasCapabilityForResource(doc.uri, ClientCapability.Semantic)) {
+            this.updateState(IntellisenseState.SyntaxOnly);
+            return;
+        }
+        const file = this._client.toOpenTsFilePath(doc, { suppressAlertOnFailure: true });
+        if (!file) {
+            this.updateState(IntellisenseState.None);
+            return;
+        }
+        if (!this._ready) {
+            return;
+        }
+        const projectType = isTypeScriptDocument(doc) ? ProjectType.TypeScript : ProjectType.JavaScript;
+        const pendingState = new IntellisenseState.Pending(doc.uri, projectType);
+        this.updateState(pendingState);
+        const response = await this._client.execute('projectInfo', { file, needFileNameList: false }, pendingState.cancellation.token);
+        if (response.type === 'response' && response.body) {
+            if (this._state === pendingState) {
+                this.updateState(new IntellisenseState.Resolved(doc.uri, projectType, response.body.configFileName));
+            }
+        }
+    }
+    private updateState(newState: IntellisenseState.State): void {
+        if (this._state === newState) {
+            return;
+        }
+        if (this._state.type === IntellisenseState.Type.Pending) {
+            this._state.cancellation.cancel();
+            this._state.cancellation.dispose();
+        }
+        this._state = newState;
+        switch (this._state.type) {
+            case IntellisenseState.Type.None: {
+                this._statusItem?.dispose();
+                this._statusItem = undefined;
+                break;
+            }
+            case IntellisenseState.Type.Pending: {
+                const statusItem = this.ensureStatusItem();
+                statusItem.severity = vscode.LanguageStatusSeverity.Information;
+                statusItem.text = vscode.l10n.t("Loading IntelliSense status");
+                statusItem.detail = undefined;
+                statusItem.command = undefined;
+                statusItem.busy = true;
+                break;
+            }
+            case IntellisenseState.Type.Resolved: {
+                const noConfigFileText = this._state.projectType === ProjectType.TypeScript
+                    ? vscode.l10n.t("No tsconfig")
+                    : vscode.l10n.t("No jsconfig");
+                const rootPath = this._client.getWorkspaceRootForResource(this._state.resource);
+                if (!rootPath) {
+                    if (this._statusItem) {
+                        this._statusItem.text = noConfigFileText;
+                        this._statusItem.detail = !vscode.workspace.workspaceFolders
+                            ? vscode.l10n.t("No opened folders")
+                            : vscode.l10n.t("File is not part opened folders");
+                        this._statusItem.busy = false;
+                    }
+                    return;
+                }
+                const statusItem = this.ensureStatusItem();
+                statusItem.busy = false;
+                statusItem.detail = undefined;
+                statusItem.severity = vscode.LanguageStatusSeverity.Information;
+                if (isImplicitProjectConfigFile(this._state.configFile)) {
+                    statusItem.text = noConfigFileText;
+                    statusItem.detail = undefined;
+                    statusItem.command = {
+                        command: this.createOrOpenConfigCommandId,
+                        title: this._state.projectType === ProjectType.TypeScript
+                            ? vscode.l10n.t("Configure tsconfig")
+                            : vscode.l10n.t("Configure jsconfig"),
+                        arguments: [rootPath, this._state.projectType] satisfies CreateOrOpenConfigCommandArgs,
+                    };
+                }
+                else {
+                    statusItem.text = vscode.workspace.asRelativePath(this._state.configFile);
+                    statusItem.detail = undefined;
+                    statusItem.command = {
+                        command: this.openOpenConfigCommandId,
+                        title: vscode.l10n.t("Open config file"),
+                        arguments: [rootPath, this._state.projectType] satisfies CreateOrOpenConfigCommandArgs,
+                    };
+                }
+                break;
+            }
+            case IntellisenseState.Type.SyntaxOnly: {
+                const statusItem = this.ensureStatusItem();
+                statusItem.severity = vscode.LanguageStatusSeverity.Warning;
+                statusItem.text = vscode.l10n.t("Partial Mode");
+                statusItem.detail = vscode.l10n.t("Project Wide IntelliSense not available");
+                statusItem.busy = false;
+                statusItem.command = {
+                    title: vscode.l10n.t("Learn More"),
+                    command: 'vscode.open',
+                    arguments: [
+                        vscode.Uri.parse('https://aka.ms/vscode/jsts/partial-mode'),
+                    ]
+                };
+                break;
+            }
+        }
+    }
+    private ensureStatusItem(): vscode.LanguageStatusItem {
+        if (!this._statusItem) {
+            this._statusItem = vscode.languages.createLanguageStatusItem('typescript.projectStatus', jsTsLanguageModes);
+            this._statusItem.name = vscode.l10n.t("JS/TS IntelliSense Status");
+        }
+        return this._statusItem;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/ui/largeProjectStatus.ts b/extensions/typescript-language-features/Source/ui/largeProjectStatus.ts
index dc1018d0306e7..ea2464b159f54 100644
--- a/extensions/typescript-language-features/Source/ui/largeProjectStatus.ts
+++ b/extensions/typescript-language-features/Source/ui/largeProjectStatus.ts
@@ -2,124 +2,97 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { TelemetryReporter } from '../logging/telemetry';
 import { isImplicitProjectConfigFile, openOrCreateConfig, ProjectType } from '../tsconfig';
 import { ITypeScriptServiceClient } from '../typescriptService';
-
-
 interface Hint {
-	message: string;
+    message: string;
 }
-
 class ExcludeHintItem {
-	public configFileName?: string;
-	private readonly _item: vscode.StatusBarItem;
-	private _currentHint?: Hint;
-
-	constructor(
-		private readonly telemetryReporter: TelemetryReporter
-	) {
-		this._item = vscode.window.createStatusBarItem('status.typescript.exclude', vscode.StatusBarAlignment.Right, 98 /* to the right of typescript version status (99) */);
-		this._item.name = vscode.l10n.t("TypeScript: Configure Excludes");
-		this._item.command = 'js.projectStatus.command';
-	}
-
-	public getCurrentHint(): Hint {
-		return this._currentHint!;
-	}
-
-	public hide() {
-		this._item.hide();
-	}
-
-	public show(largeRoots?: string) {
-		this._currentHint = {
-			message: largeRoots
-				? vscode.l10n.t("To enable project-wide JavaScript/TypeScript language features, exclude folders with many files, like: {0}", largeRoots)
-				: vscode.l10n.t("To enable project-wide JavaScript/TypeScript language features, exclude large folders with source files that you do not work on.")
-		};
-		this._item.tooltip = this._currentHint.message;
-		this._item.text = vscode.l10n.t("Configure Excludes");
-		this._item.tooltip = vscode.l10n.t("To enable project-wide JavaScript/TypeScript language features, exclude large folders with source files that you do not work on.");
-		this._item.color = '#A5DF3B';
-		this._item.show();
-		/* __GDPR__
-			"js.hintProjectExcludes" : {
-				"owner": "mjbvz",
-				"${include}": [
-					"${TypeScriptCommonProperties}"
-				]
-			}
-		*/
-		this.telemetryReporter.logTelemetry('js.hintProjectExcludes');
-	}
+    public configFileName?: string;
+    private readonly _item: vscode.StatusBarItem;
+    private _currentHint?: Hint;
+    constructor(private readonly telemetryReporter: TelemetryReporter) {
+        this._item = vscode.window.createStatusBarItem('status.typescript.exclude', vscode.StatusBarAlignment.Right, 98 /* to the right of typescript version status (99) */);
+        this._item.name = vscode.l10n.t("TypeScript: Configure Excludes");
+        this._item.command = 'js.projectStatus.command';
+    }
+    public getCurrentHint(): Hint {
+        return this._currentHint!;
+    }
+    public hide() {
+        this._item.hide();
+    }
+    public show(largeRoots?: string) {
+        this._currentHint = {
+            message: largeRoots
+                ? vscode.l10n.t("To enable project-wide JavaScript/TypeScript language features, exclude folders with many files, like: {0}", largeRoots)
+                : vscode.l10n.t("To enable project-wide JavaScript/TypeScript language features, exclude large folders with source files that you do not work on.")
+        };
+        this._item.tooltip = this._currentHint.message;
+        this._item.text = vscode.l10n.t("Configure Excludes");
+        this._item.tooltip = vscode.l10n.t("To enable project-wide JavaScript/TypeScript language features, exclude large folders with source files that you do not work on.");
+        this._item.color = '#A5DF3B';
+        this._item.show();
+        /* __GDPR__
+            "js.hintProjectExcludes" : {
+                "owner": "mjbvz",
+                "${include}": [
+                    "${TypeScriptCommonProperties}"
+                ]
+            }
+        */
+        this.telemetryReporter.logTelemetry('js.hintProjectExcludes');
+    }
 }
-
-
 function createLargeProjectMonitorFromTypeScript(item: ExcludeHintItem, client: ITypeScriptServiceClient): vscode.Disposable {
-
-	interface LargeProjectMessageItem extends vscode.MessageItem {
-		index: number;
-	}
-
-	return client.onProjectLanguageServiceStateChanged(body => {
-		if (body.languageServiceEnabled) {
-			item.hide();
-		} else {
-			item.show();
-			const configFileName = body.projectName;
-			if (configFileName) {
-				item.configFileName = configFileName;
-				vscode.window.showWarningMessage(item.getCurrentHint().message,
-					{
-						title: vscode.l10n.t("Configure Excludes"),
-						index: 0
-					}).then(selected => {
-						if (selected && selected.index === 0) {
-							onConfigureExcludesSelected(client, configFileName);
-						}
-					});
-			}
-		}
-	});
+    interface LargeProjectMessageItem extends vscode.MessageItem {
+        index: number;
+    }
+    return client.onProjectLanguageServiceStateChanged(body => {
+        if (body.languageServiceEnabled) {
+            item.hide();
+        }
+        else {
+            item.show();
+            const configFileName = body.projectName;
+            if (configFileName) {
+                item.configFileName = configFileName;
+                vscode.window.showWarningMessage(item.getCurrentHint().message, {
+                    title: vscode.l10n.t("Configure Excludes"),
+                    index: 0
+                }).then(selected => {
+                    if (selected && selected.index === 0) {
+                        onConfigureExcludesSelected(client, configFileName);
+                    }
+                });
+            }
+        }
+    });
 }
-
-function onConfigureExcludesSelected(
-	client: ITypeScriptServiceClient,
-	configFileName: string
-) {
-	if (!isImplicitProjectConfigFile(configFileName)) {
-		vscode.workspace.openTextDocument(configFileName)
-			.then(vscode.window.showTextDocument);
-	} else {
-		const root = client.getWorkspaceRootForResource(vscode.Uri.file(configFileName));
-		if (root) {
-			openOrCreateConfig(
-				client.apiVersion,
-				/tsconfig\.?.*\.json/.test(configFileName) ? ProjectType.TypeScript : ProjectType.JavaScript,
-				root,
-				client.configuration);
-		}
-	}
+function onConfigureExcludesSelected(client: ITypeScriptServiceClient, configFileName: string) {
+    if (!isImplicitProjectConfigFile(configFileName)) {
+        vscode.workspace.openTextDocument(configFileName)
+            .then(vscode.window.showTextDocument);
+    }
+    else {
+        const root = client.getWorkspaceRootForResource(vscode.Uri.file(configFileName));
+        if (root) {
+            openOrCreateConfig(client.apiVersion, /tsconfig\.?.*\.json/.test(configFileName) ? ProjectType.TypeScript : ProjectType.JavaScript, root, client.configuration);
+        }
+    }
 }
-
-export function create(
-	client: ITypeScriptServiceClient,
-): vscode.Disposable {
-	const toDispose: vscode.Disposable[] = [];
-
-	const item = new ExcludeHintItem(client.telemetryReporter);
-	toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
-		if (item.configFileName) {
-			onConfigureExcludesSelected(client, item.configFileName);
-		}
-		const { message } = item.getCurrentHint();
-		return vscode.window.showInformationMessage(message);
-	}));
-
-	toDispose.push(createLargeProjectMonitorFromTypeScript(item, client));
-
-	return vscode.Disposable.from(...toDispose);
+export function create(client: ITypeScriptServiceClient): vscode.Disposable {
+    const toDispose: vscode.Disposable[] = [];
+    const item = new ExcludeHintItem(client.telemetryReporter);
+    toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
+        if (item.configFileName) {
+            onConfigureExcludesSelected(client, item.configFileName);
+        }
+        const { message } = item.getCurrentHint();
+        return vscode.window.showInformationMessage(message);
+    }));
+    toDispose.push(createLargeProjectMonitorFromTypeScript(item, client));
+    return vscode.Disposable.from(...toDispose);
 }
diff --git a/extensions/typescript-language-features/Source/ui/managedFileContext.ts b/extensions/typescript-language-features/Source/ui/managedFileContext.ts
index 1da4588a334fd..21db5cdc8f802 100644
--- a/extensions/typescript-language-features/Source/ui/managedFileContext.ts
+++ b/extensions/typescript-language-features/Source/ui/managedFileContext.ts
@@ -2,56 +2,45 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { disabledSchemes } from '../configuration/fileSchemes';
 import { isJsConfigOrTsConfigFileName } from '../configuration/languageDescription';
 import { isSupportedLanguageMode } from '../configuration/languageIds';
 import { Disposable } from '../utils/dispose';
 import { ActiveJsTsEditorTracker } from './activeJsTsEditorTracker';
-
 /**
  * When clause context set when the current file is managed by vscode's built-in typescript extension.
  */
 export default class ManagedFileContextManager extends Disposable {
-	private static readonly contextName = 'typescript.isManagedFile';
-
-	private isInManagedFileContext: boolean = false;
-
-	public constructor(activeJsTsEditorTracker: ActiveJsTsEditorTracker) {
-		super();
-		activeJsTsEditorTracker.onDidChangeActiveJsTsEditor(this.onDidChangeActiveTextEditor, this, this._disposables);
-
-		this.onDidChangeActiveTextEditor(activeJsTsEditorTracker.activeJsTsEditor);
-	}
-
-	private onDidChangeActiveTextEditor(editor?: vscode.TextEditor): void {
-		if (editor) {
-			this.updateContext(this.isManagedFile(editor));
-		} else {
-			this.updateContext(false);
-		}
-	}
-
-	private updateContext(newValue: boolean) {
-		if (newValue === this.isInManagedFileContext) {
-			return;
-		}
-
-		vscode.commands.executeCommand('setContext', ManagedFileContextManager.contextName, newValue);
-		this.isInManagedFileContext = newValue;
-	}
-
-	private isManagedFile(editor: vscode.TextEditor): boolean {
-		return this.isManagedScriptFile(editor) || this.isManagedConfigFile(editor);
-	}
-
-	private isManagedScriptFile(editor: vscode.TextEditor): boolean {
-		return isSupportedLanguageMode(editor.document) && !disabledSchemes.has(editor.document.uri.scheme);
-	}
-
-	private isManagedConfigFile(editor: vscode.TextEditor): boolean {
-		return isJsConfigOrTsConfigFileName(editor.document.fileName);
-	}
+    private static readonly contextName = 'typescript.isManagedFile';
+    private isInManagedFileContext: boolean = false;
+    public constructor(activeJsTsEditorTracker: ActiveJsTsEditorTracker) {
+        super();
+        activeJsTsEditorTracker.onDidChangeActiveJsTsEditor(this.onDidChangeActiveTextEditor, this, this._disposables);
+        this.onDidChangeActiveTextEditor(activeJsTsEditorTracker.activeJsTsEditor);
+    }
+    private onDidChangeActiveTextEditor(editor?: vscode.TextEditor): void {
+        if (editor) {
+            this.updateContext(this.isManagedFile(editor));
+        }
+        else {
+            this.updateContext(false);
+        }
+    }
+    private updateContext(newValue: boolean) {
+        if (newValue === this.isInManagedFileContext) {
+            return;
+        }
+        vscode.commands.executeCommand('setContext', ManagedFileContextManager.contextName, newValue);
+        this.isInManagedFileContext = newValue;
+    }
+    private isManagedFile(editor: vscode.TextEditor): boolean {
+        return this.isManagedScriptFile(editor) || this.isManagedConfigFile(editor);
+    }
+    private isManagedScriptFile(editor: vscode.TextEditor): boolean {
+        return isSupportedLanguageMode(editor.document) && !disabledSchemes.has(editor.document.uri.scheme);
+    }
+    private isManagedConfigFile(editor: vscode.TextEditor): boolean {
+        return isJsConfigOrTsConfigFileName(editor.document.fileName);
+    }
 }
-
diff --git a/extensions/typescript-language-features/Source/ui/typingsStatus.ts b/extensions/typescript-language-features/Source/ui/typingsStatus.ts
index 3e8d7c4efac4c..2cf466dc387e4 100644
--- a/extensions/typescript-language-features/Source/ui/typingsStatus.ts
+++ b/extensions/typescript-language-features/Source/ui/typingsStatus.ts
@@ -2,115 +2,86 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import { Disposable } from '../utils/dispose';
-
-
 const typingsInstallTimeout = 30 * 1000;
-
 export default class TypingsStatus extends Disposable {
-	private readonly _acquiringTypings = new Map();
-	private readonly _client: ITypeScriptServiceClient;
-
-	constructor(client: ITypeScriptServiceClient) {
-		super();
-		this._client = client;
-
-		this._register(
-			this._client.onDidBeginInstallTypings(event => this.onBeginInstallTypings(event.eventId)));
-
-		this._register(
-			this._client.onDidEndInstallTypings(event => this.onEndInstallTypings(event.eventId)));
-	}
-
-	public override dispose(): void {
-		super.dispose();
-
-		for (const timeout of this._acquiringTypings.values()) {
-			clearTimeout(timeout);
-		}
-	}
-
-	public get isAcquiringTypings(): boolean {
-		return Object.keys(this._acquiringTypings).length > 0;
-	}
-
-	private onBeginInstallTypings(eventId: number): void {
-		if (this._acquiringTypings.has(eventId)) {
-			return;
-		}
-		this._acquiringTypings.set(eventId, setTimeout(() => {
-			this.onEndInstallTypings(eventId);
-		}, typingsInstallTimeout));
-	}
-
-	private onEndInstallTypings(eventId: number): void {
-		const timer = this._acquiringTypings.get(eventId);
-		if (timer) {
-			clearTimeout(timer);
-		}
-		this._acquiringTypings.delete(eventId);
-	}
+    private readonly _acquiringTypings = new Map();
+    private readonly _client: ITypeScriptServiceClient;
+    constructor(client: ITypeScriptServiceClient) {
+        super();
+        this._client = client;
+        this._register(this._client.onDidBeginInstallTypings(event => this.onBeginInstallTypings(event.eventId)));
+        this._register(this._client.onDidEndInstallTypings(event => this.onEndInstallTypings(event.eventId)));
+    }
+    public override dispose(): void {
+        super.dispose();
+        for (const timeout of this._acquiringTypings.values()) {
+            clearTimeout(timeout);
+        }
+    }
+    public get isAcquiringTypings(): boolean {
+        return Object.keys(this._acquiringTypings).length > 0;
+    }
+    private onBeginInstallTypings(eventId: number): void {
+        if (this._acquiringTypings.has(eventId)) {
+            return;
+        }
+        this._acquiringTypings.set(eventId, setTimeout(() => {
+            this.onEndInstallTypings(eventId);
+        }, typingsInstallTimeout));
+    }
+    private onEndInstallTypings(eventId: number): void {
+        const timer = this._acquiringTypings.get(eventId);
+        if (timer) {
+            clearTimeout(timer);
+        }
+        this._acquiringTypings.delete(eventId);
+    }
 }
-
 export class AtaProgressReporter extends Disposable {
-
-	private readonly _promises = new Map();
-
-	constructor(client: ITypeScriptServiceClient) {
-		super();
-		this._register(client.onDidBeginInstallTypings(e => this._onBegin(e.eventId)));
-		this._register(client.onDidEndInstallTypings(e => this._onEndOrTimeout(e.eventId)));
-		this._register(client.onTypesInstallerInitializationFailed(_ => this.onTypesInstallerInitializationFailed()));
-	}
-
-	override dispose(): void {
-		super.dispose();
-		this._promises.forEach(value => value());
-	}
-
-	private _onBegin(eventId: number): void {
-		const handle = setTimeout(() => this._onEndOrTimeout(eventId), typingsInstallTimeout);
-		const promise = new Promise(resolve => {
-			this._promises.set(eventId, () => {
-				clearTimeout(handle);
-				resolve();
-			});
-		});
-
-		vscode.window.withProgress({
-			location: vscode.ProgressLocation.Window,
-			title: vscode.l10n.t("Fetching data for better TypeScript IntelliSense")
-		}, () => promise);
-	}
-
-	private _onEndOrTimeout(eventId: number): void {
-		const resolve = this._promises.get(eventId);
-		if (resolve) {
-			this._promises.delete(eventId);
-			resolve();
-		}
-	}
-
-	private async onTypesInstallerInitializationFailed() {
-		const config = vscode.workspace.getConfiguration('typescript');
-
-		if (config.get('check.npmIsInstalled', true)) {
-			const dontShowAgain: vscode.MessageItem = {
-				title: vscode.l10n.t("Don't Show Again"),
-			};
-			const selected = await vscode.window.showWarningMessage(
-				vscode.l10n.t(
-					"Could not install typings files for JavaScript language features. Please ensure that NPM is installed, or configure 'typescript.npm' in your user settings. Alternatively, check the [documentation]({0}) to learn more.",
-					'https://go.microsoft.com/fwlink/?linkid=847635'
-				),
-				dontShowAgain);
-
-			if (selected === dontShowAgain) {
-				config.update('check.npmIsInstalled', false, true);
-			}
-		}
-	}
+    private readonly _promises = new Map();
+    constructor(client: ITypeScriptServiceClient) {
+        super();
+        this._register(client.onDidBeginInstallTypings(e => this._onBegin(e.eventId)));
+        this._register(client.onDidEndInstallTypings(e => this._onEndOrTimeout(e.eventId)));
+        this._register(client.onTypesInstallerInitializationFailed(_ => this.onTypesInstallerInitializationFailed()));
+    }
+    override dispose(): void {
+        super.dispose();
+        this._promises.forEach(value => value());
+    }
+    private _onBegin(eventId: number): void {
+        const handle = setTimeout(() => this._onEndOrTimeout(eventId), typingsInstallTimeout);
+        const promise = new Promise(resolve => {
+            this._promises.set(eventId, () => {
+                clearTimeout(handle);
+                resolve();
+            });
+        });
+        vscode.window.withProgress({
+            location: vscode.ProgressLocation.Window,
+            title: vscode.l10n.t("Fetching data for better TypeScript IntelliSense")
+        }, () => promise);
+    }
+    private _onEndOrTimeout(eventId: number): void {
+        const resolve = this._promises.get(eventId);
+        if (resolve) {
+            this._promises.delete(eventId);
+            resolve();
+        }
+    }
+    private async onTypesInstallerInitializationFailed() {
+        const config = vscode.workspace.getConfiguration('typescript');
+        if (config.get('check.npmIsInstalled', true)) {
+            const dontShowAgain: vscode.MessageItem = {
+                title: vscode.l10n.t("Don't Show Again"),
+            };
+            const selected = await vscode.window.showWarningMessage(vscode.l10n.t("Could not install typings files for JavaScript language features. Please ensure that NPM is installed, or configure 'typescript.npm' in your user settings. Alternatively, check the [documentation]({0}) to learn more.", 'https://go.microsoft.com/fwlink/?linkid=847635'), dontShowAgain);
+            if (selected === dontShowAgain) {
+                config.update('check.npmIsInstalled', false, true);
+            }
+        }
+    }
 }
diff --git a/extensions/typescript-language-features/Source/ui/versionStatus.ts b/extensions/typescript-language-features/Source/ui/versionStatus.ts
index a4f377782a8f6..9d154b65e9da5 100644
--- a/extensions/typescript-language-features/Source/ui/versionStatus.ts
+++ b/extensions/typescript-language-features/Source/ui/versionStatus.ts
@@ -2,38 +2,27 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import { SelectTypeScriptVersionCommand } from '../commands/selectTypeScriptVersion';
 import { jsTsLanguageModes } from '../configuration/languageIds';
 import { TypeScriptVersion } from '../tsServer/versionProvider';
 import { ITypeScriptServiceClient } from '../typescriptService';
 import { Disposable } from '../utils/dispose';
-
-
 export class VersionStatus extends Disposable {
-
-	private readonly _statusItem: vscode.LanguageStatusItem;
-
-	constructor(
-		private readonly _client: ITypeScriptServiceClient,
-	) {
-		super();
-
-		this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.version', jsTsLanguageModes));
-
-		this._statusItem.name = vscode.l10n.t("TypeScript Version");
-		this._statusItem.detail = vscode.l10n.t("TypeScript Version");
-
-		this._register(this._client.onTsServerStarted(({ version }) => this.onDidChangeTypeScriptVersion(version)));
-	}
-
-	private onDidChangeTypeScriptVersion(version: TypeScriptVersion) {
-		this._statusItem.text = version.displayName;
-		this._statusItem.command = {
-			command: SelectTypeScriptVersionCommand.id,
-			title: vscode.l10n.t("Select Version"),
-			tooltip: version.path
-		};
-	}
+    private readonly _statusItem: vscode.LanguageStatusItem;
+    constructor(private readonly _client: ITypeScriptServiceClient) {
+        super();
+        this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.version', jsTsLanguageModes));
+        this._statusItem.name = vscode.l10n.t("TypeScript Version");
+        this._statusItem.detail = vscode.l10n.t("TypeScript Version");
+        this._register(this._client.onTsServerStarted(({ version }) => this.onDidChangeTypeScriptVersion(version)));
+    }
+    private onDidChangeTypeScriptVersion(version: TypeScriptVersion) {
+        this._statusItem.text = version.displayName;
+        this._statusItem.command = {
+            command: SelectTypeScriptVersionCommand.id,
+            title: vscode.l10n.t("Select Version"),
+            tooltip: version.path
+        };
+    }
 }
diff --git a/extensions/typescript-language-features/Source/utils/arrays.ts b/extensions/typescript-language-features/Source/utils/arrays.ts
index 61850a3de3ed8..eeded62ee3e42 100644
--- a/extensions/typescript-language-features/Source/utils/arrays.ts
+++ b/extensions/typescript-language-features/Source/utils/arrays.ts
@@ -2,23 +2,16 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export const empty = Object.freeze([]);
-
-export function equals(
-	a: ReadonlyArray,
-	b: ReadonlyArray,
-	itemEquals: (a: T, b: T) => boolean = (a, b) => a === b
-): boolean {
-	if (a === b) {
-		return true;
-	}
-	if (a.length !== b.length) {
-		return false;
-	}
-	return a.every((x, i) => itemEquals(x, b[i]));
+export function equals(a: ReadonlyArray, b: ReadonlyArray, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
+    if (a === b) {
+        return true;
+    }
+    if (a.length !== b.length) {
+        return false;
+    }
+    return a.every((x, i) => itemEquals(x, b[i]));
 }
-
 export function coalesce(array: ReadonlyArray): T[] {
-	return array.filter((e): e is T => !!e);
+    return array.filter((e): e is T => !!e);
 }
diff --git a/extensions/typescript-language-features/Source/utils/async.ts b/extensions/typescript-language-features/Source/utils/async.ts
index 9523d7fe67a5b..aa3b892a07a2d 100644
--- a/extensions/typescript-language-features/Source/utils/async.ts
+++ b/extensions/typescript-language-features/Source/utils/async.ts
@@ -2,76 +2,64 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { Disposable } from 'vscode';
-
 export interface ITask {
-	(): T;
+    (): T;
 }
-
 export class Delayer {
-
-	public defaultDelay: number;
-	private timeout: any; // Timer
-	private completionPromise: Promise | null;
-	private onSuccess: ((value: T | PromiseLike | undefined) => void) | null;
-	private task: ITask | null;
-
-	constructor(defaultDelay: number) {
-		this.defaultDelay = defaultDelay;
-		this.timeout = null;
-		this.completionPromise = null;
-		this.onSuccess = null;
-		this.task = null;
-	}
-
-	public trigger(task: ITask, delay: number = this.defaultDelay): Promise {
-		this.task = task;
-		if (delay >= 0) {
-			this.cancelTimeout();
-		}
-
-		if (!this.completionPromise) {
-			this.completionPromise = new Promise((resolve) => {
-				this.onSuccess = resolve;
-			}).then(() => {
-				this.completionPromise = null;
-				this.onSuccess = null;
-				const result = this.task?.();
-				this.task = null;
-				return result;
-			});
-		}
-
-		if (delay >= 0 || this.timeout === null) {
-			this.timeout = setTimeout(() => {
-				this.timeout = null;
-				this.onSuccess?.(undefined);
-			}, delay >= 0 ? delay : this.defaultDelay);
-		}
-
-		return this.completionPromise;
-	}
-
-	private cancelTimeout(): void {
-		if (this.timeout !== null) {
-			clearTimeout(this.timeout);
-			this.timeout = null;
-		}
-	}
+    public defaultDelay: number;
+    private timeout: any; // Timer
+    private completionPromise: Promise | null;
+    private onSuccess: ((value: T | PromiseLike | undefined) => void) | null;
+    private task: ITask | null;
+    constructor(defaultDelay: number) {
+        this.defaultDelay = defaultDelay;
+        this.timeout = null;
+        this.completionPromise = null;
+        this.onSuccess = null;
+        this.task = null;
+    }
+    public trigger(task: ITask, delay: number = this.defaultDelay): Promise {
+        this.task = task;
+        if (delay >= 0) {
+            this.cancelTimeout();
+        }
+        if (!this.completionPromise) {
+            this.completionPromise = new Promise((resolve) => {
+                this.onSuccess = resolve;
+            }).then(() => {
+                this.completionPromise = null;
+                this.onSuccess = null;
+                const result = this.task?.();
+                this.task = null;
+                return result;
+            });
+        }
+        if (delay >= 0 || this.timeout === null) {
+            this.timeout = setTimeout(() => {
+                this.timeout = null;
+                this.onSuccess?.(undefined);
+            }, delay >= 0 ? delay : this.defaultDelay);
+        }
+        return this.completionPromise;
+    }
+    private cancelTimeout(): void {
+        if (this.timeout !== null) {
+            clearTimeout(this.timeout);
+            this.timeout = null;
+        }
+    }
 }
-
 export function setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable {
-	if (global.setImmediate) {
-		const handle = global.setImmediate(callback, ...args);
-		return { dispose: () => global.clearImmediate(handle) };
-	} else {
-		const handle = setTimeout(callback, 0, ...args);
-		return { dispose: () => clearTimeout(handle) };
-	}
+    if (global.setImmediate) {
+        const handle = global.setImmediate(callback, ...args);
+        return { dispose: () => global.clearImmediate(handle) };
+    }
+    else {
+        const handle = setTimeout(callback, 0, ...args);
+        return { dispose: () => clearTimeout(handle) };
+    }
 }
-
-
 /**
  * A helper to prevent accumulation of sequential async tasks.
  *
@@ -99,65 +87,51 @@ export function setImmediate(callback: (...args: any[]) => void, ...args: any[])
  * 		}
  */
 export class Throttler {
-
-	private activePromise: Promise | null;
-	private queuedPromise: Promise | null;
-	private queuedPromiseFactory: ITask> | null;
-
-	private isDisposed = false;
-
-	constructor() {
-		this.activePromise = null;
-		this.queuedPromise = null;
-		this.queuedPromiseFactory = null;
-	}
-
-	queue(promiseFactory: ITask>): Promise {
-		if (this.isDisposed) {
-			return Promise.reject(new Error('Throttler is disposed'));
-		}
-
-		if (this.activePromise) {
-			this.queuedPromiseFactory = promiseFactory;
-
-			if (!this.queuedPromise) {
-				const onComplete = () => {
-					this.queuedPromise = null;
-
-					if (this.isDisposed) {
-						return;
-					}
-
-					const result = this.queue(this.queuedPromiseFactory!);
-					this.queuedPromiseFactory = null;
-
-					return result;
-				};
-
-				this.queuedPromise = new Promise(resolve => {
-					this.activePromise!.then(onComplete, onComplete).then(resolve);
-				});
-			}
-
-			return new Promise((resolve, reject) => {
-				this.queuedPromise!.then(resolve, reject);
-			});
-		}
-
-		this.activePromise = promiseFactory();
-
-		return new Promise((resolve, reject) => {
-			this.activePromise!.then((result: T) => {
-				this.activePromise = null;
-				resolve(result);
-			}, (err: unknown) => {
-				this.activePromise = null;
-				reject(err);
-			});
-		});
-	}
-
-	dispose(): void {
-		this.isDisposed = true;
-	}
+    private activePromise: Promise | null;
+    private queuedPromise: Promise | null;
+    private queuedPromiseFactory: ITask> | null;
+    private isDisposed = false;
+    constructor() {
+        this.activePromise = null;
+        this.queuedPromise = null;
+        this.queuedPromiseFactory = null;
+    }
+    queue(promiseFactory: ITask>): Promise {
+        if (this.isDisposed) {
+            return Promise.reject(new Error('Throttler is disposed'));
+        }
+        if (this.activePromise) {
+            this.queuedPromiseFactory = promiseFactory;
+            if (!this.queuedPromise) {
+                const onComplete = () => {
+                    this.queuedPromise = null;
+                    if (this.isDisposed) {
+                        return;
+                    }
+                    const result = this.queue(this.queuedPromiseFactory!);
+                    this.queuedPromiseFactory = null;
+                    return result;
+                };
+                this.queuedPromise = new Promise(resolve => {
+                    this.activePromise!.then(onComplete, onComplete).then(resolve);
+                });
+            }
+            return new Promise((resolve, reject) => {
+                this.queuedPromise!.then(resolve, reject);
+            });
+        }
+        this.activePromise = promiseFactory();
+        return new Promise((resolve, reject) => {
+            this.activePromise!.then((result: T) => {
+                this.activePromise = null;
+                resolve(result);
+            }, (err: unknown) => {
+                this.activePromise = null;
+                reject(err);
+            });
+        });
+    }
+    dispose(): void {
+        this.isDisposed = true;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/utils/cancellation.ts b/extensions/typescript-language-features/Source/utils/cancellation.ts
index 72672663ad275..83b95e5f4cb7e 100644
--- a/extensions/typescript-language-features/Source/utils/cancellation.ts
+++ b/extensions/typescript-language-features/Source/utils/cancellation.ts
@@ -2,12 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 const noopDisposable = vscode.Disposable.from();
-
 export const nulToken: vscode.CancellationToken = {
-	isCancellationRequested: false,
-	onCancellationRequested: () => noopDisposable
+    isCancellationRequested: false,
+    onCancellationRequested: () => noopDisposable
 };
diff --git a/extensions/typescript-language-features/Source/utils/dispose.ts b/extensions/typescript-language-features/Source/utils/dispose.ts
index e7687bb6941ef..0c80d0925f244 100644
--- a/extensions/typescript-language-features/Source/utils/dispose.ts
+++ b/extensions/typescript-language-features/Source/utils/dispose.ts
@@ -2,64 +2,53 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
-
 export function disposeAll(disposables: Iterable) {
-	const errors: any[] = [];
-
-	for (const disposable of disposables) {
-		try {
-			disposable.dispose();
-		} catch (e) {
-			errors.push(e);
-		}
-	}
-
-	if (errors.length === 1) {
-		throw errors[0];
-	} else if (errors.length > 1) {
-		throw new AggregateError(errors, 'Encountered errors while disposing of store');
-	}
+    const errors: any[] = [];
+    for (const disposable of disposables) {
+        try {
+            disposable.dispose();
+        }
+        catch (e) {
+            errors.push(e);
+        }
+    }
+    if (errors.length === 1) {
+        throw errors[0];
+    }
+    else if (errors.length > 1) {
+        throw new AggregateError(errors, 'Encountered errors while disposing of store');
+    }
 }
-
 export interface IDisposable {
-	dispose(): void;
+    dispose(): void;
 }
-
 export abstract class Disposable {
-	private _isDisposed = false;
-
-	protected _disposables: vscode.Disposable[] = [];
-
-	public dispose(): any {
-		if (this._isDisposed) {
-			return;
-		}
-		this._isDisposed = true;
-		disposeAll(this._disposables);
-	}
-
-	protected _register(value: T): T {
-		if (this._isDisposed) {
-			value.dispose();
-		} else {
-			this._disposables.push(value);
-		}
-		return value;
-	}
-
-	protected get isDisposed() {
-		return this._isDisposed;
-	}
+    private _isDisposed = false;
+    protected _disposables: vscode.Disposable[] = [];
+    public dispose(): any {
+        if (this._isDisposed) {
+            return;
+        }
+        this._isDisposed = true;
+        disposeAll(this._disposables);
+    }
+    protected _register(value: T): T {
+        if (this._isDisposed) {
+            value.dispose();
+        }
+        else {
+            this._disposables.push(value);
+        }
+        return value;
+    }
+    protected get isDisposed() {
+        return this._isDisposed;
+    }
 }
-
 export class DisposableStore extends Disposable {
-
-	public add(disposable: T): T {
-		this._register(disposable);
-
-		return disposable;
-	}
+    public add(disposable: T): T {
+        this._register(disposable);
+        return disposable;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/utils/fs.electron.ts b/extensions/typescript-language-features/Source/utils/fs.electron.ts
index 867b075bef3e5..918ef6ecd0d3f 100644
--- a/extensions/typescript-language-features/Source/utils/fs.electron.ts
+++ b/extensions/typescript-language-features/Source/utils/fs.electron.ts
@@ -2,24 +2,24 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as fs from 'fs';
 import { getTempFile } from './temp.electron';
-
 export const onCaseInsensitiveFileSystem = (() => {
-	let value: boolean | undefined;
-	return (): boolean => {
-		if (typeof value === 'undefined') {
-			if (process.platform === 'win32') {
-				value = true;
-			} else if (process.platform !== 'darwin') {
-				value = false;
-			} else {
-				const temp = getTempFile('typescript-case-check');
-				fs.writeFileSync(temp, '');
-				value = fs.existsSync(temp.toUpperCase());
-			}
-		}
-		return value;
-	};
+    let value: boolean | undefined;
+    return (): boolean => {
+        if (typeof value === 'undefined') {
+            if (process.platform === 'win32') {
+                value = true;
+            }
+            else if (process.platform !== 'darwin') {
+                value = false;
+            }
+            else {
+                const temp = getTempFile('typescript-case-check');
+                fs.writeFileSync(temp, '');
+                value = fs.existsSync(temp.toUpperCase());
+            }
+        }
+        return value;
+    };
 })();
diff --git a/extensions/typescript-language-features/Source/utils/fs.ts b/extensions/typescript-language-features/Source/utils/fs.ts
index a742b9604f839..7cccf25ccd895 100644
--- a/extensions/typescript-language-features/Source/utils/fs.ts
+++ b/extensions/typescript-language-features/Source/utils/fs.ts
@@ -2,19 +2,17 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export async function exists(resource: vscode.Uri): Promise {
-	try {
-		const stat = await vscode.workspace.fs.stat(resource);
-		// stat.type is an enum flag
-		return !!(stat.type & vscode.FileType.File);
-	} catch {
-		return false;
-	}
+    try {
+        const stat = await vscode.workspace.fs.stat(resource);
+        // stat.type is an enum flag
+        return !!(stat.type & vscode.FileType.File);
+    }
+    catch {
+        return false;
+    }
 }
-
 export function looksLikeAbsoluteWindowsPath(path: string): boolean {
-	return /^[a-zA-Z]:[\/\\]/.test(path);
+    return /^[a-zA-Z]:[\/\\]/.test(path);
 }
diff --git a/extensions/typescript-language-features/Source/utils/hash.ts b/extensions/typescript-language-features/Source/utils/hash.ts
index b009808968d73..0a28eefb46a80 100644
--- a/extensions/typescript-language-features/Source/utils/hash.ts
+++ b/extensions/typescript-language-features/Source/utils/hash.ts
@@ -2,57 +2,52 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 /**
  * Return a hash value for an object.
  */
 export function hash(obj: any, hashVal = 0): number {
-	switch (typeof obj) {
-		case 'object':
-			if (obj === null) {
-				return numberHash(349, hashVal);
-			} else if (Array.isArray(obj)) {
-				return arrayHash(obj, hashVal);
-			}
-			return objectHash(obj, hashVal);
-		case 'string':
-			return stringHash(obj, hashVal);
-		case 'boolean':
-			return booleanHash(obj, hashVal);
-		case 'number':
-			return numberHash(obj, hashVal);
-		case 'undefined':
-			return 937 * 31;
-		default:
-			return numberHash(obj, 617);
-	}
+    switch (typeof obj) {
+        case 'object':
+            if (obj === null) {
+                return numberHash(349, hashVal);
+            }
+            else if (Array.isArray(obj)) {
+                return arrayHash(obj, hashVal);
+            }
+            return objectHash(obj, hashVal);
+        case 'string':
+            return stringHash(obj, hashVal);
+        case 'boolean':
+            return booleanHash(obj, hashVal);
+        case 'number':
+            return numberHash(obj, hashVal);
+        case 'undefined':
+            return 937 * 31;
+        default:
+            return numberHash(obj, 617);
+    }
 }
-
 function numberHash(val: number, initialHashVal: number): number {
-	return (((initialHashVal << 5) - initialHashVal) + val) | 0;  // hashVal * 31 + ch, keep as int32
+    return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
 }
-
 function booleanHash(b: boolean, initialHashVal: number): number {
-	return numberHash(b ? 433 : 863, initialHashVal);
+    return numberHash(b ? 433 : 863, initialHashVal);
 }
-
 function stringHash(s: string, hashVal: number) {
-	hashVal = numberHash(149417, hashVal);
-	for (let i = 0, length = s.length; i < length; i++) {
-		hashVal = numberHash(s.charCodeAt(i), hashVal);
-	}
-	return hashVal;
+    hashVal = numberHash(149417, hashVal);
+    for (let i = 0, length = s.length; i < length; i++) {
+        hashVal = numberHash(s.charCodeAt(i), hashVal);
+    }
+    return hashVal;
 }
-
 function arrayHash(arr: any[], initialHashVal: number): number {
-	initialHashVal = numberHash(104579, initialHashVal);
-	return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal);
+    initialHashVal = numberHash(104579, initialHashVal);
+    return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal);
 }
-
 function objectHash(obj: any, initialHashVal: number): number {
-	initialHashVal = numberHash(181387, initialHashVal);
-	return Object.keys(obj).sort().reduce((hashVal, key) => {
-		hashVal = stringHash(key, hashVal);
-		return hash(obj[key], hashVal);
-	}, initialHashVal);
+    initialHashVal = numberHash(181387, initialHashVal);
+    return Object.keys(obj).sort().reduce((hashVal, key) => {
+        hashVal = stringHash(key, hashVal);
+        return hash(obj[key], hashVal);
+    }, initialHashVal);
 }
diff --git a/extensions/typescript-language-features/Source/utils/lazy.ts b/extensions/typescript-language-features/Source/utils/lazy.ts
index 23c000bc65d60..31db59ff0baf4 100644
--- a/extensions/typescript-language-features/Source/utils/lazy.ts
+++ b/extensions/typescript-language-features/Source/utils/lazy.ts
@@ -2,38 +2,29 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export interface Lazy {
-	value: T;
-	hasValue: boolean;
-	map(f: (x: T) => R): Lazy;
+    value: T;
+    hasValue: boolean;
+    map(f: (x: T) => R): Lazy;
 }
-
 class LazyValue implements Lazy {
-	private _hasValue: boolean = false;
-	private _value?: T;
-
-	constructor(
-		private readonly _getValue: () => T
-	) { }
-
-	get value(): T {
-		if (!this._hasValue) {
-			this._hasValue = true;
-			this._value = this._getValue();
-		}
-		return this._value!;
-	}
-
-	get hasValue(): boolean {
-		return this._hasValue;
-	}
-
-	public map(f: (x: T) => R): Lazy {
-		return new LazyValue(() => f(this.value));
-	}
+    private _hasValue: boolean = false;
+    private _value?: T;
+    constructor(private readonly _getValue: () => T) { }
+    get value(): T {
+        if (!this._hasValue) {
+            this._hasValue = true;
+            this._value = this._getValue();
+        }
+        return this._value!;
+    }
+    get hasValue(): boolean {
+        return this._hasValue;
+    }
+    public map(f: (x: T) => R): Lazy {
+        return new LazyValue(() => f(this.value));
+    }
 }
-
 export function lazy(getValue: () => T): Lazy {
-	return new LazyValue(getValue);
-}
\ No newline at end of file
+    return new LazyValue(getValue);
+}
diff --git a/extensions/typescript-language-features/Source/utils/memoize.ts b/extensions/typescript-language-features/Source/utils/memoize.ts
index 12ce836dd0dd0..28f1363ad24fa 100644
--- a/extensions/typescript-language-features/Source/utils/memoize.ts
+++ b/extensions/typescript-language-features/Source/utils/memoize.ts
@@ -2,33 +2,30 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function memoize(_target: any, key: string, descriptor: any) {
-	let fnKey: string | undefined;
-	let fn: Function | undefined;
-
-	if (typeof descriptor.value === 'function') {
-		fnKey = 'value';
-		fn = descriptor.value;
-	} else if (typeof descriptor.get === 'function') {
-		fnKey = 'get';
-		fn = descriptor.get;
-	} else {
-		throw new Error('not supported');
-	}
-
-	const memoizeKey = `$memoize$${key}`;
-
-	descriptor[fnKey] = function (...args: any[]) {
-		if (!this.hasOwnProperty(memoizeKey)) {
-			Object.defineProperty(this, memoizeKey, {
-				configurable: false,
-				enumerable: false,
-				writable: false,
-				value: fn!.apply(this, args)
-			});
-		}
-
-		return this[memoizeKey];
-	};
+    let fnKey: string | undefined;
+    let fn: Function | undefined;
+    if (typeof descriptor.value === 'function') {
+        fnKey = 'value';
+        fn = descriptor.value;
+    }
+    else if (typeof descriptor.get === 'function') {
+        fnKey = 'get';
+        fn = descriptor.get;
+    }
+    else {
+        throw new Error('not supported');
+    }
+    const memoizeKey = `$memoize$${key}`;
+    descriptor[fnKey] = function (...args: any[]) {
+        if (!this.hasOwnProperty(memoizeKey)) {
+            Object.defineProperty(this, memoizeKey, {
+                configurable: false,
+                enumerable: false,
+                writable: false,
+                value: fn!.apply(this, args)
+            });
+        }
+        return this[memoizeKey];
+    };
 }
diff --git a/extensions/typescript-language-features/Source/utils/objects.ts b/extensions/typescript-language-features/Source/utils/objects.ts
index a31467bd8d664..d9f6e58bf4f42 100644
--- a/extensions/typescript-language-features/Source/utils/objects.ts
+++ b/extensions/typescript-language-features/Source/utils/objects.ts
@@ -2,42 +2,40 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as array from './arrays';
-
 export function equals(one: any, other: any): boolean {
-	if (one === other) {
-		return true;
-	}
-	if (one === null || one === undefined || other === null || other === undefined) {
-		return false;
-	}
-	if (typeof one !== typeof other) {
-		return false;
-	}
-	if (typeof one !== 'object') {
-		return false;
-	}
-	if (Array.isArray(one) !== Array.isArray(other)) {
-		return false;
-	}
-
-	if (Array.isArray(one)) {
-		return array.equals(one, other, equals);
-	} else {
-		const oneKeys: string[] = [];
-		for (const key in one) {
-			oneKeys.push(key);
-		}
-		oneKeys.sort();
-		const otherKeys: string[] = [];
-		for (const key in other) {
-			otherKeys.push(key);
-		}
-		otherKeys.sort();
-		if (!array.equals(oneKeys, otherKeys)) {
-			return false;
-		}
-		return oneKeys.every(key => equals(one[key], other[key]));
-	}
+    if (one === other) {
+        return true;
+    }
+    if (one === null || one === undefined || other === null || other === undefined) {
+        return false;
+    }
+    if (typeof one !== typeof other) {
+        return false;
+    }
+    if (typeof one !== 'object') {
+        return false;
+    }
+    if (Array.isArray(one) !== Array.isArray(other)) {
+        return false;
+    }
+    if (Array.isArray(one)) {
+        return array.equals(one, other, equals);
+    }
+    else {
+        const oneKeys: string[] = [];
+        for (const key in one) {
+            oneKeys.push(key);
+        }
+        oneKeys.sort();
+        const otherKeys: string[] = [];
+        for (const key in other) {
+            otherKeys.push(key);
+        }
+        otherKeys.sort();
+        if (!array.equals(oneKeys, otherKeys)) {
+            return false;
+        }
+        return oneKeys.every(key => equals(one[key], other[key]));
+    }
 }
diff --git a/extensions/typescript-language-features/Source/utils/packageInfo.ts b/extensions/typescript-language-features/Source/utils/packageInfo.ts
index 09536ab4141c9..056879c0f9ef7 100644
--- a/extensions/typescript-language-features/Source/utils/packageInfo.ts
+++ b/extensions/typescript-language-features/Source/utils/packageInfo.ts
@@ -2,23 +2,20 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export interface PackageInfo {
-	name: string;
-	version: string;
-	aiKey: string;
+    name: string;
+    version: string;
+    aiKey: string;
 }
-
 export function getPackageInfo(context: vscode.ExtensionContext) {
-	const packageJSON = context.extension.packageJSON;
-	if (packageJSON && typeof packageJSON === 'object') {
-		return {
-			name: packageJSON.name ?? '',
-			version: packageJSON.version ?? '',
-			aiKey: packageJSON.aiKey ?? '',
-		};
-	}
-	return null;
+    const packageJSON = context.extension.packageJSON;
+    if (packageJSON && typeof packageJSON === 'object') {
+        return {
+            name: packageJSON.name ?? '',
+            version: packageJSON.version ?? '',
+            aiKey: packageJSON.aiKey ?? '',
+        };
+    }
+    return null;
 }
diff --git a/extensions/typescript-language-features/Source/utils/platform.ts b/extensions/typescript-language-features/Source/utils/platform.ts
index ba954f59da30b..b3cb124f5177d 100644
--- a/extensions/typescript-language-features/Source/utils/platform.ts
+++ b/extensions/typescript-language-features/Source/utils/platform.ts
@@ -2,18 +2,13 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export function isWeb(): boolean {
-	return 'navigator' in globalThis && vscode.env.uiKind === vscode.UIKind.Web;
+    return 'navigator' in globalThis && vscode.env.uiKind === vscode.UIKind.Web;
 }
-
 export function isWebAndHasSharedArrayBuffers(): boolean {
-	return isWeb() && (globalThis as any)['crossOriginIsolated'];
+    return isWeb() && (globalThis as any)['crossOriginIsolated'];
 }
-
 export function supportsReadableByteStreams(): boolean {
-	return isWeb() && 'ReadableByteStreamController' in globalThis;
+    return isWeb() && 'ReadableByteStreamController' in globalThis;
 }
-
diff --git a/extensions/typescript-language-features/Source/utils/regexp.ts b/extensions/typescript-language-features/Source/utils/regexp.ts
index 10c522f58a09a..66b557173f296 100644
--- a/extensions/typescript-language-features/Source/utils/regexp.ts
+++ b/extensions/typescript-language-features/Source/utils/regexp.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 export function escapeRegExp(text: string) {
-	return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
-}
\ No newline at end of file
+    return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+}
diff --git a/extensions/typescript-language-features/Source/utils/relativePathResolver.ts b/extensions/typescript-language-features/Source/utils/relativePathResolver.ts
index 0ef03af22a4dc..0d4b16dc1acec 100644
--- a/extensions/typescript-language-features/Source/utils/relativePathResolver.ts
+++ b/extensions/typescript-language-features/Source/utils/relativePathResolver.ts
@@ -4,18 +4,16 @@
  *--------------------------------------------------------------------------------------------*/
 import * as path from 'path';
 import * as vscode from 'vscode';
-
 export class RelativeWorkspacePathResolver {
-	public static asAbsoluteWorkspacePath(relativePath: string): string | undefined {
-		for (const root of vscode.workspace.workspaceFolders || []) {
-			const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
-			for (const rootPrefix of rootPrefixes) {
-				if (relativePath.startsWith(rootPrefix)) {
-					return path.join(root.uri.fsPath, relativePath.replace(rootPrefix, ''));
-				}
-			}
-		}
-
-		return undefined;
-	}
+    public static asAbsoluteWorkspacePath(relativePath: string): string | undefined {
+        for (const root of vscode.workspace.workspaceFolders || []) {
+            const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
+            for (const rootPrefix of rootPrefixes) {
+                if (relativePath.startsWith(rootPrefix)) {
+                    return path.join(root.uri.fsPath, relativePath.replace(rootPrefix, ''));
+                }
+            }
+        }
+        return undefined;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/utils/resourceMap.ts b/extensions/typescript-language-features/Source/utils/resourceMap.ts
index 2328156445b7c..8732530eb0193 100644
--- a/extensions/typescript-language-features/Source/utils/resourceMap.ts
+++ b/extensions/typescript-language-features/Source/utils/resourceMap.ts
@@ -2,11 +2,9 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as fileSchemes from '../configuration/fileSchemes';
 import { looksLikeAbsoluteWindowsPath } from './fs';
-
 /**
  * Maps of file resources
  *
@@ -14,85 +12,76 @@ import { looksLikeAbsoluteWindowsPath } from './fs';
  * file systems.
  */
 export class ResourceMap {
-
-	private static readonly defaultPathNormalizer = (resource: vscode.Uri): string => {
-		if (resource.scheme === fileSchemes.file) {
-			return resource.fsPath;
-		}
-		return resource.toString(true);
-	};
-
-	private readonly _map = new Map();
-
-	constructor(
-		protected readonly _normalizePath: (resource: vscode.Uri) => string | undefined = ResourceMap.defaultPathNormalizer,
-		protected readonly config: {
-			readonly onCaseInsensitiveFileSystem: boolean;
-		},
-	) { }
-
-	public get size() {
-		return this._map.size;
-	}
-
-	public has(resource: vscode.Uri): boolean {
-		const file = this.toKey(resource);
-		return !!file && this._map.has(file);
-	}
-
-	public get(resource: vscode.Uri): T | undefined {
-		const file = this.toKey(resource);
-		if (!file) {
-			return undefined;
-		}
-		const entry = this._map.get(file);
-		return entry ? entry.value : undefined;
-	}
-
-	public set(resource: vscode.Uri, value: T) {
-		const file = this.toKey(resource);
-		if (!file) {
-			return;
-		}
-		const entry = this._map.get(file);
-		if (entry) {
-			entry.value = value;
-		} else {
-			this._map.set(file, { resource, value });
-		}
-	}
-
-	public delete(resource: vscode.Uri): void {
-		const file = this.toKey(resource);
-		if (file) {
-			this._map.delete(file);
-		}
-	}
-
-	public clear(): void {
-		this._map.clear();
-	}
-
-	public values(): Iterable {
-		return Array.from(this._map.values(), x => x.value);
-	}
-
-	public entries(): Iterable<{ resource: vscode.Uri; value: T }> {
-		return this._map.values();
-	}
-
-	private toKey(resource: vscode.Uri): string | undefined {
-		const key = this._normalizePath(resource);
-		if (!key) {
-			return key;
-		}
-		return this.isCaseInsensitivePath(key) ? key.toLowerCase() : key;
-	}
-
-	private isCaseInsensitivePath(path: string) {
-		if (looksLikeAbsoluteWindowsPath(path)) {
-			return true;
-		}
-		return path[0] === '/' && this.config.onCaseInsensitiveFileSystem;
-	}
+    private static readonly defaultPathNormalizer = (resource: vscode.Uri): string => {
+        if (resource.scheme === fileSchemes.file) {
+            return resource.fsPath;
+        }
+        return resource.toString(true);
+    };
+    private readonly _map = new Map();
+    constructor(protected readonly _normalizePath: (resource: vscode.Uri) => string | undefined = ResourceMap.defaultPathNormalizer, protected readonly config: {
+        readonly onCaseInsensitiveFileSystem: boolean;
+    }) { }
+    public get size() {
+        return this._map.size;
+    }
+    public has(resource: vscode.Uri): boolean {
+        const file = this.toKey(resource);
+        return !!file && this._map.has(file);
+    }
+    public get(resource: vscode.Uri): T | undefined {
+        const file = this.toKey(resource);
+        if (!file) {
+            return undefined;
+        }
+        const entry = this._map.get(file);
+        return entry ? entry.value : undefined;
+    }
+    public set(resource: vscode.Uri, value: T) {
+        const file = this.toKey(resource);
+        if (!file) {
+            return;
+        }
+        const entry = this._map.get(file);
+        if (entry) {
+            entry.value = value;
+        }
+        else {
+            this._map.set(file, { resource, value });
+        }
+    }
+    public delete(resource: vscode.Uri): void {
+        const file = this.toKey(resource);
+        if (file) {
+            this._map.delete(file);
+        }
+    }
+    public clear(): void {
+        this._map.clear();
+    }
+    public values(): Iterable {
+        return Array.from(this._map.values(), x => x.value);
+    }
+    public entries(): Iterable<{
+        resource: vscode.Uri;
+        value: T;
+    }> {
+        return this._map.values();
+    }
+    private toKey(resource: vscode.Uri): string | undefined {
+        const key = this._normalizePath(resource);
+        if (!key) {
+            return key;
+        }
+        return this.isCaseInsensitivePath(key) ? key.toLowerCase() : key;
+    }
+    private isCaseInsensitivePath(path: string) {
+        if (looksLikeAbsoluteWindowsPath(path)) {
+            return true;
+        }
+        return path[0] === '/' && this.config.onCaseInsensitiveFileSystem;
+    }
 }
diff --git a/extensions/typescript-language-features/Source/utils/temp.electron.ts b/extensions/typescript-language-features/Source/utils/temp.electron.ts
index cf09941124670..8ceba714a97e1 100644
--- a/extensions/typescript-language-features/Source/utils/temp.electron.ts
+++ b/extensions/typescript-language-features/Source/utils/temp.electron.ts
@@ -2,33 +2,28 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as fs from 'fs';
 import * as os from 'os';
 import * as path from 'path';
 import { lazy } from './lazy';
-
 function makeRandomHexString(length: number): string {
-	const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
-	let result = '';
-	for (let i = 0; i < length; i++) {
-		const idx = Math.floor(chars.length * Math.random());
-		result += chars[idx];
-	}
-	return result;
+    const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
+    let result = '';
+    for (let i = 0; i < length; i++) {
+        const idx = Math.floor(chars.length * Math.random());
+        result += chars[idx];
+    }
+    return result;
 }
-
 const rootTempDir = lazy(() => {
-	const filename = `vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`;
-	return path.join(os.tmpdir(), filename);
+    const filename = `vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`;
+    return path.join(os.tmpdir(), filename);
 });
-
 export const instanceTempDir = lazy(() => {
-	const dir = path.join(rootTempDir.value, makeRandomHexString(20));
-	fs.mkdirSync(dir, { recursive: true });
-	return dir;
+    const dir = path.join(rootTempDir.value, makeRandomHexString(20));
+    fs.mkdirSync(dir, { recursive: true });
+    return dir;
 });
-
 export function getTempFile(prefix: string): string {
-	return path.join(instanceTempDir.value, `${prefix}-${makeRandomHexString(20)}.tmp`);
+    return path.join(instanceTempDir.value, `${prefix}-${makeRandomHexString(20)}.tmp`);
 }
diff --git a/extensions/typescript-language-features/test-workspace/bar.ts b/extensions/typescript-language-features/test-workspace/bar.ts
index 4098e94951d34..4a8987c58d525 100644
--- a/extensions/typescript-language-features/test-workspace/bar.ts
+++ b/extensions/typescript-language-features/test-workspace/bar.ts
@@ -1 +1 @@
-// export const foo = 1;
\ No newline at end of file
+// export const foo = 1;
diff --git a/extensions/typescript-language-features/web/Source/fileWatcherManager.ts b/extensions/typescript-language-features/web/Source/fileWatcherManager.ts
index 6ae4472e503d0..7744392ff7eac 100644
--- a/extensions/typescript-language-features/web/Source/fileWatcherManager.ts
+++ b/extensions/typescript-language-features/web/Source/fileWatcherManager.ts
@@ -2,125 +2,111 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import type * as ts from 'typescript/lib/tsserverlibrary';
 import { URI } from 'vscode-uri';
 import { Logger } from './logging';
 import { PathMapper, fromResource, looksLikeLibDtsPath, looksLikeNodeModules, mapUri } from './pathMapper';
-
 /**
  * Copied from `ts.FileWatcherEventKind` to avoid direct dependency.
  */
 enum FileWatcherEventKind {
-	Created = 0,
-	Changed = 1,
-	Deleted = 2,
+    Created = 0,
+    Changed = 1,
+    Deleted = 2
 }
-
 export class FileWatcherManager {
-	private static readonly noopWatcher: ts.FileWatcher = { close() { } };
-
-	private readonly watchFiles = new Map();
-	private readonly watchDirectories = new Map();
-
-	private watchId = 0;
-
-	constructor(
-		private readonly watchPort: MessagePort,
-		extensionUri: URI,
-		private readonly enabledExperimentalTypeAcquisition: boolean,
-		private readonly pathMapper: PathMapper,
-		private readonly logger: Logger
-	) {
-		watchPort.onmessage = (e: any) => this.updateWatch(e.data.event, URI.from(e.data.uri), extensionUri);
-	}
-
-	watchFile(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions): ts.FileWatcher {
-		if (looksLikeLibDtsPath(path)) { // We don't support watching lib files on web since they are readonly
-			return FileWatcherManager.noopWatcher;
-		}
-
-		this.logger.logVerbose('fs.watchFile', { path });
-
-		let uri: URI;
-		try {
-			uri = this.pathMapper.toResource(path);
-		} catch (e) {
-			console.error(e);
-			return FileWatcherManager.noopWatcher;
-		}
-
-		this.watchFiles.set(path, { callback, pollingInterval, options });
-		const watchIds = [++this.watchId];
-		this.watchPort.postMessage({ type: 'watchFile', uri: uri, id: watchIds[0] });
-		if (this.enabledExperimentalTypeAcquisition && looksLikeNodeModules(path) && uri.scheme !== 'vscode-global-typings') {
-			watchIds.push(++this.watchId);
-			this.watchPort.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-global-typings'), id: watchIds[1] });
-		}
-		return {
-			close: () => {
-				this.logger.logVerbose('fs.watchFile.close', { path });
-				this.watchFiles.delete(path);
-				for (const id of watchIds) {
-					this.watchPort.postMessage({ type: 'dispose', id });
-				}
-			}
-		};
-	}
-
-	watchDirectory(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions): ts.FileWatcher {
-		this.logger.logVerbose('fs.watchDirectory', { path });
-
-		let uri: URI;
-		try {
-			uri = this.pathMapper.toResource(path);
-		} catch (e) {
-			console.error(e);
-			return FileWatcherManager.noopWatcher;
-		}
-
-		this.watchDirectories.set(path, { callback, recursive, options });
-		const watchIds = [++this.watchId];
-		this.watchPort.postMessage({ type: 'watchDirectory', recursive, uri, id: this.watchId });
-		return {
-			close: () => {
-				this.logger.logVerbose('fs.watchDirectory.close', { path });
-
-				this.watchDirectories.delete(path);
-				for (const id of watchIds) {
-					this.watchPort.postMessage({ type: 'dispose', id });
-				}
-			}
-		};
-	}
-
-	private updateWatch(event: 'create' | 'change' | 'delete', uri: URI, extensionUri: URI) {
-		const kind = this.toTsWatcherKind(event);
-		const path = fromResource(extensionUri, uri);
-
-		const fileWatcher = this.watchFiles.get(path);
-		if (fileWatcher) {
-			fileWatcher.callback(path, kind);
-			return;
-		}
-
-		for (const watch of Array.from(this.watchDirectories.keys()).filter(dir => path.startsWith(dir))) {
-			this.watchDirectories.get(watch)!.callback(path);
-			return;
-		}
-
-		console.error(`no watcher found for ${path}`);
-	}
-
-	private toTsWatcherKind(event: 'create' | 'change' | 'delete') {
-		if (event === 'create') {
-			return FileWatcherEventKind.Created;
-		} else if (event === 'change') {
-			return FileWatcherEventKind.Changed;
-		} else if (event === 'delete') {
-			return FileWatcherEventKind.Deleted;
-		}
-		throw new Error(`Unknown event: ${event}`);
-	}
+    private static readonly noopWatcher: ts.FileWatcher = { close() { } };
+    private readonly watchFiles = new Map();
+    private readonly watchDirectories = new Map();
+    private watchId = 0;
+    constructor(private readonly watchPort: MessagePort, extensionUri: URI, private readonly enabledExperimentalTypeAcquisition: boolean, private readonly pathMapper: PathMapper, private readonly logger: Logger) {
+        watchPort.onmessage = (e: any) => this.updateWatch(e.data.event, URI.from(e.data.uri), extensionUri);
+    }
+    watchFile(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions): ts.FileWatcher {
+        if (looksLikeLibDtsPath(path)) { // We don't support watching lib files on web since they are readonly
+            return FileWatcherManager.noopWatcher;
+        }
+        this.logger.logVerbose('fs.watchFile', { path });
+        let uri: URI;
+        try {
+            uri = this.pathMapper.toResource(path);
+        }
+        catch (e) {
+            console.error(e);
+            return FileWatcherManager.noopWatcher;
+        }
+        this.watchFiles.set(path, { callback, pollingInterval, options });
+        const watchIds = [++this.watchId];
+        this.watchPort.postMessage({ type: 'watchFile', uri: uri, id: watchIds[0] });
+        if (this.enabledExperimentalTypeAcquisition && looksLikeNodeModules(path) && uri.scheme !== 'vscode-global-typings') {
+            watchIds.push(++this.watchId);
+            this.watchPort.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-global-typings'), id: watchIds[1] });
+        }
+        return {
+            close: () => {
+                this.logger.logVerbose('fs.watchFile.close', { path });
+                this.watchFiles.delete(path);
+                for (const id of watchIds) {
+                    this.watchPort.postMessage({ type: 'dispose', id });
+                }
+            }
+        };
+    }
+    watchDirectory(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions): ts.FileWatcher {
+        this.logger.logVerbose('fs.watchDirectory', { path });
+        let uri: URI;
+        try {
+            uri = this.pathMapper.toResource(path);
+        }
+        catch (e) {
+            console.error(e);
+            return FileWatcherManager.noopWatcher;
+        }
+        this.watchDirectories.set(path, { callback, recursive, options });
+        const watchIds = [++this.watchId];
+        this.watchPort.postMessage({ type: 'watchDirectory', recursive, uri, id: this.watchId });
+        return {
+            close: () => {
+                this.logger.logVerbose('fs.watchDirectory.close', { path });
+                this.watchDirectories.delete(path);
+                for (const id of watchIds) {
+                    this.watchPort.postMessage({ type: 'dispose', id });
+                }
+            }
+        };
+    }
+    private updateWatch(event: 'create' | 'change' | 'delete', uri: URI, extensionUri: URI) {
+        const kind = this.toTsWatcherKind(event);
+        const path = fromResource(extensionUri, uri);
+        const fileWatcher = this.watchFiles.get(path);
+        if (fileWatcher) {
+            fileWatcher.callback(path, kind);
+            return;
+        }
+        for (const watch of Array.from(this.watchDirectories.keys()).filter(dir => path.startsWith(dir))) {
+            this.watchDirectories.get(watch)!.callback(path);
+            return;
+        }
+        console.error(`no watcher found for ${path}`);
+    }
+    private toTsWatcherKind(event: 'create' | 'change' | 'delete') {
+        if (event === 'create') {
+            return FileWatcherEventKind.Created;
+        }
+        else if (event === 'change') {
+            return FileWatcherEventKind.Changed;
+        }
+        else if (event === 'delete') {
+            return FileWatcherEventKind.Deleted;
+        }
+        throw new Error(`Unknown event: ${event}`);
+    }
 }
-
diff --git a/extensions/typescript-language-features/web/Source/logging.ts b/extensions/typescript-language-features/web/Source/logging.ts
index 843228f0df097..c72fd78d2d38c 100644
--- a/extensions/typescript-language-features/web/Source/logging.ts
+++ b/extensions/typescript-language-features/web/Source/logging.ts
@@ -3,58 +3,50 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import type * as ts from 'typescript/lib/tsserverlibrary';
-
 /**
  * Matches the ts.server.LogLevel enum
  */
 export enum LogLevel {
-	terse = 0,
-	normal = 1,
-	requestTime = 2,
-	verbose = 3,
+    terse = 0,
+    normal = 1,
+    requestTime = 2,
+    verbose = 3
 }
-
 export class Logger {
-	public readonly tsLogger: ts.server.Logger;
-
-	constructor(logLevel: LogLevel | undefined) {
-		const doLog = typeof logLevel === 'undefined'
-			? (_message: string) => { }
-			: (message: string) => { postMessage({ type: 'log', body: message }); };
-
-		this.tsLogger = {
-			close: () => { },
-			hasLevel: level => typeof logLevel === 'undefined' ? false : level <= logLevel,
-			loggingEnabled: () => true,
-			perftrc: () => { },
-			info: doLog,
-			msg: doLog,
-			startGroup: () => { },
-			endGroup: () => { },
-			getLogFileName: () => undefined
-		};
-	}
-
-	log(level: LogLevel, message: string, data?: any) {
-		if (this.tsLogger.hasLevel(level)) {
-			this.tsLogger.info(message + (data ? ' ' + JSON.stringify(data) : ''));
-		}
-	}
-
-	logNormal(message: string, data?: any) {
-		this.log(LogLevel.normal, message, data);
-	}
-
-	logVerbose(message: string, data?: any) {
-		this.log(LogLevel.verbose, message, data);
-	}
+    public readonly tsLogger: ts.server.Logger;
+    constructor(logLevel: LogLevel | undefined) {
+        const doLog = typeof logLevel === 'undefined'
+            ? (_message: string) => { }
+            : (message: string) => { postMessage({ type: 'log', body: message }); };
+        this.tsLogger = {
+            close: () => { },
+            hasLevel: level => typeof logLevel === 'undefined' ? false : level <= logLevel,
+            loggingEnabled: () => true,
+            perftrc: () => { },
+            info: doLog,
+            msg: doLog,
+            startGroup: () => { },
+            endGroup: () => { },
+            getLogFileName: () => undefined
+        };
+    }
+    log(level: LogLevel, message: string, data?: any) {
+        if (this.tsLogger.hasLevel(level)) {
+            this.tsLogger.info(message + (data ? ' ' + JSON.stringify(data) : ''));
+        }
+    }
+    logNormal(message: string, data?: any) {
+        this.log(LogLevel.normal, message, data);
+    }
+    logVerbose(message: string, data?: any) {
+        this.log(LogLevel.verbose, message, data);
+    }
 }
-
 export function parseLogLevel(input: string | undefined): LogLevel | undefined {
-	switch (input) {
-		case 'normal': return LogLevel.normal;
-		case 'terse': return LogLevel.terse;
-		case 'verbose': return LogLevel.verbose;
-		default: return undefined;
-	}
+    switch (input) {
+        case 'normal': return LogLevel.normal;
+        case 'terse': return LogLevel.terse;
+        case 'verbose': return LogLevel.verbose;
+        default: return undefined;
+    }
 }
diff --git a/extensions/typescript-language-features/web/Source/pathMapper.ts b/extensions/typescript-language-features/web/Source/pathMapper.ts
index e92548950fc4c..48974a91f97f7 100644
--- a/extensions/typescript-language-features/web/Source/pathMapper.ts
+++ b/extensions/typescript-language-features/web/Source/pathMapper.ts
@@ -3,110 +3,87 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import { URI } from 'vscode-uri';
-
 export class PathMapper {
-
-	private readonly projectRootPaths = new Map();
-
-	constructor(
-		private readonly extensionUri: URI
-	) { }
-
-	/**
-	 * Copied from toResource in typescriptServiceClient.ts
-	 */
-	toResource(filepath: string): URI {
-		if (looksLikeLibDtsPath(filepath)) {
-			return URI.from({
-				scheme: this.extensionUri.scheme,
-				authority: this.extensionUri.authority,
-				path: this.extensionUri.path + '/dist/browser/typescript/' + filepath.slice(1)
-			});
-		}
-
-		const uri = filePathToResourceUri(filepath);
-		if (!uri) {
-			throw new Error(`Could not parse path ${filepath}`);
-		}
-
-		// Check if TS is trying to read a file outside of the project root.
-		// We allow reading files on unknown scheme as these may be loose files opened by the user.
-		// However we block reading files on schemes that are on a known file system with an unknown root
-		let allowRead: 'implicit' | 'block' | 'allow' = 'implicit';
-		for (const projectRoot of this.projectRootPaths.values()) {
-			if (uri.scheme === projectRoot.scheme) {
-				if (uri.toString().startsWith(projectRoot.toString())) {
-					allowRead = 'allow';
-					break;
-				}
-
-				// Tentatively block the read but a future loop may allow it
-				allowRead = 'block';
-			}
-		}
-
-		if (allowRead === 'block') {
-			throw new AccessOutsideOfRootError(filepath, Array.from(this.projectRootPaths.keys()));
-		}
-
-		return uri;
-	}
-
-	addProjectRoot(projectRootPath: string) {
-		const uri = filePathToResourceUri(projectRootPath);
-		if (uri) {
-			this.projectRootPaths.set(projectRootPath, uri);
-		}
-	}
+    private readonly projectRootPaths = new Map();
+    constructor(private readonly extensionUri: URI) { }
+    /**
+     * Copied from toResource in typescriptServiceClient.ts
+     */
+    toResource(filepath: string): URI {
+        if (looksLikeLibDtsPath(filepath)) {
+            return URI.from({
+                scheme: this.extensionUri.scheme,
+                authority: this.extensionUri.authority,
+                path: this.extensionUri.path + '/dist/browser/typescript/' + filepath.slice(1)
+            });
+        }
+        const uri = filePathToResourceUri(filepath);
+        if (!uri) {
+            throw new Error(`Could not parse path ${filepath}`);
+        }
+        // Check if TS is trying to read a file outside of the project root.
+        // We allow reading files on unknown scheme as these may be loose files opened by the user.
+        // However we block reading files on schemes that are on a known file system with an unknown root
+        let allowRead: 'implicit' | 'block' | 'allow' = 'implicit';
+        for (const projectRoot of this.projectRootPaths.values()) {
+            if (uri.scheme === projectRoot.scheme) {
+                if (uri.toString().startsWith(projectRoot.toString())) {
+                    allowRead = 'allow';
+                    break;
+                }
+                // Tentatively block the read but a future loop may allow it
+                allowRead = 'block';
+            }
+        }
+        if (allowRead === 'block') {
+            throw new AccessOutsideOfRootError(filepath, Array.from(this.projectRootPaths.keys()));
+        }
+        return uri;
+    }
+    addProjectRoot(projectRootPath: string) {
+        const uri = filePathToResourceUri(projectRootPath);
+        if (uri) {
+            this.projectRootPaths.set(projectRootPath, uri);
+        }
+    }
 }
-
 class AccessOutsideOfRootError extends Error {
-	constructor(
-		public readonly filepath: string,
-		public readonly projectRootPaths: readonly string[]
-	) {
-		super(`Could not read file outside of project root ${filepath}`);
-	}
+    constructor(public readonly filepath: string, public readonly projectRootPaths: readonly string[]) {
+        super(`Could not read file outside of project root ${filepath}`);
+    }
 }
-
 export function fromResource(extensionUri: URI, uri: URI) {
-	if (uri.scheme === extensionUri.scheme
-		&& uri.authority === extensionUri.authority
-		&& uri.path.startsWith(extensionUri.path + '/dist/browser/typescript/lib.')
-		&& uri.path.endsWith('.d.ts')) {
-		return uri.path;
-	}
-	return `/${uri.scheme}/${uri.authority}${uri.path}`;
+    if (uri.scheme === extensionUri.scheme
+        && uri.authority === extensionUri.authority
+        && uri.path.startsWith(extensionUri.path + '/dist/browser/typescript/lib.')
+        && uri.path.endsWith('.d.ts')) {
+        return uri.path;
+    }
+    return `/${uri.scheme}/${uri.authority}${uri.path}`;
 }
-
 export function looksLikeLibDtsPath(filepath: string) {
-	return filepath.startsWith('/lib.') && filepath.endsWith('.d.ts');
+    return filepath.startsWith('/lib.') && filepath.endsWith('.d.ts');
 }
-
 export function looksLikeNodeModules(filepath: string) {
-	return filepath.includes('/node_modules');
+    return filepath.includes('/node_modules');
 }
-
 function filePathToResourceUri(filepath: string): URI | undefined {
-	const parts = filepath.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/);
-	if (!parts) {
-		return undefined;
-	}
-
-	const scheme = parts[1];
-	const authority = parts[2] === 'ts-nul-authority' ? '' : parts[2];
-	const path = parts[3];
-	return URI.from({ scheme, authority, path: (path ? '/' + path : path) });
+    const parts = filepath.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/);
+    if (!parts) {
+        return undefined;
+    }
+    const scheme = parts[1];
+    const authority = parts[2] === 'ts-nul-authority' ? '' : parts[2];
+    const path = parts[3];
+    return URI.from({ scheme, authority, path: (path ? '/' + path : path) });
 }
-
 export function mapUri(uri: URI, mappedScheme: string): URI {
-	if (uri.scheme === 'vscode-global-typings') {
-		throw new Error('can\'t map vscode-global-typings');
-	}
-	if (!uri.authority) {
-		uri = uri.with({ authority: 'ts-nul-authority' });
-	}
-	uri = uri.with({ scheme: mappedScheme, path: `/${uri.scheme}/${uri.authority || 'ts-nul-authority'}${uri.path}` });
-
-	return uri;
+    if (uri.scheme === 'vscode-global-typings') {
+        throw new Error('can\'t map vscode-global-typings');
+    }
+    if (!uri.authority) {
+        uri = uri.with({ authority: 'ts-nul-authority' });
+    }
+    uri = uri.with({ scheme: mappedScheme, path: `/${uri.scheme}/${uri.authority || 'ts-nul-authority'}${uri.path}` });
+    return uri;
 }
diff --git a/extensions/typescript-language-features/web/Source/serverHost.ts b/extensions/typescript-language-features/web/Source/serverHost.ts
index dedec85991fbb..3202f38f85096 100644
--- a/extensions/typescript-language-features/web/Source/serverHost.ts
+++ b/extensions/typescript-language-features/web/Source/serverHost.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import { ApiClient, FileStat, FileType, Requests } from '@vscode/sync-api-client';
 import { ClientConnection } from '@vscode/sync-api-common/browser';
 import { basename } from 'path';
@@ -12,429 +11,399 @@ import { Logger } from './logging';
 import { PathMapper, looksLikeNodeModules, mapUri } from './pathMapper';
 import { findArgument, hasArgument } from './util/args';
 import { URI } from 'vscode-uri';
-
-type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise };
-
-function createServerHost(
-	ts: typeof import('typescript/lib/tsserverlibrary'),
-	logger: Logger,
-	apiClient: ApiClient | undefined,
-	args: readonly string[],
-	watchManager: FileWatcherManager,
-	pathMapper: PathMapper,
-	enabledExperimentalTypeAcquisition: boolean,
-	exit: () => void,
-): ServerHostWithImport {
-	const currentDirectory = '/';
-	const fs = apiClient?.vscode.workspace.fileSystem;
-
-	// Internals
-	const combinePaths: (path: string, ...paths: (string | undefined)[]) => string = (ts as any).combinePaths;
-	const byteOrderMarkIndicator = '\uFEFF';
-	const matchFiles: (
-		path: string,
-		extensions: readonly string[] | undefined,
-		excludes: readonly string[] | undefined,
-		includes: readonly string[] | undefined,
-		useCaseSensitiveFileNames: boolean,
-		currentDirectory: string,
-		depth: number | undefined,
-		getFileSystemEntries: (path: string) => { files: readonly string[]; directories: readonly string[] },
-		realpath: (path: string) => string
-	) => string[] = (ts as any).matchFiles;
-	const generateDjb2Hash = (ts as any).generateDjb2Hash;
-
-	// Legacy web
-	const memoize: (callback: () => T) => () => T = (ts as any).memoize;
-	const ensureTrailingDirectorySeparator: (path: string) => string = (ts as any).ensureTrailingDirectorySeparator;
-	const getDirectoryPath: (path: string) => string = (ts as any).getDirectoryPath;
-	const directorySeparator: string = (ts as any).directorySeparator;
-	const executingFilePath = findArgument(args, '--executingFilePath') || location + '';
-	const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(executingFilePath))));
-	const getWebPath = (path: string) => path.startsWith(directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined;
-
-	const textDecoder = new TextDecoder();
-	const textEncoder = new TextEncoder();
-
-	return {
-		watchFile: watchManager.watchFile.bind(watchManager),
-		watchDirectory: watchManager.watchDirectory.bind(watchManager),
-		setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
-			return setTimeout(callback, ms, ...args);
-		},
-		clearTimeout(timeoutId: any): void {
-			clearTimeout(timeoutId);
-		},
-		setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
-			return this.setTimeout(callback, 0, ...args);
-		},
-		clearImmediate(timeoutId: any): void {
-			this.clearTimeout(timeoutId);
-		},
-		importPlugin: async (root, moduleName) => {
-			const packageRoot = combinePaths(root, moduleName);
-
-			let packageJson: any | undefined;
-			try {
-				const packageJsonResponse = await fetch(combinePaths(packageRoot, 'package.json'));
-				packageJson = await packageJsonResponse.json();
-			} catch (e) {
-				return { module: undefined, error: new Error(`Could not load plugin. Could not load 'package.json'.`) };
-			}
-
-			const browser = packageJson.browser;
-			if (!browser) {
-				return { module: undefined, error: new Error(`Could not load plugin. No 'browser' field found in package.json.`) };
-			}
-
-			const scriptPath = combinePaths(packageRoot, browser);
-			try {
-				const { default: module } = await import(/* webpackIgnore: true */ scriptPath);
-				return { module, error: undefined };
-			} catch (e) {
-				return { module: undefined, error: e };
-			}
-		},
-		args: Array.from(args),
-		newLine: '\n',
-		useCaseSensitiveFileNames: true,
-		write: s => {
-			apiClient?.vscode.terminal.write(s);
-		},
-		writeOutputIsTTY() {
-			return true;
-		},
-		readFile(path) {
-			logger.logVerbose('fs.readFile', { path });
-
-			if (!fs) {
-				const webPath = getWebPath(path);
-				if (webPath) {
-					const request = new XMLHttpRequest();
-					request.open('GET', webPath, /* asynchronous */ false);
-					request.send();
-					return request.status === 200 ? request.responseText : undefined;
-				} else {
-					return undefined;
-				}
-			}
-
-			let uri;
-			try {
-				uri = pathMapper.toResource(path);
-			} catch (e) {
-				return undefined;
-			}
-
-			let contents: Uint8Array | undefined;
-			try {
-				// We need to slice the bytes since we can't pass a shared array to text decoder
-				contents = fs.readFile(uri);
-			} catch (error) {
-				if (!enabledExperimentalTypeAcquisition) {
-					return undefined;
-				}
-				try {
-					contents = fs.readFile(mapUri(uri, 'vscode-node-modules'));
-				} catch (e) {
-					return undefined;
-				}
-			}
-			return textDecoder.decode(contents.slice());
-		},
-		getFileSize(path) {
-			logger.logVerbose('fs.getFileSize', { path });
-
-			if (!fs) {
-				throw new Error('not supported');
-			}
-
-			const uri = pathMapper.toResource(path);
-			let ret = 0;
-			try {
-				ret = fs.stat(uri).size;
-			} catch (_error) {
-				if (enabledExperimentalTypeAcquisition) {
-					try {
-						ret = fs.stat(mapUri(uri, 'vscode-node-modules')).size;
-					} catch (_error) {
-					}
-				}
-			}
-			return ret;
-		},
-		writeFile(path, data, writeByteOrderMark) {
-			logger.logVerbose('fs.writeFile', { path });
-
-			if (!fs) {
-				throw new Error('not supported');
-			}
-
-			if (writeByteOrderMark) {
-				data = byteOrderMarkIndicator + data;
-			}
-
-			let uri;
-			try {
-				uri = pathMapper.toResource(path);
-			} catch (e) {
-				return;
-			}
-			const encoded = textEncoder.encode(data);
-			try {
-				fs.writeFile(uri, encoded);
-				const name = basename(uri.path);
-				if (uri.scheme !== 'vscode-global-typings' && (name === 'package.json' || name === 'package-lock.json' || name === 'package-lock.kdl')) {
-					fs.writeFile(mapUri(uri, 'vscode-node-modules'), encoded);
-				}
-			} catch (error) {
-				console.error('fs.writeFile', { path, error });
-			}
-		},
-		resolvePath(path: string): string {
-			return path;
-		},
-		fileExists(path: string): boolean {
-			logger.logVerbose('fs.fileExists', { path });
-
-			if (!fs) {
-				const webPath = getWebPath(path);
-				if (!webPath) {
-					return false;
-				}
-
-				const request = new XMLHttpRequest();
-				request.open('HEAD', webPath, /* asynchronous */ false);
-				request.send();
-				return request.status === 200;
-			}
-
-			let uri;
-			try {
-				uri = pathMapper.toResource(path);
-			} catch (e) {
-				return false;
-			}
-			let ret = false;
-			try {
-				ret = fs.stat(uri).type === FileType.File;
-			} catch (_error) {
-				if (enabledExperimentalTypeAcquisition) {
-					try {
-						ret = fs.stat(mapUri(uri, 'vscode-node-modules')).type === FileType.File;
-					} catch (_error) {
-					}
-				}
-			}
-			return ret;
-		},
-		directoryExists(path: string): boolean {
-			logger.logVerbose('fs.directoryExists', { path });
-
-			if (!fs) {
-				return false;
-			}
-
-			let uri;
-			try {
-				uri = pathMapper.toResource(path);
-			} catch (_error) {
-				return false;
-			}
-
-			let stat: FileStat | undefined = undefined;
-			try {
-				stat = fs.stat(uri);
-			} catch (_error) {
-				if (enabledExperimentalTypeAcquisition) {
-					try {
-						stat = fs.stat(mapUri(uri, 'vscode-node-modules'));
-					} catch (_error) {
-					}
-				}
-			}
-			if (stat) {
-				if (path.startsWith('/https') && !path.endsWith('.d.ts')) {
-					// TODO: Hack, https 'file system' can't actually tell what is a file vs directory
-					return stat.type === FileType.File || stat.type === FileType.Directory;
-				}
-
-				return stat.type === FileType.Directory;
-			} else {
-				return false;
-			}
-		},
-		createDirectory(path: string): void {
-			logger.logVerbose('fs.createDirectory', { path });
-			if (!fs) {
-				throw new Error('not supported');
-			}
-
-			try {
-				fs.createDirectory(pathMapper.toResource(path));
-			} catch (error) {
-				logger.logNormal('Error fs.createDirectory', { path, error: error + '' });
-			}
-		},
-		getExecutingFilePath(): string {
-			return currentDirectory;
-		},
-		getCurrentDirectory(): string {
-			return currentDirectory;
-		},
-		getDirectories(path: string): string[] {
-			logger.logVerbose('fs.getDirectories', { path });
-
-			return getAccessibleFileSystemEntries(path).directories.slice();
-		},
-		readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] {
-			logger.logVerbose('fs.readDirectory', { path });
-
-			return matchFiles(path, extensions, excludes, includes, /*useCaseSensitiveFileNames*/ true, currentDirectory, depth, getAccessibleFileSystemEntries, realpath);
-		},
-		getModifiedTime(path: string): Date | undefined {
-			logger.logVerbose('fs.getModifiedTime', { path });
-
-			if (!fs) {
-				throw new Error('not supported');
-			}
-
-			const uri = pathMapper.toResource(path);
-			let s: FileStat | undefined = undefined;
-			try {
-				s = fs.stat(uri);
-			} catch (_e) {
-				if (enabledExperimentalTypeAcquisition) {
-					try {
-						s = fs.stat(mapUri(uri, 'vscode-node-modules'));
-					} catch (_e) {
-					}
-				}
-			}
-			return s && new Date(s.mtime);
-		},
-		deleteFile(path: string): void {
-			logger.logVerbose('fs.deleteFile', { path });
-
-			if (!fs) {
-				throw new Error('not supported');
-			}
-
-			try {
-				fs.delete(pathMapper.toResource(path));
-			} catch (error) {
-				logger.logNormal('Error fs.deleteFile', { path, error: error + '' });
-			}
-		},
-		createHash: generateDjb2Hash,
-		/** This must be cryptographically secure.
-			The browser implementation, crypto.subtle.digest, is async so not possible to call from tsserver. */
-		createSHA256Hash: undefined,
-		exit: exit,
-		realpath,
-		base64decode: input => Buffer.from(input, 'base64').toString('utf8'),
-		base64encode: input => Buffer.from(input).toString('base64'),
-	};
-
-	// For module resolution only. `node_modules` is also automatically mapped
-	// as if all node_modules-like paths are symlinked.
-	function realpath(path: string): string {
-		if (path.startsWith('/^/')) {
-			// In memory file. No mapping needed
-			return path;
-		}
-
-		const isNm = looksLikeNodeModules(path) && !path.startsWith('/vscode-global-typings/');
-		// skip paths without .. or ./ or /. And things that look like node_modules
-		if (!isNm && !path.match(/\.\.|\/\.|\.\//)) {
-			return path;
-		}
-
-		let uri: URI;
-		try {
-			uri = pathMapper.toResource(path);
-		} catch {
-			return path;
-		}
-
-		if (isNm) {
-			uri = mapUri(uri, 'vscode-node-modules');
-		}
-		const out = [uri.scheme];
-		if (uri.authority) { out.push(uri.authority); }
-		for (const part of uri.path.split('/')) {
-			switch (part) {
-				case '':
-				case '.':
-					break;
-				case '..':
-					//delete if there is something there to delete
-					out.pop();
-					break;
-				default:
-					out.push(part);
-			}
-		}
-		return '/' + out.join('/');
-	}
-
-	function getAccessibleFileSystemEntries(path: string): { files: readonly string[]; directories: readonly string[] } {
-		if (!fs) {
-			throw new Error('not supported');
-		}
-
-		const uri = pathMapper.toResource(path || '.');
-		let entries: [string, FileType][] = [];
-		const files: string[] = [];
-		const directories: string[] = [];
-		try {
-			entries = fs.readDirectory(uri);
-		} catch (_e) {
-			try {
-				entries = fs.readDirectory(mapUri(uri, 'vscode-node-modules'));
-			} catch (_e) {
-			}
-		}
-		for (const [entry, type] of entries) {
-			// This is necessary because on some file system node fails to exclude
-			// '.' and '..'. See https://github.com/nodejs/node/issues/4002
-			if (entry === '.' || entry === '..') {
-				continue;
-			}
-
-			if (type === FileType.File) {
-				files.push(entry);
-			}
-			else if (type === FileType.Directory) {
-				directories.push(entry);
-			}
-		}
-		files.sort();
-		directories.sort();
-		return { files, directories };
-	}
+type ServerHostWithImport = ts.server.ServerHost & {
+    importPlugin(root: string, moduleName: string): Promise;
+};
+function createServerHost(ts: typeof import('typescript/lib/tsserverlibrary'), logger: Logger, apiClient: ApiClient | undefined, args: readonly string[], watchManager: FileWatcherManager, pathMapper: PathMapper, enabledExperimentalTypeAcquisition: boolean, exit: () => void): ServerHostWithImport {
+    const currentDirectory = '/';
+    const fs = apiClient?.vscode.workspace.fileSystem;
+    // Internals
+    const combinePaths: (path: string, ...paths: (string | undefined)[]) => string = (ts as any).combinePaths;
+    const byteOrderMarkIndicator = '\uFEFF';
+    const matchFiles: (path: string, extensions: readonly string[] | undefined, excludes: readonly string[] | undefined, includes: readonly string[] | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => {
+        files: readonly string[];
+        directories: readonly string[];
+    }, realpath: (path: string) => string) => string[] = (ts as any).matchFiles;
+    const generateDjb2Hash = (ts as any).generateDjb2Hash;
+    // Legacy web
+    const memoize: (callback: () => T) => () => T = (ts as any).memoize;
+    const ensureTrailingDirectorySeparator: (path: string) => string = (ts as any).ensureTrailingDirectorySeparator;
+    const getDirectoryPath: (path: string) => string = (ts as any).getDirectoryPath;
+    const directorySeparator: string = (ts as any).directorySeparator;
+    const executingFilePath = findArgument(args, '--executingFilePath') || location + '';
+    const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(executingFilePath))));
+    const getWebPath = (path: string) => path.startsWith(directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined;
+    const textDecoder = new TextDecoder();
+    const textEncoder = new TextEncoder();
+    return {
+        watchFile: watchManager.watchFile.bind(watchManager),
+        watchDirectory: watchManager.watchDirectory.bind(watchManager),
+        setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
+            return setTimeout(callback, ms, ...args);
+        },
+        clearTimeout(timeoutId: any): void {
+            clearTimeout(timeoutId);
+        },
+        setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
+            return this.setTimeout(callback, 0, ...args);
+        },
+        clearImmediate(timeoutId: any): void {
+            this.clearTimeout(timeoutId);
+        },
+        importPlugin: async (root, moduleName) => {
+            const packageRoot = combinePaths(root, moduleName);
+            let packageJson: any | undefined;
+            try {
+                const packageJsonResponse = await fetch(combinePaths(packageRoot, 'package.json'));
+                packageJson = await packageJsonResponse.json();
+            }
+            catch (e) {
+                return { module: undefined, error: new Error(`Could not load plugin. Could not load 'package.json'.`) };
+            }
+            const browser = packageJson.browser;
+            if (!browser) {
+                return { module: undefined, error: new Error(`Could not load plugin. No 'browser' field found in package.json.`) };
+            }
+            const scriptPath = combinePaths(packageRoot, browser);
+            try {
+                const { default: module } = await import(/* webpackIgnore: true */ scriptPath);
+                return { module, error: undefined };
+            }
+            catch (e) {
+                return { module: undefined, error: e };
+            }
+        },
+        args: Array.from(args),
+        newLine: '\n',
+        useCaseSensitiveFileNames: true,
+        write: s => {
+            apiClient?.vscode.terminal.write(s);
+        },
+        writeOutputIsTTY() {
+            return true;
+        },
+        readFile(path) {
+            logger.logVerbose('fs.readFile', { path });
+            if (!fs) {
+                const webPath = getWebPath(path);
+                if (webPath) {
+                    const request = new XMLHttpRequest();
+                    request.open('GET', webPath, /* asynchronous */ false);
+                    request.send();
+                    return request.status === 200 ? request.responseText : undefined;
+                }
+                else {
+                    return undefined;
+                }
+            }
+            let uri;
+            try {
+                uri = pathMapper.toResource(path);
+            }
+            catch (e) {
+                return undefined;
+            }
+            let contents: Uint8Array | undefined;
+            try {
+                // We need to slice the bytes since we can't pass a shared array to text decoder
+                contents = fs.readFile(uri);
+            }
+            catch (error) {
+                if (!enabledExperimentalTypeAcquisition) {
+                    return undefined;
+                }
+                try {
+                    contents = fs.readFile(mapUri(uri, 'vscode-node-modules'));
+                }
+                catch (e) {
+                    return undefined;
+                }
+            }
+            return textDecoder.decode(contents.slice());
+        },
+        getFileSize(path) {
+            logger.logVerbose('fs.getFileSize', { path });
+            if (!fs) {
+                throw new Error('not supported');
+            }
+            const uri = pathMapper.toResource(path);
+            let ret = 0;
+            try {
+                ret = fs.stat(uri).size;
+            }
+            catch (_error) {
+                if (enabledExperimentalTypeAcquisition) {
+                    try {
+                        ret = fs.stat(mapUri(uri, 'vscode-node-modules')).size;
+                    }
+                    catch (_error) {
+                    }
+                }
+            }
+            return ret;
+        },
+        writeFile(path, data, writeByteOrderMark) {
+            logger.logVerbose('fs.writeFile', { path });
+            if (!fs) {
+                throw new Error('not supported');
+            }
+            if (writeByteOrderMark) {
+                data = byteOrderMarkIndicator + data;
+            }
+            let uri;
+            try {
+                uri = pathMapper.toResource(path);
+            }
+            catch (e) {
+                return;
+            }
+            const encoded = textEncoder.encode(data);
+            try {
+                fs.writeFile(uri, encoded);
+                const name = basename(uri.path);
+                if (uri.scheme !== 'vscode-global-typings' && (name === 'package.json' || name === 'package-lock.json' || name === 'package-lock.kdl')) {
+                    fs.writeFile(mapUri(uri, 'vscode-node-modules'), encoded);
+                }
+            }
+            catch (error) {
+                console.error('fs.writeFile', { path, error });
+            }
+        },
+        resolvePath(path: string): string {
+            return path;
+        },
+        fileExists(path: string): boolean {
+            logger.logVerbose('fs.fileExists', { path });
+            if (!fs) {
+                const webPath = getWebPath(path);
+                if (!webPath) {
+                    return false;
+                }
+                const request = new XMLHttpRequest();
+                request.open('HEAD', webPath, /* asynchronous */ false);
+                request.send();
+                return request.status === 200;
+            }
+            let uri;
+            try {
+                uri = pathMapper.toResource(path);
+            }
+            catch (e) {
+                return false;
+            }
+            let ret = false;
+            try {
+                ret = fs.stat(uri).type === FileType.File;
+            }
+            catch (_error) {
+                if (enabledExperimentalTypeAcquisition) {
+                    try {
+                        ret = fs.stat(mapUri(uri, 'vscode-node-modules')).type === FileType.File;
+                    }
+                    catch (_error) {
+                    }
+                }
+            }
+            return ret;
+        },
+        directoryExists(path: string): boolean {
+            logger.logVerbose('fs.directoryExists', { path });
+            if (!fs) {
+                return false;
+            }
+            let uri;
+            try {
+                uri = pathMapper.toResource(path);
+            }
+            catch (_error) {
+                return false;
+            }
+            let stat: FileStat | undefined = undefined;
+            try {
+                stat = fs.stat(uri);
+            }
+            catch (_error) {
+                if (enabledExperimentalTypeAcquisition) {
+                    try {
+                        stat = fs.stat(mapUri(uri, 'vscode-node-modules'));
+                    }
+                    catch (_error) {
+                    }
+                }
+            }
+            if (stat) {
+                if (path.startsWith('/https') && !path.endsWith('.d.ts')) {
+                    // TODO: Hack, https 'file system' can't actually tell what is a file vs directory
+                    return stat.type === FileType.File || stat.type === FileType.Directory;
+                }
+                return stat.type === FileType.Directory;
+            }
+            else {
+                return false;
+            }
+        },
+        createDirectory(path: string): void {
+            logger.logVerbose('fs.createDirectory', { path });
+            if (!fs) {
+                throw new Error('not supported');
+            }
+            try {
+                fs.createDirectory(pathMapper.toResource(path));
+            }
+            catch (error) {
+                logger.logNormal('Error fs.createDirectory', { path, error: error + '' });
+            }
+        },
+        getExecutingFilePath(): string {
+            return currentDirectory;
+        },
+        getCurrentDirectory(): string {
+            return currentDirectory;
+        },
+        getDirectories(path: string): string[] {
+            logger.logVerbose('fs.getDirectories', { path });
+            return getAccessibleFileSystemEntries(path).directories.slice();
+        },
+        readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] {
+            logger.logVerbose('fs.readDirectory', { path });
+            return matchFiles(path, extensions, excludes, includes, /*useCaseSensitiveFileNames*/ true, currentDirectory, depth, getAccessibleFileSystemEntries, realpath);
+        },
+        getModifiedTime(path: string): Date | undefined {
+            logger.logVerbose('fs.getModifiedTime', { path });
+            if (!fs) {
+                throw new Error('not supported');
+            }
+            const uri = pathMapper.toResource(path);
+            let s: FileStat | undefined = undefined;
+            try {
+                s = fs.stat(uri);
+            }
+            catch (_e) {
+                if (enabledExperimentalTypeAcquisition) {
+                    try {
+                        s = fs.stat(mapUri(uri, 'vscode-node-modules'));
+                    }
+                    catch (_e) {
+                    }
+                }
+            }
+            return s && new Date(s.mtime);
+        },
+        deleteFile(path: string): void {
+            logger.logVerbose('fs.deleteFile', { path });
+            if (!fs) {
+                throw new Error('not supported');
+            }
+            try {
+                fs.delete(pathMapper.toResource(path));
+            }
+            catch (error) {
+                logger.logNormal('Error fs.deleteFile', { path, error: error + '' });
+            }
+        },
+        createHash: generateDjb2Hash,
+        /** This must be cryptographically secure.
+            The browser implementation, crypto.subtle.digest, is async so not possible to call from tsserver. */
+        createSHA256Hash: undefined,
+        exit: exit,
+        realpath,
+        base64decode: input => Buffer.from(input, 'base64').toString('utf8'),
+        base64encode: input => Buffer.from(input).toString('base64'),
+    };
+    // For module resolution only. `node_modules` is also automatically mapped
+    // as if all node_modules-like paths are symlinked.
+    function realpath(path: string): string {
+        if (path.startsWith('/^/')) {
+            // In memory file. No mapping needed
+            return path;
+        }
+        const isNm = looksLikeNodeModules(path) && !path.startsWith('/vscode-global-typings/');
+        // skip paths without .. or ./ or /. And things that look like node_modules
+        if (!isNm && !path.match(/\.\.|\/\.|\.\//)) {
+            return path;
+        }
+        let uri: URI;
+        try {
+            uri = pathMapper.toResource(path);
+        }
+        catch {
+            return path;
+        }
+        if (isNm) {
+            uri = mapUri(uri, 'vscode-node-modules');
+        }
+        const out = [uri.scheme];
+        if (uri.authority) {
+            out.push(uri.authority);
+        }
+        for (const part of uri.path.split('/')) {
+            switch (part) {
+                case '':
+                case '.':
+                    break;
+                case '..':
+                    //delete if there is something there to delete
+                    out.pop();
+                    break;
+                default:
+                    out.push(part);
+            }
+        }
+        return '/' + out.join('/');
+    }
+    function getAccessibleFileSystemEntries(path: string): {
+        files: readonly string[];
+        directories: readonly string[];
+    } {
+        if (!fs) {
+            throw new Error('not supported');
+        }
+        const uri = pathMapper.toResource(path || '.');
+        let entries: [
+            string,
+            FileType
+        ][] = [];
+        const files: string[] = [];
+        const directories: string[] = [];
+        try {
+            entries = fs.readDirectory(uri);
+        }
+        catch (_e) {
+            try {
+                entries = fs.readDirectory(mapUri(uri, 'vscode-node-modules'));
+            }
+            catch (_e) {
+            }
+        }
+        for (const [entry, type] of entries) {
+            // This is necessary because on some file system node fails to exclude
+            // '.' and '..'. See https://github.com/nodejs/node/issues/4002
+            if (entry === '.' || entry === '..') {
+                continue;
+            }
+            if (type === FileType.File) {
+                files.push(entry);
+            }
+            else if (type === FileType.Directory) {
+                directories.push(entry);
+            }
+        }
+        files.sort();
+        directories.sort();
+        return { files, directories };
+    }
 }
-
-export async function createSys(
-	ts: typeof import('typescript/lib/tsserverlibrary'),
-	args: readonly string[],
-	fsPort: MessagePort,
-	logger: Logger,
-	watchManager: FileWatcherManager,
-	pathMapper: PathMapper,
-	onExit: () => void,
-) {
-	if (hasArgument(args, '--enableProjectWideIntelliSenseOnWeb')) {
-		const enabledExperimentalTypeAcquisition = hasArgument(args, '--experimentalTypeAcquisition');
-		const connection = new ClientConnection(fsPort);
-		await connection.serviceReady();
-
-		const apiClient = new ApiClient(connection);
-		const fs = apiClient.vscode.workspace.fileSystem;
-		const sys = createServerHost(ts, logger, apiClient, args, watchManager, pathMapper, enabledExperimentalTypeAcquisition, onExit);
-		return { sys, fs };
-	} else {
-		return { sys: createServerHost(ts, logger, undefined, args, watchManager, pathMapper, false, onExit) };
-	}
+export async function createSys(ts: typeof import('typescript/lib/tsserverlibrary'), args: readonly string[], fsPort: MessagePort, logger: Logger, watchManager: FileWatcherManager, pathMapper: PathMapper, onExit: () => void) {
+    if (hasArgument(args, '--enableProjectWideIntelliSenseOnWeb')) {
+        const enabledExperimentalTypeAcquisition = hasArgument(args, '--experimentalTypeAcquisition');
+        const connection = new ClientConnection(fsPort);
+        await connection.serviceReady();
+        const apiClient = new ApiClient(connection);
+        const fs = apiClient.vscode.workspace.fileSystem;
+        const sys = createServerHost(ts, logger, apiClient, args, watchManager, pathMapper, enabledExperimentalTypeAcquisition, onExit);
+        return { sys, fs };
+    }
+    else {
+        return { sys: createServerHost(ts, logger, undefined, args, watchManager, pathMapper, false, onExit) };
+    }
 }
-
diff --git a/extensions/typescript-language-features/web/Source/typingsInstaller/jsTyping.ts b/extensions/typescript-language-features/web/Source/typingsInstaller/jsTyping.ts
index bd940e88c1c37..116de61b9f0d2 100644
--- a/extensions/typescript-language-features/web/Source/typingsInstaller/jsTyping.ts
+++ b/extensions/typescript-language-features/web/Source/typingsInstaller/jsTyping.ts
@@ -3,76 +3,66 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 /// Utilities copied from ts.JsTyping internals
-
 export const enum NameValidationResult {
-	Ok,
-	EmptyName,
-	NameTooLong,
-	NameStartsWithDot,
-	NameStartsWithUnderscore,
-	NameContainsNonURISafeCharacters
+    Ok,
+    EmptyName,
+    NameTooLong,
+    NameStartsWithDot,
+    NameStartsWithUnderscore,
+    NameContainsNonURISafeCharacters
 }
-
 type PackageNameValidationResult = NameValidationResult | ScopedPackageNameValidationResult;
-
 interface ScopedPackageNameValidationResult {
-	readonly name: string;
-	readonly isScopeName: boolean;
-	readonly result: NameValidationResult;
+    readonly name: string;
+    readonly isScopeName: boolean;
+    readonly result: NameValidationResult;
 }
-
 enum CharacterCodes {
-	_ = 0x5F,
-	dot = 0x2E,
+    _ = 0x5F,
+    dot = 0x2E
 }
-
 const maxPackageNameLength = 214;
-
 // Validates package name using rules defined at https://docs.npmjs.com/files/package.json
 // Copied from typescript/jsTypings.ts
 export function validatePackageNameWorker(packageName: string, supportScopedPackage: true): ScopedPackageNameValidationResult;
 export function validatePackageNameWorker(packageName: string, supportScopedPackage: false): NameValidationResult;
 export function validatePackageNameWorker(packageName: string, supportScopedPackage: boolean): PackageNameValidationResult {
-	if (!packageName) {
-		return NameValidationResult.EmptyName;
-	}
-	if (packageName.length > maxPackageNameLength) {
-		return NameValidationResult.NameTooLong;
-	}
-	if (packageName.charCodeAt(0) === CharacterCodes.dot) {
-		return NameValidationResult.NameStartsWithDot;
-	}
-	if (packageName.charCodeAt(0) === CharacterCodes._) {
-		return NameValidationResult.NameStartsWithUnderscore;
-	}
-
-	// check if name is scope package like: starts with @ and has one '/' in the middle
-	// scoped packages are not currently supported
-	if (supportScopedPackage) {
-		const matches = /^@([^/]+)\/([^/]+)$/.exec(packageName);
-		if (matches) {
-			const scopeResult = validatePackageNameWorker(matches[1], /*supportScopedPackage*/ false);
-			if (scopeResult !== NameValidationResult.Ok) {
-				return { name: matches[1], isScopeName: true, result: scopeResult };
-			}
-			const packageResult = validatePackageNameWorker(matches[2], /*supportScopedPackage*/ false);
-			if (packageResult !== NameValidationResult.Ok) {
-				return { name: matches[2], isScopeName: false, result: packageResult };
-			}
-			return NameValidationResult.Ok;
-		}
-	}
-
-	if (encodeURIComponent(packageName) !== packageName) {
-		return NameValidationResult.NameContainsNonURISafeCharacters;
-	}
-
-	return NameValidationResult.Ok;
+    if (!packageName) {
+        return NameValidationResult.EmptyName;
+    }
+    if (packageName.length > maxPackageNameLength) {
+        return NameValidationResult.NameTooLong;
+    }
+    if (packageName.charCodeAt(0) === CharacterCodes.dot) {
+        return NameValidationResult.NameStartsWithDot;
+    }
+    if (packageName.charCodeAt(0) === CharacterCodes._) {
+        return NameValidationResult.NameStartsWithUnderscore;
+    }
+    // check if name is scope package like: starts with @ and has one '/' in the middle
+    // scoped packages are not currently supported
+    if (supportScopedPackage) {
+        const matches = /^@([^/]+)\/([^/]+)$/.exec(packageName);
+        if (matches) {
+            const scopeResult = validatePackageNameWorker(matches[1], /*supportScopedPackage*/ false);
+            if (scopeResult !== NameValidationResult.Ok) {
+                return { name: matches[1], isScopeName: true, result: scopeResult };
+            }
+            const packageResult = validatePackageNameWorker(matches[2], /*supportScopedPackage*/ false);
+            if (packageResult !== NameValidationResult.Ok) {
+                return { name: matches[2], isScopeName: false, result: packageResult };
+            }
+            return NameValidationResult.Ok;
+        }
+    }
+    if (encodeURIComponent(packageName) !== packageName) {
+        return NameValidationResult.NameContainsNonURISafeCharacters;
+    }
+    return NameValidationResult.Ok;
 }
-
 export interface TypingResolutionHost {
-	directoryExists(path: string): boolean;
-	fileExists(fileName: string): boolean;
-	readFile(path: string, encoding?: string): string | undefined;
-	readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[] | undefined, depth?: number): string[];
+    directoryExists(path: string): boolean;
+    fileExists(fileName: string): boolean;
+    readFile(path: string, encoding?: string): string | undefined;
+    readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[] | undefined, depth?: number): string[];
 }
diff --git a/extensions/typescript-language-features/web/Source/typingsInstaller/typingsInstaller.ts b/extensions/typescript-language-features/web/Source/typingsInstaller/typingsInstaller.ts
index 1f7790dc783b0..4e92160035200 100644
--- a/extensions/typescript-language-features/web/Source/typingsInstaller/typingsInstaller.ts
+++ b/extensions/typescript-language-features/web/Source/typingsInstaller/typingsInstaller.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 /*
  * This file implements the global typings installer API for web clients. It
  * uses [nassun](https://docs.rs/nassun) and
@@ -22,102 +21,78 @@
  * pure "server/client" model, so we play along a bit for the sake of reusing
  * the stuff the abstract class is already doing for us.
  */
-
 import { PackageManager, PackageType } from '@vscode/ts-package-manager';
 import { join } from 'path';
 import * as ts from 'typescript/lib/tsserverlibrary';
 import { NameValidationResult, validatePackageNameWorker } from './jsTyping';
-
 type InstallerResponse = ts.server.PackageInstalledResponse | ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.BeginInstallTypes | ts.server.EndInstallTypes | ts.server.WatchTypingLocations;
-
 /**
  * The "server" part of the "server/client" model. This is the part that
  * actually gets instantiated and passed to tsserver.
  */
 export class WebTypingsInstallerClient implements ts.server.ITypingsInstaller {
-
-	private projectService: ts.server.ProjectService | undefined;
-
-	private requestedRegistry = false;
-
-	private typesRegistryCache: Map> = new Map();
-
-	private readonly server: Promise;
-
-	constructor(
-		private readonly fs: ts.server.ServerHost,
-		readonly globalTypingsCacheLocation: string,
-	) {
-		this.server = WebTypingsInstallerServer.initialize(
-			(response: InstallerResponse) => this.handleResponse(response),
-			this.fs,
-			globalTypingsCacheLocation
-		);
-	}
-
-	/**
-	 * TypingsInstaller expects a "server/client" model, and as such, some of
-	 * its methods are implemented in terms of sending responses back to a
-	 * client. This method is a catch-all for those responses generated by
-	 * TypingsInstaller internals.
-	 */
-	private async handleResponse(response: InstallerResponse): Promise {
-		switch (response.kind) {
-			case 'action::packageInstalled':
-			case 'action::invalidate':
-			case 'action::set':
-				this.projectService!.updateTypingsForProject(response);
-				break;
-			case 'event::beginInstallTypes':
-			case 'event::endInstallTypes':
-			// TODO(@zkat): maybe do something with this?
-			case 'action::watchTypingLocations':
-				// Don't care.
-				break;
-			default:
-				throw new Error(`unexpected response: ${JSON.stringify(response)}`);
-		}
-	}
-
-	// NB(kmarchan): this is a code action that expects an actual NPM-specific
-	// installation. We shouldn't mess with this ourselves.
-	async installPackage(_options: ts.server.InstallPackageOptionsWithProject): Promise {
-		throw new Error('not implemented');
-	}
-
-	// NB(kmarchan): As far as I can tell, this is only ever used for
-	// completions?
-	isKnownTypesPackageName(packageName: string): boolean {
-		console.log('isKnownTypesPackageName', packageName);
-		const looksLikeValidName = validatePackageNameWorker(packageName, true);
-		if (looksLikeValidName.result !== NameValidationResult.Ok) {
-			return false;
-		}
-
-		if (this.requestedRegistry) {
-			return !!this.typesRegistryCache && this.typesRegistryCache.has(packageName);
-		}
-
-		this.requestedRegistry = true;
-		this.server.then(s => this.typesRegistryCache = s.typesRegistry);
-		return false;
-	}
-
-	enqueueInstallTypingsRequest(p: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray): void {
-		console.log('enqueueInstallTypingsRequest', typeAcquisition, unresolvedImports);
-		const req = ts.server.createInstallTypingsRequest(p, typeAcquisition, unresolvedImports);
-		this.server.then(s => s.install(req));
-	}
-
-	attach(projectService: ts.server.ProjectService): void {
-		this.projectService = projectService;
-	}
-
-	onProjectClosed(_projectService: ts.server.Project): void {
-		// noop
-	}
+    private projectService: ts.server.ProjectService | undefined;
+    private requestedRegistry = false;
+    private typesRegistryCache: Map> = new Map();
+    private readonly server: Promise;
+    constructor(private readonly fs: ts.server.ServerHost, readonly globalTypingsCacheLocation: string) {
+        this.server = WebTypingsInstallerServer.initialize((response: InstallerResponse) => this.handleResponse(response), this.fs, globalTypingsCacheLocation);
+    }
+    /**
+     * TypingsInstaller expects a "server/client" model, and as such, some of
+     * its methods are implemented in terms of sending responses back to a
+     * client. This method is a catch-all for those responses generated by
+     * TypingsInstaller internals.
+     */
+    private async handleResponse(response: InstallerResponse): Promise {
+        switch (response.kind) {
+            case 'action::packageInstalled':
+            case 'action::invalidate':
+            case 'action::set':
+                this.projectService!.updateTypingsForProject(response);
+                break;
+            case 'event::beginInstallTypes':
+            case 'event::endInstallTypes':
+            // TODO(@zkat): maybe do something with this?
+            case 'action::watchTypingLocations':
+                // Don't care.
+                break;
+            default:
+                throw new Error(`unexpected response: ${JSON.stringify(response)}`);
+        }
+    }
+    // NB(kmarchan): this is a code action that expects an actual NPM-specific
+    // installation. We shouldn't mess with this ourselves.
+    async installPackage(_options: ts.server.InstallPackageOptionsWithProject): Promise {
+        throw new Error('not implemented');
+    }
+    // NB(kmarchan): As far as I can tell, this is only ever used for
+    // completions?
+    isKnownTypesPackageName(packageName: string): boolean {
+        console.log('isKnownTypesPackageName', packageName);
+        const looksLikeValidName = validatePackageNameWorker(packageName, true);
+        if (looksLikeValidName.result !== NameValidationResult.Ok) {
+            return false;
+        }
+        if (this.requestedRegistry) {
+            return !!this.typesRegistryCache && this.typesRegistryCache.has(packageName);
+        }
+        this.requestedRegistry = true;
+        this.server.then(s => this.typesRegistryCache = s.typesRegistry);
+        return false;
+    }
+    enqueueInstallTypingsRequest(p: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray): void {
+        console.log('enqueueInstallTypingsRequest', typeAcquisition, unresolvedImports);
+        const req = ts.server.createInstallTypingsRequest(p, typeAcquisition, unresolvedImports);
+        this.server.then(s => s.install(req));
+    }
+    attach(projectService: ts.server.ProjectService): void {
+        this.projectService = projectService;
+    }
+    onProjectClosed(_projectService: ts.server.Project): void {
+        // noop
+    }
 }
-
 /**
  * Internal implementation of the "server" part of the "server/client" model.
  * This takes advantage of the existing TypingsInstaller to reuse a lot of
@@ -125,91 +100,75 @@ export class WebTypingsInstallerClient implements ts.server.ITypingsInstaller {
  * installation details handled by Nassun/Node Maintainer.
  */
 class WebTypingsInstallerServer extends ts.server.typingsInstaller.TypingsInstaller {
-
-	private static readonly typesRegistryPackageName = 'types-registry';
-
-	private constructor(
-		override typesRegistry: Map>,
-		private readonly handleResponse: (response: InstallerResponse) => void,
-		fs: ts.server.ServerHost,
-		private readonly packageManager: PackageManager,
-		globalTypingsCachePath: string,
-	) {
-		super(fs, globalTypingsCachePath, join(globalTypingsCachePath, 'fakeSafeList') as ts.Path, join(globalTypingsCachePath, 'fakeTypesMapLocation') as ts.Path, Infinity);
-	}
-
-	/**
-	 * Because loading the typesRegistry is an async operation for us, we need
-	 * to have a separate "constructor" that will be used by
-	 * WebTypingsInstallerClient.
-	 *
-	 * @returns a promise that resolves to a WebTypingsInstallerServer
-	 */
-	static async initialize(
-		handleResponse: (response: InstallerResponse) => void,
-		fs: ts.server.ServerHost,
-		globalTypingsCachePath: string,
-	): Promise {
-		const pm = new PackageManager(fs);
-		const pkgJson = join(globalTypingsCachePath, 'package.json');
-		if (!fs.fileExists(pkgJson)) {
-			fs.writeFile(pkgJson, '{"private":true}');
-		}
-		const resolved = await pm.resolveProject(globalTypingsCachePath, {
-			addPackages: [this.typesRegistryPackageName]
-		});
-		await resolved.restore();
-
-		const registry = new Map>();
-		const indexPath = join(globalTypingsCachePath, 'node_modules/types-registry/index.json');
-		const index = WebTypingsInstallerServer.readJson(fs, indexPath);
-		for (const [packageName, entry] of Object.entries(index.entries)) {
-			registry.set(packageName, entry as ts.MapLike);
-		}
-		console.log('ATA registry loaded');
-		return new WebTypingsInstallerServer(registry, handleResponse, fs, pm, globalTypingsCachePath);
-	}
-
-	/**
-	 * Implements the actual logic of installing a set of given packages. It
-	 * does this by looking up the latest versions of those packages using
-	 * Nassun, then handing Node Maintainer the updated package.json to run a
-	 * full install (modulo existing lockfiles, which can make this faster).
-	 */
-	protected override installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: ts.server.typingsInstaller.RequestCompletedAction): void {
-		console.log('installWorker', requestId, cwd);
-		(async () => {
-			try {
-				const resolved = await this.packageManager.resolveProject(cwd, {
-					addPackages: packageNames,
-					packageType: PackageType.DevDependency
-				});
-				await resolved.restore();
-				onRequestCompleted(true);
-			} catch (e) {
-				onRequestCompleted(false);
-			}
-		})();
-	}
-
-	/**
-	 * This is a thing that TypingsInstaller uses internally to send
-	 * responses, and we'll need to handle this in the Client later.
-	 */
-	protected override sendResponse(response: InstallerResponse): void {
-		this.handleResponse(response);
-	}
-
-	/**
-	 * What it says on the tin. Reads a JSON file from the given path. Throws
-	 * if the file doesn't exist (as opposed to returning `undefined`, like
-	 * fs.readFile does).
-	 */
-	private static readJson(fs: ts.server.ServerHost, path: string): any {
-		const data = fs.readFile(path);
-		if (!data) {
-			throw new Error('Failed to read file: ' + path);
-		}
-		return JSON.parse(data.trim());
-	}
+    private static readonly typesRegistryPackageName = 'types-registry';
+    private constructor(override typesRegistry: Map>, private readonly handleResponse: (response: InstallerResponse) => void, fs: ts.server.ServerHost, private readonly packageManager: PackageManager, globalTypingsCachePath: string) {
+        super(fs, globalTypingsCachePath, join(globalTypingsCachePath, 'fakeSafeList') as ts.Path, join(globalTypingsCachePath, 'fakeTypesMapLocation') as ts.Path, Infinity);
+    }
+    /**
+     * Because loading the typesRegistry is an async operation for us, we need
+     * to have a separate "constructor" that will be used by
+     * WebTypingsInstallerClient.
+     *
+     * @returns a promise that resolves to a WebTypingsInstallerServer
+     */
+    static async initialize(handleResponse: (response: InstallerResponse) => void, fs: ts.server.ServerHost, globalTypingsCachePath: string): Promise {
+        const pm = new PackageManager(fs);
+        const pkgJson = join(globalTypingsCachePath, 'package.json');
+        if (!fs.fileExists(pkgJson)) {
+            fs.writeFile(pkgJson, '{"private":true}');
+        }
+        const resolved = await pm.resolveProject(globalTypingsCachePath, {
+            addPackages: [this.typesRegistryPackageName]
+        });
+        await resolved.restore();
+        const registry = new Map>();
+        const indexPath = join(globalTypingsCachePath, 'node_modules/types-registry/index.json');
+        const index = WebTypingsInstallerServer.readJson(fs, indexPath);
+        for (const [packageName, entry] of Object.entries(index.entries)) {
+            registry.set(packageName, entry as ts.MapLike);
+        }
+        console.log('ATA registry loaded');
+        return new WebTypingsInstallerServer(registry, handleResponse, fs, pm, globalTypingsCachePath);
+    }
+    /**
+     * Implements the actual logic of installing a set of given packages. It
+     * does this by looking up the latest versions of those packages using
+     * Nassun, then handing Node Maintainer the updated package.json to run a
+     * full install (modulo existing lockfiles, which can make this faster).
+     */
+    protected override installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: ts.server.typingsInstaller.RequestCompletedAction): void {
+        console.log('installWorker', requestId, cwd);
+        (async () => {
+            try {
+                const resolved = await this.packageManager.resolveProject(cwd, {
+                    addPackages: packageNames,
+                    packageType: PackageType.DevDependency
+                });
+                await resolved.restore();
+                onRequestCompleted(true);
+            }
+            catch (e) {
+                onRequestCompleted(false);
+            }
+        })();
+    }
+    /**
+     * This is a thing that TypingsInstaller uses internally to send
+     * responses, and we'll need to handle this in the Client later.
+     */
+    protected override sendResponse(response: InstallerResponse): void {
+        this.handleResponse(response);
+    }
+    /**
+     * What it says on the tin. Reads a JSON file from the given path. Throws
+     * if the file doesn't exist (as opposed to returning `undefined`, like
+     * fs.readFile does).
+     */
+    private static readJson(fs: ts.server.ServerHost, path: string): any {
+        const data = fs.readFile(path);
+        if (!data) {
+            throw new Error('Failed to read file: ' + path);
+        }
+        return JSON.parse(data.trim());
+    }
 }
diff --git a/extensions/typescript-language-features/web/Source/util/args.ts b/extensions/typescript-language-features/web/Source/util/args.ts
index 8a9224ddf6b8f..3b69737285066 100644
--- a/extensions/typescript-language-features/web/Source/util/args.ts
+++ b/extensions/typescript-language-features/web/Source/util/args.ts
@@ -3,40 +3,36 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import type * as ts from 'typescript/lib/tsserverlibrary';
-
 export function hasArgument(args: readonly string[], name: string): boolean {
-	return args.indexOf(name) >= 0;
+    return args.indexOf(name) >= 0;
 }
-
 export function findArgument(args: readonly string[], name: string): string | undefined {
-	const index = args.indexOf(name);
-	return 0 <= index && index < args.length - 1
-		? args[index + 1]
-		: undefined;
+    const index = args.indexOf(name);
+    return 0 <= index && index < args.length - 1
+        ? args[index + 1]
+        : undefined;
 }
-
 export function findArgumentStringArray(args: readonly string[], name: string): readonly string[] {
-	const arg = findArgument(args, name);
-	return arg === undefined ? [] : arg.split(',').filter(name => name !== '');
+    const arg = findArgument(args, name);
+    return arg === undefined ? [] : arg.split(',').filter(name => name !== '');
 }
-
 /**
  * Copied from `ts.LanguageServiceMode` to avoid direct dependency.
  */
 export enum LanguageServiceMode {
-	Semantic = 0,
-	PartialSemantic = 1,
-	Syntactic = 2,
+    Semantic = 0,
+    PartialSemantic = 1,
+    Syntactic = 2
 }
-
 export function parseServerMode(args: readonly string[]): ts.LanguageServiceMode | string | undefined {
-	const mode = findArgument(args, '--serverMode');
-	if (!mode) { return undefined; }
-
-	switch (mode.toLowerCase()) {
-		case 'semantic': return LanguageServiceMode.Semantic;
-		case 'partialsemantic': return LanguageServiceMode.PartialSemantic;
-		case 'syntactic': return LanguageServiceMode.Syntactic;
-		default: return mode;
-	}
+    const mode = findArgument(args, '--serverMode');
+    if (!mode) {
+        return undefined;
+    }
+    switch (mode.toLowerCase()) {
+        case 'semantic': return LanguageServiceMode.Semantic;
+        case 'partialsemantic': return LanguageServiceMode.PartialSemantic;
+        case 'syntactic': return LanguageServiceMode.Syntactic;
+        default: return mode;
+    }
 }
diff --git a/extensions/typescript-language-features/web/Source/util/hrtime.ts b/extensions/typescript-language-features/web/Source/util/hrtime.ts
index 76ed9db834f73..2056e668f4fc3 100644
--- a/extensions/typescript-language-features/web/Source/util/hrtime.ts
+++ b/extensions/typescript-language-features/web/Source/util/hrtime.ts
@@ -2,18 +2,24 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-export function hrtime(previous?: [number, number]): [number, number] {
-	const now = self.performance.now() * 0.001;
-	let seconds = Math.floor(now);
-	let nanoseconds = Math.floor((now % 1) * 1000000000);
-	// NOTE: This check is added probably because it's missed without strictFunctionTypes on
-	if (previous?.[0] !== undefined && previous?.[1] !== undefined) {
-		seconds = seconds - previous[0];
-		nanoseconds = nanoseconds - previous[1];
-		if (nanoseconds < 0) {
-			seconds--;
-			nanoseconds += 1000000000;
-		}
-	}
-	return [seconds, nanoseconds];
+export function hrtime(previous?: [
+    number,
+    number
+]): [
+    number,
+    number
+] {
+    const now = self.performance.now() * 0.001;
+    let seconds = Math.floor(now);
+    let nanoseconds = Math.floor((now % 1) * 1000000000);
+    // NOTE: This check is added probably because it's missed without strictFunctionTypes on
+    if (previous?.[0] !== undefined && previous?.[1] !== undefined) {
+        seconds = seconds - previous[0];
+        nanoseconds = nanoseconds - previous[1];
+        if (nanoseconds < 0) {
+            seconds--;
+            nanoseconds += 1000000000;
+        }
+    }
+    return [seconds, nanoseconds];
 }
diff --git a/extensions/typescript-language-features/web/Source/wasmCancellationToken.ts b/extensions/typescript-language-features/web/Source/wasmCancellationToken.ts
index 3885d107f2536..0437506376a0e 100644
--- a/extensions/typescript-language-features/web/Source/wasmCancellationToken.ts
+++ b/extensions/typescript-language-features/web/Source/wasmCancellationToken.ts
@@ -3,24 +3,21 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 import type * as ts from 'typescript/lib/tsserverlibrary';
-
 export class WasmCancellationToken implements ts.server.ServerCancellationToken {
-	shouldCancel: (() => boolean) | undefined;
-	currentRequestId: number | undefined = undefined;
-
-	setRequest(requestId: number) {
-		this.currentRequestId = requestId;
-	}
-
-	resetRequest(requestId: number) {
-		if (requestId === this.currentRequestId) {
-			this.currentRequestId = undefined;
-		} else {
-			throw new Error(`Mismatched request id, expected ${this.currentRequestId} but got ${requestId}`);
-		}
-	}
-
-	isCancellationRequested(): boolean {
-		return this.currentRequestId !== undefined && !!this.shouldCancel && this.shouldCancel();
-	}
+    shouldCancel: (() => boolean) | undefined;
+    currentRequestId: number | undefined = undefined;
+    setRequest(requestId: number) {
+        this.currentRequestId = requestId;
+    }
+    resetRequest(requestId: number) {
+        if (requestId === this.currentRequestId) {
+            this.currentRequestId = undefined;
+        }
+        else {
+            throw new Error(`Mismatched request id, expected ${this.currentRequestId} but got ${requestId}`);
+        }
+    }
+    isCancellationRequested(): boolean {
+        return this.currentRequestId !== undefined && !!this.shouldCancel && this.shouldCancel();
+    }
 }
diff --git a/extensions/typescript-language-features/web/Source/webServer.ts b/extensions/typescript-language-features/web/Source/webServer.ts
index 3d2d5f9dfeb9d..7a7b66ba82dd2 100644
--- a/extensions/typescript-language-features/web/Source/webServer.ts
+++ b/extensions/typescript-language-features/web/Source/webServer.ts
@@ -3,7 +3,6 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 /// 
-
 import ts from 'typescript/lib/tsserverlibrary';
 import { URI } from 'vscode-uri';
 import { FileWatcherManager } from './fileWatcherManager';
@@ -12,66 +11,59 @@ import { PathMapper } from './pathMapper';
 import { createSys } from './serverHost';
 import { findArgument, findArgumentStringArray, hasArgument, parseServerMode } from './util/args';
 import { StartSessionOptions, startWorkerSession } from './workerSession';
-
 const setSys: (s: ts.System) => void = (ts as any).setSys;
-
-async function initializeSession(
-	args: readonly string[],
-	extensionUri: URI,
-	ports: { tsserver: MessagePort; sync: MessagePort; watcher: MessagePort },
-): Promise {
-	const logLevel = parseLogLevel(findArgument(args, '--logVerbosity'));
-	const logger = new Logger(logLevel);
-
-	const modeOrUnknown = parseServerMode(args);
-	const serverMode = typeof modeOrUnknown === 'number' ? modeOrUnknown : undefined;
-	const unknownServerMode = typeof modeOrUnknown === 'string' ? modeOrUnknown : undefined;
-	logger.tsLogger.info(`Starting TS Server`);
-	logger.tsLogger.info(`Version: 0.0.0`);
-	logger.tsLogger.info(`Arguments: ${args.join(' ')}`);
-	logger.tsLogger.info(`ServerMode: ${serverMode} unknownServerMode: ${unknownServerMode}`);
-	const sessionOptions = parseSessionOptions(args, serverMode);
-
-	const enabledExperimentalTypeAcquisition = hasArgument(args, '--enableProjectWideIntelliSenseOnWeb') && hasArgument(args, '--experimentalTypeAcquisition');
-
-	const pathMapper = new PathMapper(extensionUri);
-	const watchManager = new FileWatcherManager(ports.watcher, extensionUri, enabledExperimentalTypeAcquisition, pathMapper, logger);
-
-	const { sys, fs } = await createSys(ts, args, ports.sync, logger, watchManager, pathMapper, () => {
-		removeEventListener('message', listener);
-	});
-	setSys(sys);
-	startWorkerSession(ts, sys, fs, sessionOptions, ports.tsserver, pathMapper, logger);
+async function initializeSession(args: readonly string[], extensionUri: URI, ports: {
+    tsserver: MessagePort;
+    sync: MessagePort;
+    watcher: MessagePort;
+}): Promise {
+    const logLevel = parseLogLevel(findArgument(args, '--logVerbosity'));
+    const logger = new Logger(logLevel);
+    const modeOrUnknown = parseServerMode(args);
+    const serverMode = typeof modeOrUnknown === 'number' ? modeOrUnknown : undefined;
+    const unknownServerMode = typeof modeOrUnknown === 'string' ? modeOrUnknown : undefined;
+    logger.tsLogger.info(`Starting TS Server`);
+    logger.tsLogger.info(`Version: 0.0.0`);
+    logger.tsLogger.info(`Arguments: ${args.join(' ')}`);
+    logger.tsLogger.info(`ServerMode: ${serverMode} unknownServerMode: ${unknownServerMode}`);
+    const sessionOptions = parseSessionOptions(args, serverMode);
+    const enabledExperimentalTypeAcquisition = hasArgument(args, '--enableProjectWideIntelliSenseOnWeb') && hasArgument(args, '--experimentalTypeAcquisition');
+    const pathMapper = new PathMapper(extensionUri);
+    const watchManager = new FileWatcherManager(ports.watcher, extensionUri, enabledExperimentalTypeAcquisition, pathMapper, logger);
+    const { sys, fs } = await createSys(ts, args, ports.sync, logger, watchManager, pathMapper, () => {
+        removeEventListener('message', listener);
+    });
+    setSys(sys);
+    startWorkerSession(ts, sys, fs, sessionOptions, ports.tsserver, pathMapper, logger);
 }
-
 function parseSessionOptions(args: readonly string[], serverMode: ts.LanguageServiceMode | undefined): StartSessionOptions {
-	return {
-		globalPlugins: findArgumentStringArray(args, '--globalPlugins'),
-		pluginProbeLocations: findArgumentStringArray(args, '--pluginProbeLocations'),
-		allowLocalPluginLoads: hasArgument(args, '--allowLocalPluginLoads'),
-		useSingleInferredProject: hasArgument(args, '--useSingleInferredProject'),
-		useInferredProjectPerProjectRoot: hasArgument(args, '--useInferredProjectPerProjectRoot'),
-		suppressDiagnosticEvents: hasArgument(args, '--suppressDiagnosticEvents'),
-		noGetErrOnBackgroundUpdate: hasArgument(args, '--noGetErrOnBackgroundUpdate'),
-		serverMode,
-		disableAutomaticTypingAcquisition: hasArgument(args, '--disableAutomaticTypingAcquisition'),
-	};
+    return {
+        globalPlugins: findArgumentStringArray(args, '--globalPlugins'),
+        pluginProbeLocations: findArgumentStringArray(args, '--pluginProbeLocations'),
+        allowLocalPluginLoads: hasArgument(args, '--allowLocalPluginLoads'),
+        useSingleInferredProject: hasArgument(args, '--useSingleInferredProject'),
+        useInferredProjectPerProjectRoot: hasArgument(args, '--useInferredProjectPerProjectRoot'),
+        suppressDiagnosticEvents: hasArgument(args, '--suppressDiagnosticEvents'),
+        noGetErrOnBackgroundUpdate: hasArgument(args, '--noGetErrOnBackgroundUpdate'),
+        serverMode,
+        disableAutomaticTypingAcquisition: hasArgument(args, '--disableAutomaticTypingAcquisition'),
+    };
 }
-
 let hasInitialized = false;
 const listener = async (e: any) => {
-	if (!hasInitialized) {
-		hasInitialized = true;
-		if ('args' in e.data) {
-			const args = e.data.args;
-			const extensionUri = URI.from(e.data.extensionUri);
-			const [sync, tsserver, watcher] = e.ports as MessagePort[];
-			await initializeSession(args, extensionUri, { sync, tsserver, watcher });
-		} else {
-			console.error('unexpected message in place of initial message: ' + JSON.stringify(e.data));
-		}
-		return;
-	}
-	console.error(`unexpected message on main channel: ${JSON.stringify(e)}`);
+    if (!hasInitialized) {
+        hasInitialized = true;
+        if ('args' in e.data) {
+            const args = e.data.args;
+            const extensionUri = URI.from(e.data.extensionUri);
+            const [sync, tsserver, watcher] = e.ports as MessagePort[];
+            await initializeSession(args, extensionUri, { sync, tsserver, watcher });
+        }
+        else {
+            console.error('unexpected message in place of initial message: ' + JSON.stringify(e.data));
+        }
+        return;
+    }
+    console.error(`unexpected message on main channel: ${JSON.stringify(e)}`);
 };
 addEventListener('message', listener);
diff --git a/extensions/typescript-language-features/web/Source/workerSession.ts b/extensions/typescript-language-features/web/Source/workerSession.ts
index 6ae517ccc32cd..e8a18df462705 100644
--- a/extensions/typescript-language-features/web/Source/workerSession.ts
+++ b/extensions/typescript-language-features/web/Source/workerSession.ts
@@ -9,118 +9,95 @@ import { WebTypingsInstallerClient } from './typingsInstaller/typingsInstaller';
 import { hrtime } from './util/hrtime';
 import { WasmCancellationToken } from './wasmCancellationToken';
 import { PathMapper } from './pathMapper';
-
 export interface StartSessionOptions {
-	readonly globalPlugins: ts.server.SessionOptions['globalPlugins'];
-	readonly pluginProbeLocations: ts.server.SessionOptions['pluginProbeLocations'];
-	readonly allowLocalPluginLoads: ts.server.SessionOptions['allowLocalPluginLoads'];
-	readonly useSingleInferredProject: ts.server.SessionOptions['useSingleInferredProject'];
-	readonly useInferredProjectPerProjectRoot: ts.server.SessionOptions['useInferredProjectPerProjectRoot'];
-	readonly suppressDiagnosticEvents: ts.server.SessionOptions['suppressDiagnosticEvents'];
-	readonly noGetErrOnBackgroundUpdate: ts.server.SessionOptions['noGetErrOnBackgroundUpdate'];
-	readonly serverMode: ts.server.SessionOptions['serverMode'];
-	readonly disableAutomaticTypingAcquisition: boolean;
+    readonly globalPlugins: ts.server.SessionOptions['globalPlugins'];
+    readonly pluginProbeLocations: ts.server.SessionOptions['pluginProbeLocations'];
+    readonly allowLocalPluginLoads: ts.server.SessionOptions['allowLocalPluginLoads'];
+    readonly useSingleInferredProject: ts.server.SessionOptions['useSingleInferredProject'];
+    readonly useInferredProjectPerProjectRoot: ts.server.SessionOptions['useInferredProjectPerProjectRoot'];
+    readonly suppressDiagnosticEvents: ts.server.SessionOptions['suppressDiagnosticEvents'];
+    readonly noGetErrOnBackgroundUpdate: ts.server.SessionOptions['noGetErrOnBackgroundUpdate'];
+    readonly serverMode: ts.server.SessionOptions['serverMode'];
+    readonly disableAutomaticTypingAcquisition: boolean;
 }
-
-export function startWorkerSession(
-	ts: typeof import('typescript/lib/tsserverlibrary'),
-	host: ts.server.ServerHost,
-	fs: FileSystem | undefined,
-	options: StartSessionOptions,
-	port: MessagePort,
-	pathMapper: PathMapper,
-	logger: Logger,
-): void {
-	const indent: (str: string) => string = (ts as any).server.indent;
-
-	const worker = new class WorkerSession extends ts.server.Session<{}> {
-
-		private readonly wasmCancellationToken: WasmCancellationToken;
-		private readonly listener: (message: any) => void;
-
-		constructor() {
-			const cancellationToken = new WasmCancellationToken();
-			const typingsInstaller = options.disableAutomaticTypingAcquisition || !fs ? ts.server.nullTypingsInstaller : new WebTypingsInstallerClient(host, '/vscode-global-typings/ts-nul-authority/projects');
-
-			super({
-				host,
-				cancellationToken,
-				...options,
-				typingsInstaller,
-				byteLength: () => { throw new Error('Not implemented'); }, // Formats the message text in send of Session which is overridden in this class so not needed
-				hrtime,
-				logger: logger.tsLogger,
-				canUseEvents: true,
-			});
-			this.wasmCancellationToken = cancellationToken;
-
-			this.listener = (message: any) => {
-				// TEMP fix since Cancellation.retrieveCheck is not correct
-				function retrieveCheck2(data: any) {
-					if (!globalThis.crossOriginIsolated || !(data.$cancellationData instanceof SharedArrayBuffer)) {
-						return () => false;
-					}
-					const typedArray = new Int32Array(data.$cancellationData, 0, 1);
-					return () => {
-						return Atomics.load(typedArray, 0) === 1;
-					};
-				}
-
-				const shouldCancel = retrieveCheck2(message.data);
-				if (shouldCancel) {
-					this.wasmCancellationToken.shouldCancel = shouldCancel;
-				}
-
-				try {
-					if (message.data.command === 'updateOpen') {
-						const args = message.data.arguments as ts.server.protocol.UpdateOpenRequestArgs;
-						for (const open of args.openFiles ?? []) {
-							if (open.projectRootPath) {
-								pathMapper.addProjectRoot(open.projectRootPath);
-							}
-						}
-					}
-				} catch {
-					// Noop
-				}
-
-				this.onMessage(message.data);
-			};
-		}
-
-		public override send(msg: ts.server.protocol.Message) {
-			if (msg.type === 'event' && !this.canUseEvents) {
-				if (this.logger.hasLevel(ts.server.LogLevel.verbose)) {
-					this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
-				}
-				return;
-			}
-			if (this.logger.hasLevel(ts.server.LogLevel.verbose)) {
-				this.logger.info(`${msg.type}:${indent(JSON.stringify(msg))}`);
-			}
-			port.postMessage(msg);
-		}
-
-		protected override parseMessage(message: {}): ts.server.protocol.Request {
-			return message as ts.server.protocol.Request;
-		}
-
-		protected override toStringMessage(message: {}) {
-			return JSON.stringify(message, undefined, 2);
-		}
-
-		override exit() {
-			this.logger.info('Exiting...');
-			port.removeEventListener('message', this.listener);
-			this.projectService.closeLog();
-			close();
-		}
-
-		listen() {
-			this.logger.info(`webServer.ts: tsserver starting to listen for messages on 'message'...`);
-			port.onmessage = this.listener;
-		}
-	}();
-
-	worker.listen();
+export function startWorkerSession(ts: typeof import('typescript/lib/tsserverlibrary'), host: ts.server.ServerHost, fs: FileSystem | undefined, options: StartSessionOptions, port: MessagePort, pathMapper: PathMapper, logger: Logger): void {
+    const indent: (str: string) => string = (ts as any).server.indent;
+    const worker = new class WorkerSession extends ts.server.Session<{}> {
+        private readonly wasmCancellationToken: WasmCancellationToken;
+        private readonly listener: (message: any) => void;
+        constructor() {
+            const cancellationToken = new WasmCancellationToken();
+            const typingsInstaller = options.disableAutomaticTypingAcquisition || !fs ? ts.server.nullTypingsInstaller : new WebTypingsInstallerClient(host, '/vscode-global-typings/ts-nul-authority/projects');
+            super({
+                host,
+                cancellationToken,
+                ...options,
+                typingsInstaller,
+                byteLength: () => { throw new Error('Not implemented'); }, // Formats the message text in send of Session which is overridden in this class so not needed
+                hrtime,
+                logger: logger.tsLogger,
+                canUseEvents: true,
+            });
+            this.wasmCancellationToken = cancellationToken;
+            this.listener = (message: any) => {
+                // TEMP fix since Cancellation.retrieveCheck is not correct
+                function retrieveCheck2(data: any) {
+                    if (!globalThis.crossOriginIsolated || !(data.$cancellationData instanceof SharedArrayBuffer)) {
+                        return () => false;
+                    }
+                    const typedArray = new Int32Array(data.$cancellationData, 0, 1);
+                    return () => {
+                        return Atomics.load(typedArray, 0) === 1;
+                    };
+                }
+                const shouldCancel = retrieveCheck2(message.data);
+                if (shouldCancel) {
+                    this.wasmCancellationToken.shouldCancel = shouldCancel;
+                }
+                try {
+                    if (message.data.command === 'updateOpen') {
+                        const args = message.data.arguments as ts.server.protocol.UpdateOpenRequestArgs;
+                        for (const open of args.openFiles ?? []) {
+                            if (open.projectRootPath) {
+                                pathMapper.addProjectRoot(open.projectRootPath);
+                            }
+                        }
+                    }
+                }
+                catch {
+                    // Noop
+                }
+                this.onMessage(message.data);
+            };
+        }
+        public override send(msg: ts.server.protocol.Message) {
+            if (msg.type === 'event' && !this.canUseEvents) {
+                if (this.logger.hasLevel(ts.server.LogLevel.verbose)) {
+                    this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
+                }
+                return;
+            }
+            if (this.logger.hasLevel(ts.server.LogLevel.verbose)) {
+                this.logger.info(`${msg.type}:${indent(JSON.stringify(msg))}`);
+            }
+            port.postMessage(msg);
+        }
+        protected override parseMessage(message: {}): ts.server.protocol.Request {
+            return message as ts.server.protocol.Request;
+        }
+        protected override toStringMessage(message: {}) {
+            return JSON.stringify(message, undefined, 2);
+        }
+        override exit() {
+            this.logger.info('Exiting...');
+            port.removeEventListener('message', this.listener);
+            this.projectService.closeLog();
+            close();
+        }
+        listen() {
+            this.logger.info(`webServer.ts: tsserver starting to listen for messages on 'message'...`);
+            port.onmessage = this.listener;
+        }
+    }();
+    worker.listen();
 }
diff --git a/extensions/vscode-api-tests/Source/extension.ts b/extensions/vscode-api-tests/Source/extension.ts
index 79223b12361f5..79a3f6d9c868b 100644
--- a/extensions/vscode-api-tests/Source/extension.ts
+++ b/extensions/vscode-api-tests/Source/extension.ts
@@ -2,10 +2,8 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export function activate(_context: vscode.ExtensionContext) {
-	// Set context as a global as some tests depend on it
-	(global as any).testExtensionContext = _context;
+    // Set context as a global as some tests depend on it
+    (global as any).testExtensionContext = _context;
 }
diff --git a/extensions/vscode-api-tests/Source/memfs.ts b/extensions/vscode-api-tests/Source/memfs.ts
index cd422682499f6..a6253ebf9df84 100644
--- a/extensions/vscode-api-tests/Source/memfs.ts
+++ b/extensions/vscode-api-tests/Source/memfs.ts
@@ -2,241 +2,208 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
-
 import * as path from 'path';
 import * as vscode from 'vscode';
-
 class File implements vscode.FileStat {
-
-	type: vscode.FileType;
-	ctime: number;
-	mtime: number;
-	size: number;
-
-	name: string;
-	data?: Uint8Array;
-
-	constructor(name: string) {
-		this.type = vscode.FileType.File;
-		this.ctime = Date.now();
-		this.mtime = Date.now();
-		this.size = 0;
-		this.name = name;
-	}
+    type: vscode.FileType;
+    ctime: number;
+    mtime: number;
+    size: number;
+    name: string;
+    data?: Uint8Array;
+    constructor(name: string) {
+        this.type = vscode.FileType.File;
+        this.ctime = Date.now();
+        this.mtime = Date.now();
+        this.size = 0;
+        this.name = name;
+    }
 }
-
 class Directory implements vscode.FileStat {
-
-	type: vscode.FileType;
-	ctime: number;
-	mtime: number;
-	size: number;
-
-	name: string;
-	entries: Map;
-
-	constructor(name: string) {
-		this.type = vscode.FileType.Directory;
-		this.ctime = Date.now();
-		this.mtime = Date.now();
-		this.size = 0;
-		this.name = name;
-		this.entries = new Map();
-	}
+    type: vscode.FileType;
+    ctime: number;
+    mtime: number;
+    size: number;
+    name: string;
+    entries: Map;
+    constructor(name: string) {
+        this.type = vscode.FileType.Directory;
+        this.ctime = Date.now();
+        this.mtime = Date.now();
+        this.size = 0;
+        this.name = name;
+        this.entries = new Map();
+    }
 }
-
 export type Entry = File | Directory;
-
 export class TestFS implements vscode.FileSystemProvider {
-
-	constructor(
-		readonly scheme: string,
-		readonly isCaseSensitive: boolean
-	) { }
-
-	readonly root = new Directory('');
-
-	// --- manage file metadata
-
-	stat(uri: vscode.Uri): vscode.FileStat {
-		return this._lookup(uri, false);
-	}
-
-	readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
-		const entry = this._lookupAsDirectory(uri, false);
-		const result: [string, vscode.FileType][] = [];
-		for (const [name, child] of entry.entries) {
-			result.push([name, child.type]);
-		}
-		return result;
-	}
-
-	// --- manage file contents
-
-	readFile(uri: vscode.Uri): Uint8Array {
-		const data = this._lookupAsFile(uri, false).data;
-		if (data) {
-			return data;
-		}
-		throw vscode.FileSystemError.FileNotFound();
-	}
-
-	writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean }): void {
-		const basename = path.posix.basename(uri.path);
-		const parent = this._lookupParentDirectory(uri);
-		let entry = parent.entries.get(basename);
-		if (entry instanceof Directory) {
-			throw vscode.FileSystemError.FileIsADirectory(uri);
-		}
-		if (!entry && !options.create) {
-			throw vscode.FileSystemError.FileNotFound(uri);
-		}
-		if (entry && options.create && !options.overwrite) {
-			throw vscode.FileSystemError.FileExists(uri);
-		}
-		if (!entry) {
-			entry = new File(basename);
-			parent.entries.set(basename, entry);
-			this._fireSoon({ type: vscode.FileChangeType.Created, uri });
-		}
-		entry.mtime = Date.now();
-		entry.size = content.byteLength;
-		entry.data = content;
-
-		this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
-	}
-
-	// --- manage files/folders
-
-	rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void {
-
-		if (!options.overwrite && this._lookup(newUri, true)) {
-			throw vscode.FileSystemError.FileExists(newUri);
-		}
-
-		const entry = this._lookup(oldUri, false);
-		const oldParent = this._lookupParentDirectory(oldUri);
-
-		const newParent = this._lookupParentDirectory(newUri);
-		const newName = path.posix.basename(newUri.path);
-
-		oldParent.entries.delete(entry.name);
-		entry.name = newName;
-		newParent.entries.set(newName, entry);
-
-		this._fireSoon(
-			{ type: vscode.FileChangeType.Deleted, uri: oldUri },
-			{ type: vscode.FileChangeType.Created, uri: newUri }
-		);
-	}
-
-	delete(uri: vscode.Uri): void {
-		const dirname = uri.with({ path: path.posix.dirname(uri.path) });
-		const basename = path.posix.basename(uri.path);
-		const parent = this._lookupAsDirectory(dirname, false);
-		if (!parent.entries.has(basename)) {
-			throw vscode.FileSystemError.FileNotFound(uri);
-		}
-		parent.entries.delete(basename);
-		parent.mtime = Date.now();
-		parent.size -= 1;
-		this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted });
-	}
-
-	createDirectory(uri: vscode.Uri): void {
-		const basename = path.posix.basename(uri.path);
-		const dirname = uri.with({ path: path.posix.dirname(uri.path) });
-		const parent = this._lookupAsDirectory(dirname, false);
-
-		const entry = new Directory(basename);
-		parent.entries.set(entry.name, entry);
-		parent.mtime = Date.now();
-		parent.size += 1;
-		this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri });
-	}
-
-	// --- lookup
-
-	private _lookup(uri: vscode.Uri, silent: false): Entry;
-	private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined;
-	private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined {
-		const parts = uri.path.split('/');
-		let entry: Entry = this.root;
-		for (const part of parts) {
-			const partLow = part.toLowerCase();
-			if (!part) {
-				continue;
-			}
-			let child: Entry | undefined;
-			if (entry instanceof Directory) {
-				if (this.isCaseSensitive) {
-					child = entry.entries.get(part);
-				} else {
-					for (const [key, value] of entry.entries) {
-						if (key.toLowerCase() === partLow) {
-							child = value;
-							break;
-						}
-					}
-				}
-			}
-			if (!child) {
-				if (!silent) {
-					throw vscode.FileSystemError.FileNotFound(uri);
-				} else {
-					return undefined;
-				}
-			}
-			entry = child;
-		}
-		return entry;
-	}
-
-	private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory {
-		const entry = this._lookup(uri, silent);
-		if (entry instanceof Directory) {
-			return entry;
-		}
-		throw vscode.FileSystemError.FileNotADirectory(uri);
-	}
-
-	private _lookupAsFile(uri: vscode.Uri, silent: boolean): File {
-		const entry = this._lookup(uri, silent);
-		if (entry instanceof File) {
-			return entry;
-		}
-		throw vscode.FileSystemError.FileIsADirectory(uri);
-	}
-
-	private _lookupParentDirectory(uri: vscode.Uri): Directory {
-		const dirname = uri.with({ path: path.posix.dirname(uri.path) });
-		return this._lookupAsDirectory(dirname, false);
-	}
-
-	// --- manage file events
-
-	private _emitter = new vscode.EventEmitter();
-	private _bufferedEvents: vscode.FileChangeEvent[] = [];
-	private _fireSoonHandle?: NodeJS.Timeout;
-
-	readonly onDidChangeFile: vscode.Event = this._emitter.event;
-
-	watch(_resource: vscode.Uri, _options: { recursive: boolean; excludes: string[] }): vscode.Disposable {
-		// ignore, fires for all changes...
-		return new vscode.Disposable(() => { });
-	}
-
-	private _fireSoon(...events: vscode.FileChangeEvent[]): void {
-		this._bufferedEvents.push(...events);
-
-		if (this._fireSoonHandle) {
-			clearTimeout(this._fireSoonHandle);
-		}
-
-		this._fireSoonHandle = setTimeout(() => {
-			this._emitter.fire(this._bufferedEvents);
-			this._bufferedEvents.length = 0;
-		}, 5);
-	}
+    constructor(readonly scheme: string, readonly isCaseSensitive: boolean) { }
+    readonly root = new Directory('');
+    // --- manage file metadata
+    stat(uri: vscode.Uri): vscode.FileStat {
+        return this._lookup(uri, false);
+    }
+    readDirectory(uri: vscode.Uri): [
+        string,
+        vscode.FileType
+    ][] {
+        const entry = this._lookupAsDirectory(uri, false);
+        const result: [
+            string,
+            vscode.FileType
+        ][] = [];
+        for (const [name, child] of entry.entries) {
+            result.push([name, child.type]);
+        }
+        return result;
+    }
+    // --- manage file contents
+    readFile(uri: vscode.Uri): Uint8Array {
+        const data = this._lookupAsFile(uri, false).data;
+        if (data) {
+            return data;
+        }
+        throw vscode.FileSystemError.FileNotFound();
+    }
+    writeFile(uri: vscode.Uri, content: Uint8Array, options: {
+        create: boolean;
+        overwrite: boolean;
+    }): void {
+        const basename = path.posix.basename(uri.path);
+        const parent = this._lookupParentDirectory(uri);
+        let entry = parent.entries.get(basename);
+        if (entry instanceof Directory) {
+            throw vscode.FileSystemError.FileIsADirectory(uri);
+        }
+        if (!entry && !options.create) {
+            throw vscode.FileSystemError.FileNotFound(uri);
+        }
+        if (entry && options.create && !options.overwrite) {
+            throw vscode.FileSystemError.FileExists(uri);
+        }
+        if (!entry) {
+            entry = new File(basename);
+            parent.entries.set(basename, entry);
+            this._fireSoon({ type: vscode.FileChangeType.Created, uri });
+        }
+        entry.mtime = Date.now();
+        entry.size = content.byteLength;
+        entry.data = content;
+        this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
+    }
+    // --- manage files/folders
+    rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: {
+        overwrite: boolean;
+    }): void {
+        if (!options.overwrite && this._lookup(newUri, true)) {
+            throw vscode.FileSystemError.FileExists(newUri);
+        }
+        const entry = this._lookup(oldUri, false);
+        const oldParent = this._lookupParentDirectory(oldUri);
+        const newParent = this._lookupParentDirectory(newUri);
+        const newName = path.posix.basename(newUri.path);
+        oldParent.entries.delete(entry.name);
+        entry.name = newName;
+        newParent.entries.set(newName, entry);
+        this._fireSoon({ type: vscode.FileChangeType.Deleted, uri: oldUri }, { type: vscode.FileChangeType.Created, uri: newUri });
+    }
+    delete(uri: vscode.Uri): void {
+        const dirname = uri.with({ path: path.posix.dirname(uri.path) });
+        const basename = path.posix.basename(uri.path);
+        const parent = this._lookupAsDirectory(dirname, false);
+        if (!parent.entries.has(basename)) {
+            throw vscode.FileSystemError.FileNotFound(uri);
+        }
+        parent.entries.delete(basename);
+        parent.mtime = Date.now();
+        parent.size -= 1;
+        this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted });
+    }
+    createDirectory(uri: vscode.Uri): void {
+        const basename = path.posix.basename(uri.path);
+        const dirname = uri.with({ path: path.posix.dirname(uri.path) });
+        const parent = this._lookupAsDirectory(dirname, false);
+        const entry = new Directory(basename);
+        parent.entries.set(entry.name, entry);
+        parent.mtime = Date.now();
+        parent.size += 1;
+        this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri });
+    }
+    // --- lookup
+    private _lookup(uri: vscode.Uri, silent: false): Entry;
+    private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined;
+    private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined {
+        const parts = uri.path.split('/');
+        let entry: Entry = this.root;
+        for (const part of parts) {
+            const partLow = part.toLowerCase();
+            if (!part) {
+                continue;
+            }
+            let child: Entry | undefined;
+            if (entry instanceof Directory) {
+                if (this.isCaseSensitive) {
+                    child = entry.entries.get(part);
+                }
+                else {
+                    for (const [key, value] of entry.entries) {
+                        if (key.toLowerCase() === partLow) {
+                            child = value;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (!child) {
+                if (!silent) {
+                    throw vscode.FileSystemError.FileNotFound(uri);
+                }
+                else {
+                    return undefined;
+                }
+            }
+            entry = child;
+        }
+        return entry;
+    }
+    private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory {
+        const entry = this._lookup(uri, silent);
+        if (entry instanceof Directory) {
+            return entry;
+        }
+        throw vscode.FileSystemError.FileNotADirectory(uri);
+    }
+    private _lookupAsFile(uri: vscode.Uri, silent: boolean): File {
+        const entry = this._lookup(uri, silent);
+        if (entry instanceof File) {
+            return entry;
+        }
+        throw vscode.FileSystemError.FileIsADirectory(uri);
+    }
+    private _lookupParentDirectory(uri: vscode.Uri): Directory {
+        const dirname = uri.with({ path: path.posix.dirname(uri.path) });
+        return this._lookupAsDirectory(dirname, false);
+    }
+    // --- manage file events
+    private _emitter = new vscode.EventEmitter();
+    private _bufferedEvents: vscode.FileChangeEvent[] = [];
+    private _fireSoonHandle?: NodeJS.Timeout;
+    readonly onDidChangeFile: vscode.Event = this._emitter.event;
+    watch(_resource: vscode.Uri, _options: {
+        recursive: boolean;
+        excludes: string[];
+    }): vscode.Disposable {
+        // ignore, fires for all changes...
+        return new vscode.Disposable(() => { });
+    }
+    private _fireSoon(...events: vscode.FileChangeEvent[]): void {
+        this._bufferedEvents.push(...events);
+        if (this._fireSoonHandle) {
+            clearTimeout(this._fireSoonHandle);
+        }
+        this._fireSoonHandle = setTimeout(() => {
+            this._emitter.fire(this._bufferedEvents);
+            this._bufferedEvents.length = 0;
+        }, 5);
+    }
 }
diff --git a/extensions/vscode-api-tests/Source/singlefolder-tests/index.ts b/extensions/vscode-api-tests/Source/singlefolder-tests/index.ts
index 6798fc5a1e031..c1ede7d2691b3 100644
--- a/extensions/vscode-api-tests/Source/singlefolder-tests/index.ts
+++ b/extensions/vscode-api-tests/Source/singlefolder-tests/index.ts
@@ -2,39 +2,35 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as testRunner from '../../../../test/integration/electron/testrunner';
-
 const options: any = {
-	ui: 'tdd',
-	color: true,
-	timeout: 60000
+    ui: 'tdd',
+    color: true,
+    timeout: 60000
 };
-
 // These integration tests is being run in multiple environments (electron, web, remote)
 // so we need to set the suite name based on the environment as the suite name is used
 // for the test results file name
 let suite = '';
 if (process.env.VSCODE_BROWSER) {
-	suite = `${process.env.VSCODE_BROWSER} Browser Integration Single Folder Tests`;
-} else if (process.env.REMOTE_VSCODE) {
-	suite = 'Remote Integration Single Folder Tests';
-} else {
-	suite = 'Integration Single Folder Tests';
+    suite = `${process.env.VSCODE_BROWSER} Browser Integration Single Folder Tests`;
+}
+else if (process.env.REMOTE_VSCODE) {
+    suite = 'Remote Integration Single Folder Tests';
+}
+else {
+    suite = 'Integration Single Folder Tests';
 }
-
 if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
-	options.reporter = 'mocha-multi-reporters';
-	options.reporterOptions = {
-		reporterEnabled: 'spec, mocha-junit-reporter',
-		mochaJunitReporterReporterOptions: {
-			testsuitesTitle: `${suite} ${process.platform}`,
-			mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
-		}
-	};
+    options.reporter = 'mocha-multi-reporters';
+    options.reporterOptions = {
+        reporterEnabled: 'spec, mocha-junit-reporter',
+        mochaJunitReporterReporterOptions: {
+            testsuitesTitle: `${suite} ${process.platform}`,
+            mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
+        }
+    };
 }
-
 testRunner.configure(options);
-
 export = testRunner;
diff --git a/extensions/vscode-api-tests/Source/utils.ts b/extensions/vscode-api-tests/Source/utils.ts
index 28cd422ce40fd..9b4249dd4f482 100644
--- a/extensions/vscode-api-tests/Source/utils.ts
+++ b/extensions/vscode-api-tests/Source/utils.ts
@@ -2,251 +2,211 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as assert from 'assert';
 import { EOL } from 'os';
 import * as crypto from 'crypto';
 import * as vscode from 'vscode';
 import { TestFS } from './memfs';
-
 export function rndName() {
-	return crypto.randomBytes(8).toString('hex');
+    return crypto.randomBytes(8).toString('hex');
 }
-
 export const testFs = new TestFS('fake-fs', true);
 vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensitive: testFs.isCaseSensitive });
-
 export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, ext = ''): Promise {
-	let fakeFile: vscode.Uri;
-	if (dir) {
-		assert.strictEqual(dir.scheme, testFs.scheme);
-		fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext });
-	} else {
-		fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${rndName() + ext}`);
-	}
-	testFs.writeFile(fakeFile, Buffer.from(contents), { create: true, overwrite: true });
-	return fakeFile;
-}
-
+    let fakeFile: vscode.Uri;
+    if (dir) {
+        assert.strictEqual(dir.scheme, testFs.scheme);
+        fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext });
+    }
+    else {
+        fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${rndName() + ext}`);
+    }
+    testFs.writeFile(fakeFile, Buffer.from(contents), { create: true, overwrite: true });
+    return fakeFile;
+}
 export async function deleteFile(file: vscode.Uri): Promise {
-	try {
-		testFs.delete(file);
-		return true;
-	} catch {
-		return false;
-	}
-}
-
+    try {
+        testFs.delete(file);
+        return true;
+    }
+    catch {
+        return false;
+    }
+}
 export function pathEquals(path1: string, path2: string): boolean {
-	if (process.platform !== 'linux') {
-		path1 = path1.toLowerCase();
-		path2 = path2.toLowerCase();
-	}
-
-	return path1 === path2;
-}
-
+    if (process.platform !== 'linux') {
+        path1 = path1.toLowerCase();
+        path2 = path2.toLowerCase();
+    }
+    return path1 === path2;
+}
 export function closeAllEditors(): Thenable {
-	return vscode.commands.executeCommand('workbench.action.closeAllEditors');
+    return vscode.commands.executeCommand('workbench.action.closeAllEditors');
 }
-
 export function saveAllEditors(): Thenable {
-	return vscode.commands.executeCommand('workbench.action.files.saveAll');
+    return vscode.commands.executeCommand('workbench.action.files.saveAll');
 }
-
 export async function revertAllDirty(): Promise {
-	return vscode.commands.executeCommand('_workbench.revertAllDirty');
+    return vscode.commands.executeCommand('_workbench.revertAllDirty');
 }
-
 export function disposeAll(disposables: vscode.Disposable[]) {
-	vscode.Disposable.from(...disposables).dispose();
+    vscode.Disposable.from(...disposables).dispose();
 }
-
 export function delay(ms: number) {
-	return new Promise(resolve => setTimeout(resolve, ms));
+    return new Promise(resolve => setTimeout(resolve, ms));
 }
-
 function withLogLevel(level: string, runnable: () => Promise): () => Promise {
-	return async (): Promise => {
-		const logLevel = await vscode.commands.executeCommand('_extensionTests.getLogLevel');
-		await vscode.commands.executeCommand('_extensionTests.setLogLevel', level);
-
-		try {
-			await runnable();
-		} finally {
-			await vscode.commands.executeCommand('_extensionTests.setLogLevel', logLevel);
-		}
-	};
-}
-
+    return async (): Promise => {
+        const logLevel = await vscode.commands.executeCommand('_extensionTests.getLogLevel');
+        await vscode.commands.executeCommand('_extensionTests.setLogLevel', level);
+        try {
+            await runnable();
+        }
+        finally {
+            await vscode.commands.executeCommand('_extensionTests.setLogLevel', logLevel);
+        }
+    };
+}
 export function withLogDisabled(runnable: () => Promise): () => Promise {
-	return withLogLevel('off', runnable);
+    return withLogLevel('off', runnable);
 }
-
 export function withVerboseLogs(runnable: () => Promise): () => Promise {
-	return withLogLevel('trace', runnable);
+    return withLogLevel('trace', runnable);
 }
-
 export function assertNoRpc() {
-	assertNoRpcFromEntry([vscode, 'vscode']);
-}
-
-export function assertNoRpcFromEntry(entry: [obj: any, name: string]) {
-
-	const symProxy = Symbol.for('rpcProxy');
-	const symProtocol = Symbol.for('rpcProtocol');
-
-	const proxyPaths: string[] = [];
-	const rpcPaths: string[] = [];
-
-	function walk(obj: any, path: string, seen: Set) {
-		if (!obj) {
-			return;
-		}
-		if (typeof obj !== 'object' && typeof obj !== 'function') {
-			return;
-		}
-		if (seen.has(obj)) {
-			return;
-		}
-		seen.add(obj);
-
-		if (obj[symProtocol]) {
-			rpcPaths.push(`PROTOCOL via ${path}`);
-		}
-		if (obj[symProxy]) {
-			proxyPaths.push(`PROXY '${obj[symProxy]}' via ${path}`);
-		}
-
-		for (const key in obj) {
-			walk(obj[key], `${path}.${String(key)}`, seen);
-		}
-	}
-
-	try {
-		walk(entry[0], entry[1], new Set());
-	} catch (err) {
-		assert.fail(err);
-	}
-	assert.strictEqual(rpcPaths.length, 0, rpcPaths.join('\n'));
-	assert.strictEqual(proxyPaths.length, 0, proxyPaths.join('\n')); // happens...
-}
-
+    assertNoRpcFromEntry([vscode, 'vscode']);
+}
+export function assertNoRpcFromEntry(entry: [
+    obj: any,
+    name: string
+]) {
+    const symProxy = Symbol.for('rpcProxy');
+    const symProtocol = Symbol.for('rpcProtocol');
+    const proxyPaths: string[] = [];
+    const rpcPaths: string[] = [];
+    function walk(obj: any, path: string, seen: Set) {
+        if (!obj) {
+            return;
+        }
+        if (typeof obj !== 'object' && typeof obj !== 'function') {
+            return;
+        }
+        if (seen.has(obj)) {
+            return;
+        }
+        seen.add(obj);
+        if (obj[symProtocol]) {
+            rpcPaths.push(`PROTOCOL via ${path}`);
+        }
+        if (obj[symProxy]) {
+            proxyPaths.push(`PROXY '${obj[symProxy]}' via ${path}`);
+        }
+        for (const key in obj) {
+            walk(obj[key], `${path}.${String(key)}`, seen);
+        }
+    }
+    try {
+        walk(entry[0], entry[1], new Set());
+    }
+    catch (err) {
+        assert.fail(err);
+    }
+    assert.strictEqual(rpcPaths.length, 0, rpcPaths.join('\n'));
+    assert.strictEqual(proxyPaths.length, 0, proxyPaths.join('\n')); // happens...
+}
 export async function asPromise(event: vscode.Event, timeout = vscode.env.uiKind === vscode.UIKind.Desktop ? 5000 : 15000): Promise {
-	const error = new Error('asPromise TIMEOUT reached');
-	return new Promise((resolve, reject) => {
-
-		const handle = setTimeout(() => {
-			sub.dispose();
-			reject(error);
-		}, timeout);
-
-		const sub = event(e => {
-			clearTimeout(handle);
-			sub.dispose();
-			resolve(e);
-		});
-	});
-}
-
+    const error = new Error('asPromise TIMEOUT reached');
+    return new Promise((resolve, reject) => {
+        const handle = setTimeout(() => {
+            sub.dispose();
+            reject(error);
+        }, timeout);
+        const sub = event(e => {
+            clearTimeout(handle);
+            sub.dispose();
+            resolve(e);
+        });
+    });
+}
 export function testRepeat(n: number, description: string, callback: (this: any) => any): void {
-	for (let i = 0; i < n; i++) {
-		test(`${description} (iteration ${i})`, callback);
-	}
+    for (let i = 0; i < n; i++) {
+        test(`${description} (iteration ${i})`, callback);
+    }
 }
-
 export function suiteRepeat(n: number, description: string, callback: (this: any) => any): void {
-	for (let i = 0; i < n; i++) {
-		suite(`${description} (iteration ${i})`, callback);
-	}
-}
-
-export async function poll(
-	fn: () => Thenable,
-	acceptFn: (result: T) => boolean,
-	timeoutMessage: string,
-	retryCount: number = 200,
-	retryInterval: number = 100 // millis
+    for (let i = 0; i < n; i++) {
+        suite(`${description} (iteration ${i})`, callback);
+    }
+}
+export async function poll(fn: () => Thenable, acceptFn: (result: T) => boolean, timeoutMessage: string, retryCount: number = 200, retryInterval: number = 100 // millis
 ): Promise {
-	let trial = 1;
-	let lastError: string = '';
-
-	while (true) {
-		if (trial > retryCount) {
-			throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.\r${lastError}`);
-		}
-
-		let result;
-		try {
-			result = await fn();
-			if (acceptFn(result)) {
-				return result;
-			} else {
-				lastError = 'Did not pass accept function';
-			}
-		} catch (e: any) {
-			lastError = Array.isArray(e.stack) ? e.stack.join(EOL) : e.stack;
-		}
-
-		await new Promise(resolve => setTimeout(resolve, retryInterval));
-		trial++;
-	}
-}
-
+    let trial = 1;
+    let lastError: string = '';
+    while (true) {
+        if (trial > retryCount) {
+            throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.\r${lastError}`);
+        }
+        let result;
+        try {
+            result = await fn();
+            if (acceptFn(result)) {
+                return result;
+            }
+            else {
+                lastError = 'Did not pass accept function';
+            }
+        }
+        catch (e: any) {
+            lastError = Array.isArray(e.stack) ? e.stack.join(EOL) : e.stack;
+        }
+        await new Promise(resolve => setTimeout(resolve, retryInterval));
+        trial++;
+    }
+}
 export type ValueCallback = (value: T | Promise) => void;
-
 /**
  * Creates a promise whose resolution or rejection can be controlled imperatively.
  */
 export class DeferredPromise {
-
-	private completeCallback!: ValueCallback;
-	private errorCallback!: (err: unknown) => void;
-	private rejected = false;
-	private resolved = false;
-
-	public get isRejected() {
-		return this.rejected;
-	}
-
-	public get isResolved() {
-		return this.resolved;
-	}
-
-	public get isSettled() {
-		return this.rejected || this.resolved;
-	}
-
-	public readonly p: Promise;
-
-	constructor() {
-		this.p = new Promise((c, e) => {
-			this.completeCallback = c;
-			this.errorCallback = e;
-		});
-	}
-
-	public complete(value: T) {
-		return new Promise(resolve => {
-			this.completeCallback(value);
-			this.resolved = true;
-			resolve();
-		});
-	}
-
-	public error(err: unknown) {
-		return new Promise(resolve => {
-			this.errorCallback(err);
-			this.rejected = true;
-			resolve();
-		});
-	}
-
-	public cancel() {
-		new Promise(resolve => {
-			this.errorCallback(new Error('Canceled'));
-			this.rejected = true;
-			resolve();
-		});
-	}
+    private completeCallback!: ValueCallback;
+    private errorCallback!: (err: unknown) => void;
+    private rejected = false;
+    private resolved = false;
+    public get isRejected() {
+        return this.rejected;
+    }
+    public get isResolved() {
+        return this.resolved;
+    }
+    public get isSettled() {
+        return this.rejected || this.resolved;
+    }
+    public readonly p: Promise;
+    constructor() {
+        this.p = new Promise((c, e) => {
+            this.completeCallback = c;
+            this.errorCallback = e;
+        });
+    }
+    public complete(value: T) {
+        return new Promise(resolve => {
+            this.completeCallback(value);
+            this.resolved = true;
+            resolve();
+        });
+    }
+    public error(err: unknown) {
+        return new Promise(resolve => {
+            this.errorCallback(err);
+            this.rejected = true;
+            resolve();
+        });
+    }
+    public cancel() {
+        new Promise(resolve => {
+            this.errorCallback(new Error('Canceled'));
+            this.rejected = true;
+            resolve();
+        });
+    }
 }
diff --git a/extensions/vscode-api-tests/Source/workspace-tests/index.ts b/extensions/vscode-api-tests/Source/workspace-tests/index.ts
index 314a2e0d8f495..13c7dbe8adfdf 100644
--- a/extensions/vscode-api-tests/Source/workspace-tests/index.ts
+++ b/extensions/vscode-api-tests/Source/workspace-tests/index.ts
@@ -2,39 +2,35 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as testRunner from '../../../../test/integration/electron/testrunner';
-
 const options: any = {
-	ui: 'tdd',
-	color: true,
-	timeout: 60000
+    ui: 'tdd',
+    color: true,
+    timeout: 60000
 };
-
 // These integration tests is being run in multiple environments (electron, web, remote)
 // so we need to set the suite name based on the environment as the suite name is used
 // for the test results file name
 let suite = '';
 if (process.env.VSCODE_BROWSER) {
-	suite = `${process.env.VSCODE_BROWSER} Browser Integration Workspace Tests`;
-} else if (process.env.REMOTE_VSCODE) {
-	suite = 'Remote Integration Workspace Tests';
-} else {
-	suite = 'Integration Workspace Tests';
+    suite = `${process.env.VSCODE_BROWSER} Browser Integration Workspace Tests`;
+}
+else if (process.env.REMOTE_VSCODE) {
+    suite = 'Remote Integration Workspace Tests';
+}
+else {
+    suite = 'Integration Workspace Tests';
 }
-
 if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
-	options.reporter = 'mocha-multi-reporters';
-	options.reporterOptions = {
-		reporterEnabled: 'spec, mocha-junit-reporter',
-		mochaJunitReporterReporterOptions: {
-			testsuitesTitle: `${suite} ${process.platform}`,
-			mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
-		}
-	};
+    options.reporter = 'mocha-multi-reporters';
+    options.reporterOptions = {
+        reporterEnabled: 'spec, mocha-junit-reporter',
+        mochaJunitReporterReporterOptions: {
+            testsuitesTitle: `${suite} ${process.platform}`,
+            mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
+        }
+    };
 }
-
 testRunner.configure(options);
-
 export = testRunner;
diff --git a/extensions/vscode-api-tests/testWorkspace/10linefile.ts b/extensions/vscode-api-tests/testWorkspace/10linefile.ts
index fadcc7fee40f2..1b1eb201f36ca 100644
--- a/extensions/vscode-api-tests/testWorkspace/10linefile.ts
+++ b/extensions/vscode-api-tests/testWorkspace/10linefile.ts
@@ -7,4 +7,4 @@ function foo(): void {
     a = 1;
     a = 1;
     a = 1;
-}
\ No newline at end of file
+}
diff --git a/extensions/vscode-api-tests/testWorkspace/30linefile.ts b/extensions/vscode-api-tests/testWorkspace/30linefile.ts
index 2307a1dd4ca40..4c48aa86a17ac 100644
--- a/extensions/vscode-api-tests/testWorkspace/30linefile.ts
+++ b/extensions/vscode-api-tests/testWorkspace/30linefile.ts
@@ -27,4 +27,4 @@ function bar(): void {
     a = 1;
     a = 1;
     a = 1;
-}
\ No newline at end of file
+}
diff --git a/extensions/vscode-colorize-tests/Source/colorizerTestMain.ts b/extensions/vscode-colorize-tests/Source/colorizerTestMain.ts
index 9e6e9cce59cce..cc8a9dd41e90f 100644
--- a/extensions/vscode-colorize-tests/Source/colorizerTestMain.ts
+++ b/extensions/vscode-colorize-tests/Source/colorizerTestMain.ts
@@ -2,72 +2,57 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as jsoncParser from 'jsonc-parser';
 import * as vscode from 'vscode';
-
 export function activate(context: vscode.ExtensionContext): any {
-
-	const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable', 'testToken'];
-	const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async', 'testModifier'];
-
-	const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
-
-	const outputChannel = vscode.window.createOutputChannel('Semantic Tokens Test');
-
-	const documentSemanticHighlightProvider: vscode.DocumentSemanticTokensProvider = {
-		provideDocumentSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult {
-			const builder = new vscode.SemanticTokensBuilder();
-
-			function addToken(value: string, startLine: number, startCharacter: number, length: number) {
-				const [type, ...modifiers] = value.split('.');
-
-				const selectedModifiers = [];
-
-				let tokenType = legend.tokenTypes.indexOf(type);
-				if (tokenType === -1) {
-					if (type === 'notInLegend') {
-						tokenType = tokenTypes.length + 2;
-					} else {
-						return;
-					}
-				}
-
-				let tokenModifiers = 0;
-				for (const modifier of modifiers) {
-					const index = legend.tokenModifiers.indexOf(modifier);
-					if (index !== -1) {
-						tokenModifiers = tokenModifiers | 1 << index;
-						selectedModifiers.push(modifier);
-					} else if (modifier === 'notInLegend') {
-						tokenModifiers = tokenModifiers | 1 << (legend.tokenModifiers.length + 2);
-						selectedModifiers.push(modifier);
-					}
-				}
-				builder.push(startLine, startCharacter, length, tokenType, tokenModifiers);
-
-				outputChannel.appendLine(`line: ${startLine}, character: ${startCharacter}, length ${length}, ${type} (${tokenType}), ${selectedModifiers} ${tokenModifiers.toString(2)}`);
-			}
-
-			outputChannel.appendLine('---');
-
-			const visitor: jsoncParser.JSONVisitor = {
-				onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => {
-					addToken(property, startLine, startCharacter, property.length + 2);
-				},
-				onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => {
-					if (typeof value === 'string') {
-						addToken(value, startLine, startCharacter, length);
-					}
-				}
-			};
-			jsoncParser.visit(document.getText(), visitor);
-
-			return builder.build();
-		}
-	};
-
-
-	context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ pattern: '**/*semantic-test.json' }, documentSemanticHighlightProvider, legend));
-
+    const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable', 'testToken'];
+    const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async', 'testModifier'];
+    const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
+    const outputChannel = vscode.window.createOutputChannel('Semantic Tokens Test');
+    const documentSemanticHighlightProvider: vscode.DocumentSemanticTokensProvider = {
+        provideDocumentSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult {
+            const builder = new vscode.SemanticTokensBuilder();
+            function addToken(value: string, startLine: number, startCharacter: number, length: number) {
+                const [type, ...modifiers] = value.split('.');
+                const selectedModifiers = [];
+                let tokenType = legend.tokenTypes.indexOf(type);
+                if (tokenType === -1) {
+                    if (type === 'notInLegend') {
+                        tokenType = tokenTypes.length + 2;
+                    }
+                    else {
+                        return;
+                    }
+                }
+                let tokenModifiers = 0;
+                for (const modifier of modifiers) {
+                    const index = legend.tokenModifiers.indexOf(modifier);
+                    if (index !== -1) {
+                        tokenModifiers = tokenModifiers | 1 << index;
+                        selectedModifiers.push(modifier);
+                    }
+                    else if (modifier === 'notInLegend') {
+                        tokenModifiers = tokenModifiers | 1 << (legend.tokenModifiers.length + 2);
+                        selectedModifiers.push(modifier);
+                    }
+                }
+                builder.push(startLine, startCharacter, length, tokenType, tokenModifiers);
+                outputChannel.appendLine(`line: ${startLine}, character: ${startCharacter}, length ${length}, ${type} (${tokenType}), ${selectedModifiers} ${tokenModifiers.toString(2)}`);
+            }
+            outputChannel.appendLine('---');
+            const visitor: jsoncParser.JSONVisitor = {
+                onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => {
+                    addToken(property, startLine, startCharacter, property.length + 2);
+                },
+                onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => {
+                    if (typeof value === 'string') {
+                        addToken(value, startLine, startCharacter, length);
+                    }
+                }
+            };
+            jsoncParser.visit(document.getText(), visitor);
+            return builder.build();
+        }
+    };
+    context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ pattern: '**/*semantic-test.json' }, documentSemanticHighlightProvider, legend));
 }
diff --git a/extensions/vscode-colorize-tests/Source/index.ts b/extensions/vscode-colorize-tests/Source/index.ts
index 5163421304031..cb945e4962d99 100644
--- a/extensions/vscode-colorize-tests/Source/index.ts
+++ b/extensions/vscode-colorize-tests/Source/index.ts
@@ -2,29 +2,23 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as path from 'path';
 import * as testRunner from '../../../test/integration/electron/testrunner';
-
 const suite = 'Integration Colorize Tests';
-
 const options: import('mocha').MochaOptions = {
-	ui: 'tdd',
-	color: true,
-	timeout: 60000
+    ui: 'tdd',
+    color: true,
+    timeout: 60000
 };
-
 if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
-	options.reporter = 'mocha-multi-reporters';
-	options.reporterOptions = {
-		reporterEnabled: 'spec, mocha-junit-reporter',
-		mochaJunitReporterReporterOptions: {
-			testsuitesTitle: `${suite} ${process.platform}`,
-			mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
-		}
-	};
+    options.reporter = 'mocha-multi-reporters';
+    options.reporterOptions = {
+        reporterEnabled: 'spec, mocha-junit-reporter',
+        mochaJunitReporterReporterOptions: {
+            testsuitesTitle: `${suite} ${process.platform}`,
+            mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
+        }
+    };
 }
-
 testRunner.configure(options);
-
 export = testRunner;
diff --git a/extensions/vscode-test-resolver/Source/download.ts b/extensions/vscode-test-resolver/Source/download.ts
index fa001b5a17809..8f6f166578d76 100644
--- a/extensions/vscode-test-resolver/Source/download.ts
+++ b/extensions/vscode-test-resolver/Source/download.ts
@@ -2,117 +2,111 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as https from 'https';
 import * as fs from 'fs';
 import * as path from 'path';
 import * as cp from 'child_process';
 import { parse as parseUrl } from 'url';
-
 function ensureFolderExists(loc: string) {
-	if (!fs.existsSync(loc)) {
-		const parent = path.dirname(loc);
-		if (parent) {
-			ensureFolderExists(parent);
-		}
-		fs.mkdirSync(loc);
-	}
+    if (!fs.existsSync(loc)) {
+        const parent = path.dirname(loc);
+        if (parent) {
+            ensureFolderExists(parent);
+        }
+        fs.mkdirSync(loc);
+    }
 }
-
 function getDownloadUrl(updateUrl: string, commit: string, platform: string, quality: string): string {
-	return `${updateUrl}/commit:${commit}/server-${platform}/${quality}`;
+    return `${updateUrl}/commit:${commit}/server-${platform}/${quality}`;
 }
-
 async function downloadVSCodeServerArchive(updateUrl: string, commit: string, quality: string, destDir: string, log: (messsage: string) => void): Promise {
-	ensureFolderExists(destDir);
-
-	const platform = process.platform === 'win32' ? 'win32-x64' : process.platform === 'darwin' ? 'darwin' : 'linux-x64';
-	const downloadUrl = getDownloadUrl(updateUrl, commit, platform, quality);
-
-	return new Promise((resolve, reject) => {
-		log(`Downloading VS Code Server from: ${downloadUrl}`);
-		const requestOptions: https.RequestOptions = parseUrl(downloadUrl);
-
-		https.get(requestOptions, res => {
-			if (res.statusCode !== 302) {
-				reject('Failed to get VS Code server archive location');
-			}
-			const archiveUrl = res.headers.location;
-			if (!archiveUrl) {
-				reject('Failed to get VS Code server archive location');
-				return;
-			}
-
-			const archiveRequestOptions: https.RequestOptions = parseUrl(archiveUrl);
-			if (archiveUrl.endsWith('.zip')) {
-				const archivePath = path.resolve(destDir, `vscode-server-${commit}.zip`);
-				const outStream = fs.createWriteStream(archivePath);
-				outStream.on('close', () => {
-					resolve(archivePath);
-				});
-				https.get(archiveRequestOptions, res => {
-					res.pipe(outStream);
-				});
-			} else {
-				const zipPath = path.resolve(destDir, `vscode-server-${commit}.tgz`);
-				const outStream = fs.createWriteStream(zipPath);
-				https.get(archiveRequestOptions, res => {
-					res.pipe(outStream);
-				});
-				outStream.on('close', () => {
-					resolve(zipPath);
-				});
-			}
-		});
-	});
+    ensureFolderExists(destDir);
+    const platform = process.platform === 'win32' ? 'win32-x64' : process.platform === 'darwin' ? 'darwin' : 'linux-x64';
+    const downloadUrl = getDownloadUrl(updateUrl, commit, platform, quality);
+    return new Promise((resolve, reject) => {
+        log(`Downloading VS Code Server from: ${downloadUrl}`);
+        const requestOptions: https.RequestOptions = parseUrl(downloadUrl);
+        https.get(requestOptions, res => {
+            if (res.statusCode !== 302) {
+                reject('Failed to get VS Code server archive location');
+            }
+            const archiveUrl = res.headers.location;
+            if (!archiveUrl) {
+                reject('Failed to get VS Code server archive location');
+                return;
+            }
+            const archiveRequestOptions: https.RequestOptions = parseUrl(archiveUrl);
+            if (archiveUrl.endsWith('.zip')) {
+                const archivePath = path.resolve(destDir, `vscode-server-${commit}.zip`);
+                const outStream = fs.createWriteStream(archivePath);
+                outStream.on('close', () => {
+                    resolve(archivePath);
+                });
+                https.get(archiveRequestOptions, res => {
+                    res.pipe(outStream);
+                });
+            }
+            else {
+                const zipPath = path.resolve(destDir, `vscode-server-${commit}.tgz`);
+                const outStream = fs.createWriteStream(zipPath);
+                https.get(archiveRequestOptions, res => {
+                    res.pipe(outStream);
+                });
+                outStream.on('close', () => {
+                    resolve(zipPath);
+                });
+            }
+        });
+    });
 }
-
 /**
  * Unzip a .zip or .tar.gz VS Code archive
  */
 function unzipVSCodeServer(vscodeArchivePath: string, extractDir: string, destDir: string, log: (messsage: string) => void) {
-	log(`Extracting ${vscodeArchivePath}`);
-	if (vscodeArchivePath.endsWith('.zip')) {
-		const tempDir = fs.mkdtempSync(path.join(destDir, 'vscode-server-extract'));
-		if (process.platform === 'win32') {
-			cp.spawnSync('powershell.exe', [
-				'-NoProfile',
-				'-ExecutionPolicy', 'Bypass',
-				'-NonInteractive',
-				'-NoLogo',
-				'-Command',
-				`Microsoft.PowerShell.Archive\\Expand-Archive -Path "${vscodeArchivePath}" -DestinationPath "${tempDir}"`
-			]);
-		} else {
-			cp.spawnSync('unzip', [vscodeArchivePath, '-d', `${tempDir}`]);
-		}
-		fs.renameSync(path.join(tempDir, process.platform === 'win32' ? 'vscode-server-win32-x64' : 'vscode-server-darwin-x64'), extractDir);
-	} else {
-		// tar does not create extractDir by default
-		if (!fs.existsSync(extractDir)) {
-			fs.mkdirSync(extractDir);
-		}
-		cp.spawnSync('tar', ['-xzf', vscodeArchivePath, '-C', extractDir, '--strip-components', '1']);
-	}
+    log(`Extracting ${vscodeArchivePath}`);
+    if (vscodeArchivePath.endsWith('.zip')) {
+        const tempDir = fs.mkdtempSync(path.join(destDir, 'vscode-server-extract'));
+        if (process.platform === 'win32') {
+            cp.spawnSync('powershell.exe', [
+                '-NoProfile',
+                '-ExecutionPolicy', 'Bypass',
+                '-NonInteractive',
+                '-NoLogo',
+                '-Command',
+                `Microsoft.PowerShell.Archive\\Expand-Archive -Path "${vscodeArchivePath}" -DestinationPath "${tempDir}"`
+            ]);
+        }
+        else {
+            cp.spawnSync('unzip', [vscodeArchivePath, '-d', `${tempDir}`]);
+        }
+        fs.renameSync(path.join(tempDir, process.platform === 'win32' ? 'vscode-server-win32-x64' : 'vscode-server-darwin-x64'), extractDir);
+    }
+    else {
+        // tar does not create extractDir by default
+        if (!fs.existsSync(extractDir)) {
+            fs.mkdirSync(extractDir);
+        }
+        cp.spawnSync('tar', ['-xzf', vscodeArchivePath, '-C', extractDir, '--strip-components', '1']);
+    }
 }
-
 export async function downloadAndUnzipVSCodeServer(updateUrl: string, commit: string, quality: string = 'stable', destDir: string, log: (messsage: string) => void): Promise {
-
-	const extractDir = path.join(destDir, commit);
-	if (fs.existsSync(extractDir)) {
-		log(`Found ${extractDir}. Skipping download.`);
-	} else {
-		log(`Downloading VS Code Server ${quality} - ${commit} into ${extractDir}.`);
-		try {
-			const vscodeArchivePath = await downloadVSCodeServerArchive(updateUrl, commit, quality, destDir, log);
-			if (fs.existsSync(vscodeArchivePath)) {
-				unzipVSCodeServer(vscodeArchivePath, extractDir, destDir, log);
-				// Remove archive
-				fs.unlinkSync(vscodeArchivePath);
-			}
-		} catch (err) {
-			throw Error(`Failed to download and unzip VS Code ${quality} - ${commit}`);
-		}
-	}
-	return Promise.resolve(extractDir);
+    const extractDir = path.join(destDir, commit);
+    if (fs.existsSync(extractDir)) {
+        log(`Found ${extractDir}. Skipping download.`);
+    }
+    else {
+        log(`Downloading VS Code Server ${quality} - ${commit} into ${extractDir}.`);
+        try {
+            const vscodeArchivePath = await downloadVSCodeServerArchive(updateUrl, commit, quality, destDir, log);
+            if (fs.existsSync(vscodeArchivePath)) {
+                unzipVSCodeServer(vscodeArchivePath, extractDir, destDir, log);
+                // Remove archive
+                fs.unlinkSync(vscodeArchivePath);
+            }
+        }
+        catch (err) {
+            throw Error(`Failed to download and unzip VS Code ${quality} - ${commit}`);
+        }
+    }
+    return Promise.resolve(extractDir);
 }
diff --git a/extensions/vscode-test-resolver/Source/extension.browser.ts b/extensions/vscode-test-resolver/Source/extension.browser.ts
index 93703fde4df50..ae06469b7d1b4 100644
--- a/extensions/vscode-test-resolver/Source/extension.browser.ts
+++ b/extensions/vscode-test-resolver/Source/extension.browser.ts
@@ -2,132 +2,109 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
-
 export function activate(_context: vscode.ExtensionContext) {
-	vscode.workspace.registerRemoteAuthorityResolver('test', {
-		async resolve(_authority: string): Promise {
-			console.log(`Resolving ${_authority}`);
-			console.log(`Activating vscode.github-authentication to simulate auth`);
-			await vscode.extensions.getExtension('vscode.github-authentication')?.activate();
-			return new vscode.ManagedResolvedAuthority(async () => {
-				return new InitialManagedMessagePassing();
-			});
-		}
-	});
+    vscode.workspace.registerRemoteAuthorityResolver('test', {
+        async resolve(_authority: string): Promise {
+            console.log(`Resolving ${_authority}`);
+            console.log(`Activating vscode.github-authentication to simulate auth`);
+            await vscode.extensions.getExtension('vscode.github-authentication')?.activate();
+            return new vscode.ManagedResolvedAuthority(async () => {
+                return new InitialManagedMessagePassing();
+            });
+        }
+    });
 }
-
 /**
  * The initial message passing is a bit special because we need to
  * wait for the HTTP headers to arrive before we can create the
  * actual WebSocket.
  */
 class InitialManagedMessagePassing implements vscode.ManagedMessagePassing {
-	private readonly dataEmitter = new vscode.EventEmitter();
-	private readonly closeEmitter = new vscode.EventEmitter();
-	private readonly endEmitter = new vscode.EventEmitter();
-
-	public readonly onDidReceiveMessage = this.dataEmitter.event;
-	public readonly onDidClose = this.closeEmitter.event;
-	public readonly onDidEnd = this.endEmitter.event;
-
-	private _actual: OpeningManagedMessagePassing | null = null;
-	private _isDisposed = false;
-
-	public send(d: Uint8Array): void {
-		if (this._actual) {
-			// we already got the HTTP headers
-			this._actual.send(d);
-			return;
-		}
-
-		if (this._isDisposed) {
-			// got disposed in the meantime, ignore
-			return;
-		}
-
-		// we now received the HTTP headers
-		const decoder = new TextDecoder();
-		const str = decoder.decode(d);
-
-		// example str GET ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true HTTP/1.1
-		const match = str.match(/GET\s+(\S+)\s+HTTP/);
-		if (!match) {
-			console.error(`Coult not parse ${str}`);
-			this.closeEmitter.fire(new Error(`Coult not parse ${str}`));
-			return;
-		}
-
-		// example url ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true
-		const url = new URL(match[1]);
-
-		// extract path and query from url using browser's URL
-		const parsedUrl = new URL(url);
-		this._actual = new OpeningManagedMessagePassing(parsedUrl, this.dataEmitter, this.closeEmitter, this.endEmitter);
-	}
-
-	public end(): void {
-		if (this._actual) {
-			this._actual.end();
-			return;
-		}
-		this._isDisposed = true;
-	}
+    private readonly dataEmitter = new vscode.EventEmitter();
+    private readonly closeEmitter = new vscode.EventEmitter();
+    private readonly endEmitter = new vscode.EventEmitter();
+    public readonly onDidReceiveMessage = this.dataEmitter.event;
+    public readonly onDidClose = this.closeEmitter.event;
+    public readonly onDidEnd = this.endEmitter.event;
+    private _actual: OpeningManagedMessagePassing | null = null;
+    private _isDisposed = false;
+    public send(d: Uint8Array): void {
+        if (this._actual) {
+            // we already got the HTTP headers
+            this._actual.send(d);
+            return;
+        }
+        if (this._isDisposed) {
+            // got disposed in the meantime, ignore
+            return;
+        }
+        // we now received the HTTP headers
+        const decoder = new TextDecoder();
+        const str = decoder.decode(d);
+        // example str GET ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true HTTP/1.1
+        const match = str.match(/GET\s+(\S+)\s+HTTP/);
+        if (!match) {
+            console.error(`Coult not parse ${str}`);
+            this.closeEmitter.fire(new Error(`Coult not parse ${str}`));
+            return;
+        }
+        // example url ws://localhost/oss-dev?reconnectionToken=4354a323-a45a-452c-b5d7-d8d586e1cd5c&reconnection=false&skipWebSocketFrames=true
+        const url = new URL(match[1]);
+        // extract path and query from url using browser's URL
+        const parsedUrl = new URL(url);
+        this._actual = new OpeningManagedMessagePassing(parsedUrl, this.dataEmitter, this.closeEmitter, this.endEmitter);
+    }
+    public end(): void {
+        if (this._actual) {
+            this._actual.end();
+            return;
+        }
+        this._isDisposed = true;
+    }
 }
-
 class OpeningManagedMessagePassing {
-
-	private readonly socket: WebSocket;
-	private isOpen = false;
-	private bufferedData: Uint8Array[] = [];
-
-	constructor(
-		url: URL,
-		dataEmitter: vscode.EventEmitter,
-		closeEmitter: vscode.EventEmitter,
-		_endEmitter: vscode.EventEmitter
-	) {
-		this.socket = new WebSocket(`ws://localhost:9888${url.pathname}${url.search.replace(/skipWebSocketFrames=true/, 'skipWebSocketFrames=false')}`);
-		this.socket.addEventListener('close', () => closeEmitter.fire(undefined));
-		this.socket.addEventListener('error', (e) => closeEmitter.fire(new Error(String(e))));
-		this.socket.addEventListener('message', async (e) => {
-			const arrayBuffer = await e.data.arrayBuffer();
-			dataEmitter.fire(new Uint8Array(arrayBuffer));
-		});
-		this.socket.addEventListener('open', () => {
-			while (this.bufferedData.length > 0) {
-				const first = this.bufferedData.shift()!;
-				this.socket.send(first);
-			}
-			this.isOpen = true;
-
-			// https://tools.ietf.org/html/rfc6455#section-4
-			// const requestNonce = req.headers['sec-websocket-key'];
-			// const hash = crypto.createHash('sha1');
-			// hash.update(requestNonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
-			// const responseNonce = hash.digest('base64');
-			const responseHeaders = [
-				`HTTP/1.1 101 Switching Protocols`,
-				`Upgrade: websocket`,
-				`Connection: Upgrade`,
-				`Sec-WebSocket-Accept: TODO`
-			];
-			const textEncoder = new TextEncoder();
-			textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n');
-			dataEmitter.fire(textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n'));
-		});
-	}
-
-	public send(d: Uint8Array): void {
-		if (!this.isOpen) {
-			this.bufferedData.push(d);
-			return;
-		}
-		this.socket.send(d);
-	}
-
-	public end(): void {
-		this.socket.close();
-	}
+    private readonly socket: WebSocket;
+    private isOpen = false;
+    private bufferedData: Uint8Array[] = [];
+    constructor(url: URL, dataEmitter: vscode.EventEmitter, closeEmitter: vscode.EventEmitter, _endEmitter: vscode.EventEmitter) {
+        this.socket = new WebSocket(`ws://localhost:9888${url.pathname}${url.search.replace(/skipWebSocketFrames=true/, 'skipWebSocketFrames=false')}`);
+        this.socket.addEventListener('close', () => closeEmitter.fire(undefined));
+        this.socket.addEventListener('error', (e) => closeEmitter.fire(new Error(String(e))));
+        this.socket.addEventListener('message', async (e) => {
+            const arrayBuffer = await e.data.arrayBuffer();
+            dataEmitter.fire(new Uint8Array(arrayBuffer));
+        });
+        this.socket.addEventListener('open', () => {
+            while (this.bufferedData.length > 0) {
+                const first = this.bufferedData.shift()!;
+                this.socket.send(first);
+            }
+            this.isOpen = true;
+            // https://tools.ietf.org/html/rfc6455#section-4
+            // const requestNonce = req.headers['sec-websocket-key'];
+            // const hash = crypto.createHash('sha1');
+            // hash.update(requestNonce + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
+            // const responseNonce = hash.digest('base64');
+            const responseHeaders = [
+                `HTTP/1.1 101 Switching Protocols`,
+                `Upgrade: websocket`,
+                `Connection: Upgrade`,
+                `Sec-WebSocket-Accept: TODO`
+            ];
+            const textEncoder = new TextEncoder();
+            textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n');
+            dataEmitter.fire(textEncoder.encode(responseHeaders.join('\r\n') + '\r\n\r\n'));
+        });
+    }
+    public send(d: Uint8Array): void {
+        if (!this.isOpen) {
+            this.bufferedData.push(d);
+            return;
+        }
+        this.socket.send(d);
+    }
+    public end(): void {
+        this.socket.close();
+    }
 }
diff --git a/extensions/vscode-test-resolver/Source/extension.ts b/extensions/vscode-test-resolver/Source/extension.ts
index 2fab3ec306a27..27b3e36092997 100644
--- a/extensions/vscode-test-resolver/Source/extension.ts
+++ b/extensions/vscode-test-resolver/Source/extension.ts
@@ -2,7 +2,6 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as vscode from 'vscode';
 import * as cp from 'child_process';
 import * as path from 'path';
@@ -13,578 +12,540 @@ import * as http from 'http';
 import * as crypto from 'crypto';
 import { downloadAndUnzipVSCodeServer } from './download';
 import { terminateProcess } from './util/processes';
-
 let extHostProcess: cp.ChildProcess | undefined;
 const enum CharCode {
-	Backspace = 8,
-	LineFeed = 10
+    Backspace = 8,
+    LineFeed = 10
 }
-
 let outputChannel: vscode.OutputChannel;
-
 const SLOWED_DOWN_CONNECTION_DELAY = 800;
-
 export function activate(context: vscode.ExtensionContext) {
-
-	let connectionPaused = false;
-	const connectionPausedEvent = new vscode.EventEmitter();
-
-	let connectionSlowedDown = false;
-	const connectionSlowedDownEvent = new vscode.EventEmitter();
-	const slowedDownConnections = new Set();
-	connectionSlowedDownEvent.event(slowed => {
-		if (!slowed) {
-			for (const cb of slowedDownConnections) {
-				cb();
-			}
-			slowedDownConnections.clear();
-		}
-	});
-
-	function getTunnelFeatures(): vscode.TunnelInformation['tunnelFeatures'] {
-		return {
-			elevation: true,
-			privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
-				{
-					id: 'public',
-					label: 'Public',
-					themeIcon: 'eye'
-				},
-				{
-					id: 'other',
-					label: 'Other',
-					themeIcon: 'circuit-board'
-				},
-				{
-					id: 'private',
-					label: 'Private',
-					themeIcon: 'eye-closed'
-				}
-			] : []
-		};
-	}
-
-	function maybeSlowdown(): Promise | void {
-		if (connectionSlowedDown) {
-			return new Promise(resolve => {
-				const handle = setTimeout(() => {
-					resolve();
-					slowedDownConnections.delete(resolve);
-				}, SLOWED_DOWN_CONNECTION_DELAY);
-
-				slowedDownConnections.add(() => {
-					resolve();
-					clearTimeout(handle);
-				});
-			});
-		}
-	}
-
-	function doResolve(authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise {
-		if (connectionPaused) {
-			throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable('Not available right now');
-		}
-		const connectionToken = String(crypto.randomInt(0xffffffffff));
-
-		// eslint-disable-next-line no-async-promise-executor
-		const serverPromise = new Promise(async (res, rej) => {
-			progress.report({ message: 'Starting Test Resolver' });
-			outputChannel = vscode.window.createOutputChannel('TestResolver');
-
-			let isResolved = false;
-			async function processError(message: string) {
-				outputChannel.appendLine(message);
-				if (!isResolved) {
-					isResolved = true;
-					outputChannel.show();
-
-					const result = await vscode.window.showErrorMessage(message, { modal: true }, ...getActions());
-					if (result) {
-						await result.execute();
-					}
-					rej(vscode.RemoteAuthorityResolverError.NotAvailable(message, true));
-				}
-			}
-
-			let lastProgressLine = '';
-			function processOutput(output: string) {
-				outputChannel.append(output);
-				for (let i = 0; i < output.length; i++) {
-					const chr = output.charCodeAt(i);
-					if (chr === CharCode.LineFeed) {
-						const match = lastProgressLine.match(/Extension host agent listening on (\d+)/);
-						if (match) {
-							isResolved = true;
-							res(new vscode.ResolvedAuthority('127.0.0.1', parseInt(match[1], 10), connectionToken)); // success!
-						}
-						lastProgressLine = '';
-					} else if (chr === CharCode.Backspace) {
-						lastProgressLine = lastProgressLine.substr(0, lastProgressLine.length - 1);
-					} else {
-						lastProgressLine += output.charAt(i);
-					}
-				}
-			}
-			const delay = getConfiguration('startupDelay');
-			if (typeof delay === 'number') {
-				let remaining = Math.ceil(delay);
-				outputChannel.append(`Delaying startup by ${remaining} seconds (configured by "testresolver.startupDelay").`);
-				while (remaining > 0) {
-					progress.report({ message: `Delayed resolving: Remaining ${remaining}s` });
-					await (sleep(1000));
-					remaining--;
-				}
-			}
-
-			if (getConfiguration('startupError') === true) {
-				processError('Test Resolver failed for testing purposes (configured by "testresolver.startupError").');
-				return;
-			}
-
-			const { updateUrl, commit, quality, serverDataFolderName, serverApplicationName, dataFolderName } = getProductConfiguration();
-			const commandArgs = ['--host=127.0.0.1', '--port=0', '--disable-telemetry', '--use-host-proxy', '--accept-server-license-terms'];
-			const env = getNewEnv();
-			const remoteDataDir = process.env['TESTRESOLVER_DATA_FOLDER'] || path.join(os.homedir(), `${serverDataFolderName || dataFolderName}-testresolver`);
-			const logsDir = process.env['TESTRESOLVER_LOGS_FOLDER'];
-			if (logsDir) {
-				commandArgs.push('--logsPath', logsDir);
-			}
-			const logLevel = process.env['TESTRESOLVER_LOG_LEVEL'];
-			if (logLevel) {
-				commandArgs.push('--log', logLevel);
-			}
-			outputChannel.appendLine(`Using data folder at ${remoteDataDir}`);
-			commandArgs.push('--server-data-dir', remoteDataDir);
-
-			commandArgs.push('--connection-token', connectionToken);
-
-			if (!commit) { // dev mode
-				const serverCommand = process.platform === 'win32' ? 'code-server.bat' : 'code-server.sh';
-				const vscodePath = path.resolve(path.join(context.extensionPath, '..', '..'));
-				const serverCommandPath = path.join(vscodePath, 'scripts', serverCommand);
-
-				outputChannel.appendLine(`Launching server: "${serverCommandPath}" ${commandArgs.join(' ')}`);
-				const shell = (process.platform === 'win32');
-				extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath, shell });
-			} else {
-				const extensionToInstall = process.env['TESTRESOLVER_INSTALL_BUILTIN_EXTENSION'];
-				if (extensionToInstall) {
-					commandArgs.push('--install-builtin-extension', extensionToInstall);
-					commandArgs.push('--start-server');
-				}
-				const serverCommand = `${serverApplicationName}${process.platform === 'win32' ? '.cmd' : ''}`;
-				let serverLocation = env['VSCODE_REMOTE_SERVER_PATH']; // support environment variable to specify location of server on disk
-				if (!serverLocation) {
-					const serverBin = path.join(remoteDataDir, 'bin');
-					progress.report({ message: 'Installing VSCode Server' });
-					serverLocation = await downloadAndUnzipVSCodeServer(updateUrl, commit, quality, serverBin, m => outputChannel.appendLine(m));
-				}
-
-				outputChannel.appendLine(`Using server build at ${serverLocation}`);
-				outputChannel.appendLine(`Server arguments ${commandArgs.join(' ')}`);
-				const shell = (process.platform === 'win32');
-				extHostProcess = cp.spawn(path.join(serverLocation, 'bin', serverCommand), commandArgs, { env, cwd: serverLocation, shell });
-			}
-			extHostProcess.stdout!.on('data', (data: Buffer) => processOutput(data.toString()));
-			extHostProcess.stderr!.on('data', (data: Buffer) => processOutput(data.toString()));
-			extHostProcess.on('error', (error: Error) => {
-				processError(`server failed with error:\n${error.message}`);
-				extHostProcess = undefined;
-			});
-			extHostProcess.on('close', (code: number) => {
-				processError(`server closed unexpectedly.\nError code: ${code}`);
-				extHostProcess = undefined;
-			});
-			context.subscriptions.push({
-				dispose: () => {
-					if (extHostProcess) {
-						terminateProcess(extHostProcess, context.extensionPath);
-					}
-				}
-			});
-		});
-
-		return serverPromise.then((serverAddr): Promise => {
-			if (authority.includes('managed')) {
-				console.log('Connecting via a managed authority');
-				return Promise.resolve(new vscode.ManagedResolvedAuthority(async () => {
-					const remoteSocket = net.createConnection({ port: serverAddr.port });
-					const dataEmitter = new vscode.EventEmitter();
-					const closeEmitter = new vscode.EventEmitter();
-					const endEmitter = new vscode.EventEmitter();
-
-					await new Promise((res, rej) => {
-						remoteSocket.on('data', d => dataEmitter.fire(d))
-							.on('error', err => { rej(); closeEmitter.fire(err); })
-							.on('close', () => endEmitter.fire())
-							.on('end', () => endEmitter.fire())
-							.on('connect', res);
-					});
-
-
-					return {
-						onDidReceiveMessage: dataEmitter.event,
-						onDidClose: closeEmitter.event,
-						onDidEnd: endEmitter.event,
-						send: d => remoteSocket.write(d),
-						end: () => remoteSocket.end(),
-					};
-				}, connectionToken));
-			}
-
-			return new Promise((res, _rej) => {
-				const proxyServer = net.createServer(proxySocket => {
-					outputChannel.appendLine(`Proxy connection accepted`);
-					let remoteReady = true, localReady = true;
-					const remoteSocket = net.createConnection({ port: serverAddr.port });
-
-					let isDisconnected = false;
-					const handleConnectionPause = () => {
-						const newIsDisconnected = connectionPaused;
-						if (isDisconnected !== newIsDisconnected) {
-							outputChannel.appendLine(`Connection state: ${newIsDisconnected ? 'open' : 'paused'}`);
-							isDisconnected = newIsDisconnected;
-							if (!isDisconnected) {
-								outputChannel.appendLine(`Resume remote and proxy sockets.`);
-								if (remoteSocket.isPaused() && localReady) {
-									remoteSocket.resume();
-								}
-								if (proxySocket.isPaused() && remoteReady) {
-									proxySocket.resume();
-								}
-							} else {
-								outputChannel.appendLine(`Pausing remote and proxy sockets.`);
-								if (!remoteSocket.isPaused()) {
-									remoteSocket.pause();
-								}
-								if (!proxySocket.isPaused()) {
-									proxySocket.pause();
-								}
-							}
-						}
-					};
-
-					connectionPausedEvent.event(_ => handleConnectionPause());
-					handleConnectionPause();
-
-					proxySocket.on('data', async (data) => {
-						await maybeSlowdown();
-						remoteReady = remoteSocket.write(data);
-						if (!remoteReady) {
-							proxySocket.pause();
-						}
-					});
-					remoteSocket.on('data', async (data) => {
-						await maybeSlowdown();
-						localReady = proxySocket.write(data);
-						if (!localReady) {
-							remoteSocket.pause();
-						}
-					});
-					proxySocket.on('drain', () => {
-						localReady = true;
-						if (!isDisconnected) {
-							remoteSocket.resume();
-						}
-					});
-					remoteSocket.on('drain', () => {
-						remoteReady = true;
-						if (!isDisconnected) {
-							proxySocket.resume();
-						}
-					});
-					proxySocket.on('close', () => {
-						outputChannel.appendLine(`Proxy socket closed, closing remote socket.`);
-						remoteSocket.end();
-					});
-					remoteSocket.on('close', () => {
-						outputChannel.appendLine(`Remote socket closed, closing proxy socket.`);
-						proxySocket.end();
-					});
-					context.subscriptions.push({
-						dispose: () => {
-							proxySocket.end();
-							remoteSocket.end();
-						}
-					});
-				});
-				proxyServer.listen(0, '127.0.0.1', () => {
-					const port = (proxyServer.address()).port;
-					outputChannel.appendLine(`Going through proxy at port ${port}`);
-					res(new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken));
-				});
-				context.subscriptions.push({
-					dispose: () => {
-						proxyServer.close();
-					}
-				});
-			});
-		});
-	}
-
-	const authorityResolverDisposable = vscode.workspace.registerRemoteAuthorityResolver('test', {
-		async getCanonicalURI(uri: vscode.Uri): Promise {
-			return vscode.Uri.file(uri.path);
-		},
-		resolve(_authority: string): Thenable {
-			return vscode.window.withProgress({
-				location: vscode.ProgressLocation.Notification,
-				title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
-				cancellable: false
-			}, async (progress) => {
-				const rr = await doResolve(_authority, progress);
-				rr.tunnelFeatures = getTunnelFeatures();
-				return rr;
-			});
-		},
-		tunnelFactory,
-		showCandidatePort
-	});
-	context.subscriptions.push(authorityResolverDisposable);
-
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindow', () => {
-		return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test' });
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindow', () => {
-		return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test', reuseWindow: true });
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindowManaged', () => {
-		return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+managed', reuseWindow: true });
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindowWithError', () => {
-		return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+error' });
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.killServerAndTriggerHandledError', () => {
-		authorityResolverDisposable.dispose();
-		if (extHostProcess) {
-			terminateProcess(extHostProcess, context.extensionPath);
-		}
-		vscode.workspace.registerRemoteAuthorityResolver('test', {
-			async resolve(_authority: string): Promise {
-				setTimeout(async () => {
-					await vscode.window.showErrorMessage('Just a custom message.', { modal: true, useCustom: true }, 'OK', 'Great');
-				}, 2000);
-				throw vscode.RemoteAuthorityResolverError.NotAvailable('Intentional Error', true);
-			}
-		});
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.showLog', () => {
-		if (outputChannel) {
-			outputChannel.show();
-		}
-	}));
-
-	const pauseStatusBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
-	pauseStatusBarEntry.text = 'Remote connection paused. Click to undo';
-	pauseStatusBarEntry.command = 'vscode-testresolver.toggleConnectionPause';
-	pauseStatusBarEntry.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground');
-
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.toggleConnectionPause', () => {
-		if (!connectionPaused) {
-			connectionPaused = true;
-			pauseStatusBarEntry.show();
-		} else {
-			connectionPaused = false;
-			pauseStatusBarEntry.hide();
-		}
-		connectionPausedEvent.fire(connectionPaused);
-	}));
-
-	const slowdownStatusBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
-	slowdownStatusBarEntry.text = 'Remote connection slowed down. Click to undo';
-	slowdownStatusBarEntry.command = 'vscode-testresolver.toggleConnectionSlowdown';
-	slowdownStatusBarEntry.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground');
-
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.toggleConnectionSlowdown', () => {
-		if (!connectionSlowedDown) {
-			connectionSlowedDown = true;
-			slowdownStatusBarEntry.show();
-		} else {
-			connectionSlowedDown = false;
-			slowdownStatusBarEntry.hide();
-		}
-		connectionSlowedDownEvent.fire(connectionSlowedDown);
-	}));
-
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.openTunnel', async () => {
-		const result = await vscode.window.showInputBox({
-			prompt: 'Enter the remote port for the tunnel',
-			value: '5000',
-			validateInput: input => /^[\d]+$/.test(input) ? undefined : 'Not a valid number'
-		});
-		if (result) {
-			const port = Number.parseInt(result);
-			vscode.workspace.openTunnel({
-				remoteAddress: {
-					host: '127.0.0.1',
-					port: port
-				},
-				localAddressPort: port + 1
-			});
-		}
-
-	}));
-	context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.startRemoteServer', async () => {
-		const result = await vscode.window.showInputBox({
-			prompt: 'Enter the port for the remote server',
-			value: '5000',
-			validateInput: input => /^[\d]+$/.test(input) ? undefined : 'Not a valid number'
-		});
-		if (result) {
-			runHTTPTestServer(Number.parseInt(result));
-		}
-
-	}));
-	vscode.commands.executeCommand('setContext', 'forwardedPortsViewEnabled', true);
+    let connectionPaused = false;
+    const connectionPausedEvent = new vscode.EventEmitter();
+    let connectionSlowedDown = false;
+    const connectionSlowedDownEvent = new vscode.EventEmitter();
+    const slowedDownConnections = new Set();
+    connectionSlowedDownEvent.event(slowed => {
+        if (!slowed) {
+            for (const cb of slowedDownConnections) {
+                cb();
+            }
+            slowedDownConnections.clear();
+        }
+    });
+    function getTunnelFeatures(): vscode.TunnelInformation['tunnelFeatures'] {
+        return {
+            elevation: true,
+            privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
+                {
+                    id: 'public',
+                    label: 'Public',
+                    themeIcon: 'eye'
+                },
+                {
+                    id: 'other',
+                    label: 'Other',
+                    themeIcon: 'circuit-board'
+                },
+                {
+                    id: 'private',
+                    label: 'Private',
+                    themeIcon: 'eye-closed'
+                }
+            ] : []
+        };
+    }
+    function maybeSlowdown(): Promise | void {
+        if (connectionSlowedDown) {
+            return new Promise(resolve => {
+                const handle = setTimeout(() => {
+                    resolve();
+                    slowedDownConnections.delete(resolve);
+                }, SLOWED_DOWN_CONNECTION_DELAY);
+                slowedDownConnections.add(() => {
+                    resolve();
+                    clearTimeout(handle);
+                });
+            });
+        }
+    }
+    function doResolve(authority: string, progress: vscode.Progress<{
+        message?: string;
+        increment?: number;
+    }>): Promise {
+        if (connectionPaused) {
+            throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable('Not available right now');
+        }
+        const connectionToken = String(crypto.randomInt(0xffffffffff));
+        // eslint-disable-next-line no-async-promise-executor
+        const serverPromise = new Promise(async (res, rej) => {
+            progress.report({ message: 'Starting Test Resolver' });
+            outputChannel = vscode.window.createOutputChannel('TestResolver');
+            let isResolved = false;
+            async function processError(message: string) {
+                outputChannel.appendLine(message);
+                if (!isResolved) {
+                    isResolved = true;
+                    outputChannel.show();
+                    const result = await vscode.window.showErrorMessage(message, { modal: true }, ...getActions());
+                    if (result) {
+                        await result.execute();
+                    }
+                    rej(vscode.RemoteAuthorityResolverError.NotAvailable(message, true));
+                }
+            }
+            let lastProgressLine = '';
+            function processOutput(output: string) {
+                outputChannel.append(output);
+                for (let i = 0; i < output.length; i++) {
+                    const chr = output.charCodeAt(i);
+                    if (chr === CharCode.LineFeed) {
+                        const match = lastProgressLine.match(/Extension host agent listening on (\d+)/);
+                        if (match) {
+                            isResolved = true;
+                            res(new vscode.ResolvedAuthority('127.0.0.1', parseInt(match[1], 10), connectionToken)); // success!
+                        }
+                        lastProgressLine = '';
+                    }
+                    else if (chr === CharCode.Backspace) {
+                        lastProgressLine = lastProgressLine.substr(0, lastProgressLine.length - 1);
+                    }
+                    else {
+                        lastProgressLine += output.charAt(i);
+                    }
+                }
+            }
+            const delay = getConfiguration('startupDelay');
+            if (typeof delay === 'number') {
+                let remaining = Math.ceil(delay);
+                outputChannel.append(`Delaying startup by ${remaining} seconds (configured by "testresolver.startupDelay").`);
+                while (remaining > 0) {
+                    progress.report({ message: `Delayed resolving: Remaining ${remaining}s` });
+                    await (sleep(1000));
+                    remaining--;
+                }
+            }
+            if (getConfiguration('startupError') === true) {
+                processError('Test Resolver failed for testing purposes (configured by "testresolver.startupError").');
+                return;
+            }
+            const { updateUrl, commit, quality, serverDataFolderName, serverApplicationName, dataFolderName } = getProductConfiguration();
+            const commandArgs = ['--host=127.0.0.1', '--port=0', '--disable-telemetry', '--use-host-proxy', '--accept-server-license-terms'];
+            const env = getNewEnv();
+            const remoteDataDir = process.env['TESTRESOLVER_DATA_FOLDER'] || path.join(os.homedir(), `${serverDataFolderName || dataFolderName}-testresolver`);
+            const logsDir = process.env['TESTRESOLVER_LOGS_FOLDER'];
+            if (logsDir) {
+                commandArgs.push('--logsPath', logsDir);
+            }
+            const logLevel = process.env['TESTRESOLVER_LOG_LEVEL'];
+            if (logLevel) {
+                commandArgs.push('--log', logLevel);
+            }
+            outputChannel.appendLine(`Using data folder at ${remoteDataDir}`);
+            commandArgs.push('--server-data-dir', remoteDataDir);
+            commandArgs.push('--connection-token', connectionToken);
+            if (!commit) { // dev mode
+                const serverCommand = process.platform === 'win32' ? 'code-server.bat' : 'code-server.sh';
+                const vscodePath = path.resolve(path.join(context.extensionPath, '..', '..'));
+                const serverCommandPath = path.join(vscodePath, 'scripts', serverCommand);
+                outputChannel.appendLine(`Launching server: "${serverCommandPath}" ${commandArgs.join(' ')}`);
+                const shell = (process.platform === 'win32');
+                extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath, shell });
+            }
+            else {
+                const extensionToInstall = process.env['TESTRESOLVER_INSTALL_BUILTIN_EXTENSION'];
+                if (extensionToInstall) {
+                    commandArgs.push('--install-builtin-extension', extensionToInstall);
+                    commandArgs.push('--start-server');
+                }
+                const serverCommand = `${serverApplicationName}${process.platform === 'win32' ? '.cmd' : ''}`;
+                let serverLocation = env['VSCODE_REMOTE_SERVER_PATH']; // support environment variable to specify location of server on disk
+                if (!serverLocation) {
+                    const serverBin = path.join(remoteDataDir, 'bin');
+                    progress.report({ message: 'Installing VSCode Server' });
+                    serverLocation = await downloadAndUnzipVSCodeServer(updateUrl, commit, quality, serverBin, m => outputChannel.appendLine(m));
+                }
+                outputChannel.appendLine(`Using server build at ${serverLocation}`);
+                outputChannel.appendLine(`Server arguments ${commandArgs.join(' ')}`);
+                const shell = (process.platform === 'win32');
+                extHostProcess = cp.spawn(path.join(serverLocation, 'bin', serverCommand), commandArgs, { env, cwd: serverLocation, shell });
+            }
+            extHostProcess.stdout!.on('data', (data: Buffer) => processOutput(data.toString()));
+            extHostProcess.stderr!.on('data', (data: Buffer) => processOutput(data.toString()));
+            extHostProcess.on('error', (error: Error) => {
+                processError(`server failed with error:\n${error.message}`);
+                extHostProcess = undefined;
+            });
+            extHostProcess.on('close', (code: number) => {
+                processError(`server closed unexpectedly.\nError code: ${code}`);
+                extHostProcess = undefined;
+            });
+            context.subscriptions.push({
+                dispose: () => {
+                    if (extHostProcess) {
+                        terminateProcess(extHostProcess, context.extensionPath);
+                    }
+                }
+            });
+        });
+        return serverPromise.then((serverAddr): Promise => {
+            if (authority.includes('managed')) {
+                console.log('Connecting via a managed authority');
+                return Promise.resolve(new vscode.ManagedResolvedAuthority(async () => {
+                    const remoteSocket = net.createConnection({ port: serverAddr.port });
+                    const dataEmitter = new vscode.EventEmitter();
+                    const closeEmitter = new vscode.EventEmitter();
+                    const endEmitter = new vscode.EventEmitter();
+                    await new Promise((res, rej) => {
+                        remoteSocket.on('data', d => dataEmitter.fire(d))
+                            .on('error', err => { rej(); closeEmitter.fire(err); })
+                            .on('close', () => endEmitter.fire())
+                            .on('end', () => endEmitter.fire())
+                            .on('connect', res);
+                    });
+                    return {
+                        onDidReceiveMessage: dataEmitter.event,
+                        onDidClose: closeEmitter.event,
+                        onDidEnd: endEmitter.event,
+                        send: d => remoteSocket.write(d),
+                        end: () => remoteSocket.end(),
+                    };
+                }, connectionToken));
+            }
+            return new Promise((res, _rej) => {
+                const proxyServer = net.createServer(proxySocket => {
+                    outputChannel.appendLine(`Proxy connection accepted`);
+                    let remoteReady = true, localReady = true;
+                    const remoteSocket = net.createConnection({ port: serverAddr.port });
+                    let isDisconnected = false;
+                    const handleConnectionPause = () => {
+                        const newIsDisconnected = connectionPaused;
+                        if (isDisconnected !== newIsDisconnected) {
+                            outputChannel.appendLine(`Connection state: ${newIsDisconnected ? 'open' : 'paused'}`);
+                            isDisconnected = newIsDisconnected;
+                            if (!isDisconnected) {
+                                outputChannel.appendLine(`Resume remote and proxy sockets.`);
+                                if (remoteSocket.isPaused() && localReady) {
+                                    remoteSocket.resume();
+                                }
+                                if (proxySocket.isPaused() && remoteReady) {
+                                    proxySocket.resume();
+                                }
+                            }
+                            else {
+                                outputChannel.appendLine(`Pausing remote and proxy sockets.`);
+                                if (!remoteSocket.isPaused()) {
+                                    remoteSocket.pause();
+                                }
+                                if (!proxySocket.isPaused()) {
+                                    proxySocket.pause();
+                                }
+                            }
+                        }
+                    };
+                    connectionPausedEvent.event(_ => handleConnectionPause());
+                    handleConnectionPause();
+                    proxySocket.on('data', async (data) => {
+                        await maybeSlowdown();
+                        remoteReady = remoteSocket.write(data);
+                        if (!remoteReady) {
+                            proxySocket.pause();
+                        }
+                    });
+                    remoteSocket.on('data', async (data) => {
+                        await maybeSlowdown();
+                        localReady = proxySocket.write(data);
+                        if (!localReady) {
+                            remoteSocket.pause();
+                        }
+                    });
+                    proxySocket.on('drain', () => {
+                        localReady = true;
+                        if (!isDisconnected) {
+                            remoteSocket.resume();
+                        }
+                    });
+                    remoteSocket.on('drain', () => {
+                        remoteReady = true;
+                        if (!isDisconnected) {
+                            proxySocket.resume();
+                        }
+                    });
+                    proxySocket.on('close', () => {
+                        outputChannel.appendLine(`Proxy socket closed, closing remote socket.`);
+                        remoteSocket.end();
+                    });
+                    remoteSocket.on('close', () => {
+                        outputChannel.appendLine(`Remote socket closed, closing proxy socket.`);
+                        proxySocket.end();
+                    });
+                    context.subscriptions.push({
+                        dispose: () => {
+                            proxySocket.end();
+                            remoteSocket.end();
+                        }
+                    });
+                });
+                proxyServer.listen(0, '127.0.0.1', () => {
+                    const port = (proxyServer.address()).port;
+                    outputChannel.appendLine(`Going through proxy at port ${port}`);
+                    res(new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken));
+                });
+                context.subscriptions.push({
+                    dispose: () => {
+                        proxyServer.close();
+                    }
+                });
+            });
+        });
+    }
+    const authorityResolverDisposable = vscode.workspace.registerRemoteAuthorityResolver('test', {
+        async getCanonicalURI(uri: vscode.Uri): Promise {
+            return vscode.Uri.file(uri.path);
+        },
+        resolve(_authority: string): Thenable {
+            return vscode.window.withProgress({
+                location: vscode.ProgressLocation.Notification,
+                title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
+                cancellable: false
+            }, async (progress) => {
+                const rr = await doResolve(_authority, progress);
+                rr.tunnelFeatures = getTunnelFeatures();
+                return rr;
+            });
+        },
+        tunnelFactory,
+        showCandidatePort
+    });
+    context.subscriptions.push(authorityResolverDisposable);
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindow', () => {
+        return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test' });
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindow', () => {
+        return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test', reuseWindow: true });
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindowManaged', () => {
+        return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+managed', reuseWindow: true });
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindowWithError', () => {
+        return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+error' });
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.killServerAndTriggerHandledError', () => {
+        authorityResolverDisposable.dispose();
+        if (extHostProcess) {
+            terminateProcess(extHostProcess, context.extensionPath);
+        }
+        vscode.workspace.registerRemoteAuthorityResolver('test', {
+            async resolve(_authority: string): Promise {
+                setTimeout(async () => {
+                    await vscode.window.showErrorMessage('Just a custom message.', { modal: true, useCustom: true }, 'OK', 'Great');
+                }, 2000);
+                throw vscode.RemoteAuthorityResolverError.NotAvailable('Intentional Error', true);
+            }
+        });
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.showLog', () => {
+        if (outputChannel) {
+            outputChannel.show();
+        }
+    }));
+    const pauseStatusBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+    pauseStatusBarEntry.text = 'Remote connection paused. Click to undo';
+    pauseStatusBarEntry.command = 'vscode-testresolver.toggleConnectionPause';
+    pauseStatusBarEntry.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground');
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.toggleConnectionPause', () => {
+        if (!connectionPaused) {
+            connectionPaused = true;
+            pauseStatusBarEntry.show();
+        }
+        else {
+            connectionPaused = false;
+            pauseStatusBarEntry.hide();
+        }
+        connectionPausedEvent.fire(connectionPaused);
+    }));
+    const slowdownStatusBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+    slowdownStatusBarEntry.text = 'Remote connection slowed down. Click to undo';
+    slowdownStatusBarEntry.command = 'vscode-testresolver.toggleConnectionSlowdown';
+    slowdownStatusBarEntry.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground');
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.toggleConnectionSlowdown', () => {
+        if (!connectionSlowedDown) {
+            connectionSlowedDown = true;
+            slowdownStatusBarEntry.show();
+        }
+        else {
+            connectionSlowedDown = false;
+            slowdownStatusBarEntry.hide();
+        }
+        connectionSlowedDownEvent.fire(connectionSlowedDown);
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.openTunnel', async () => {
+        const result = await vscode.window.showInputBox({
+            prompt: 'Enter the remote port for the tunnel',
+            value: '5000',
+            validateInput: input => /^[\d]+$/.test(input) ? undefined : 'Not a valid number'
+        });
+        if (result) {
+            const port = Number.parseInt(result);
+            vscode.workspace.openTunnel({
+                remoteAddress: {
+                    host: '127.0.0.1',
+                    port: port
+                },
+                localAddressPort: port + 1
+            });
+        }
+    }));
+    context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.startRemoteServer', async () => {
+        const result = await vscode.window.showInputBox({
+            prompt: 'Enter the port for the remote server',
+            value: '5000',
+            validateInput: input => /^[\d]+$/.test(input) ? undefined : 'Not a valid number'
+        });
+        if (result) {
+            runHTTPTestServer(Number.parseInt(result));
+        }
+    }));
+    vscode.commands.executeCommand('setContext', 'forwardedPortsViewEnabled', true);
 }
-
-type ActionItem = (vscode.MessageItem & { execute: () => void });
-
+type ActionItem = (vscode.MessageItem & {
+    execute: () => void;
+});
 function getActions(): ActionItem[] {
-	const actions: ActionItem[] = [];
-	const isDirty = vscode.workspace.textDocuments.some(d => d.isDirty) || vscode.workspace.workspaceFile && vscode.workspace.workspaceFile.scheme === 'untitled';
-
-	actions.push({
-		title: 'Retry',
-		execute: async () => {
-			await vscode.commands.executeCommand('workbench.action.reloadWindow');
-		}
-	});
-	if (!isDirty) {
-		actions.push({
-			title: 'Close Remote',
-			execute: async () => {
-				await vscode.commands.executeCommand('vscode.newWindow', { reuseWindow: true, remoteAuthority: null });
-			}
-		});
-	}
-	actions.push({
-		title: 'Ignore',
-		isCloseAffordance: true,
-		execute: async () => {
-			vscode.commands.executeCommand('vscode-testresolver.showLog'); // no need to wait
-		}
-	});
-	return actions;
+    const actions: ActionItem[] = [];
+    const isDirty = vscode.workspace.textDocuments.some(d => d.isDirty) || vscode.workspace.workspaceFile && vscode.workspace.workspaceFile.scheme === 'untitled';
+    actions.push({
+        title: 'Retry',
+        execute: async () => {
+            await vscode.commands.executeCommand('workbench.action.reloadWindow');
+        }
+    });
+    if (!isDirty) {
+        actions.push({
+            title: 'Close Remote',
+            execute: async () => {
+                await vscode.commands.executeCommand('vscode.newWindow', { reuseWindow: true, remoteAuthority: null });
+            }
+        });
+    }
+    actions.push({
+        title: 'Ignore',
+        isCloseAffordance: true,
+        execute: async () => {
+            vscode.commands.executeCommand('vscode-testresolver.showLog'); // no need to wait
+        }
+    });
+    return actions;
 }
-
 export interface IProductConfiguration {
-	updateUrl: string;
-	commit: string;
-	quality: string;
-	dataFolderName: string;
-	serverApplicationName?: string;
-	serverDataFolderName?: string;
+    updateUrl: string;
+    commit: string;
+    quality: string;
+    dataFolderName: string;
+    serverApplicationName?: string;
+    serverDataFolderName?: string;
 }
-
 function getProductConfiguration(): IProductConfiguration {
-	const content = fs.readFileSync(path.join(vscode.env.appRoot, 'product.json')).toString();
-	return JSON.parse(content) as IProductConfiguration;
+    const content = fs.readFileSync(path.join(vscode.env.appRoot, 'product.json')).toString();
+    return JSON.parse(content) as IProductConfiguration;
 }
-
-function getNewEnv(): { [x: string]: string | undefined } {
-	const env = { ...process.env };
-	delete env['ELECTRON_RUN_AS_NODE'];
-	return env;
+function getNewEnv(): {
+    [x: string]: string | undefined;
+} {
+    const env = { ...process.env };
+    delete env['ELECTRON_RUN_AS_NODE'];
+    return env;
 }
-
 function sleep(ms: number): Promise {
-	return new Promise(resolve => {
-		setTimeout(resolve, ms);
-	});
+    return new Promise(resolve => {
+        setTimeout(resolve, ms);
+    });
 }
-
 function getConfiguration(id: string): T | undefined {
-	return vscode.workspace.getConfiguration('testresolver').get(id);
+    return vscode.workspace.getConfiguration('testresolver').get(id);
 }
-
 const remoteServers: number[] = [];
-
 async function showCandidatePort(_host: string, port: number, _detail: string): Promise {
-	return remoteServers.includes(port) || port === 100;
+    return remoteServers.includes(port) || port === 100;
 }
-
 async function tunnelFactory(tunnelOptions: vscode.TunnelOptions, tunnelCreationOptions: vscode.TunnelCreationOptions): Promise {
-	outputChannel.appendLine(`Tunnel factory request: Remote ${tunnelOptions.remoteAddress.port} -> local ${tunnelOptions.localAddressPort}`);
-	if (tunnelCreationOptions.elevationRequired) {
-		await vscode.window.showInformationMessage('This is a fake elevation message. A real resolver would show a native elevation prompt.', { modal: true }, 'Ok');
-	}
-
-	return createTunnelService();
-
-	function newTunnel(localAddress: { host: string; port: number }): vscode.Tunnel {
-		const onDidDispose: vscode.EventEmitter = new vscode.EventEmitter();
-		let isDisposed = false;
-		return {
-			localAddress,
-			remoteAddress: tunnelOptions.remoteAddress,
-			public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') && tunnelOptions.public,
-			privacy: tunnelOptions.privacy,
-			protocol: tunnelOptions.protocol,
-			onDidDispose: onDidDispose.event,
-			dispose: () => {
-				if (!isDisposed) {
-					isDisposed = true;
-					onDidDispose.fire();
-				}
-			}
-		};
-	}
-
-	function createTunnelService(): Promise {
-		return new Promise((res, _rej) => {
-			const proxyServer = net.createServer(proxySocket => {
-				const remoteSocket = net.createConnection({ host: tunnelOptions.remoteAddress.host, port: tunnelOptions.remoteAddress.port });
-				remoteSocket.pipe(proxySocket);
-				proxySocket.pipe(remoteSocket);
-			});
-			let localPort = 0;
-
-			if (tunnelOptions.localAddressPort) {
-				// When the tunnelOptions include a localAddressPort, we should use that.
-				// However, the test resolver all runs on one machine, so if the localAddressPort is the same as the remote port,
-				// then we must use a different port number.
-				localPort = tunnelOptions.localAddressPort;
-			} else {
-				localPort = tunnelOptions.remoteAddress.port;
-			}
-
-			if (localPort === tunnelOptions.remoteAddress.port) {
-				localPort += 1;
-			}
-
-			// The test resolver can't actually handle privileged ports, it only pretends to.
-			if (localPort < 1024 && process.platform !== 'win32') {
-				localPort = 0;
-			}
-			proxyServer.listen(localPort, '127.0.0.1', () => {
-				const localPort = (proxyServer.address()).port;
-				outputChannel.appendLine(`New test resolver tunnel service: Remote ${tunnelOptions.remoteAddress.port} -> local ${localPort}`);
-				const tunnel = newTunnel({ host: '127.0.0.1', port: localPort });
-				tunnel.onDidDispose(() => proxyServer.close());
-				res(tunnel);
-			});
-		});
-	}
+    outputChannel.appendLine(`Tunnel factory request: Remote ${tunnelOptions.remoteAddress.port} -> local ${tunnelOptions.localAddressPort}`);
+    if (tunnelCreationOptions.elevationRequired) {
+        await vscode.window.showInformationMessage('This is a fake elevation message. A real resolver would show a native elevation prompt.', { modal: true }, 'Ok');
+    }
+    return createTunnelService();
+    function newTunnel(localAddress: {
+        host: string;
+        port: number;
+    }): vscode.Tunnel {
+        const onDidDispose: vscode.EventEmitter = new vscode.EventEmitter();
+        let isDisposed = false;
+        return {
+            localAddress,
+            remoteAddress: tunnelOptions.remoteAddress,
+            public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') && tunnelOptions.public,
+            privacy: tunnelOptions.privacy,
+            protocol: tunnelOptions.protocol,
+            onDidDispose: onDidDispose.event,
+            dispose: () => {
+                if (!isDisposed) {
+                    isDisposed = true;
+                    onDidDispose.fire();
+                }
+            }
+        };
+    }
+    function createTunnelService(): Promise {
+        return new Promise((res, _rej) => {
+            const proxyServer = net.createServer(proxySocket => {
+                const remoteSocket = net.createConnection({ host: tunnelOptions.remoteAddress.host, port: tunnelOptions.remoteAddress.port });
+                remoteSocket.pipe(proxySocket);
+                proxySocket.pipe(remoteSocket);
+            });
+            let localPort = 0;
+            if (tunnelOptions.localAddressPort) {
+                // When the tunnelOptions include a localAddressPort, we should use that.
+                // However, the test resolver all runs on one machine, so if the localAddressPort is the same as the remote port,
+                // then we must use a different port number.
+                localPort = tunnelOptions.localAddressPort;
+            }
+            else {
+                localPort = tunnelOptions.remoteAddress.port;
+            }
+            if (localPort === tunnelOptions.remoteAddress.port) {
+                localPort += 1;
+            }
+            // The test resolver can't actually handle privileged ports, it only pretends to.
+            if (localPort < 1024 && process.platform !== 'win32') {
+                localPort = 0;
+            }
+            proxyServer.listen(localPort, '127.0.0.1', () => {
+                const localPort = (proxyServer.address()).port;
+                outputChannel.appendLine(`New test resolver tunnel service: Remote ${tunnelOptions.remoteAddress.port} -> local ${localPort}`);
+                const tunnel = newTunnel({ host: '127.0.0.1', port: localPort });
+                tunnel.onDidDispose(() => proxyServer.close());
+                res(tunnel);
+            });
+        });
+    }
 }
-
 function runHTTPTestServer(port: number): vscode.Disposable {
-	const server = http.createServer((_req, res) => {
-		res.writeHead(200);
-		res.end(`Hello, World from test server running on port ${port}!`);
-	});
-	remoteServers.push(port);
-	server.listen(port, '127.0.0.1');
-	const message = `Opened HTTP server on http://127.0.0.1:${port}`;
-	console.log(message);
-	outputChannel.appendLine(message);
-	return {
-		dispose: () => {
-			server.close();
-			const index = remoteServers.indexOf(port);
-			if (index !== -1) {
-				remoteServers.splice(index, 1);
-			}
-		}
-	};
+    const server = http.createServer((_req, res) => {
+        res.writeHead(200);
+        res.end(`Hello, World from test server running on port ${port}!`);
+    });
+    remoteServers.push(port);
+    server.listen(port, '127.0.0.1');
+    const message = `Opened HTTP server on http://127.0.0.1:${port}`;
+    console.log(message);
+    outputChannel.appendLine(message);
+    return {
+        dispose: () => {
+            server.close();
+            const index = remoteServers.indexOf(port);
+            if (index !== -1) {
+                remoteServers.splice(index, 1);
+            }
+        }
+    };
 }
diff --git a/extensions/vscode-test-resolver/Source/util/processes.ts b/extensions/vscode-test-resolver/Source/util/processes.ts
index f16d0678c5268..097d65f0dbd97 100644
--- a/extensions/vscode-test-resolver/Source/util/processes.ts
+++ b/extensions/vscode-test-resolver/Source/util/processes.ts
@@ -4,34 +4,36 @@
  *--------------------------------------------------------------------------------------------*/
 import * as cp from 'child_process';
 import * as path from 'path';
-
 export interface TerminateResponse {
-	success: boolean;
-	error?: any;
+    success: boolean;
+    error?: any;
 }
-
 export function terminateProcess(p: cp.ChildProcess, extensionPath: string): TerminateResponse {
-	if (process.platform === 'win32') {
-		try {
-			const options: any = {
-				stdio: ['pipe', 'pipe', 'ignore']
-			};
-			cp.execFileSync('taskkill', ['/T', '/F', '/PID', p.pid!.toString()], options);
-		} catch (err) {
-			return { success: false, error: err };
-		}
-	} else if (process.platform === 'darwin' || process.platform === 'linux') {
-		try {
-			const cmd = path.join(extensionPath, 'scripts', 'terminateProcess.sh');
-			const result = cp.spawnSync(cmd, [p.pid!.toString()]);
-			if (result.error) {
-				return { success: false, error: result.error };
-			}
-		} catch (err) {
-			return { success: false, error: err };
-		}
-	} else {
-		p.kill('SIGKILL');
-	}
-	return { success: true };
+    if (process.platform === 'win32') {
+        try {
+            const options: any = {
+                stdio: ['pipe', 'pipe', 'ignore']
+            };
+            cp.execFileSync('taskkill', ['/T', '/F', '/PID', p.pid!.toString()], options);
+        }
+        catch (err) {
+            return { success: false, error: err };
+        }
+    }
+    else if (process.platform === 'darwin' || process.platform === 'linux') {
+        try {
+            const cmd = path.join(extensionPath, 'scripts', 'terminateProcess.sh');
+            const result = cp.spawnSync(cmd, [p.pid!.toString()]);
+            if (result.error) {
+                return { success: false, error: result.error };
+            }
+        }
+        catch (err) {
+            return { success: false, error: err };
+        }
+    }
+    else {
+        p.kill('SIGKILL');
+    }
+    return { success: true };
 }
diff --git a/scripts/playground-server.ts b/scripts/playground-server.ts
index 474f83def861f..6c72b0612a005 100644
--- a/scripts/playground-server.ts
+++ b/scripts/playground-server.ts
@@ -2,895 +2,798 @@
  *  Copyright (c) Microsoft Corporation. All rights reserved.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
-
 import * as fsPromise from 'fs/promises';
 import path from 'path';
 import * as http from 'http';
 import * as parcelWatcher from '@parcel/watcher';
-
 /**
  * Launches the server for the monaco editor playground
  */
 function main() {
-	const server = new HttpServer({ host: 'localhost', port: 5001, cors: true });
-	server.use('/', redirectToMonacoEditorPlayground());
-
-	const rootDir = path.join(__dirname, '..');
-	const fileServer = new FileServer(rootDir);
-	server.use(fileServer.handleRequest);
-
-	const moduleIdMapper = new SimpleModuleIdPathMapper(path.join(rootDir, 'out'));
-	const editorMainBundle = new CachedBundle('vs/editor/editor.main', moduleIdMapper);
-	fileServer.overrideFileContent(editorMainBundle.entryModulePath, () => editorMainBundle.bundle());
-
-	const loaderPath = path.join(rootDir, 'out/vs/loader.js');
-	fileServer.overrideFileContent(loaderPath, async () =>
-		Buffer.from(new TextEncoder().encode(makeLoaderJsHotReloadable(await fsPromise.readFile(loaderPath, 'utf8'), new URL('/file-changes', server.url))))
-	);
-
-	const watcher = DirWatcher.watchRecursively(moduleIdMapper.rootDir);
-	watcher.onDidChange((path, newContent) => {
-		editorMainBundle.setModuleContent(path, newContent);
-		editorMainBundle.bundle();
-		console.log(`${new Date().toLocaleTimeString()}, file change: ${path}`);
-	});
-	server.use('/file-changes', handleGetFileChangesRequest(watcher, fileServer, moduleIdMapper));
-
-	console.log(`Server listening on ${server.url}`);
+    const server = new HttpServer({ host: 'localhost', port: 5001, cors: true });
+    server.use('/', redirectToMonacoEditorPlayground());
+    const rootDir = path.join(__dirname, '..');
+    const fileServer = new FileServer(rootDir);
+    server.use(fileServer.handleRequest);
+    const moduleIdMapper = new SimpleModuleIdPathMapper(path.join(rootDir, 'out'));
+    const editorMainBundle = new CachedBundle('vs/editor/editor.main', moduleIdMapper);
+    fileServer.overrideFileContent(editorMainBundle.entryModulePath, () => editorMainBundle.bundle());
+    const loaderPath = path.join(rootDir, 'out/vs/loader.js');
+    fileServer.overrideFileContent(loaderPath, async () => Buffer.from(new TextEncoder().encode(makeLoaderJsHotReloadable(await fsPromise.readFile(loaderPath, 'utf8'), new URL('/file-changes', server.url)))));
+    const watcher = DirWatcher.watchRecursively(moduleIdMapper.rootDir);
+    watcher.onDidChange((path, newContent) => {
+        editorMainBundle.setModuleContent(path, newContent);
+        editorMainBundle.bundle();
+        console.log(`${new Date().toLocaleTimeString()}, file change: ${path}`);
+    });
+    server.use('/file-changes', handleGetFileChangesRequest(watcher, fileServer, moduleIdMapper));
+    console.log(`Server listening on ${server.url}`);
 }
 setTimeout(main, 0);
-
 // #region Http/File Server
-
 type RequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise;
 type ChainableRequestHandler = (req: http.IncomingMessage, res: http.ServerResponse, next: RequestHandler) => Promise;
-
 class HttpServer {
-	private readonly server: http.Server;
-	public readonly url: URL;
-
-	private handler: ChainableRequestHandler[] = [];
-
-	constructor(options: { host: string; port: number; cors: boolean }) {
-		this.server = http.createServer(async (req, res) => {
-			if (options.cors) {
-				res.setHeader('Access-Control-Allow-Origin', '*');
-			}
-
-			let i = 0;
-			const next = async (req: http.IncomingMessage, res: http.ServerResponse) => {
-				if (i >= this.handler.length) {
-					res.writeHead(404, { 'Content-Type': 'text/plain' });
-					res.end('404 Not Found');
-					return;
-				}
-				const handler = this.handler[i];
-				i++;
-				await handler(req, res, next);
-			};
-			await next(req, res);
-		});
-		this.server.listen(options.port, options.host);
-		this.url = new URL(`http://${options.host}:${options.port}`);
-	}
-
-	use(handler: ChainableRequestHandler);
-	use(path: string, handler: ChainableRequestHandler);
-	use(...args: [path: string, handler: ChainableRequestHandler] | [handler: ChainableRequestHandler]) {
-		const handler = args.length === 1 ? args[0] : (req, res, next) => {
-			const path = args[0];
-			const requestedUrl = new URL(req.url, this.url);
-			if (requestedUrl.pathname === path) {
-				return args[1](req, res, next);
-			} else {
-				return next(req, res);
-			}
-		};
-
-		this.handler.push(handler);
-	}
+    private readonly server: http.Server;
+    public readonly url: URL;
+    private handler: ChainableRequestHandler[] = [];
+    constructor(options: {
+        host: string;
+        port: number;
+        cors: boolean;
+    }) {
+        this.server = http.createServer(async (req, res) => {
+            if (options.cors) {
+                res.setHeader('Access-Control-Allow-Origin', '*');
+            }
+            let i = 0;
+            const next = async (req: http.IncomingMessage, res: http.ServerResponse) => {
+                if (i >= this.handler.length) {
+                    res.writeHead(404, { 'Content-Type': 'text/plain' });
+                    res.end('404 Not Found');
+                    return;
+                }
+                const handler = this.handler[i];
+                i++;
+                await handler(req, res, next);
+            };
+            await next(req, res);
+        });
+        this.server.listen(options.port, options.host);
+        this.url = new URL(`http://${options.host}:${options.port}`);
+    }
+    use(handler: ChainableRequestHandler);
+    use(path: string, handler: ChainableRequestHandler);
+    use(...args: [
+        path: string,
+        handler: ChainableRequestHandler
+    ] | [
+        handler: ChainableRequestHandler
+    ]) {
+        const handler = args.length === 1 ? args[0] : (req, res, next) => {
+            const path = args[0];
+            const requestedUrl = new URL(req.url, this.url);
+            if (requestedUrl.pathname === path) {
+                return args[1](req, res, next);
+            }
+            else {
+                return next(req, res);
+            }
+        };
+        this.handler.push(handler);
+    }
 }
-
 function redirectToMonacoEditorPlayground(): ChainableRequestHandler {
-	return async (req, res) => {
-		const url = new URL('https://microsoft.github.io/monaco-editor/playground.html');
-		url.searchParams.append('source', `http://${req.headers.host}/out/vs`);
-		res.writeHead(302, { Location: url.toString() });
-		res.end();
-	};
+    return async (req, res) => {
+        const url = new URL('https://microsoft.github.io/monaco-editor/playground.html');
+        url.searchParams.append('source', `http://${req.headers.host}/out/vs`);
+        res.writeHead(302, { Location: url.toString() });
+        res.end();
+    };
 }
-
 class FileServer {
-	private readonly overrides = new Map Promise>();
-
-	constructor(public readonly publicDir: string) { }
-
-	public readonly handleRequest: ChainableRequestHandler = async (req, res, next) => {
-		const requestedUrl = new URL(req.url!, `http://${req.headers.host}`);
-
-		const pathName = requestedUrl.pathname;
-
-		const filePath = path.join(this.publicDir, pathName);
-		if (!filePath.startsWith(this.publicDir)) {
-			res.writeHead(403, { 'Content-Type': 'text/plain' });
-			res.end('403 Forbidden');
-			return;
-		}
-
-		try {
-			const override = this.overrides.get(filePath);
-			let content: Buffer;
-			if (override) {
-				content = await override();
-			} else {
-				content = await fsPromise.readFile(filePath);
-			}
-
-			const contentType = getContentType(filePath);
-			res.writeHead(200, { 'Content-Type': contentType });
-			res.end(content);
-		} catch (err) {
-			if (err.code === 'ENOENT') {
-				next(req, res);
-			} else {
-				res.writeHead(500, { 'Content-Type': 'text/plain' });
-				res.end('500 Internal Server Error');
-			}
-		}
-	};
-
-	public filePathToUrlPath(filePath: string): string | undefined {
-		const relative = path.relative(this.publicDir, filePath);
-		const isSubPath = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
-
-		if (!isSubPath) {
-			return undefined;
-		}
-		const relativePath = relative.replace(/\\/g, '/');
-		return `/${relativePath}`;
-	}
-
-	public overrideFileContent(filePath: string, content: () => Promise): void {
-		this.overrides.set(filePath, content);
-	}
+    private readonly overrides = new Map Promise>();
+    constructor(public readonly publicDir: string) { }
+    public readonly handleRequest: ChainableRequestHandler = async (req, res, next) => {
+        const requestedUrl = new URL(req.url!, `http://${req.headers.host}`);
+        const pathName = requestedUrl.pathname;
+        const filePath = path.join(this.publicDir, pathName);
+        if (!filePath.startsWith(this.publicDir)) {
+            res.writeHead(403, { 'Content-Type': 'text/plain' });
+            res.end('403 Forbidden');
+            return;
+        }
+        try {
+            const override = this.overrides.get(filePath);
+            let content: Buffer;
+            if (override) {
+                content = await override();
+            }
+            else {
+                content = await fsPromise.readFile(filePath);
+            }
+            const contentType = getContentType(filePath);
+            res.writeHead(200, { 'Content-Type': contentType });
+            res.end(content);
+        }
+        catch (err) {
+            if (err.code === 'ENOENT') {
+                next(req, res);
+            }
+            else {
+                res.writeHead(500, { 'Content-Type': 'text/plain' });
+                res.end('500 Internal Server Error');
+            }
+        }
+    };
+    public filePathToUrlPath(filePath: string): string | undefined {
+        const relative = path.relative(this.publicDir, filePath);
+        const isSubPath = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
+        if (!isSubPath) {
+            return undefined;
+        }
+        const relativePath = relative.replace(/\\/g, '/');
+        return `/${relativePath}`;
+    }
+    public overrideFileContent(filePath: string, content: () => Promise): void {
+        this.overrides.set(filePath, content);
+    }
 }
-
 function getContentType(filePath: string): string {
-	const extname = path.extname(filePath);
-	switch (extname) {
-		case '.js':
-			return 'text/javascript';
-		case '.css':
-			return 'text/css';
-		case '.json':
-			return 'application/json';
-		case '.png':
-			return 'image/png';
-		case '.jpg':
-			return 'image/jpg';
-		case '.svg':
-			return 'image/svg+xml';
-		case '.html':
-			return 'text/html';
-		case '.wasm':
-			return 'application/wasm';
-		default:
-			return 'text/plain';
-	}
+    const extname = path.extname(filePath);
+    switch (extname) {
+        case '.js':
+            return 'text/javascript';
+        case '.css':
+            return 'text/css';
+        case '.json':
+            return 'application/json';
+        case '.png':
+            return 'image/png';
+        case '.jpg':
+            return 'image/jpg';
+        case '.svg':
+            return 'image/svg+xml';
+        case '.html':
+            return 'text/html';
+        case '.wasm':
+            return 'application/wasm';
+        default:
+            return 'text/plain';
+    }
 }
-
 // #endregion
-
 // #region File Watching
-
 interface IDisposable {
-	dispose(): void;
+    dispose(): void;
 }
-
 class DirWatcher {
-	public static watchRecursively(dir: string): DirWatcher {
-		const listeners: ((path: string, newContent: string) => void)[] = [];
-		const fileContents = new Map();
-		const event = (handler: (path: string, newContent: string) => void) => {
-			listeners.push(handler);
-			return {
-				dispose: () => {
-					const idx = listeners.indexOf(handler);
-					if (idx >= 0) {
-						listeners.splice(idx, 1);
-					}
-				}
-			};
-		};
-		parcelWatcher.subscribe(dir, async (err, events) => {
-			for (const e of events) {
-				if (e.type === 'update') {
-					const newContent = await fsPromise.readFile(e.path, 'utf8');
-					if (fileContents.get(e.path) !== newContent) {
-						fileContents.set(e.path, newContent);
-						listeners.forEach(l => l(e.path, newContent));
-					}
-				}
-			}
-		});
-		return new DirWatcher(event);
-	}
-
-	constructor(public readonly onDidChange: (handler: (path: string, newContent: string) => void) => IDisposable) {
-	}
+    public static watchRecursively(dir: string): DirWatcher {
+        const listeners: ((path: string, newContent: string) => void)[] = [];
+        const fileContents = new Map();
+        const event = (handler: (path: string, newContent: string) => void) => {
+            listeners.push(handler);
+            return {
+                dispose: () => {
+                    const idx = listeners.indexOf(handler);
+                    if (idx >= 0) {
+                        listeners.splice(idx, 1);
+                    }
+                }
+            };
+        };
+        parcelWatcher.subscribe(dir, async (err, events) => {
+            for (const e of events) {
+                if (e.type === 'update') {
+                    const newContent = await fsPromise.readFile(e.path, 'utf8');
+                    if (fileContents.get(e.path) !== newContent) {
+                        fileContents.set(e.path, newContent);
+                        listeners.forEach(l => l(e.path, newContent));
+                    }
+                }
+            }
+        });
+        return new DirWatcher(event);
+    }
+    constructor(public readonly onDidChange: (handler: (path: string, newContent: string) => void) => IDisposable) {
+    }
 }
-
 function handleGetFileChangesRequest(watcher: DirWatcher, fileServer: FileServer, moduleIdMapper: SimpleModuleIdPathMapper): ChainableRequestHandler {
-	return async (req, res) => {
-		res.writeHead(200, { 'Content-Type': 'text/plain' });
-		const d = watcher.onDidChange((fsPath, newContent) => {
-			const path = fileServer.filePathToUrlPath(fsPath);
-			if (path) {
-				res.write(JSON.stringify({ changedPath: path, moduleId: moduleIdMapper.getModuleId(fsPath), newContent }) + '\n');
-			}
-		});
-		res.on('close', () => d.dispose());
-	};
+    return async (req, res) => {
+        res.writeHead(200, { 'Content-Type': 'text/plain' });
+        const d = watcher.onDidChange((fsPath, newContent) => {
+            const path = fileServer.filePathToUrlPath(fsPath);
+            if (path) {
+                res.write(JSON.stringify({ changedPath: path, moduleId: moduleIdMapper.getModuleId(fsPath), newContent }) + '\n');
+            }
+        });
+        res.on('close', () => d.dispose());
+    };
 }
 function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): string {
-	loaderJsCode = loaderJsCode.replace(
-		/constructor\(env, scriptLoader, defineFunc, requireFunc, loaderAvailableTimestamp = 0\) {/,
-		'$&globalThis.___globalModuleManager = this; globalThis.vscode = { process: { env: { VSCODE_DEV: true } } }'
-	);
-
-	const ___globalModuleManager: any = undefined;
-
-	// This code will be appended to loader.js
-	function $watchChanges(fileChangesUrl: string) {
-		interface HotReloadConfig { }
-
-		let reloadFn;
-		if (globalThis.$sendMessageToParent) {
-			reloadFn = () => globalThis.$sendMessageToParent({ kind: 'reload' });
-		} else if (typeof window !== 'undefined') {
-			reloadFn = () => window.location.reload();
-		} else {
-			reloadFn = () => { };
-		}
-
-		console.log('Connecting to server to watch for changes...');
-		(fetch as any)(fileChangesUrl)
-			.then(async request => {
-				const reader = request.body.getReader();
-				let buffer = '';
-				while (true) {
-					const { done, value } = await reader.read();
-					if (done) { break; }
-					buffer += new TextDecoder().decode(value);
-					const lines = buffer.split('\n');
-					buffer = lines.pop()!;
-
-					const changes: { relativePath: string; config: HotReloadConfig | undefined; path: string; newContent: string }[] = [];
-
-					for (const line of lines) {
-						const data = JSON.parse(line);
-						const relativePath = data.changedPath.replace(/\\/g, '/').split('/out/')[1];
-						changes.push({ config: {}, path: data.changedPath, relativePath, newContent: data.newContent });
-					}
-
-					const result = handleChanges(changes, 'playground-server');
-					if (result.reloadFailedJsFiles.length > 0) {
-						reloadFn();
-					}
-				}
-			}).catch(err => {
-				console.error(err);
-				setTimeout(() => $watchChanges(fileChangesUrl), 1000);
-			});
-
-
-		function handleChanges(changes: {
-			relativePath: string;
-			config: HotReloadConfig | undefined;
-			path: string;
-			newContent: string;
-		}[], debugSessionName: string) {
-			// This function is stringified and injected into the debuggee.
-
-			const hotReloadData: { count: number; originalWindowTitle: any; timeout: any; shouldReload: boolean } = globalThis.$hotReloadData || (globalThis.$hotReloadData = { count: 0, messageHideTimeout: undefined, shouldReload: false });
-
-			const reloadFailedJsFiles: { relativePath: string; path: string }[] = [];
-
-			for (const change of changes) {
-				handleChange(change.relativePath, change.path, change.newContent, change.config);
-			}
-
-			return { reloadFailedJsFiles };
-
-			function handleChange(relativePath: string, path: string, newSrc: string, config: any) {
-				if (relativePath.endsWith('.css')) {
-					handleCssChange(relativePath);
-				} else if (relativePath.endsWith('.js')) {
-					handleJsChange(relativePath, path, newSrc, config);
-				}
-			}
-
-			function handleCssChange(relativePath: string) {
-				if (typeof document === 'undefined') {
-					return;
-				}
-
-				const styleSheet = (([...document.querySelectorAll(`link[rel='stylesheet']`)] as HTMLLinkElement[]))
-					.find(l => new URL(l.href, document.location.href).pathname.endsWith(relativePath));
-				if (styleSheet) {
-					setMessage(`reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
-					console.log(debugSessionName, 'css reloaded', relativePath);
-					styleSheet.href = styleSheet.href.replace(/\?.*/, '') + '?' + Date.now();
-				} else {
-					setMessage(`could not reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
-					console.log(debugSessionName, 'ignoring css change, as stylesheet is not loaded', relativePath);
-				}
-			}
-
-
-			function handleJsChange(relativePath: string, path: string, newSrc: string, config: any) {
-				const moduleIdStr = trimEnd(relativePath, '.js');
-
-				const requireFn: any = globalThis.require;
-				const moduleManager = (requireFn as any).moduleManager;
-				if (!moduleManager) {
-					console.log(debugSessionName, 'ignoring js change, as moduleManager is not available', relativePath);
-					return;
-				}
-
-				const moduleId = moduleManager._moduleIdProvider.getModuleId(moduleIdStr);
-				const oldModule = moduleManager._modules2[moduleId];
-
-				if (!oldModule) {
-					console.log(debugSessionName, 'ignoring js change, as module is not loaded', relativePath);
-					return;
-				}
-
-				// Check if we can reload
-				const g = globalThis as any;
-
-				// A frozen copy of the previous exports
-				const oldExports = Object.freeze({ ...oldModule.exports });
-				const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc, config });
-
-				if (!reloadFn) {
-					console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath);
-					hotReloadData.shouldReload = true;
-
-					reloadFailedJsFiles.push({ relativePath, path });
-
-					setMessage(`hot reload not supported for ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
-					return;
-				}
-
-				// Eval maintains source maps
-				function newScript(/* this parameter is used by newSrc */ define) {
-					// eslint-disable-next-line no-eval
-					eval(newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality.
-				}
-
-				newScript(/* define */ function (deps, callback) {
-					// Evaluating the new code was successful.
-
-					// Redefine the module
-					delete moduleManager._modules2[moduleId];
-					moduleManager.defineModule(moduleIdStr, deps, callback);
-					const newModule = moduleManager._modules2[moduleId];
-
-
-					// Patch the exports of the old module, so that modules using the old module get the new exports
-					Object.assign(oldModule.exports, newModule.exports);
-					// We override the exports so that future reloads still patch the initial exports.
-					newModule.exports = oldModule.exports;
-
-					const successful = reloadFn(newModule.exports);
-					if (!successful) {
-						hotReloadData.shouldReload = true;
-						setMessage(`hot reload failed ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
-						console.log(debugSessionName, 'hot reload was not successful', relativePath);
-						return;
-					}
-
-					console.log(debugSessionName, 'hot reloaded', moduleIdStr);
-					setMessage(`successfully reloaded ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
-				});
-			}
-
-			function setMessage(message: string) {
-				const domElem = (document.querySelector('.titlebar-center .window-title')) as HTMLDivElement | undefined;
-				if (!domElem) { return; }
-				if (!hotReloadData.timeout) {
-					hotReloadData.originalWindowTitle = domElem.innerText;
-				} else {
-					clearTimeout(hotReloadData.timeout);
-				}
-				if (hotReloadData.shouldReload) {
-					message += ' (manual reload required)';
-				}
-
-				domElem.innerText = message;
-				hotReloadData.timeout = setTimeout(() => {
-					hotReloadData.timeout = undefined;
-					// If wanted, we can restore the previous title message
-					// domElem.replaceChildren(hotReloadData.originalWindowTitle);
-				}, 5000);
-			}
-
-			function formatPath(path: string): string {
-				const parts = path.split('/');
-				parts.reverse();
-				let result = parts[0];
-				parts.shift();
-				for (const p of parts) {
-					if (result.length + p.length > 40) {
-						break;
-					}
-					result = p + '/' + result;
-					if (result.length > 20) {
-						break;
-					}
-				}
-				return result;
-			}
-
-			function trimEnd(str, suffix) {
-				if (str.endsWith(suffix)) {
-					return str.substring(0, str.length - suffix.length);
-				}
-				return str;
-			}
-		}
-	}
-
-	const additionalJsCode = `
+    loaderJsCode = loaderJsCode.replace(/constructor\(env, scriptLoader, defineFunc, requireFunc, loaderAvailableTimestamp = 0\) {/, '$&globalThis.___globalModuleManager = this; globalThis.vscode = { process: { env: { VSCODE_DEV: true } } }');
+    const ___globalModuleManager: any = undefined;
+    // This code will be appended to loader.js
+    function $watchChanges(fileChangesUrl: string) {
+        interface HotReloadConfig {
+        }
+        let reloadFn;
+        if (globalThis.$sendMessageToParent) {
+            reloadFn = () => globalThis.$sendMessageToParent({ kind: 'reload' });
+        }
+        else if (typeof window !== 'undefined') {
+            reloadFn = () => window.location.reload();
+        }
+        else {
+            reloadFn = () => { };
+        }
+        console.log('Connecting to server to watch for changes...');
+        (fetch as any)(fileChangesUrl)
+            .then(async (request) => {
+            const reader = request.body.getReader();
+            let buffer = '';
+            while (true) {
+                const { done, value } = await reader.read();
+                if (done) {
+                    break;
+                }
+                buffer += new TextDecoder().decode(value);
+                const lines = buffer.split('\n');
+                buffer = lines.pop()!;
+                const changes: {
+                    relativePath: string;
+                    config: HotReloadConfig | undefined;
+                    path: string;
+                    newContent: string;
+                }[] = [];
+                for (const line of lines) {
+                    const data = JSON.parse(line);
+                    const relativePath = data.changedPath.replace(/\\/g, '/').split('/out/')[1];
+                    changes.push({ config: {}, path: data.changedPath, relativePath, newContent: data.newContent });
+                }
+                const result = handleChanges(changes, 'playground-server');
+                if (result.reloadFailedJsFiles.length > 0) {
+                    reloadFn();
+                }
+            }
+        }).catch(err => {
+            console.error(err);
+            setTimeout(() => $watchChanges(fileChangesUrl), 1000);
+        });
+        function handleChanges(changes: {
+            relativePath: string;
+            config: HotReloadConfig | undefined;
+            path: string;
+            newContent: string;
+        }[], debugSessionName: string) {
+            // This function is stringified and injected into the debuggee.
+            const hotReloadData: {
+                count: number;
+                originalWindowTitle: any;
+                timeout: any;
+                shouldReload: boolean;
+            } = globalThis.$hotReloadData || (globalThis.$hotReloadData = { count: 0, messageHideTimeout: undefined, shouldReload: false });
+            const reloadFailedJsFiles: {
+                relativePath: string;
+                path: string;
+            }[] = [];
+            for (const change of changes) {
+                handleChange(change.relativePath, change.path, change.newContent, change.config);
+            }
+            return { reloadFailedJsFiles };
+            function handleChange(relativePath: string, path: string, newSrc: string, config: any) {
+                if (relativePath.endsWith('.css')) {
+                    handleCssChange(relativePath);
+                }
+                else if (relativePath.endsWith('.js')) {
+                    handleJsChange(relativePath, path, newSrc, config);
+                }
+            }
+            function handleCssChange(relativePath: string) {
+                if (typeof document === 'undefined') {
+                    return;
+                }
+                const styleSheet = (([...document.querySelectorAll(`link[rel='stylesheet']`)] as HTMLLinkElement[]))
+                    .find(l => new URL(l.href, document.location.href).pathname.endsWith(relativePath));
+                if (styleSheet) {
+                    setMessage(`reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
+                    console.log(debugSessionName, 'css reloaded', relativePath);
+                    styleSheet.href = styleSheet.href.replace(/\?.*/, '') + '?' + Date.now();
+                }
+                else {
+                    setMessage(`could not reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
+                    console.log(debugSessionName, 'ignoring css change, as stylesheet is not loaded', relativePath);
+                }
+            }
+            function handleJsChange(relativePath: string, path: string, newSrc: string, config: any) {
+                const moduleIdStr = trimEnd(relativePath, '.js');
+                const requireFn: any = globalThis.require;
+                const moduleManager = (requireFn as any).moduleManager;
+                if (!moduleManager) {
+                    console.log(debugSessionName, 'ignoring js change, as moduleManager is not available', relativePath);
+                    return;
+                }
+                const moduleId = moduleManager._moduleIdProvider.getModuleId(moduleIdStr);
+                const oldModule = moduleManager._modules2[moduleId];
+                if (!oldModule) {
+                    console.log(debugSessionName, 'ignoring js change, as module is not loaded', relativePath);
+                    return;
+                }
+                // Check if we can reload
+                const g = globalThis as any;
+                // A frozen copy of the previous exports
+                const oldExports = Object.freeze({ ...oldModule.exports });
+                const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc, config });
+                if (!reloadFn) {
+                    console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath);
+                    hotReloadData.shouldReload = true;
+                    reloadFailedJsFiles.push({ relativePath, path });
+                    setMessage(`hot reload not supported for ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
+                    return;
+                }
+                // Eval maintains source maps
+                function newScript(/* this parameter is used by newSrc */ define) {
+                    // eslint-disable-next-line no-eval
+                    eval(newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality.
+                }
+                newScript(/* define */ function (deps, callback) {
+                    // Evaluating the new code was successful.
+                    // Redefine the module
+                    delete moduleManager._modules2[moduleId];
+                    moduleManager.defineModule(moduleIdStr, deps, callback);
+                    const newModule = moduleManager._modules2[moduleId];
+                    // Patch the exports of the old module, so that modules using the old module get the new exports
+                    Object.assign(oldModule.exports, newModule.exports);
+                    // We override the exports so that future reloads still patch the initial exports.
+                    newModule.exports = oldModule.exports;
+                    const successful = reloadFn(newModule.exports);
+                    if (!successful) {
+                        hotReloadData.shouldReload = true;
+                        setMessage(`hot reload failed ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
+                        console.log(debugSessionName, 'hot reload was not successful', relativePath);
+                        return;
+                    }
+                    console.log(debugSessionName, 'hot reloaded', moduleIdStr);
+                    setMessage(`successfully reloaded ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`);
+                });
+            }
+            function setMessage(message: string) {
+                const domElem = (document.querySelector('.titlebar-center .window-title')) as HTMLDivElement | undefined;
+                if (!domElem) {
+                    return;
+                }
+                if (!hotReloadData.timeout) {
+                    hotReloadData.originalWindowTitle = domElem.innerText;
+                }
+                else {
+                    clearTimeout(hotReloadData.timeout);
+                }
+                if (hotReloadData.shouldReload) {
+                    message += ' (manual reload required)';
+                }
+                domElem.innerText = message;
+                hotReloadData.timeout = setTimeout(() => {
+                    hotReloadData.timeout = undefined;
+                    // If wanted, we can restore the previous title message
+                    // domElem.replaceChildren(hotReloadData.originalWindowTitle);
+                }, 5000);
+            }
+            function formatPath(path: string): string {
+                const parts = path.split('/');
+                parts.reverse();
+                let result = parts[0];
+                parts.shift();
+                for (const p of parts) {
+                    if (result.length + p.length > 40) {
+                        break;
+                    }
+                    result = p + '/' + result;
+                    if (result.length > 20) {
+                        break;
+                    }
+                }
+                return result;
+            }
+            function trimEnd(str, suffix) {
+                if (str.endsWith(suffix)) {
+                    return str.substring(0, str.length - suffix.length);
+                }
+                return str;
+            }
+        }
+    }
+    const additionalJsCode = `
 (${(function () {
-			globalThis.$hotReload_deprecateExports = new Set<(oldExports: any, newExports: any) => void>();
-		}).toString()})();
+        globalThis.$hotReload_deprecateExports = new Set<(oldExports: any, newExports: any) => void>();
+    }).toString()})();
 ${$watchChanges.toString()}
 $watchChanges(${JSON.stringify(fileChangesUrl)});
 `;
-
-	return `${loaderJsCode}\n${additionalJsCode}`;
+    return `${loaderJsCode}\n${additionalJsCode}`;
 }
-
 // #endregion
-
 // #region Bundling
-
 class CachedBundle {
-	public readonly entryModulePath = this.mapper.resolveRequestToPath(this.moduleId)!;
-
-	constructor(
-		private readonly moduleId: string,
-		private readonly mapper: SimpleModuleIdPathMapper,
-	) {
-	}
-
-	private loader: ModuleLoader | undefined = undefined;
-
-	private bundlePromise: Promise | undefined = undefined;
-	public async bundle(): Promise {
-		if (!this.bundlePromise) {
-			this.bundlePromise = (async () => {
-				if (!this.loader) {
-					this.loader = new ModuleLoader(this.mapper);
-					await this.loader.addModuleAndDependencies(this.entryModulePath);
-				}
-				const editorEntryPoint = await this.loader.getModule(this.entryModulePath);
-				const content = bundleWithDependencies(editorEntryPoint!);
-				return content;
-			})();
-		}
-		return this.bundlePromise;
-	}
-
-	public async setModuleContent(path: string, newContent: string): Promise {
-		if (!this.loader) {
-			return;
-		}
-		const module = await this.loader!.getModule(path);
-		if (module) {
-			if (!this.loader.updateContent(module, newContent)) {
-				this.loader = undefined;
-			}
-		}
-		this.bundlePromise = undefined;
-	}
+    public readonly entryModulePath = this.mapper.resolveRequestToPath(this.moduleId)!;
+    constructor(private readonly moduleId: string, private readonly mapper: SimpleModuleIdPathMapper) {
+    }
+    private loader: ModuleLoader | undefined = undefined;
+    private bundlePromise: Promise | undefined = undefined;
+    public async bundle(): Promise {
+        if (!this.bundlePromise) {
+            this.bundlePromise = (async () => {
+                if (!this.loader) {
+                    this.loader = new ModuleLoader(this.mapper);
+                    await this.loader.addModuleAndDependencies(this.entryModulePath);
+                }
+                const editorEntryPoint = await this.loader.getModule(this.entryModulePath);
+                const content = bundleWithDependencies(editorEntryPoint!);
+                return content;
+            })();
+        }
+        return this.bundlePromise;
+    }
+    public async setModuleContent(path: string, newContent: string): Promise {
+        if (!this.loader) {
+            return;
+        }
+        const module = await this.loader!.getModule(path);
+        if (module) {
+            if (!this.loader.updateContent(module, newContent)) {
+                this.loader = undefined;
+            }
+        }
+        this.bundlePromise = undefined;
+    }
 }
-
 function bundleWithDependencies(module: IModule): Buffer {
-	const visited = new Set();
-	const builder = new SourceMapBuilder();
-
-	function visit(module: IModule) {
-		if (visited.has(module)) {
-			return;
-		}
-		visited.add(module);
-		for (const dep of module.dependencies) {
-			visit(dep);
-		}
-		builder.addSource(module.source);
-	}
-
-	visit(module);
-
-	const sourceMap = builder.toSourceMap();
-	sourceMap.sourceRoot = module.source.sourceMap.sourceRoot;
-	const sourceMapBase64Str = Buffer.from(JSON.stringify(sourceMap)).toString('base64');
-
-	builder.addLine(`//# sourceMappingURL=data:application/json;base64,${sourceMapBase64Str}`);
-
-	return builder.toContent();
+    const visited = new Set();
+    const builder = new SourceMapBuilder();
+    function visit(module: IModule) {
+        if (visited.has(module)) {
+            return;
+        }
+        visited.add(module);
+        for (const dep of module.dependencies) {
+            visit(dep);
+        }
+        builder.addSource(module.source);
+    }
+    visit(module);
+    const sourceMap = builder.toSourceMap();
+    sourceMap.sourceRoot = module.source.sourceMap.sourceRoot;
+    const sourceMapBase64Str = Buffer.from(JSON.stringify(sourceMap)).toString('base64');
+    builder.addLine(`//# sourceMappingURL=data:application/json;base64,${sourceMapBase64Str}`);
+    return builder.toContent();
 }
-
 class ModuleLoader {
-	private readonly modules = new Map>();
-
-	constructor(private readonly mapper: SimpleModuleIdPathMapper) { }
-
-	public getModule(path: string): Promise {
-		return Promise.resolve(this.modules.get(path));
-	}
-
-	public updateContent(module: IModule, newContent: string): boolean {
-		const parsedModule = parseModule(newContent, module.path, this.mapper);
-		if (!parsedModule) {
-			return false;
-		}
-		if (!arrayEquals(parsedModule.dependencyRequests, module.dependencyRequests)) {
-			return false;
-		}
-
-		module.dependencyRequests = parsedModule.dependencyRequests;
-		module.source = parsedModule.source;
-
-		return true;
-	}
-
-	async addModuleAndDependencies(path: string): Promise {
-		if (this.modules.has(path)) {
-			return this.modules.get(path)!;
-		}
-
-		const promise = (async () => {
-			const content = await fsPromise.readFile(path, { encoding: 'utf-8' });
-
-			const parsedModule = parseModule(content, path, this.mapper);
-			if (!parsedModule) {
-				return undefined;
-			}
-
-			const dependencies = (await Promise.all(parsedModule.dependencyRequests.map(async r => {
-				if (r === 'require' || r === 'exports' || r === 'module') {
-					return null;
-				}
-
-				const depPath = this.mapper.resolveRequestToPath(r, path);
-				if (!depPath) {
-					return null;
-				}
-				return await this.addModuleAndDependencies(depPath);
-			}))).filter((d): d is IModule => !!d);
-
-			const module: IModule = {
-				id: this.mapper.getModuleId(path)!,
-				dependencyRequests: parsedModule.dependencyRequests,
-				dependencies,
-				path,
-				source: parsedModule.source,
-			};
-			return module;
-		})();
-
-		this.modules.set(path, promise);
-		return promise;
-	}
+    private readonly modules = new Map>();
+    constructor(private readonly mapper: SimpleModuleIdPathMapper) { }
+    public getModule(path: string): Promise {
+        return Promise.resolve(this.modules.get(path));
+    }
+    public updateContent(module: IModule, newContent: string): boolean {
+        const parsedModule = parseModule(newContent, module.path, this.mapper);
+        if (!parsedModule) {
+            return false;
+        }
+        if (!arrayEquals(parsedModule.dependencyRequests, module.dependencyRequests)) {
+            return false;
+        }
+        module.dependencyRequests = parsedModule.dependencyRequests;
+        module.source = parsedModule.source;
+        return true;
+    }
+    async addModuleAndDependencies(path: string): Promise {
+        if (this.modules.has(path)) {
+            return this.modules.get(path)!;
+        }
+        const promise = (async () => {
+            const content = await fsPromise.readFile(path, { encoding: 'utf-8' });
+            const parsedModule = parseModule(content, path, this.mapper);
+            if (!parsedModule) {
+                return undefined;
+            }
+            const dependencies = (await Promise.all(parsedModule.dependencyRequests.map(async (r) => {
+                if (r === 'require' || r === 'exports' || r === 'module') {
+                    return null;
+                }
+                const depPath = this.mapper.resolveRequestToPath(r, path);
+                if (!depPath) {
+                    return null;
+                }
+                return await this.addModuleAndDependencies(depPath);
+            }))).filter((d): d is IModule => !!d);
+            const module: IModule = {
+                id: this.mapper.getModuleId(path)!,
+                dependencyRequests: parsedModule.dependencyRequests,
+                dependencies,
+                path,
+                source: parsedModule.source,
+            };
+            return module;
+        })();
+        this.modules.set(path, promise);
+        return promise;
+    }
 }
-
 function arrayEquals(a: T[], b: T[]): boolean {
-	if (a.length !== b.length) {
-		return false;
-	}
-	for (let i = 0; i < a.length; i++) {
-		if (a[i] !== b[i]) {
-			return false;
-		}
-	}
-	return true;
+    if (a.length !== b.length) {
+        return false;
+    }
+    for (let i = 0; i < a.length; i++) {
+        if (a[i] !== b[i]) {
+            return false;
+        }
+    }
+    return true;
 }
-
 const encoder = new TextEncoder();
-
-function parseModule(content: string, path: string, mapper: SimpleModuleIdPathMapper): { source: Source; dependencyRequests: string[] } | undefined {
-	const m = content.match(/define\((\[.*?\])/);
-	if (!m) {
-		return undefined;
-	}
-
-	const dependencyRequests = JSON.parse(m[1].replace(/'/g, '"')) as string[];
-
-	const sourceMapHeader = '//# sourceMappingURL=data:application/json;base64,';
-	const idx = content.indexOf(sourceMapHeader);
-
-	let sourceMap: any = null;
-	if (idx !== -1) {
-		const sourceMapJsonStr = Buffer.from(content.substring(idx + sourceMapHeader.length), 'base64').toString('utf-8');
-		sourceMap = JSON.parse(sourceMapJsonStr);
-		content = content.substring(0, idx);
-	}
-
-	content = content.replace('define([', `define("${mapper.getModuleId(path)}", [`);
-
-	const contentBuffer = Buffer.from(encoder.encode(content));
-	const source = new Source(contentBuffer, sourceMap);
-
-	return { dependencyRequests, source };
+function parseModule(content: string, path: string, mapper: SimpleModuleIdPathMapper): {
+    source: Source;
+    dependencyRequests: string[];
+} | undefined {
+    const m = content.match(/define\((\[.*?\])/);
+    if (!m) {
+        return undefined;
+    }
+    const dependencyRequests = JSON.parse(m[1].replace(/'/g, '"')) as string[];
+    const sourceMapHeader = '//# sourceMappingURL=data:application/json;base64,';
+    const idx = content.indexOf(sourceMapHeader);
+    let sourceMap: any = null;
+    if (idx !== -1) {
+        const sourceMapJsonStr = Buffer.from(content.substring(idx + sourceMapHeader.length), 'base64').toString('utf-8');
+        sourceMap = JSON.parse(sourceMapJsonStr);
+        content = content.substring(0, idx);
+    }
+    content = content.replace('define([', `define("${mapper.getModuleId(path)}", [`);
+    const contentBuffer = Buffer.from(encoder.encode(content));
+    const source = new Source(contentBuffer, sourceMap);
+    return { dependencyRequests, source };
 }
-
 class SimpleModuleIdPathMapper {
-	constructor(public readonly rootDir: string) { }
-
-	public getModuleId(path: string): string | null {
-		if (!path.startsWith(this.rootDir) || !path.endsWith('.js')) {
-			return null;
-		}
-		const moduleId = path.substring(this.rootDir.length + 1);
-
-
-		return moduleId.replace(/\\/g, '/').substring(0, moduleId.length - 3);
-	}
-
-	public resolveRequestToPath(request: string, requestingModulePath?: string): string | null {
-		if (request.indexOf('css!') !== -1) {
-			return null;
-		}
-
-		if (request.startsWith('.')) {
-			return path.join(path.dirname(requestingModulePath!), request + '.js');
-		} else {
-			return path.join(this.rootDir, request + '.js');
-		}
-	}
+    constructor(public readonly rootDir: string) { }
+    public getModuleId(path: string): string | null {
+        if (!path.startsWith(this.rootDir) || !path.endsWith('.js')) {
+            return null;
+        }
+        const moduleId = path.substring(this.rootDir.length + 1);
+        return moduleId.replace(/\\/g, '/').substring(0, moduleId.length - 3);
+    }
+    public resolveRequestToPath(request: string, requestingModulePath?: string): string | null {
+        if (request.indexOf('css!') !== -1) {
+            return null;
+        }
+        if (request.startsWith('.')) {
+            return path.join(path.dirname(requestingModulePath!), request + '.js');
+        }
+        else {
+            return path.join(this.rootDir, request + '.js');
+        }
+    }
 }
-
 interface IModule {
-	id: string;
-	dependencyRequests: string[];
-	dependencies: IModule[];
-	path: string;
-	source: Source;
+    id: string;
+    dependencyRequests: string[];
+    dependencies: IModule[];
+    path: string;
+    source: Source;
 }
-
 // #endregion
-
 // #region SourceMapBuilder
-
 // From https://stackoverflow.com/questions/29905373/how-to-create-sourcemaps-for-concatenated-files with modifications
-
 class Source {
-	// Ends with \n
-	public readonly content: Buffer;
-	public readonly sourceMap: SourceMap;
-	public readonly sourceLines: number;
-
-	public readonly sourceMapMappings: Buffer;
-
-
-	constructor(content: Buffer, sourceMap: SourceMap | undefined) {
-		if (!sourceMap) {
-			sourceMap = SourceMapBuilder.emptySourceMap;
-		}
-
-		let sourceLines = countNL(content);
-		if (content.length > 0 && content[content.length - 1] !== 10) {
-			sourceLines++;
-			content = Buffer.concat([content, Buffer.from([10])]);
-		}
-
-		this.content = content;
-		this.sourceMap = sourceMap;
-		this.sourceLines = sourceLines;
-		this.sourceMapMappings = typeof this.sourceMap.mappings === 'string'
-			? Buffer.from(encoder.encode(sourceMap.mappings as string))
-			: this.sourceMap.mappings;
-	}
+    // Ends with \n
+    public readonly content: Buffer;
+    public readonly sourceMap: SourceMap;
+    public readonly sourceLines: number;
+    public readonly sourceMapMappings: Buffer;
+    constructor(content: Buffer, sourceMap: SourceMap | undefined) {
+        if (!sourceMap) {
+            sourceMap = SourceMapBuilder.emptySourceMap;
+        }
+        let sourceLines = countNL(content);
+        if (content.length > 0 && content[content.length - 1] !== 10) {
+            sourceLines++;
+            content = Buffer.concat([content, Buffer.from([10])]);
+        }
+        this.content = content;
+        this.sourceMap = sourceMap;
+        this.sourceLines = sourceLines;
+        this.sourceMapMappings = typeof this.sourceMap.mappings === 'string'
+            ? Buffer.from(encoder.encode(sourceMap.mappings as string))
+            : this.sourceMap.mappings;
+    }
 }
-
 class SourceMapBuilder {
-	public static emptySourceMap: SourceMap = { version: 3, sources: [], mappings: Buffer.alloc(0) };
-
-	private readonly outputBuffer = new DynamicBuffer();
-	private readonly sources: string[] = [];
-	private readonly mappings = new DynamicBuffer();
-	private lastSourceIndex = 0;
-	private lastSourceLine = 0;
-	private lastSourceCol = 0;
-
-	addLine(text: string) {
-		this.outputBuffer.addString(text);
-		this.outputBuffer.addByte(10);
-		this.mappings.addByte(59); // ;
-	}
-
-	addSource(source: Source) {
-		const sourceMap = source.sourceMap;
-		this.outputBuffer.addBuffer(source.content);
-
-		const sourceRemap: number[] = [];
-		for (const v of sourceMap.sources) {
-			let pos = this.sources.indexOf(v);
-			if (pos < 0) {
-				pos = this.sources.length;
-				this.sources.push(v);
-			}
-			sourceRemap.push(pos);
-		}
-		let lastOutputCol = 0;
-
-		const inputMappings = source.sourceMapMappings;
-		let outputLine = 0;
-		let ip = 0;
-		let inOutputCol = 0;
-		let inSourceIndex = 0;
-		let inSourceLine = 0;
-		let inSourceCol = 0;
-		let shift = 0;
-		let value = 0;
-		let valpos = 0;
-		const commit = () => {
-			if (valpos === 0) { return; }
-			this.mappings.addVLQ(inOutputCol - lastOutputCol);
-			lastOutputCol = inOutputCol;
-			if (valpos === 1) {
-				valpos = 0;
-				return;
-			}
-			const outSourceIndex = sourceRemap[inSourceIndex];
-			this.mappings.addVLQ(outSourceIndex - this.lastSourceIndex);
-			this.lastSourceIndex = outSourceIndex;
-			this.mappings.addVLQ(inSourceLine - this.lastSourceLine);
-			this.lastSourceLine = inSourceLine;
-			this.mappings.addVLQ(inSourceCol - this.lastSourceCol);
-			this.lastSourceCol = inSourceCol;
-			valpos = 0;
-		};
-		while (ip < inputMappings.length) {
-			let b = inputMappings[ip++];
-			if (b === 59) { // ;
-				commit();
-				this.mappings.addByte(59);
-				inOutputCol = 0;
-				lastOutputCol = 0;
-				outputLine++;
-			} else if (b === 44) { // ,
-				commit();
-				this.mappings.addByte(44);
-			} else {
-				b = charToInteger[b];
-				if (b === 255) { throw new Error('Invalid sourceMap'); }
-				value += (b & 31) << shift;
-				if (b & 32) {
-					shift += 5;
-				} else {
-					const shouldNegate = value & 1;
-					value >>= 1;
-					if (shouldNegate) { value = -value; }
-					switch (valpos) {
-						case 0: inOutputCol += value; break;
-						case 1: inSourceIndex += value; break;
-						case 2: inSourceLine += value; break;
-						case 3: inSourceCol += value; break;
-					}
-					valpos++;
-					value = shift = 0;
-				}
-			}
-		}
-		commit();
-		while (outputLine < source.sourceLines) {
-			this.mappings.addByte(59);
-			outputLine++;
-		}
-	}
-
-	toContent(): Buffer {
-		return this.outputBuffer.toBuffer();
-	}
-
-	toSourceMap(sourceRoot?: string): SourceMap {
-		return { version: 3, sourceRoot, sources: this.sources, mappings: this.mappings.toBuffer().toString() };
-	}
+    public static emptySourceMap: SourceMap = { version: 3, sources: [], mappings: Buffer.alloc(0) };
+    private readonly outputBuffer = new DynamicBuffer();
+    private readonly sources: string[] = [];
+    private readonly mappings = new DynamicBuffer();
+    private lastSourceIndex = 0;
+    private lastSourceLine = 0;
+    private lastSourceCol = 0;
+    addLine(text: string) {
+        this.outputBuffer.addString(text);
+        this.outputBuffer.addByte(10);
+        this.mappings.addByte(59); // ;
+    }
+    addSource(source: Source) {
+        const sourceMap = source.sourceMap;
+        this.outputBuffer.addBuffer(source.content);
+        const sourceRemap: number[] = [];
+        for (const v of sourceMap.sources) {
+            let pos = this.sources.indexOf(v);
+            if (pos < 0) {
+                pos = this.sources.length;
+                this.sources.push(v);
+            }
+            sourceRemap.push(pos);
+        }
+        let lastOutputCol = 0;
+        const inputMappings = source.sourceMapMappings;
+        let outputLine = 0;
+        let ip = 0;
+        let inOutputCol = 0;
+        let inSourceIndex = 0;
+        let inSourceLine = 0;
+        let inSourceCol = 0;
+        let shift = 0;
+        let value = 0;
+        let valpos = 0;
+        const commit = () => {
+            if (valpos === 0) {
+                return;
+            }
+            this.mappings.addVLQ(inOutputCol - lastOutputCol);
+            lastOutputCol = inOutputCol;
+            if (valpos === 1) {
+                valpos = 0;
+                return;
+            }
+            const outSourceIndex = sourceRemap[inSourceIndex];
+            this.mappings.addVLQ(outSourceIndex - this.lastSourceIndex);
+            this.lastSourceIndex = outSourceIndex;
+            this.mappings.addVLQ(inSourceLine - this.lastSourceLine);
+            this.lastSourceLine = inSourceLine;
+            this.mappings.addVLQ(inSourceCol - this.lastSourceCol);
+            this.lastSourceCol = inSourceCol;
+            valpos = 0;
+        };
+        while (ip < inputMappings.length) {
+            let b = inputMappings[ip++];
+            if (b === 59) { // ;
+                commit();
+                this.mappings.addByte(59);
+                inOutputCol = 0;
+                lastOutputCol = 0;
+                outputLine++;
+            }
+            else if (b === 44) { // ,
+                commit();
+                this.mappings.addByte(44);
+            }
+            else {
+                b = charToInteger[b];
+                if (b === 255) {
+                    throw new Error('Invalid sourceMap');
+                }
+                value += (b & 31) << shift;
+                if (b & 32) {
+                    shift += 5;
+                }
+                else {
+                    const shouldNegate = value & 1;
+                    value >>= 1;
+                    if (shouldNegate) {
+                        value = -value;
+                    }
+                    switch (valpos) {
+                        case 0:
+                            inOutputCol += value;
+                            break;
+                        case 1:
+                            inSourceIndex += value;
+                            break;
+                        case 2:
+                            inSourceLine += value;
+                            break;
+                        case 3:
+                            inSourceCol += value;
+                            break;
+                    }
+                    valpos++;
+                    value = shift = 0;
+                }
+            }
+        }
+        commit();
+        while (outputLine < source.sourceLines) {
+            this.mappings.addByte(59);
+            outputLine++;
+        }
+    }
+    toContent(): Buffer {
+        return this.outputBuffer.toBuffer();
+    }
+    toSourceMap(sourceRoot?: string): SourceMap {
+        return { version: 3, sourceRoot, sources: this.sources, mappings: this.mappings.toBuffer().toString() };
+    }
 }
-
 export interface SourceMap {
-	version: number; // always 3
-	file?: string;
-	sourceRoot?: string;
-	sources: string[];
-	sourcesContent?: string[];
-	names?: string[];
-	mappings: string | Buffer;
+    version: number; // always 3
+    file?: string;
+    sourceRoot?: string;
+    sources: string[];
+    sourcesContent?: string[];
+    names?: string[];
+    mappings: string | Buffer;
 }
-
 const charToInteger = Buffer.alloc(256);
 const integerToChar = Buffer.alloc(64);
-
 charToInteger.fill(255);
-
 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('').forEach((char, i) => {
-	charToInteger[char.charCodeAt(0)] = i;
-	integerToChar[i] = char.charCodeAt(0);
+    charToInteger[char.charCodeAt(0)] = i;
+    integerToChar[i] = char.charCodeAt(0);
 });
-
 class DynamicBuffer {
-	private buffer: Buffer;
-	private size: number;
-
-	constructor() {
-		this.buffer = Buffer.alloc(512);
-		this.size = 0;
-	}
-
-	ensureCapacity(capacity: number) {
-		if (this.buffer.length >= capacity) {
-			return;
-		}
-		const oldBuffer = this.buffer;
-		this.buffer = Buffer.alloc(Math.max(oldBuffer.length * 2, capacity));
-		oldBuffer.copy(this.buffer);
-	}
-
-	addByte(b: number) {
-		this.ensureCapacity(this.size + 1);
-		this.buffer[this.size++] = b;
-	}
-
-	addVLQ(num: number) {
-		let clamped: number;
-
-		if (num < 0) {
-			num = (-num << 1) | 1;
-		} else {
-			num <<= 1;
-		}
-
-		do {
-			clamped = num & 31;
-			num >>= 5;
-
-			if (num > 0) {
-				clamped |= 32;
-			}
-
-			this.addByte(integerToChar[clamped]);
-		} while (num > 0);
-	}
-
-	addString(s: string) {
-		const l = Buffer.byteLength(s);
-		this.ensureCapacity(this.size + l);
-		this.buffer.write(s, this.size);
-		this.size += l;
-	}
-
-	addBuffer(b: Buffer) {
-		this.ensureCapacity(this.size + b.length);
-		b.copy(this.buffer, this.size);
-		this.size += b.length;
-	}
-
-	toBuffer(): Buffer {
-		return this.buffer.slice(0, this.size);
-	}
+    private buffer: Buffer;
+    private size: number;
+    constructor() {
+        this.buffer = Buffer.alloc(512);
+        this.size = 0;
+    }
+    ensureCapacity(capacity: number) {
+        if (this.buffer.length >= capacity) {
+            return;
+        }
+        const oldBuffer = this.buffer;
+        this.buffer = Buffer.alloc(Math.max(oldBuffer.length * 2, capacity));
+        oldBuffer.copy(this.buffer);
+    }
+    addByte(b: number) {
+        this.ensureCapacity(this.size + 1);
+        this.buffer[this.size++] = b;
+    }
+    addVLQ(num: number) {
+        let clamped: number;
+        if (num < 0) {
+            num = (-num << 1) | 1;
+        }
+        else {
+            num <<= 1;
+        }
+        do {
+            clamped = num & 31;
+            num >>= 5;
+            if (num > 0) {
+                clamped |= 32;
+            }
+            this.addByte(integerToChar[clamped]);
+        } while (num > 0);
+    }
+    addString(s: string) {
+        const l = Buffer.byteLength(s);
+        this.ensureCapacity(this.size + l);
+        this.buffer.write(s, this.size);
+        this.size += l;
+    }
+    addBuffer(b: Buffer) {
+        this.ensureCapacity(this.size + b.length);
+        b.copy(this.buffer, this.size);
+        this.size += b.length;
+    }
+    toBuffer(): Buffer {
+        return this.buffer.slice(0, this.size);
+    }
 }
-
 function countNL(b: Buffer): number {
-	let res = 0;
-	for (let i = 0; i < b.length; i++) {
-		if (b[i] === 10) { res++; }
-	}
-	return res;
+    let res = 0;
+    for (let i = 0; i < b.length; i++) {
+        if (b[i] === 10) {
+            res++;
+        }
+    }
+    return res;
 }
-
 // #endregion