from __future__ import absolute_import, division, print_function, unicode_literals from stone.backend import Backend from stone.ir.api import ApiNamespace 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.backends.helpers import ( fmt_camel, ) _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): """ 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 'Error<%s>' % 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 '%s.%s' % (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: %s]: %s}' % (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 '[%s]{@link %s}' % (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()