integrate parameters to support legacy tasks with parameters (#3567)
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
// TSON.parse.test.ts
|
||||
import { TSON } from "./tson";
|
||||
import { describe, test, expect } from "vitest";
|
||||
|
||||
describe("TSON.parse", () => {
|
||||
test("single top-level template works", () => {
|
||||
const input = "{{ hello }}";
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toEqual("<STUB>");
|
||||
});
|
||||
|
||||
test("preserves double braces inside quoted strings", () => {
|
||||
const input = '{"a": "{{ hello }}"}';
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toEqual({ a: "{{ hello }}" });
|
||||
});
|
||||
|
||||
test("replaces double braces outside strings with stub", () => {
|
||||
const input = '{"a": {{ hello }} }';
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toEqual({ a: "<STUB>" });
|
||||
});
|
||||
|
||||
test("handles double braces in keys and values", () => {
|
||||
const input = `
|
||||
{
|
||||
"hello": "world",
|
||||
{{foo}}: "bar",
|
||||
"baz": {{quux}}
|
||||
}`;
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toEqual({
|
||||
hello: "world",
|
||||
"<STUB>": "bar",
|
||||
baz: "<STUB>",
|
||||
});
|
||||
});
|
||||
|
||||
test("does not allow trailing commas", () => {
|
||||
const input = `
|
||||
{
|
||||
"hello": "world",
|
||||
{{foo}}: "bar",
|
||||
"baz": {{quux}},
|
||||
}`;
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("Expected double-quoted property name");
|
||||
});
|
||||
|
||||
test("detects unclosed double braces", () => {
|
||||
const input = "{{ unclosed";
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("Unclosed");
|
||||
});
|
||||
|
||||
test("detects unmatched closing double braces", () => {
|
||||
const input = "closed }}";
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("Unmatched");
|
||||
});
|
||||
|
||||
test("handles nested double braces", () => {
|
||||
const input = "{{ {{ nested }} }}";
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toEqual("<STUB>");
|
||||
});
|
||||
|
||||
test("handles double braces in arrays", () => {
|
||||
const input = '[{{ }}, {{ }}, "normal"]';
|
||||
const result = TSON.parse(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toEqual(["<STUB>", "<STUB>", "normal"]);
|
||||
});
|
||||
});
|
||||
@@ -1,223 +0,0 @@
|
||||
type JSONValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| JSONValue[]
|
||||
| { [key: string]: JSONValue };
|
||||
|
||||
interface ParseResult {
|
||||
success: boolean;
|
||||
data?: JSONValue;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const placeholder = () => "<STUB>";
|
||||
|
||||
/**
|
||||
* TSON ("templated JSON") is a superset of JSON, where double curly braces {{...}} can:
|
||||
* - exist anywhere outside of string literals, and
|
||||
* - are treated as placeholders
|
||||
*/
|
||||
const TSON = {
|
||||
parse(input: string): ParseResult {
|
||||
try {
|
||||
const balanceCheck = checkDoubleBraceBalance(input);
|
||||
|
||||
if (!balanceCheck.balanced) {
|
||||
return {
|
||||
success: false,
|
||||
error: balanceCheck.error,
|
||||
};
|
||||
}
|
||||
|
||||
const pipeline = [
|
||||
replaceBracesOutsideQuotes,
|
||||
// removeTrailingCommas,
|
||||
JSON.parse,
|
||||
];
|
||||
|
||||
const parsed = pipeline.reduce((acc, fn) => fn(acc), input) as JSONValue;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: parsed,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function checkDoubleBraceBalance(input: string): {
|
||||
balanced: boolean;
|
||||
error?: string;
|
||||
} {
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
let depth = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const char = input[i];
|
||||
const nextChar = input[i + 1];
|
||||
|
||||
// handle escape sequences
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "\\") {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// inside-string tracking
|
||||
if (char === '"') {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
// double braces counts (only outside strings)
|
||||
if (!inString) {
|
||||
if (char === "{" && nextChar === "{") {
|
||||
depth++;
|
||||
i++; // skip next char
|
||||
} else if (char === "}" && nextChar === "}") {
|
||||
depth--;
|
||||
if (depth < 0) {
|
||||
return {
|
||||
balanced: false,
|
||||
error: `Unmatched closing }} at position ${i}`,
|
||||
};
|
||||
}
|
||||
i++; // skip next char
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > 0) {
|
||||
return {
|
||||
balanced: false,
|
||||
error: `Unclosed {{ - missing ${depth} closing }}`,
|
||||
};
|
||||
}
|
||||
|
||||
return { balanced: true };
|
||||
}
|
||||
|
||||
function replaceBracesOutsideQuotes(input: string): string {
|
||||
let result = "";
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
let inDoubleBrace = 0; // track nesting depth of {{...}}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const char = input[i];
|
||||
const nextChar = input[i + 1];
|
||||
|
||||
// escape sequences
|
||||
if (escapeNext) {
|
||||
if (inDoubleBrace === 0) {
|
||||
result += char;
|
||||
}
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "\\") {
|
||||
if (inDoubleBrace === 0) {
|
||||
result += char;
|
||||
}
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// inside-string tracking
|
||||
if (char === '"') {
|
||||
inString = !inString;
|
||||
if (inDoubleBrace === 0) {
|
||||
result += char;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// double braces (only outside strings)
|
||||
if (!inString) {
|
||||
if (char === "{" && nextChar === "{") {
|
||||
if (inDoubleBrace === 0) {
|
||||
result += `"${placeholder()}"`;
|
||||
}
|
||||
inDoubleBrace++;
|
||||
i++; // skip next char
|
||||
continue;
|
||||
} else if (char === "}" && nextChar === "}") {
|
||||
inDoubleBrace--;
|
||||
i++; // skip next char
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// add characters when we're not inside double braces
|
||||
if (inDoubleBrace === 0) {
|
||||
result += char;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function removeTrailingCommas(input: string): string {
|
||||
let result = "";
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const char = input[i];
|
||||
|
||||
// escape sequences
|
||||
if (escapeNext) {
|
||||
result += char;
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "\\") {
|
||||
result += char;
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// inside-string tracking
|
||||
if (char === '"') {
|
||||
inString = !inString;
|
||||
result += char;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for trailing commas (outside strings)
|
||||
if (!inString && char === ",") {
|
||||
// look-ahead for the next non-whitespace character
|
||||
let j = i + 1;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
while (j < input.length && /\s/.test(input[j])) {
|
||||
j++;
|
||||
}
|
||||
|
||||
// if next non-whitespace is } or ], skip the comma
|
||||
if (j < input.length && (input[j] === "}" || input[j] === "]")) {
|
||||
continue; // Skip this comma
|
||||
}
|
||||
}
|
||||
|
||||
result += char;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export { TSON };
|
||||
Reference in New Issue
Block a user