Fixes collections with JSON comments failing in the CLI with `SerializationException` while working fine in the app, where comments are stripped before sending requests, but the CLI was sending them as-is, breaking APIs like AWS Cognito that expect valid JSON.
108 lines
3 KiB
TypeScript
108 lines
3 KiB
TypeScript
import { Node, parseTree, stripComments as stripComments_ } from "jsonc-parser"
|
|
import jsoncParse from "~/helpers/jsoncParse"
|
|
import { convertIndexToLineCh } from "../utils"
|
|
import { LinterDefinition, LinterResult } from "./linter"
|
|
|
|
const linter: LinterDefinition = (text) => {
|
|
try {
|
|
jsoncParse(text)
|
|
return Promise.resolve([])
|
|
} catch (e: any) {
|
|
return Promise.resolve([
|
|
<LinterResult>{
|
|
from: convertIndexToLineCh(text, e.start),
|
|
to: convertIndexToLineCh(text, e.end),
|
|
message: e.message,
|
|
severity: "error",
|
|
},
|
|
])
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An internal error that is thrown when an invalid JSONC node configuration
|
|
* is encountered
|
|
*/
|
|
class InvalidJSONCNodeError extends Error {
|
|
constructor() {
|
|
super()
|
|
this.message = "Invalid JSONC node"
|
|
}
|
|
}
|
|
|
|
// NOTE: If we choose to export this function, do refactor it to return a result discriminated union instead of throwing
|
|
/**
|
|
* @throws {InvalidJSONCNodeError} if the node is in an invalid configuration
|
|
* @returns The JSON string without comments and trailing commas
|
|
*/
|
|
function convertNodeToJSON(node: Node): string {
|
|
switch (node.type) {
|
|
case "string":
|
|
return JSON.stringify(node.value)
|
|
case "null":
|
|
return "null"
|
|
case "array":
|
|
if (!node.children) {
|
|
throw new InvalidJSONCNodeError()
|
|
}
|
|
|
|
return `[${node.children
|
|
.map((child) => convertNodeToJSON(child))
|
|
.join(",")}]`
|
|
case "number":
|
|
return JSON.stringify(node.value)
|
|
case "boolean":
|
|
return JSON.stringify(node.value)
|
|
case "object":
|
|
if (!node.children) {
|
|
throw new InvalidJSONCNodeError()
|
|
}
|
|
|
|
return `{${node.children
|
|
.map((child) => convertNodeToJSON(child))
|
|
.join(",")}}`
|
|
case "property":
|
|
if (!node.children || node.children.length !== 2) {
|
|
throw new InvalidJSONCNodeError()
|
|
}
|
|
|
|
const [keyNode, valueNode] = node.children
|
|
|
|
// Use keyNode.value instead of keyNode to avoid circular references.
|
|
// Attempting to JSON.stringify(keyNode) directly would throw
|
|
// "Converting circular structure to JSON" error.
|
|
// If the valueNode configuration is wrong, this will return an error, which will propagate up
|
|
return `${JSON.stringify(keyNode.value)}:${convertNodeToJSON(valueNode)}`
|
|
}
|
|
}
|
|
|
|
function stripCommentsAndCommas(text: string): string {
|
|
const tree = parseTree(text, undefined, {
|
|
allowEmptyContent: true,
|
|
allowTrailingComma: true,
|
|
})
|
|
|
|
// If we couldn't parse the tree, return the original text
|
|
if (!tree) {
|
|
return text
|
|
}
|
|
|
|
// convertNodeToJSON can throw an error if the tree is invalid
|
|
try {
|
|
return convertNodeToJSON(tree)
|
|
} catch (_) {
|
|
return text
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes comments from a JSON string.
|
|
* @param jsonString The JSON string with comments.
|
|
* @returns The JSON string without comments.
|
|
*/
|
|
|
|
export function stripComments(jsonString: string) {
|
|
return stripCommentsAndCommas(stripComments_(jsonString) ?? jsonString)
|
|
}
|
|
|
|
export default linter
|