Silent warning when argv.json has syntax errors (fix #212671) (#215551)

This commit is contained in:
Benjamin Pasero 2024-06-19 03:55:06 +02:00 committed by GitHub
parent 23c85ae40f
commit c2e20cb17f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 122 additions and 64 deletions

View File

@ -21,7 +21,7 @@ const os = require('os');
const bootstrap = require('./bootstrap');
const bootstrapNode = require('./bootstrap-node');
const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath');
const { stripComments } = require('./vs/base/common/stripComments');
const { parse } = require('./vs/base/common/jsonc');
const { getUNCHost, addUNCHostToAllowlist } = require('./vs/base/node/unc');
/** @type {Partial<IProductConfiguration>} */
// @ts-ignore
@ -316,7 +316,7 @@ function readArgvConfigSync() {
const argvConfigPath = getArgvConfigPath();
let argvConfig;
try {
argvConfig = JSON.parse(stripComments(fs.readFileSync(argvConfigPath).toString()));
argvConfig = parse(fs.readFileSync(argvConfigPath).toString());
} catch (error) {
if (error && error.code === 'ENOENT') {
createDefaultArgvConfigSync(argvConfigPath);

View File

@ -1308,40 +1308,6 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
return true;
}
/**
* Takes JSON with JavaScript-style comments and remove
* them. Optionally replaces every none-newline character
* of comments with a replaceCharacter
*/
export function stripComments(text: string, replaceCh?: string): string {
const _scanner = createScanner(text);
const parts: string[] = [];
let kind: SyntaxKind;
let offset = 0;
let pos: number;
do {
pos = _scanner.getPosition();
kind = _scanner.scan();
switch (kind) {
case SyntaxKind.LineCommentTrivia:
case SyntaxKind.BlockCommentTrivia:
case SyntaxKind.EOF:
if (offset !== pos) {
parts.push(text.substring(offset, pos));
}
if (replaceCh !== undefined) {
parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
}
offset = _scanner.getPosition();
break;
}
} while (kind !== SyntaxKind.EOF);
return parts.join('');
}
export function getNodeType(value: any): NodeType {
switch (typeof value) {
case 'boolean': return 'boolean';

View File

@ -3,11 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* A drop-in replacement for JSON.parse that can parse
* JSON with comments and trailing commas.
*
* @param content the content to strip comments from
* @returns the parsed content as JSON
*/
export function parse(content: string): any;
/**
* Strips single and multi line JavaScript comments from JSON
* content. Ignores characters in strings BUT doesn't support
* string continuation across multiple lines since it is not
* supported in JSON.
*
* @param content the content to strip comments from
* @returns the content without comments
*/

View File

@ -3,9 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
/// <reference path="../../../typings/require.d.ts" />
//@ts-check
'use strict';
(function () {
function factory(path, os, productName, cwd) {
@ -17,7 +18,6 @@
const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g;
/**
*
* @param {string} content
* @returns {string}
*/
@ -46,12 +46,27 @@
}
});
}
/**
* @param {string} content
* @returns {any}
*/
function parse(content) {
const commentsStripped = stripComments(content);
try {
return JSON.parse(commentsStripped);
} catch (error) {
const trailingCommasStriped = commentsStripped.replace(/,\s*([}\]])/g, '$1');
return JSON.parse(trailingCommasStriped);
}
}
return {
stripComments
stripComments,
parse
};
}
if (typeof define === 'function') {
// amd
define([], function () { return factory(); });
@ -59,6 +74,6 @@
// commonjs
module.exports = factory();
} else {
console.trace('strip comments defined in UNKNOWN context (neither requirejs or commonjs)');
console.trace('jsonc defined in UNKNOWN context (neither requirejs or commonjs)');
}
})();

View File

