1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
from stone.backend import Backend
from stone.backends.helpers import (
fmt_camel,
)
from stone.ir import (
Boolean,
Bytes,
Float32,
Float64,
Int32,
Int64,
List,
String,
Timestamp,
UInt32,
UInt64,
Void,
is_alias,
is_list_type,
is_struct_type,
is_map_type,
is_user_defined_type,
)
from stone.ir.api import ApiNamespace
_base_type_table = {
Boolean: 'boolean',
Bytes: 'string',
Float32: 'number',
Float64: 'number',
Int32: 'number',
Int64: 'number',
List: 'Array',
String: 'string',
UInt32: 'number',
UInt64: 'number',
Timestamp: 'Timestamp',
Void: 'void',
}
def fmt_error_type(data_type, inside_namespace=None, wrap_error_in=''):
"""
Converts the error type into a TypeScript type.
inside_namespace should be set to the namespace that the reference
occurs in, or None if this parameter is not relevant.
"""
return '{}<{}>'.format(
(wrap_error_in if (wrap_error_in != '') else 'Error'),
fmt_type(data_type, inside_namespace)
)
def fmt_type_name(data_type, inside_namespace=None):
"""
Produces a TypeScript type name for the given data type.
inside_namespace should be set to the namespace that the reference
occurs in, or None if this parameter is not relevant.
"""
if is_user_defined_type(data_type) or is_alias(data_type):
if data_type.namespace == inside_namespace:
return data_type.name
else:
return '{}.{}'.format(data_type.namespace.name, data_type.name)
else:
fmted_type = _base_type_table.get(data_type.__class__, 'Object')
if is_list_type(data_type):
fmted_type += '<' + fmt_type(data_type.data_type, inside_namespace) + '>'
elif is_map_type(data_type):
key_data_type = _base_type_table.get(data_type.key_data_type, 'string')
value_data_type = fmt_type_name(data_type.value_data_type, inside_namespace)
fmted_type = '{{[key: {}]: {}}}'.format(key_data_type, value_data_type)
return fmted_type
def fmt_polymorphic_type_reference(data_type, inside_namespace=None):
"""
Produces a TypeScript type name for the meta-type that refers to the given
struct, which belongs to an enumerated subtypes tree. This meta-type contains the
.tag field that lets developers discriminate between subtypes.
"""
# NOTE: These types are not properly namespaced, so there could be a conflict
# with other user-defined types. If this ever surfaces as a problem, we
# can defer emitting these types until the end, and emit them in a
# nested namespace (e.g., files.references.MetadataReference).
return fmt_type_name(data_type, inside_namespace) + "Reference"
def fmt_type(data_type, inside_namespace=None):
"""
Returns a TypeScript type annotation for a data type.
May contain a union of enumerated subtypes.
inside_namespace should be set to the namespace that the type reference
occurs in, or None if this parameter is not relevant.
"""
if is_struct_type(data_type) and data_type.has_enumerated_subtypes():
possible_types = []
possible_subtypes = data_type.get_all_subtypes_with_tags()
for _, subtype in possible_subtypes:
possible_types.append(fmt_polymorphic_type_reference(subtype, inside_namespace))
if data_type.is_catch_all():
possible_types.append(fmt_polymorphic_type_reference(data_type, inside_namespace))
return fmt_union(possible_types)
else:
return fmt_type_name(data_type, inside_namespace)
def fmt_union(type_strings):
"""
Returns a union type of the given types.
"""
return '|'.join(type_strings) if len(type_strings) > 1 else type_strings[0]
def fmt_func(name, version):
if version == 1:
return fmt_camel(name)
return fmt_camel(name) + 'V{}'.format(version)
def fmt_var(name):
return fmt_camel(name)
def fmt_tag(cur_namespace, tag, val):
"""
Processes a documentation reference.
"""
if tag == 'type':
fq_val = val
if '.' not in val and cur_namespace is not None:
fq_val = cur_namespace.name + '.' + fq_val
return fq_val
elif tag == 'route':
if ':' in val:
val, version = val.split(':', 1)
version = int(version)
else:
version = 1
return fmt_func(val, version) + "()"
elif tag == 'link':
anchor, link = val.rsplit(' ', 1)
# There's no way to have links in TSDoc, so simply use JSDoc's formatting.
# It's entirely possible some editors support this.
return '[{}]{{@link {}}}'.format(anchor, link)
elif tag == 'val':
# Value types seem to match JavaScript (true, false, null)
return val
elif tag == 'field':
return val
else:
raise RuntimeError('Unknown doc ref tag %r' % tag)
def check_route_name_conflict(namespace):
"""
Check name conflicts among generated route definitions. Raise a runtime exception when a
conflict is encountered.
"""
route_by_name = {}
for route in namespace.routes:
route_name = fmt_func(route.name, route.version)
if route_name in route_by_name:
other_route = route_by_name[route_name]
raise RuntimeError(
'There is a name conflict between {!r} and {!r}'.format(other_route, route))
route_by_name[route_name] = route
def generate_imports_for_referenced_namespaces(backend, namespace, module_name_prefix):
# type: (Backend, ApiNamespace, str) -> None
imported_namespaces = namespace.get_imported_namespaces()
if not imported_namespaces:
return
for ns in imported_namespaces:
backend.emit(
"import * as {namespace_name} from '{module_name_prefix}{namespace_name}';".format(
module_name_prefix=module_name_prefix,
namespace_name=ns.name
)
)
backend.emit()
def get_data_types_for_namespace(namespace):
return namespace.data_types + namespace.aliases
|