Skip to content

Commit 10466c0

Browse files
authored
Improve TypeScript escaping to handle control characters (#16)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent e07c06e commit 10466c0

File tree

5 files changed

+84
-1
lines changed

5 files changed

+84
-1
lines changed

src/generator/typescript.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <sourcemeta/codegen/generator.h>
22

3+
#include <iomanip> // std::hex, std::setfill, std::setw
34
#include <sstream> // std::ostringstream
45

56
namespace {
@@ -15,6 +16,12 @@ auto escape_string(const std::string &input) -> std::string {
1516
case '"':
1617
result << "\\\"";
1718
break;
19+
case '\b':
20+
result << "\\b";
21+
break;
22+
case '\f':
23+
result << "\\f";
24+
break;
1825
case '\n':
1926
result << "\\n";
2027
break;
@@ -25,7 +32,13 @@ auto escape_string(const std::string &input) -> std::string {
2532
result << "\\t";
2633
break;
2734
default:
28-
result << character;
35+
// Escape other control characters (< 0x20) using \uXXXX format
36+
if (static_cast<unsigned char>(character) < 0x20) {
37+
result << "\\u" << std::hex << std::setfill('0') << std::setw(4)
38+
<< static_cast<int>(static_cast<unsigned char>(character));
39+
} else {
40+
result << character;
41+
}
2942
break;
3043
}
3144
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export type TestWithus = string;
2+
3+
export type TestWithformfeed = string;
4+
5+
export type TestWithbackspace = string;
6+
7+
export type TestWithsoh = string;
8+
9+
export type TestWithnull = string;
10+
11+
export type TestNormal = string;
12+
13+
export type TestAdditionalProperties = never;
14+
15+
export interface Test {
16+
"normal"?: TestNormal;
17+
"with\bbackspace"?: TestWithbackspace;
18+
"with\fformfeed"?: TestWithformfeed;
19+
"with\u0000null"?: TestWithnull;
20+
"with\u0001soh"?: TestWithsoh;
21+
"with\u001fus"?: TestWithus;
22+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"defaultPrefix": "Test"
3+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"type": "object",
4+
"properties": {
5+
"normal": { "type": "string" },
6+
"with\bbackspace": { "type": "string" },
7+
"with\fformfeed": { "type": "string" },
8+
"with\u0000null": { "type": "string" },
9+
"with\u0001soh": { "type": "string" },
10+
"with\u001fus": { "type": "string" }
11+
},
12+
"additionalProperties": false
13+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Test } from "./expected";
2+
3+
// Valid: object with control characters in property names
4+
const validObject: Test = {
5+
"normal": "hello",
6+
"with\bbackspace": "value1",
7+
"with\fformfeed": "value2",
8+
"with\u0000null": "value3",
9+
"with\u0001soh": "value4",
10+
"with\u001fus": "value5"
11+
};
12+
13+
// Valid: empty object (all properties are optional)
14+
const emptyObject: Test = {};
15+
16+
// Valid: partial object
17+
const partialObject: Test = {
18+
"normal": "just normal"
19+
};
20+
21+
// Invalid: extra property not allowed
22+
const extraProp: Test = {
23+
"normal": "hello",
24+
// @ts-expect-error - extra property not allowed
25+
"extra": "not allowed"
26+
};
27+
28+
// Invalid: wrong type for property
29+
const wrongType: Test = {
30+
// @ts-expect-error - must be string
31+
"normal": 123
32+
};

0 commit comments

Comments
 (0)