-
Notifications
You must be signed in to change notification settings - Fork 685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure that comments are stripped before reading tasks.json file #1075
Changes from 3 commits
77d247f
4b6e2b9
1a5bf68
6cceda1
603dc03
937f2d9
b6288ae
50a28ec
5bb02be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,4 +111,4 @@ export function deleteInstallFile(type: InstallFileType): Promise<void> { | |
resolve(); | ||
}); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
const enum CharCode { | ||
lineFeed = 0x0a, | ||
carriageReturn = 0x0d, | ||
lineSeparator = 0x2028, | ||
paragraphSeparator = 0x2029, | ||
|
||
asterisk = 0x2a, | ||
backSlash = 0x5c, | ||
doubleQuote = 0x22, | ||
slash = 0x2f, | ||
} | ||
|
||
function isLineBreak(code: number) { | ||
return code === CharCode.lineFeed | ||
|| code === CharCode.carriageReturn | ||
|| code === CharCode.lineSeparator | ||
|| code === CharCode.paragraphSeparator; | ||
} | ||
|
||
function stripComments(text: string) { | ||
|
||
let parts: string[] = []; | ||
let partStart = 0; | ||
|
||
let index = 0; | ||
let length = text.length; | ||
|
||
function next(): number | undefined { | ||
const result = peek(); | ||
index++; | ||
return result; | ||
} | ||
|
||
function peek(offset: number = 0): number | undefined { | ||
if ((index + offset) < length) { | ||
return text.charCodeAt(index + offset); | ||
} | ||
else { | ||
return undefined; | ||
} | ||
} | ||
|
||
function scanString() { | ||
while (true) { | ||
if (index >= length) { // string ended unexpectedly | ||
break; | ||
} | ||
|
||
let 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; | ||
} | ||
} | ||
} | ||
|
||
while (true) { | ||
let code = next(); | ||
|
||
switch (code) { | ||
// 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; | ||
} | ||
|
||
if (index >= length && index > partStart) { | ||
parts.push(text.substring(partStart, length)); | ||
break; | ||
} | ||
} | ||
|
||
return parts.join(''); | ||
} | ||
|
||
export function tolerantParse(text: string) { | ||
text = stripComments(text); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we ignore the UTF8-bom while we are at it too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should. Also, I'd like to add tolerance around trailing commas as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. Added code to be tolerant against both byte order marks and trailing comments in object member or array element lists. |
||
return JSON.parse(text); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { should } from 'chai'; | ||
import { tolerantParse } from '../src/json'; | ||
|
||
suite("JSON", () => { | ||
suiteSetup(() => should()); | ||
|
||
test("no comments", () => { | ||
const text = | ||
`{ | ||
"hello": "world" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(text); | ||
}); | ||
|
||
test("no comments (minified)", () => { | ||
const text = | ||
`{"hello":"world","from":"json"}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world", | ||
"from": "json" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("single-line comment before JSON", () => { | ||
const text = | ||
`// comment | ||
{ | ||
"hello": "world\\"" // comment | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world\\"" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("single-line comment on separate line", () => { | ||
const text = | ||
`{ | ||
// comment | ||
"hello": "world" | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("single-line comment at end of line", () => { | ||
const text = | ||
`{ | ||
"hello": "world" // comment | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("single-line comment at end of text", () => { | ||
const text = | ||
`{ | ||
"hello": "world" | ||
} // comment`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("ignore single-line comment inside string", () => { | ||
const text = | ||
`{ | ||
"hello": "world // comment" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(text); | ||
}); | ||
|
||
test("single-line comment after string with escaped double quote", () => { | ||
const text = | ||
`{ | ||
"hello": "world\\"" // comment | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world\\"" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("multi-line comment at start of text", () => { | ||
const text = | ||
`/**/{ | ||
"hello": "world" | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("comment out key/value pair", () => { | ||
const text = | ||
`{ | ||
/*"hello": "world"*/ | ||
"from": "json" | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"from": "json" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("multi-line comment at end of text", () => { | ||
const text = | ||
`{ | ||
"hello": "world" | ||
}/**/`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "world" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
|
||
test("ignore multi-line comment inside string", () => { | ||
const text = | ||
`{ | ||
"hello": "wo/**/rld" | ||
}`; | ||
|
||
const expected = | ||
`{ | ||
"hello": "wo/**/rld" | ||
}`; | ||
|
||
let json = tolerantParse(text); | ||
let result = JSON.stringify(json, null, 4); | ||
|
||
result.should.equal(expected); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a try/catch around it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should, but it didn't before. I'll add one.