Skip to content

Commit

Permalink
Switch to jsonc-parser for tolerant json parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
dibarbet committed Sep 6, 2023
1 parent 4c6d5f2 commit 92047f7
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 259 deletions.
2 changes: 1 addition & 1 deletion l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"For more information about the 'console' field, see {0}": "For more information about the 'console' field, see {0}",
"WARNING": "WARNING",
"The C# extension was unable to automatically decode projects in the current workspace to create a runnable launch.json file. A template launch.json file has been created as a placeholder.\n\nIf the server is currently unable to load your project, you can attempt to resolve this by restoring any missing project dependencies (example: run 'dotnet restore') and by fixing any reported errors from building the projects in your workspace.\nIf this allows the server to now load your project then --\n * Delete this file\n * Open the Visual Studio Code command palette (View->Command Palette)\n * run the command: '.NET: Generate Assets for Build and Debug'.\n\nIf your project requires a more complex launch configuration, you may wish to delete this configuration and pick a different template using the 'Add Configuration...' button at the bottom of this file.": "The C# extension was unable to automatically decode projects in the current workspace to create a runnable launch.json file. A template launch.json file has been created as a placeholder.\n\nIf the server is currently unable to load your project, you can attempt to resolve this by restoring any missing project dependencies (example: run 'dotnet restore') and by fixing any reported errors from building the projects in your workspace.\nIf this allows the server to now load your project then --\n * Delete this file\n * Open the Visual Studio Code command palette (View->Command Palette)\n * run the command: '.NET: Generate Assets for Build and Debug'.\n\nIf your project requires a more complex launch configuration, you may wish to delete this configuration and pick a different template using the 'Add Configuration...' button at the bottom of this file.",
"Failed to parse tasks.json file": "Failed to parse tasks.json file",
"Failed to parse tasks.json file: {0}": "Failed to parse tasks.json file: {0}",
"Don't Ask Again": "Don't Ask Again",
"Required assets to build and debug are missing from '{0}'. Add them?": "Required assets to build and debug are missing from '{0}'. Add them?",
"Cancel": "Cancel",
Expand Down
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "csharp",
"publisher": "ms-dotnettools",
"version": "2.0.0-placeholder",
"version": "0.0.0-placeholder",
"description": "Base language support for C#",
"displayName": "C#",
"author": "Microsoft Corporation",
Expand Down
208 changes: 2 additions & 206 deletions src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,212 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

const enum CharCode {
asterisk = 0x2a, // *
backSlash = 0x5c, // \
closeBrace = 0x7d, // }
closeBracket = 0x5d, // ]
comma = 0x2c, // ,
doubleQuote = 0x22, // "
slash = 0x2f, // /

byteOrderMark = 0xfeff,

// line terminator characters (see https://en.wikipedia.org/wiki/Newline#Unicode)
carriageReturn = 0x0d,
formFeed = 0x0c,
lineFeed = 0x0a,
lineSeparator = 0x2028,
nextLine = 0x85,
paragraphSeparator = 0x2029,
verticalTab = 0x0b,

// whitespace characters (see https://en.wikipedia.org/wiki/Whitespace_character#Unicode)
tab = 0x09,
space = 0x20,
nonBreakingSpace = 0xa0,
ogham = 0x1680,
enQuad = 0x2000,
emQuad = 0x2001,
enSpace = 0x2002,
emSpace = 0x2003,
threePerEmSpace = 0x2004,
fourPerEmSpace = 0x2005,
sixPerEmSpace = 0x2006,
figureSpace = 0x2007,
punctuationSpace = 0x2008,
thinSpace = 0x2009,
hairSpace = 0x200a,
zeroWidthSpace = 0x200b,
narrowNoBreakSpace = 0x202f,
mathematicalSpace = 0x205f,
ideographicSpace = 0x3000,
}

function isLineBreak(code: number) {
return (
code === CharCode.lineFeed ||
code === CharCode.carriageReturn ||
code === CharCode.verticalTab ||
code === CharCode.formFeed ||
code === CharCode.lineSeparator ||
code === CharCode.paragraphSeparator
);
}

