Skip to content

Commit b232983

Browse files
authored
Add many more e2e TypeScript usage checks (#8)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 0fc9218 commit b232983

23 files changed

Lines changed: 2150 additions & 58 deletions

File tree

src/generator/typescript.cc

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,27 @@ auto TypeScript::operator()(const IREnumeration &entry) const -> void {
8181

8282
auto TypeScript::operator()(const IRObject &entry) const -> void {
8383
const auto type_name{sourcemeta::core::mangle(entry.pointer, this->prefix)};
84-
const auto has_additional{entry.additional.has_value()};
84+
const auto has_typed_additional{
85+
std::holds_alternative<IRType>(entry.additional)};
86+
const auto allows_any_additional{
87+
std::holds_alternative<bool>(entry.additional) &&
88+
std::get<bool>(entry.additional)};
8589

86-
if (has_additional && entry.members.empty()) {
90+
if (has_typed_additional && entry.members.empty()) {
8791
this->output << "export type " << type_name << " = Record<string, "
88-
<< sourcemeta::core::mangle(entry.additional->pointer,
89-
this->prefix)
92+
<< sourcemeta::core::mangle(
93+
std::get<IRType>(entry.additional).pointer,
94+
this->prefix)
9095
<< ">;\n";
9196
return;
9297
}
9398

99+
if (allows_any_additional && entry.members.empty()) {
100+
this->output << "export type " << type_name
101+
<< " = Record<string, unknown>;\n";
102+
return;
103+
}
104+
94105
this->output << "export interface " << type_name << " {\n";
95106

96107
// We always quote property names for safety. JSON Schema allows any string
@@ -110,20 +121,29 @@ auto TypeScript::operator()(const IRObject &entry) const -> void {
110121
<< ";\n";
111122
}
112123

113-
if (has_additional) {
124+
if (allows_any_additional) {
125+
this->output << " [key: string]: unknown | undefined;\n";
126+
} else if (has_typed_additional) {
114127
// TypeScript index signatures must be a supertype of all property value
115128
// types. We use a union of all member types plus the additional properties
116129
// type plus undefined (for optional properties).
117130
this->output << " [key: string]:\n";
131+
this->output << " // As a notable limitation, TypeScript requires index "
132+
"signatures\n";
133+
this->output << " // to also include the types of all of its "
134+
"properties, so we must\n";
135+
this->output << " // match a superset of what JSON Schema allows\n";
118136
for (const auto &[member_name, member_value] : entry.members) {
119137
this->output << " "
120138
<< sourcemeta::core::mangle(member_value.pointer,
121139
this->prefix)
122140
<< " |\n";
123141
}
142+
124143
this->output << " "
125-
<< sourcemeta::core::mangle(entry.additional->pointer,
126-
this->prefix)
144+
<< sourcemeta::core::mangle(
145+
std::get<IRType>(entry.additional).pointer,
146+
this->prefix)
127147
<< " |\n";
128148
this->output << " undefined;\n";
129149
}

src/ir/include/sourcemeta/codegen/ir.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ struct IRObjectValue : IRType {
6464
struct IRObject : IRType {
6565
// To preserve the user's ordering
6666
std::vector<std::pair<sourcemeta::core::JSON::String, IRObjectValue>> members;
67-
std::optional<IRType> additional;
67+
std::variant<bool, IRType> additional;
6868
};
6969

7070
/// @ingroup ir

src/ir/ir_default_compiler.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ auto handle_object(const sourcemeta::core::JSON &schema,
9696
members.emplace_back(entry.first, std::move(member_value));
9797
}
9898

99-
std::optional<IRType> additional{std::nullopt};
99+
std::variant<bool, IRType> additional{true};
100100
if (subschema.defines("additionalProperties")) {
101101
const auto &additional_schema{subschema.at("additionalProperties")};
102-
const auto is_false{additional_schema.is_boolean() &&
103-
!additional_schema.to_boolean()};
104-
if (!is_false) {
102+
if (additional_schema.is_boolean()) {
103+
additional = additional_schema.to_boolean();
104+
} else {
105105
auto additional_pointer{sourcemeta::core::to_pointer(location.pointer)};
106106
additional_pointer.push_back("additionalProperties");
107107
additional = IRType{.pointer = std::move(additional_pointer)};

test/e2e/typescript/2020-12/additional_properties_true/expected.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ export type FlexibleRecord_AdditionalProperties_AnyOf_ZIndex4 = string;
88

99
export type FlexibleRecord_AdditionalProperties_AnyOf_ZIndex3 = unknown[];
1010

11-
export interface FlexibleRecord_AdditionalProperties_AnyOf_ZIndex2 {
12-
}
11+
export type FlexibleRecord_AdditionalProperties_AnyOf_ZIndex2 = Record<string, unknown>;
1312

1413
export type FlexibleRecord_AdditionalProperties_AnyOf_ZIndex1 = boolean;
1514

@@ -27,6 +26,9 @@ export interface FlexibleRecord {
2726
"name": FlexibleRecord_Properties_Name;
2827
"count"?: FlexibleRecord_Properties_Count;
2928
[key: string]:
29+
// As a notable limitation, TypeScript requires index signatures
30+
// to also include the types of all of its properties, so we must
31+
// match a superset of what JSON Schema allows
3032
FlexibleRecord_Properties_Name |
3133
FlexibleRecord_Properties_Count |
3234
FlexibleRecord_AdditionalProperties |

0 commit comments

Comments
 (0)