@ -4,12 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { stripComments } from 'vs/base/common/stripComments';
import { parse, stripComments } from 'vs/base/common/jsonc';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
// We use this regular expression quite often to strip comments in JSON files.
suite('Strip Comments', () => {
suite('JSON Parse', () => {
ensureNoDisposablesAreLeakedInTestSuite();
test('Line comment', () => {
@ -23,7 +21,7 @@ suite('Strip Comments', () => {
" \"prop\": 10 ",
"}",
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Line comment - EOF', () => {
const content: string = [
@ -36,7 +34,7 @@ suite('Strip Comments', () => {
"}",
""
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Line comment - \\r\\n', () => {
const content: string = [
@ -49,7 +47,7 @@ suite('Strip Comments', () => {
" \"prop\": 10 ",
"}",
].join('\r\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Line comment - EOF - \\r\\n', () => {
const content: string = [
@ -62,7 +60,7 @@ suite('Strip Comments', () => {
"}",
""
].join('\r\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Block comment - single line', () => {
const content: string = [
@ -75,7 +73,7 @@ suite('Strip Comments', () => {
" \"prop\": 10",
"}",
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Block comment - multi line', () => {
const content: string = [
@ -92,7 +90,7 @@ suite('Strip Comments', () => {
" \"prop\": 10",
"}",
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Block comment - shortest match', () => {
const content = "/* abc */ */";
@ -110,7 +108,7 @@ suite('Strip Comments', () => {
" \"/* */\": 10",
"}"
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('No strings - single quote', () => {
const content: string = [
@ -136,7 +134,7 @@ suite('Strip Comments', () => {
` "a": 10`,
"}"
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Trailing comma in array', () => {
const content: string = [
@ -145,6 +143,52 @@ suite('Strip Comments', () => {
const expected: string = [
`[ "a", "b", "c" ]`
].join('\n');
assert.strictEqual(stripComments(content), expected);
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Trailing comma', () => {
const content: string = [
"{",
" \"propA\": 10, // a comment",
" \"propB\": false, // a trailing comma",
"}",
].join('\n');
const expected = [
"{",
" \"propA\": 10,",
" \"propB\": false",
"}",
].join('\n');
assert.deepEqual(parse(content), JSON.parse(expected));
});
test('Trailing comma - EOF', () => {
const content = `
// This configuration file allows you to pass permanent command line arguments to VS Code.
// Only a subset of arguments is currently supported to reduce the likelihood of breaking
// the installation.
//
// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT
//
// NOTE: Changing this file requires a restart of VS Code.
{
// Use software rendering instead of hardware accelerated rendering.
// This can help in cases where you see rendering issues in VS Code.
// "disable-hardware-acceleration": true,
// Allows to disable crash reporting.
// Should restart the app if the value is changed.
"enable-crash-reporter": true,
// Unique id used for correlating crash reports sent from this instance.
// Do not edit this value.
"crash-reporter-id": "aaaaab31-7453-4506-97d0-93411b2c21c7",
"locale": "en",
// "log-level": "trace"
}
`;
assert.deepEqual(parse(content), {
"enable-crash-reporter": true,
"crash-reporter-id": "aaaaab31-7453-4506-97d0-93411b2c21c7",
"locale": "en"
});
});
});

View File

@ -12,7 +12,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { Event } from 'vs/base/common/event';
import { stripComments } from 'vs/base/common/json';
import { parse } from 'vs/base/common/jsonc';
import { getPathLabel } from 'vs/base/common/labels';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network';
@ -1394,10 +1394,10 @@ export class CodeApplication extends Disposable {
// Crash reporter
this.updateCrashReporterEnablement();
// macOS: rosetta translation warning
if (isMacintosh && app.runningUnderARM64Translation) {
this.windowsMainService?.sendToFocused('vscode:showTranslatedBuildWarning');
}
}
private async installMutex(): Promise<void> {
@ -1437,7 +1437,7 @@ export class CodeApplication extends Disposable {
try {
const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource);
const argvString = argvContent.value.toString();
const argvJSON = JSON.parse(stripComments(argvString));
const argvJSON = parse(argvString);
const telemetryLevel = getTelemetryLevel(this.configurationService);
const enableCrashReporter = telemetryLevel >= TelemetryLevel.CRASH;
@ -1468,6 +1468,9 @@ export class CodeApplication extends Disposable {
}
} catch (error) {
this.logService.error(error);
// Inform the user via notification
this.windowsMainService?.sendToFocused('vscode:showArgvParseWarning');
}
}
}

View File

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { isLinux } from 'vs/base/common/platform';
import { stripComments } from 'vs/base/common/stripComments';
import { parse } from 'vs/base/common/jsonc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { Registry } from 'vs/platform/registry/common/platform';
@ -34,7 +34,7 @@ class EncryptionContribution implements IWorkbenchContribution {
}
try {
const content = await this.fileService.readFile(this.environmentService.argvResource);
const argv = JSON.parse(stripComments(content.value.toString()));
const argv = parse(content.value.toString());
if (argv['password-store'] === 'gnome' || argv['password-store'] === 'gnome-keyring') {
this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['password-store'], value: 'gnome-libsecret' }], true);
}

View File

@ -204,7 +204,10 @@ export class NativeWindow extends BaseWindow {
[{
label: localize('restart', "Restart"),
run: () => this.nativeHostService.relaunch()
}]
}],
{
priority: NotificationPriority.URGENT
}
);
});
@ -248,7 +251,7 @@ export class NativeWindow extends BaseWindow {
);
});
ipcRenderer.on('vscode:showTranslatedBuildWarning', (event: unknown, message: string) => {
ipcRenderer.on('vscode:showTranslatedBuildWarning', () => {
this.notificationService.prompt(
Severity.Warning,
localize("runningTranslated", "You are running an emulated version of {0}. For better performance download the native arm64 version of {0} build for your machine.", this.productService.nameLong),
@ -260,7 +263,24 @@ export class NativeWindow extends BaseWindow {
const insidersURL = 'https://code.visualstudio.com/docs/?dv=osx&build=insiders';
this.openerService.open(quality === 'stable' ? stableURL : insidersURL);
}
}]
}],
{
priority: NotificationPriority.URGENT
}
);
});
ipcRenderer.on('vscode:showArgvParseWarning', (event: unknown, message: string) => {
this.notificationService.prompt(
Severity.Warning,
localize("showArgvParseWarning", "The runtime arguments file 'argv.json' contains errors. Please correct them and restart."),
[{
label: localize('showArgvParseWarningAction', "Open File"),
run: () => this.editorService.openEditor({ resource: this.nativeEnvironmentService.argvResource })
}],
{
priority: NotificationPriority.URGENT
}
);
});

View File

@ -16,7 +16,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/
import { localize } from 'vs/nls';
import { toAction } from 'vs/base/common/actions';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { stripComments } from 'vs/base/common/stripComments';
import { parse } from 'vs/base/common/jsonc';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
@ -57,7 +57,7 @@ class NativeLocaleService implements ILocaleService {
// This is the same logic that we do where argv.json is parsed so mirror that:
// https://github.com/microsoft/vscode/blob/32d40cf44e893e87ac33ac4f08de1e5f7fe077fc/src/main.js#L238-L246
JSON.parse(stripComments(content.value));
parse(content.value);
} catch (error) {
this.notificationService.notify({
severity: Severity.Error,