Repository: microsoft/vscode (TypeScript)
Date: 2023-02-10 15:41:37+00:00
Task type: Bug Fix

Issue:
Cannot install extensions if extensions.json is empty
Type: <b>Bug</b>

Hi,
i just opened VS Code (after update) and all my extensions are gone and i can not install any from marketplace.  Manual installation is not working as well.
I am also getting following error in "Output"

   at Object.factory (vscode-file://vscode-app/c:/Users/me/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/sharedProcess/sharedProcessMain.js:87:91783)
2023-02-10 11:36:00.684 [error] SyntaxError: Unexpected end of JSON input

![vscode-error](https://user-images.githubusercontent.com/34610021/218075991-e023d290-9f9e-4fe1-80d8-27b95865d13d.png)


VS Code version: Code 1.75.1 (441438abd1ac652551dbe4d408dfcec8a499b8bf, 2023-02-08T21:32:34.589Z)
OS version: Windows_NT x64 10.0.19044
Modes:
Sandboxed: No

<details>
<summary>System Info</summary>

|Item|Value|
|---|---|
|CPUs|AMD Ryzen 7 5800X 8-Core Processor              (16 x 3793)|
|GPU Status|2d_canvas: enabled<br>canvas_oop_rasterization: disabled_off<br>direct_rendering_display_compositor: disabled_off_ok<br>gpu_compositing: enabled<br>multiple_raster_threads: enabled_on<br>opengl: enabled_on<br>rasterization: enabled<br>raw_draw: disabled_off_ok<br>skia_renderer: enabled_on<br>video_decode: enabled<br>video_encode: enabled<br>vulkan: disabled_off<br>webgl: enabled<br>webgl2: enabled<br>webgpu: disabled_off|
|Load (avg)|undefined|
|Memory (System)|31.92GB (22.03GB free)|
|Process Argv|--crash-reporter-id b2663a51-66b7-4260-af9a-667f8e62f823|
|Screen Reader|no|
|VM|0%|
</details>Extensions: none<details>
<summary>A/B Experiments</summary>

```
vsliv368:30146709
vsreu685:30147344
python383:30185418
vspor879:30202332
vspor708:30202333
vspor363:30204092
vswsl492cf:30256860
vslsvsres303:30308271
pythonvspyl392:30443607
vserr242cf:30382550
pythontb:30283811
vsjup518:30340749
pythonptprofiler:30281270
vshan820:30294714
vstes263cf:30335440
vscoreces:30445986
pythondataviewer:30285071
vscod805cf:30301675
binariesv615:30325510
bridge0708:30335490
bridge0723:30353136
cmake_vspar411:30581797
vsaa593cf:30376535
pythonvs932:30410667
cppdebug:30492333
vscaac:30438847
vsclangdc:30486549
c4g48928:30535728
dsvsc012:30540252
azure-dev_surveyone:30548225
pyindex848cf:30577861
nodejswelcome1cf:30587006
2e4cg342:30602488
89544117:30613380
pythonsymbol12:30657548
2i9eh265:30646982

```

</details>

<!-- generated by issue reporter -->


Issue discussion:
Possible duplicate of https://github.com/microsoft/vscode/issues/173684

Can you please share following log while you reproduce the issue?

- F1 > Open View... > Shared

Can you also please share the content in following file?

- F1 > Open Extensions Folder > `extensions.json`

1) 
2023-02-10 14:34:41.015 [error] SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at Object.factory (vscode-file://vscode-app/c:/Users/ghost_000/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-browser/sharedProcess/sharedProcessMain.js:88:93520)
2023-02-10 14:34:41.146 [info] Starting worker process with pid 26276 (type: fileWatcher, window: 1).


2) extensions.json is empty
Ok, Please delete `extensions.json` file to recover.

Solution patch:
diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts
--- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts
+++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts
@@ -224,7 +224,7 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable
 			let storedProfileExtensions: IStoredProfileExtension[] | undefined;
 			try {
 				const content = await this.fileService.readFile(file);
-				storedProfileExtensions = JSON.parse(content.value.toString());
+				storedProfileExtensions = JSON.parse(content.value.toString().trim() || '[]');
 			} catch (error) {
 				if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
 					throw error;
diff --git a/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts
--- a/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts
+++ b/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts
@@ -8,7 +8,7 @@ import * as platform from 'vs/base/common/platform';
 import { URI } from 'vs/base/common/uri';
 import { IExtensionDescription, ExtensionType } from 'vs/platform/extensions/common/extensions';
 import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil';
-import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService';
+import { IExtensionsScannerService, IScannedExtension, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService';
 import { ILogService } from 'vs/platform/log/common/log';
 import Severity from 'vs/base/common/severity';
 import { localize } from 'vs/nls';
@@ -16,6 +16,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
 import { IHostService } from 'vs/workbench/services/host/browser/host';
 import { timeout } from 'vs/base/common/async';
 import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { getErrorMessage } from 'vs/base/common/errors';
 
 export class CachedExtensionScanner {
 
@@ -53,26 +54,55 @@ export class CachedExtensionScanner {
 	private async _scanInstalledExtensions(): Promise<IExtensionDescription[]> {
 		try {
 			const language = platform.language;
-			const [scannedSystemExtensions, scannedUserExtensions] = await Promise.all([
+			const result = await Promise.allSettled([
 				this._extensionsScannerService.scanSystemExtensions({ language, useCache: true, checkControlFile: true }),
 				this._extensionsScannerService.scanUserExtensions({ language, profileLocation: this._userDataProfileService.currentProfile.extensionsResource, useCache: true })]);
-			const scannedDevelopedExtensions = await this._extensionsScannerService.scanExtensionsUnderDevelopment({ language }, [...scannedSystemExtensions, ...scannedUserExtensions]);
+
+			let scannedSystemExtensions: IScannedExtension[] = [],
+				scannedUserExtensions: IScannedExtension[] = [],
+				scannedDevelopedExtensions: IScannedExtension[] = [],
+				hasErrors = false;
+
+			if (result[0].status === 'fulfilled') {
+				scannedSystemExtensions = result[0].value;
+			} else {
+				hasErrors = true;
+				this._logService.error(`Error scanning system extensions:`, getErrorMessage(result[0].reason));
+			}
+
+			if (result[1].status === 'fulfilled') {
+				scannedUserExtensions = result[1].value;
+			} else {
+				hasErrors = true;
+				this._logService.error(`Error scanning user extensions:`, getErrorMessage(result[1].reason));
+			}
+
+			try {
+				scannedDevelopedExtensions = await this._extensionsScannerService.scanExtensionsUnderDevelopment({ language }, [...scannedSystemExtensions, ...scannedUserExtensions]);
+			} catch (error) {
+				this._logService.error(error);
+			}
+
 			const system = scannedSystemExtensions.map(e => toExtensionDescription(e, false));
 			const user = scannedUserExtensions.map(e => toExtensionDescription(e, false));
 			const development = scannedDevelopedExtensions.map(e => toExtensionDescription(e, true));
 			const r = dedupExtensions(system, user, development, this._logService);
-			const disposable = this._extensionsScannerService.onDidChangeCache(() => {
-				disposable.dispose();
-				this._notificationService.prompt(
-					Severity.Error,
-					localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."),
-					[{
-						label: localize('reloadWindow', "Reload Window"),
-						run: () => this._hostService.reload()
-					}]
-				);
-			});
-			timeout(5000).then(() => disposable.dispose());
+
+			if (!hasErrors) {
+				const disposable = this._extensionsScannerService.onDidChangeCache(() => {
+					disposable.dispose();
+					this._notificationService.prompt(
+						Severity.Error,
+						localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."),
+						[{
+							label: localize('reloadWindow', "Reload Window"),
+							run: () => this._hostService.reload()
+						}]
+					);
+				});
+				timeout(5000).then(() => disposable.dispose());
+			}
+
 			return r;
 		} catch (err) {
 			this._logService.error(`Error scanning installed extensions:`);


Test patch:
diff --git a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts
--- a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts
+++ b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts
@@ -403,6 +403,26 @@ suite('ExtensionsProfileScannerService', () => {
 		} catch (error) { /*expected*/ }
 	});
 
+	test('read extension when manifest is empty', async () => {
+		const extensionsManifest = joinPath(extensionsLocation, 'extensions.json');
+		await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(''));
+
+		const testObject = instantiationService.createInstance(TestObject, extensionsLocation);
+		const actual = await testObject.scanProfileExtensions(extensionsManifest);
+		assert.deepStrictEqual(actual, []);
+	});
+
+	test('read extension when manifest has empty lines and spaces', async () => {
+		const extensionsManifest = joinPath(extensionsLocation, 'extensions.json');
+		await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString(`   
+
+
+		`));
+		const testObject = instantiationService.createInstance(TestObject, extensionsLocation);
+		const actual = await testObject.scanProfileExtensions(extensionsManifest);
+		assert.deepStrictEqual(actual, []);
+	});
+
 	function aExtension(id: string, location: URI, e?: Partial<IExtension>): IExtension {
 		return {
 			identifier: { id },