function isWhitespace(code: number) {
return (
code === CharCode.space ||
code === CharCode.tab ||
code === CharCode.lineFeed ||
code === CharCode.verticalTab ||
code === CharCode.formFeed ||
code === CharCode.carriageReturn ||
code === CharCode.nextLine ||
code === CharCode.nonBreakingSpace ||
code === CharCode.ogham ||
(code >= CharCode.enQuad && code <= CharCode.zeroWidthSpace) ||
code === CharCode.lineSeparator ||
code === CharCode.paragraphSeparator ||
code === CharCode.narrowNoBreakSpace ||
code === CharCode.mathematicalSpace ||
code === CharCode.ideographicSpace ||
code === CharCode.byteOrderMark
);
}

function cleanJsonText(text: string) {
const parts: string[] = [];
let partStart = 0;

let index = 0;
const length = text.length;

function next(): number {
const result = peek();
index++;
return result;
}

function peek(offset = 0): number {
return text.charCodeAt(index + offset);
}

function peekPastWhitespace(): number {
let pos = index;
let code = NaN;

do {
code = text.charCodeAt(pos);
pos++;
} while (isWhitespace(code));

return code;
}

function scanString() {
while (index < length) {
const code = next();

if (code === CharCode.doubleQuote) {
// End of string. We're done
break;
}

if (code === CharCode.backSlash) {
// Skip escaped character. We don't care about verifying the escape sequence.
// We just don't want to accidentally scan an escaped double-quote as the end of the string.
index++;
}

if (isLineBreak(code)) {
// string ended unexpectedly
break;
}
}
}

// eslint-disable-next-line no-constant-condition
while (true) {
const code = next();

switch (code) {
// byte-order mark
case CharCode.byteOrderMark:
// We just skip the byte-order mark
parts.push(text.substring(partStart, index - 1));
partStart = index;
break;

// strings
case CharCode.doubleQuote:
scanString();
break;

// comments
case CharCode.slash:
// Single-line comment
if (peek() === CharCode.slash) {
// Be careful not to include the first slash in the text part.
parts.push(text.substring(partStart, index - 1));

// Start after the second slash and scan until a line-break character is encountered.
index++;
while (index < length) {
if (isLineBreak(peek())) {
break;
}

index++;
}

partStart = index;
}

// Multi-line comment
if (peek() === CharCode.asterisk) {
// Be careful not to include the first slash in the text part.
parts.push(text.substring(partStart, index - 1));

// Start after the asterisk and scan until a */ is encountered.
index++;
while (index < length) {
if (peek() === CharCode.asterisk && peek(1) === CharCode.slash) {
index += 2;
break;
}

index++;
}

partStart = index;
}

break;

case CharCode.comma: {
// Ignore trailing commas in object member lists and array element lists
const nextCode = peekPastWhitespace();
if (nextCode === CharCode.closeBrace || nextCode === CharCode.closeBracket) {
parts.push(text.substring(partStart, index - 1));
partStart = index;
}

break;
}
default:
}

if (index >= length && index > partStart) {
parts.push(text.substring(partStart, length));
break;
}
}

return parts.join('');
}
import { parse } from 'jsonc-parser';

export function tolerantParse(text: string) {
text = cleanJsonText(text);
return JSON.parse(text);
return parse(text);
}
3 changes: 2 additions & 1 deletion src/shared/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,8 @@ export async function getBuildOperations(generator: AssetGenerator): Promise<Ass
try {
tasksConfiguration = tolerantParse(text);
} catch (error) {
vscode.window.showErrorMessage(vscode.l10n.t('Failed to parse tasks.json file'));
const message = error instanceof Error ? error.message : `${error}`;
vscode.window.showErrorMessage(vscode.l10n.t('Failed to parse tasks.json file: {0}', message));
return resolve({ updateTasksJson: false });
}

Expand Down
Loading

0 comments on commit 92047f7

Please sign in to comment.