diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart index c158715c..54158a62 100644 --- a/web_generator/lib/src/ast/base.dart +++ b/web_generator/lib/src/ast/base.dart @@ -5,6 +5,7 @@ import 'package:code_builder/code_builder.dart'; import '../interop_gen/namer.dart'; +import 'documentation.dart'; import 'types.dart'; class GlobalOptions { @@ -65,6 +66,8 @@ abstract class NamedDeclaration extends Declaration { @override abstract String name; + abstract Documentation? documentation; + ReferredType asReferredType([List? typeArgs, String? url]) => ReferredType( name: name, declaration: this, typeParams: typeArgs ?? [], url: url); diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart index 2c7367b5..6fc23576 100644 --- a/web_generator/lib/src/ast/declarations.dart +++ b/web_generator/lib/src/ast/declarations.dart @@ -8,6 +8,7 @@ import '../interop_gen/namer.dart'; import '../js/typescript.types.dart'; import 'base.dart'; import 'builtin.dart'; +import 'documentation.dart'; import 'helpers.dart'; import 'types.dart'; @@ -52,6 +53,9 @@ sealed class TypeDeclaration extends NestableDeclaration final List constructors; + @override + Documentation? documentation; + TypeDeclaration( {required this.name, this.dartName, @@ -61,7 +65,8 @@ sealed class TypeDeclaration extends NestableDeclaration this.properties = const [], this.operators = const [], this.constructors = const [], - this.parent}); + this.parent, + this.documentation}); ExtensionType _emit( [covariant DeclarationOptions? options, @@ -72,6 +77,8 @@ sealed class TypeDeclaration extends NestableDeclaration final hierarchy = getMemberHierarchy(this); + final (doc, annotations) = generateFromDocumentation(documentation); + final fieldDecs = []; final methodDecs = []; @@ -98,6 +105,8 @@ sealed class TypeDeclaration extends NestableDeclaration : BuiltinType.primitiveType(PrimitiveType.object, isNullable: false); return ExtensionType((e) => e + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..name = completedDartName ..annotations.addAll([ if (parent != null) @@ -151,11 +160,15 @@ class VariableDeclaration extends FieldDeclaration @override bool exported; + @override + Documentation? documentation; + VariableDeclaration( {required this.name, required this.type, required this.modifier, - required this.exported}); + required this.exported, + this.documentation}); @override ID get id => ID(type: 'var', name: name); @@ -165,8 +178,11 @@ class VariableDeclaration extends FieldDeclaration @override Spec emit([DeclarationOptions? options]) { + final (doc, annotations) = generateFromDocumentation(documentation); if (modifier == VariableModifier.$const) { return Method((m) => m + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..name = name ..type = MethodType.getter ..annotations.add(generateJSAnnotation()) @@ -176,6 +192,8 @@ class VariableDeclaration extends FieldDeclaration } else { // getter and setter -> single variable return Field((f) => f + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..static = options?.static ?? false ..name = name @@ -217,6 +235,9 @@ class FunctionDeclaration extends CallableDeclaration @override ID id; + @override + Documentation? documentation; + FunctionDeclaration( {required this.name, required this.id, @@ -224,16 +245,20 @@ class FunctionDeclaration extends CallableDeclaration this.parameters = const [], this.typeParameters = const [], required this.exported, - required this.returnType}); + required this.returnType, + this.documentation}); @override Method emit([DeclarationOptions? options]) { options ??= DeclarationOptions(); + final (doc, annotations) = generateFromDocumentation(documentation); final (requiredParams, optionalParams) = emitParameters(parameters, options); return Method((m) => m + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = dartName ?? name ..annotations.add(generateJSAnnotation( @@ -275,20 +300,27 @@ class EnumDeclaration extends NestableDeclaration @override NestableDeclaration? parent; + @override + Documentation? documentation; + EnumDeclaration( {required this.name, required this.baseType, required this.members, required this.exported, - this.dartName}); + this.dartName, + this.documentation}); @override Spec emit([DeclarationOptions? options]) { + final (doc, annotations) = generateFromDocumentation(documentation); final baseTypeIsJSType = getJSTypeAlternative(baseType) == baseType; final externalMember = members.any((m) => m.isExternal); final shouldUseJSRepType = externalMember || baseTypeIsJSType; return ExtensionType((e) => e + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..annotations.addAll([ if (externalMember) if (parent != null) @@ -325,12 +357,18 @@ class EnumMember { bool get isExternal => value == null; + Documentation? documentation; + EnumMember(this.name, this.value, - {this.type, required this.parent, this.dartName}); + {this.type, required this.parent, this.dartName, this.documentation}); Field emit([bool? shouldUseJSRepType]) { final jsRep = shouldUseJSRepType ?? (value == null); + final (doc, annotations) = generateFromDocumentation(documentation); return Field((f) { + f + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]); // TODO(nikeokoronkwo): This does not render correctly on `code_builder`. // Until the update is made, we will omit examples concerning this // Luckily, not many real-world instances of enums use this anyways, https://github.com/dart-lang/tools/issues/2118 @@ -374,18 +412,25 @@ class TypeAliasDeclaration extends NamedDeclaration @override ID get id => ID(type: 'typealias', name: name); + @override + Documentation? documentation; + TypeAliasDeclaration( {required this.name, this.typeParameters = const [], required this.type, - required this.exported}) + required this.exported, + this.documentation}) : dartName = null; @override TypeDef emit([DeclarationOptions? options]) { options ??= DeclarationOptions(); + final (doc, annotations) = generateFromDocumentation(documentation); return TypeDef((t) => t + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..name = name ..types .addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions()))) @@ -423,6 +468,9 @@ class NamespaceDeclaration extends NestableDeclaration @override Set nodes = {}; + @override + Documentation? documentation; + NamespaceDeclaration( {required this.name, this.exported = true, @@ -430,13 +478,17 @@ class NamespaceDeclaration extends NestableDeclaration this.dartName, this.topLevelDeclarations = const {}, this.namespaceDeclarations = const {}, - this.nestableDeclarations = const {}}) + this.nestableDeclarations = const {}, + this.documentation}) : _id = id; @override ExtensionType emit([covariant DeclarationOptions? options]) { options ??= DeclarationOptions(); options.static = true; + + final (doc, annotations) = generateFromDocumentation(documentation); + // static props and vars final methods = []; final fields = []; @@ -525,6 +577,8 @@ class NamespaceDeclaration extends NestableDeclaration // put them together... return ExtensionType((eType) => eType + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..name = completedDartName ..annotations.addAll([ if (parent != null) @@ -565,7 +619,8 @@ class ClassDeclaration extends TypeDeclaration { super.constructors, required super.methods, required super.properties, - super.operators}); + super.operators, + super.documentation}); @override ExtensionType emit([covariant DeclarationOptions? options]) { @@ -602,7 +657,8 @@ class InterfaceDeclaration extends TypeDeclaration { super.methods, super.properties, super.operators, - super.constructors}) + super.constructors, + super.documentation}) : _id = id; @override @@ -642,6 +698,9 @@ class PropertyDeclaration extends FieldDeclaration @override Type type; + @override + Documentation? documentation; + PropertyDeclaration( {required this.name, this.dartName, @@ -650,15 +709,20 @@ class PropertyDeclaration extends FieldDeclaration this.scope = DeclScope.public, this.readonly = false, required this.static, - this.isNullable = false}); + this.isNullable = false, + this.documentation}); @override Spec emit([covariant DeclarationOptions? options]) { options ??= DeclarationOptions(); assert(scope == DeclScope.public, 'Only public members can be emitted'); + final (doc, annotations) = generateFromDocumentation(documentation); + if (readonly) { return Method((m) => m + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = dartName ?? name ..type = MethodType.getter @@ -669,6 +733,8 @@ class PropertyDeclaration extends FieldDeclaration ..returns = type.emit(options?.toTypeOptions(nullable: isNullable))); } else { return Field((f) => f + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = dartName ?? name ..annotations.addAll([ @@ -712,6 +778,9 @@ class MethodDeclaration extends CallableDeclaration final bool isNullable; + @override + Documentation? documentation; + MethodDeclaration( {required this.name, this.dartName, @@ -722,12 +791,15 @@ class MethodDeclaration extends CallableDeclaration required this.returnType, this.static = false, this.scope = DeclScope.public, - this.isNullable = false}); + this.isNullable = false, + this.documentation}); @override Method emit([covariant DeclarationOptions? options]) { options ??= DeclarationOptions(); + final (doc, annotations) = generateFromDocumentation(documentation); + final (requiredParams, optionalParams) = emitParameters(parameters, options); @@ -735,6 +807,8 @@ class MethodDeclaration extends CallableDeclaration if (isNullable) { return Method((m) => m + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = dartName ?? name ..type = MethodType.getter @@ -753,6 +827,8 @@ class MethodDeclaration extends CallableDeclaration } return Method((m) => m + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = dartName ?? name ..type = switch (kind) { @@ -804,12 +880,15 @@ class ConstructorDeclaration implements MemberDeclaration { final String? dartName; + Documentation? documentation; + ConstructorDeclaration( {this.parameters = const [], this.name, String? dartName, required this.id, - this.scope = DeclScope.public}) + this.scope = DeclScope.public, + this.documentation}) : dartName = dartName == 'unnamed' ? null : dartName; static ConstructorDeclaration defaultFor(TypeDeclaration decl) { @@ -819,6 +898,7 @@ class ConstructorDeclaration implements MemberDeclaration { Constructor emit([covariant DeclarationOptions? options]) { options ??= DeclarationOptions(); + final (doc, annotations) = generateFromDocumentation(documentation); final (requiredParams, optionalParams) = emitParameters(parameters, options); @@ -826,6 +906,8 @@ class ConstructorDeclaration implements MemberDeclaration { final isFactory = dartName != null && dartName != name; return Constructor((c) => c + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = dartName ?? name ..annotations @@ -869,6 +951,9 @@ class OperatorDeclaration extends CallableDeclaration final bool static; + @override + Documentation? documentation; + OperatorDeclaration( {required this.kind, this.dartName, @@ -876,12 +961,15 @@ class OperatorDeclaration extends CallableDeclaration required this.returnType, this.typeParameters = const [], this.scope = DeclScope.public, - this.static = false}); + this.static = false, + this.documentation}); @override Method emit([covariant DeclarationOptions? options]) { options ??= DeclarationOptions(); + final (doc, annotations) = generateFromDocumentation(documentation); + final requiredParams = []; final optionalParams = []; for (final p in parameters) { @@ -896,6 +984,8 @@ class OperatorDeclaration extends CallableDeclaration } return Method((m) => m + ..docs.addAll([...doc]) + ..annotations.addAll([...annotations]) ..external = true ..name = 'operator $name' ..types diff --git a/web_generator/lib/src/ast/documentation.dart b/web_generator/lib/src/ast/documentation.dart new file mode 100644 index 00000000..3f9ebb8e --- /dev/null +++ b/web_generator/lib/src/ast/documentation.dart @@ -0,0 +1,52 @@ +import 'package:code_builder/code_builder.dart'; + +/// Parsed documentation from JSDoc suitable for Dart +/// +/// The documentation produced for Dart follows a given pattern to +/// make docs that are simple and follow Dart conventions. +/// +/// Some tags used in JSDoc may also be converted to Dart +/// annotations +class Documentation { + final String docs; + + final List annotations; + + const Documentation({required this.docs, this.annotations = const []}); +} + +class Annotation { + final AnnotationKind kind; + + final List<(String, {String? name})> arguments; + + const Annotation({required this.kind, this.arguments = const []}); + + Expression emit() { + if (arguments.isEmpty) { + return refer(kind.name, kind.source); + } + final positionalArgs = []; + final namedArgs = {}; + + for (final (name, name: nameArg) in arguments) { + if (nameArg != null) { + namedArgs[nameArg] = literal(name); + } else { + positionalArgs.add(literal(name)); + } + } + + return refer(kind.name, kind.source).call(positionalArgs, namedArgs); + } +} + +enum AnnotationKind { + deprecated('Deprecated'), + experimental('experimental', source: 'package:meta/meta.dart'); + + const AnnotationKind(this.name, {this.source}); + + final String name; + final String? source; +} diff --git a/web_generator/lib/src/ast/helpers.dart b/web_generator/lib/src/ast/helpers.dart index acd3c824..472ade68 100644 --- a/web_generator/lib/src/ast/helpers.dart +++ b/web_generator/lib/src/ast/helpers.dart @@ -2,11 +2,14 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:convert'; + import 'package:code_builder/code_builder.dart'; import 'base.dart'; import 'builtin.dart'; import 'declarations.dart'; +import 'documentation.dart'; import 'types.dart'; Type getJSTypeAlternative(Type type) { @@ -113,6 +116,22 @@ Type getClassRepresentationType(ClassDeclaration cl) { } } +(List, List) generateFromDocumentation( + Documentation? docs) { + if (docs == null) return ([], []); + + if (docs.docs.trim().isEmpty) { + return ([], docs.annotations.map((d) => d.emit()).toList()); + } + return ( + const LineSplitter() + .convert(docs.docs.trim()) + .map((d) => '/// $d') + .toList(), + docs.annotations.map((d) => d.emit()).toList() + ); +} + (List, List) emitParameters( List parameters, [DeclarationOptions? options]) { diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index d209f4d8..e2e3634f 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart @@ -8,6 +8,7 @@ import 'package:path/path.dart' as p; import '../../ast/base.dart'; import '../../ast/builtin.dart'; import '../../ast/declarations.dart'; +import '../../ast/documentation.dart'; import '../../ast/helpers.dart'; import '../../ast/types.dart'; import '../../js/annotations.dart'; @@ -212,7 +213,8 @@ class Transformer { return TypeAliasDeclaration( name: name, type: _getTypeFromDeclaration(type, null), - exported: isExported); + exported: isExported, + documentation: _parseAndTransformDocumentation(typealias)); } /// Transforms a TS Namespace (identified as a [TSModuleDeclaration] with @@ -249,7 +251,8 @@ class Transformer { exported: isExported, topLevelDeclarations: {}, namespaceDeclarations: {}, - nestableDeclarations: {}); + nestableDeclarations: {}, + documentation: _parseAndTransformDocumentation(namespace)); // TODO: We can implement this in classes and interfaces. // however, since namespaces and modules are a thing, @@ -410,7 +413,8 @@ class Transformer { methods: [], properties: [], operators: [], - constructors: []) + constructors: [], + documentation: _parseAndTransformDocumentation(typeDecl)) : ClassDeclaration( name: name, dartName: dartName, @@ -423,7 +427,8 @@ class Transformer { constructors: [], methods: [], properties: [], - operators: []); + operators: [], + documentation: _parseAndTransformDocumentation(typeDecl)); final typeNamer = ScopedUniqueNamer({'get', 'set'}); @@ -528,7 +533,8 @@ class Transformer { : _transformType(property.type!)), static: isStatic, readonly: isReadonly, - isNullable: property.questionToken != null); + isNullable: property.questionToken != null, + documentation: _parseAndTransformDocumentation(property)); propertyDeclaration.parent = parent; return propertyDeclaration; } @@ -590,7 +596,8 @@ class Transformer { ? _transformType(method.type!) : BuiltinType.anyType), isNullable: (method.kind == TSSyntaxKind.MethodSignature) && - (method as TSMethodSignature).questionToken != null); + (method as TSMethodSignature).questionToken != null, + documentation: _parseAndTransformDocumentation(method)); methodDeclaration.parent = parent; return methodDeclaration; } @@ -617,7 +624,8 @@ class Transformer { dartName: dartName.isEmpty ? null : dartName, name: name, parameters: params.map(_transformParameter).toList(), - scope: scope); + scope: scope, + documentation: _parseAndTransformDocumentation(constructor)); } MethodDeclaration _transformCallSignature( @@ -655,7 +663,8 @@ class Transformer { returnType: methodType ?? (callSignature.type != null ? _transformType(callSignature.type!) - : BuiltinType.anyType)); + : BuiltinType.anyType), + documentation: _parseAndTransformDocumentation(callSignature)); methodDeclaration.parent = parent; return methodDeclaration; } @@ -686,6 +695,8 @@ class Transformer { indexerType = parent.asReferredType(parent.typeParameters); } + final doc = _parseAndTransformDocumentation(indexSignature); + final getOperatorDeclaration = OperatorDeclaration( kind: OperatorKind.squareBracket, parameters: params.map(_transformParameter).toList(), @@ -693,7 +704,8 @@ class Transformer { scope: scope, typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], - static: isStatic); + static: isStatic, + documentation: doc); final setOperatorDeclaration = isReadonly ? OperatorDeclaration( kind: OperatorKind.squareBracketSet, @@ -702,7 +714,8 @@ class Transformer { scope: scope, typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], - static: isStatic) + static: isStatic, + documentation: doc) : null; getOperatorDeclaration.parent = parent; @@ -748,7 +761,8 @@ class Transformer { returnType: methodType ?? (getter.type != null ? _transformType(getter.type!) - : BuiltinType.anyType)); + : BuiltinType.anyType), + documentation: _parseAndTransformDocumentation(getter)); methodDeclaration.parent = parent; return methodDeclaration; } @@ -791,7 +805,8 @@ class Transformer { typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], returnType: setter.type != null ? _transformType(setter.type!) - : BuiltinType.anyType); + : BuiltinType.anyType, + documentation: _parseAndTransformDocumentation(setter)); methodDeclaration.parent = parent; return methodDeclaration; } @@ -822,7 +837,8 @@ class Transformer { typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], returnType: function.type != null ? _transformType(function.type!) - : BuiltinType.anyType); + : BuiltinType.anyType, + documentation: _parseAndTransformDocumentation(function)); } List _transformVariable(TSVariableStatement variable, @@ -866,7 +882,8 @@ class Transformer { name: d.name.text, type: d.type == null ? BuiltinType.anyType : _transformType(d.type!), modifier: modifier, - exported: isExported ?? false); + exported: isExported ?? false, + documentation: _parseAndTransformDocumentation(d)); } EnumDeclaration _transformEnum(TSEnumDeclaration enumeration, @@ -904,7 +921,8 @@ class Transformer { members.add(EnumMember(memName, value, type: BuiltinType.primitiveType(primitiveType), parent: name, - dartName: dartMemName)); + dartName: dartMemName, + documentation: _parseAndTransformDocumentation(member))); if (enumRepType == null && !(primitiveType == PrimitiveType.int && enumRepType == PrimitiveType.double)) { @@ -921,7 +939,8 @@ class Transformer { members.add(EnumMember(memName, value, type: BuiltinType.primitiveType(primitiveType), parent: name, - dartName: dartMemName)); + dartName: dartMemName, + documentation: _parseAndTransformDocumentation(member))); if (enumRepType == null) { enumRepType = primitiveType; } else if (enumRepType != primitiveType) { @@ -930,13 +949,14 @@ class Transformer { break; default: // unsupported - break; } } else { // get the type - members.add( - EnumMember(memName, null, parent: name, dartName: dartMemName)); + members.add(EnumMember(memName, null, + parent: name, + dartName: dartMemName, + documentation: _parseAndTransformDocumentation(member))); } } @@ -946,7 +966,8 @@ class Transformer { name: name, baseType: BuiltinType.primitiveType(enumRepType ?? PrimitiveType.num), members: members, - exported: isExported); + exported: isExported, + documentation: _parseAndTransformDocumentation(enumeration)); } num _parseNumericLiteral(TSNumericLiteral numericLiteral) { @@ -979,7 +1000,8 @@ class Transformer { type: _transformType(type), typeParameters: typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], - exported: isExported); + exported: isExported, + documentation: _parseAndTransformDocumentation(typealias)); } ParameterDeclaration _transformParameter(TSParameterDeclaration parameter, @@ -1601,6 +1623,138 @@ class Transformer { typeArguments, isNotTypableDeclaration, typeArg); } + /// Extracts associated documentation (JSDoc) from a [TSNode] and transforms + /// the JSDoc into associated Dart documentation for the given [node] + Documentation? _parseAndTransformDocumentation(TSNamedDeclaration node) { + // get symbol + final symbol = typeChecker.getSymbolAtLocation(node.name ?? node); + final jsDocTags = symbol?.getJsDocTags(); + final doc = symbol?.getDocumentationComment(typeChecker); + + // transform documentation + if (doc == null && jsDocTags == null) { + return null; + } else { + return _transformDocumentation( + doc?.toDart ?? [], jsDocTags?.toDart ?? []); + } + } + + Documentation _transformDocumentation( + List topLevelDocParts, + List jsDocTags) { + final docBuffer = StringBuffer(); + final annotations = []; + + for (final doc in topLevelDocParts) { + final docString = _parseSymbolDisplayPart(doc); + docBuffer.write(docString); + } + + docBuffer.writeln(); + + // parse annotations + for (final tag in jsDocTags) { + switch (tag.name) { + case 'deprecated': + final tagBuffer = StringBuffer(); + for (final part in tag.text?.toDart ?? []) { + tagBuffer.write(_parseSymbolDisplayPart(part)); + } + annotations.add(Annotation( + kind: AnnotationKind.deprecated, + arguments: [ + if (tag.text?.toDart.isNotEmpty ?? false) + (tagBuffer.toString(), name: null) + ])); + break; + case 'experimental': + annotations.add(const Annotation(kind: AnnotationKind.experimental)); + if (tag.text?.toDart case final expText? when expText.isNotEmpty) { + final tagBuffer = StringBuffer(); + for (final part in expText) { + tagBuffer.write(_parseSymbolDisplayPart(part)); + } + docBuffer.writeln('**EXPERIMENTAL**: ${tagBuffer.toString()}'); + } + break; + case 'param': + final tags = tag.text?.toDart ?? []; + if (tags.isEmpty) continue; + + final parameterName = + tags.where((t) => t.kind == 'parameterName').firstOrNull; + final parameterDesc = tags + .where((t) => t.kind == 'text') + .fold('', (prev, combine) => '$prev ${combine.text}'); + + if (parameterName != null) { + docBuffer.writeln('- [${parameterName.text}]: $parameterDesc'); + } + break; + case 'returns': + final tagBuffer = StringBuffer(); + for (final part in tag.text?.toDart ?? []) { + tagBuffer.write(_parseSymbolDisplayPart(part)); + } + if (tagBuffer.length != 0) { + docBuffer.writeln('\nReturns ${tagBuffer.toString()}'); + } + break; + case 'example': + final tagBuffer = StringBuffer(); + for (final part in tag.text?.toDart ?? []) { + tagBuffer.write(_parseSymbolDisplayPart(part)); + } + docBuffer.writeAll([ + '\nExample:', + '```ts', + tagBuffer.toString(), + '```', + ], '\n'); + case 'template': + final tags = tag.text?.toDart ?? []; + if (tags.isEmpty) continue; + + final typeName = + tags.where((t) => t.kind == 'typeParameterName').firstOrNull; + + if (typeName == null) continue; + + final tagBuffer = StringBuffer(); + for (final part in tag.text?.toDart ?? []) { + if (part.kind != 'typeParameterName') { + tagBuffer.write(_parseSymbolDisplayPart(part)); + } + } + docBuffer + .writeln('Type Name [${typeName.text}]: ${tagBuffer.toString()}'); + default: + continue; + } + } + + return Documentation(docs: docBuffer.toString(), annotations: annotations); + } + + String _parseSymbolDisplayPart(TSSymbolDisplayPart part) { + // what if decl is not already parsed? + if (part.kind == 'linkName') { + final decls = nodeMap.findByName(part.text); + if (decls.isNotEmpty) { + final firstNode = decls.first; + return firstNode.dartName ?? firstNode.name ?? firstNode.id.name; + } else { + return part.text; + } + } + return switch (part.kind) { + 'text' => part.text, + 'link' => '', + _ => part.text + }; + } + /// Filters out the declarations generated from the [transform] function and /// returns the declarations needed based on: /// diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart index d43e0a52..b0c51b6e 100644 --- a/web_generator/lib/src/js/typescript.types.dart +++ b/web_generator/lib/src/js/typescript.types.dart @@ -234,7 +234,7 @@ extension type TSQualifiedName._(JSObject _) implements TSNode { } @JS('NamedDeclaration') -extension type TSNamedDeclaration._(JSObject _) implements TSNode { +extension type TSNamedDeclaration._(JSObject _) implements TSDeclaration { // TODO: Support other name specifiers external TSIdentifier? get name; } @@ -390,7 +390,7 @@ extension type TSFunctionDeclaration._(JSObject _) /// A common API for Classes and Interfaces extension type TSObjectDeclaration._(JSObject _) - implements TSDeclaration, TSStatement { + implements TSDeclarationStatement { // TODO: May be undefined for classes in default exports external TSIdentifier get name; external TSNodeArray? get modifiers; @@ -422,7 +422,7 @@ extension type TSExpressionWithTypeArguments._(JSObject _) external TSNodeArray? get typeArguments; } -extension type TSPropertyEntity._(JSObject _) implements TSDeclaration { +extension type TSPropertyEntity._(JSObject _) implements TSNamedDeclaration { external TSNodeArray? get modifiers; external TSIdentifier get name; external TSToken? get questionToken; @@ -441,7 +441,7 @@ extension type TSConstructorEntity._(JSObject _) } @JS('ClassElement') -extension type TSClassElement._(JSObject _) implements TSDeclaration { +extension type TSClassElement._(JSObject _) implements TSNamedDeclaration { external TSIdentifier? get name; } @@ -471,7 +471,7 @@ extension type TSConstructorDeclaration._(JSObject _) } @JS('TypeElement') -extension type TSTypeElement._(JSObject _) implements TSDeclaration { +extension type TSTypeElement._(JSObject _) implements TSNamedDeclaration { external TSIdentifier? get name; external TSToken? get questionToken; } @@ -531,7 +531,7 @@ extension type TSSetAccessorDeclaration._(JSObject _) @JS('TypeAliasDeclaration') extension type TSTypeAliasDeclaration._(JSObject _) - implements TSDeclaration, TSStatement { + implements TSDeclarationStatement, TSStatement { external TSNodeArray? get modifiers; external TSNodeArray? get typeParameters; external TSIdentifier get name; @@ -556,14 +556,14 @@ extension type TSTypeParameterDeclaration._(JSObject _) @JS('EnumDeclaration') extension type TSEnumDeclaration._(JSObject _) - implements TSDeclaration, TSStatement { + implements TSDeclarationStatement, TSStatement { external TSIdentifier get name; external TSNodeArray? get modifiers; external TSNodeArray get members; } @JS('EnumMember') -extension type TSEnumMember._(JSObject _) implements TSDeclaration { +extension type TSEnumMember._(JSObject _) implements TSNamedDeclaration { external TSIdentifier get name; external TSExpression? get initializer; } @@ -604,11 +604,26 @@ extension type TSNodeArray._(JSArray _) extension type TSSymbol._(JSObject _) implements JSObject { external String get name; external JSArray? getDeclarations(); + external JSArray getDocumentationComment( + TSTypeChecker? typeChecker); + external JSArray getJsDocTags([TSTypeChecker checker]); external TSSymbolTable? get exports; } typedef TSSymbolTable = JSMap; +@JS('SymbolDisplayPart') +extension type TSSymbolDisplayPart._(JSObject _) implements JSObject { + external String text; + external String kind; +} + +@JS() +extension type JSDocTagInfo._(JSObject _) implements JSObject { + external String name; + external JSArray? text; +} + @JS('Type') extension type TSType._(JSObject _) implements JSObject { external TSSymbol get symbol; diff --git a/web_generator/test/integration/interop_gen/classes_expected.dart b/web_generator/test/integration/interop_gen/classes_expected.dart index 244db760..49d010fa 100644 --- a/web_generator/test/integration/interop_gen/classes_expected.dart +++ b/web_generator/test/integration/interop_gen/classes_expected.dart @@ -296,6 +296,8 @@ extension type EpahsImpl._(_i1.JSObject _) @_i1.JS('area') external String area$1(AnonymousUnion unit); external static EpahsImpl getById(String id); + + /// Returns a string representation of an object. @_i1.JS('toString') external String toString$(); } diff --git a/web_generator/test/integration/interop_gen/classes_input.d.ts b/web_generator/test/integration/interop_gen/classes_input.d.ts index d36a4237..f1923dd8 100644 --- a/web_generator/test/integration/interop_gen/classes_input.d.ts +++ b/web_generator/test/integration/interop_gen/classes_input.d.ts @@ -19,8 +19,8 @@ export declare class User { id: number; protected username: string; private email; - constructor(id: number, // Public property - username: string, // Protected property + constructor(id: number, + username: string, email: string); greet(): string; getEmail(): string; @@ -163,7 +163,6 @@ interface Epahs { export declare class EpahsImpl implements Epahs { readonly id: string; name: string; - /* other decls in Shape */ metadata?: TMeta; constructor(name: string, type?: 'circle' | 'rectangle' | 'polygon'); onUpdate?(prev: Epahs): void; diff --git a/web_generator/test/integration/interop_gen/jsdoc_expected.dart b/web_generator/test/integration/interop_gen/jsdoc_expected.dart new file mode 100644 index 00000000..1d088ed2 --- /dev/null +++ b/web_generator/test/integration/interop_gen/jsdoc_expected.dart @@ -0,0 +1,102 @@ +// ignore_for_file: constant_identifier_names, non_constant_identifier_names + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:js_interop' as _i1; + +import 'package:meta/meta.dart' as _i2; + +/// Represents a user in the system. +extension type User._(_i1.JSObject _) implements _i1.JSObject { + /// Unique identifier for the user. + external String id; + + /// The full name of the user. + external String name; + + /// The user's email address. + external String email; + + /// The user's age in years. + external double age; +} + +/// Gets a user by their ID from the legacy store. +@Deprecated('Use `getUserById` instead.') +@_i1.JS() +external User fetchUser(String userId); + +/// Gets a user by their ID. +/// - [userId]: - The ID of the user to retrieve. +/// +/// Returns A promise that resolves to a user object or null. +@_i1.JS() +external _i1.JSPromise getUserById(String userId); + +/// Registers a new user into the system. +/// **EXPERIMENTAL**: API under development and may change without notice. +/// - [newUser]: - A new user object without an ID. +/// +/// Returns The created user with a generated ID. +@_i2.experimental +@_i1.JS() +external _i1.JSPromise registerUser(User newUser); + +/// Logs an event to the server. +/// - [event]: - The name of the event. +/// - [payload]: - Additional data associated with the event. +@_i1.JS() +external void logEvent( + String event, + _i1.JSObject payload, +); + +/// Represents a configuration object for the app. +extension type AppConfig._(_i1.JSObject _) implements _i1.JSObject { + /// The environment name (e.g., 'dev', 'prod'). + external String env; + + /// Whether debug mode is enabled. + external bool debug; + + /// Enabled features in the app. + external _i1.JSArray<_i1.JSString> features; +} + +/// Updates the application configuration. +/// - [updates]: - The config values to update. +/// +/// Returns The updated configuration. +@_i1.JS() +external AppConfig updateConfig(AppConfig updates); + +/// **EXPERIMENTAL**: This function is being tested for future use. +/// Initializes the system with async resources. +@_i2.experimental +@_i1.JS() +external _i1.JSPromise<_i1.JSAny?> initializeSystem(); + +/// Cleans up resources before shutting down. +@Deprecated('Use `shutdownSystem()` instead.') +@_i1.JS() +external void cleanup(); + +/// Properly shuts down the system and releases all resources. +@_i1.JS() +external _i1.JSPromise<_i1.JSAny?> shutdownSystem(); +extension type Logger._(_i1.JSObject _) implements _i1.JSObject { + external void info(String msg); + external void warn(String msg); + external void error(String msg); +} + +/// Creates a simple logger instance. +@_i1.JS() +external Logger createLogger(); + +/// A constant representing the current application version. +@_i1.JS() +external String get APP_VERSION; + +/// The default configuration for the application. +@_i1.JS() +external AppConfig get DEFAULT_CONFIG; diff --git a/web_generator/test/integration/interop_gen/jsdoc_input.d.ts b/web_generator/test/integration/interop_gen/jsdoc_input.d.ts new file mode 100644 index 00000000..702b3022 --- /dev/null +++ b/web_generator/test/integration/interop_gen/jsdoc_input.d.ts @@ -0,0 +1,100 @@ +/** + * @fileoverview A large example TypeScript file demonstrating extensive use + * of JSDoc comments, including @deprecated and @experimental annotations. + */ +/** + * Represents a user in the system. + */ +export interface User { + /** Unique identifier for the user. */ + id: string; + /** The full name of the user. */ + name: string; + /** The user's email address. */ + email: string; + /** The user's age in years. */ + age: number; +} +/** + * A constant representing the current application version. + * @type {string} + */ +export declare const APP_VERSION: string; +/** + * Gets a user by their ID from the legacy store. + * + * @deprecated Use `getUserById` instead. + * @param {string} userId + * @returns {User} + */ +export declare function fetchUser(userId: string): User; +/** + * Gets a user by their ID. + * @param {string} userId - The ID of the user to retrieve. + * @returns {Promise} A promise that resolves to a user object or null. + */ +export declare function getUserById(userId: string): Promise; +/** + * Registers a new user into the system. + * + * @experimental API under development and may change without notice. + * + * @param {User} newUser - A new user object without an ID. + * @returns {Promise} The created user with a generated ID. + */ +export declare function registerUser(newUser: User): Promise; +/** + * Logs an event to the server. + * @param {string} event - The name of the event. + * @param {object} payload - Additional data associated with the event. + */ +export declare function logEvent(event: string, payload: object): void; +/** + * Represents a configuration object for the app. + */ +export interface AppConfig { + /** The environment name (e.g., 'dev', 'prod'). */ + env: string; + /** Whether debug mode is enabled. */ + debug: boolean; + /** Enabled features in the app. */ + features: string[]; +} +/** + * The default configuration for the application. + * @type {AppConfig} + */ +export declare const DEFAULT_CONFIG: AppConfig; +/** + * Updates the application configuration. + * @param {AppConfig} updates - The config values to update. + * @returns {AppConfig} The updated configuration. + */ +export declare function updateConfig(updates: AppConfig): AppConfig; +/** + * @experimental This function is being tested for future use. + * Initializes the system with async resources. + * @returns {Promise} + */ +export declare function initializeSystem(): Promise; +/** + * Cleans up resources before shutting down. + * + * @deprecated Use `shutdownSystem()` instead. + */ +export declare function cleanup(): void; +/** + * Properly shuts down the system and releases all resources. + * @returns {Promise} + */ +export declare function shutdownSystem(): Promise; +export interface Logger { + info(msg: string): void; + warn(msg: string): void; + error(msg: string): void; +} +/** + * Creates a simple logger instance. + * @returns {Logger} + */ +export declare function createLogger(): Logger; diff --git a/web_generator/test/integration/interop_gen/namespaces_expected.dart b/web_generator/test/integration/interop_gen/namespaces_expected.dart index 0194efb9..8227441f 100644 --- a/web_generator/test/integration/interop_gen/namespaces_expected.dart +++ b/web_generator/test/integration/interop_gen/namespaces_expected.dart @@ -75,6 +75,9 @@ extension type Core_LogEntry._(_i1.JSObject _) implements _i1.JSObject { external String message; } + +/// Represents the core application configuration. +/// This interface is used across multiple services and modules. @_i1.JS('Core.IAppConfig') extension type Core_IAppConfig._(_i1.JSObject _) implements _i1.JSObject { external String apiEndpoint; @@ -96,6 +99,9 @@ extension type Security_IAuthToken._(_i1.JSObject _) implements _i1.JSObject { external double userId; } + +/// A service for handling user authentication. +/// Demonstrates using a type from another namespace (Core.LogEntry). @_i1.JS('Security.AuthService') extension type Security_AuthService._(_i1.JSObject _) implements _i1.JSObject { external Security_AuthService(); @@ -105,6 +111,9 @@ extension type Security_AuthService._(_i1.JSObject _) implements _i1.JSObject { String password, ); } + +/// A generic repository pattern interface. +/// T can be a class from another namespace, like Models.User. @_i1.JS('Data.IRepository') extension type Data_IRepository._(_i1.JSObject _) implements _i1.JSObject { @@ -241,11 +250,21 @@ extension type EnterpriseApp_Models_Product._(_i1.JSObject _) @_i1.JS('EnterpriseApp.Utilities') extension type EnterpriseApp_Utilities._(_i1.JSObject _) implements _i1.JSObject { + /// Formats a number as currency. + /// - [amount]: The number to format. + /// - [currency]: The currency symbol. + /// + /// Returns A formatted string. @_i1.JS() external static String formatCurrency( num amount, [ String? currency, ]); + + /// Validates an email address. + /// - [email]: The email string to validate. + /// + /// Returns True if the email is valid, false otherwise. @_i1.JS() external static bool isValidEmail(String email); } diff --git a/web_generator/test/integration/interop_gen/project/input/a.d.ts b/web_generator/test/integration/interop_gen/project/input/a.d.ts index 9445ac49..0e0e19bf 100644 --- a/web_generator/test/integration/interop_gen/project/input/a.d.ts +++ b/web_generator/test/integration/interop_gen/project/input/a.d.ts @@ -1,68 +1,231 @@ -import { Vector, Vector2D, Vector3D, Point2D, Point3D, origin2D as origin, origin3D, CoordinateSystem, origin2D } from "./b" -import { Comparator } from "./c" +import { + Vector, + Vector2D, + Vector3D, + Point2D, + Point3D, + origin2D as origin, + origin3D, + CoordinateSystem, + origin2D, +} from "./b"; +import { Comparator } from "./c"; + +/** + * Represents a point in 2D space using polar coordinates. + * - `magnitude`: radial distance from the origin. + * - `angle`: angle in radians from the positive x-axis. + */ interface PolarCoordinate { - magnitude: number; - angle: number; + magnitude: number; + angle: number; } + +/** + * Represents a point in 3D space using cylindrical coordinates. + * - `radius`: radial distance from the z-axis. + * - `angle`: angle in radians from the x-axis. + * - `z`: height along the z-axis. + */ interface CylindricalCoordinate { - radius: number; - angle: number; - z: number; + radius: number; + angle: number; + z: number; } + +/** + * Represents a point in 3D space using spherical coordinates. + * - `magnitude`: radial distance from the origin. + * - `theta`: inclination angle from the z-axis. + * - `tau`: azimuthal angle from the x-axis in the xy-plane. + */ interface SphericalCoordinate { - magnitude: number; - theta: number; - tau: number; + magnitude: number; + theta: number; + tau: number; } + +/** + * Represents a mathematical matrix. + * - `rows`: number of rows. + * - `columns`: number of columns. + * - Numeric index maps to an array of numbers (row data). + */ interface Matrix { - [index: number]: number[]; - rows: number; - columns: number; + [index: number]: number[]; + rows: number; + columns: number; } + +/** + * A transformation matrix that acts as a function on 2D vectors. + * @template V Vector2D subtype + */ interface TransformerMatrix extends Matrix { - (v: V): V; + /** + * Transforms the input vector using this matrix. + * @param v Input vector + * @returns Transformed vector + */ + (v: V): V; } + +/** + * A 2D coordinate system with vector and point operations. + */ export declare class CoordinateSystem2D implements CoordinateSystem { - constructor(origin: typeof origin2D); - points: Point2D[]; - readonly origin: Point2D; - addPoint(point: Point2D): void; - addVector(vector: Vector2D, start?: Point2D): void; - static get xAxis(): typeof unitI2D; - static get yAxis(): typeof unitJ2D; + /** + * @param origin The origin point of the coordinate system. + */ + constructor(origin: typeof origin2D); + + /** Points registered in this coordinate system. */ + points: Point2D[]; + + /** Origin of the coordinate system. */ + readonly origin: Point2D; + + /** + * Adds a point to the coordinate system. + * @param point The point to add. + */ + addPoint(point: Point2D): void; + + /** + * Adds a vector to the coordinate system from a starting point. + * @param vector The vector to add. + * @param start The start point (defaults to origin). + */ + addVector(vector: Vector2D, start?: Point2D): void; + + /** The unit vector along the x-axis. */ + static get xAxis(): typeof unitI2D; + + /** The unit vector along the y-axis. */ + static get yAxis(): typeof unitJ2D; } + +/** + * A 3D coordinate system with vector and point operations. + */ export declare class CoordinateSystem3D implements CoordinateSystem { - constructor(origin: typeof origin3D); - points: Point3D[]; - readonly origin: Point3D; - addPoint(point: Point3D): void; - addVector(vector: Vector3D, start?: Point3D): void; - static get xAxis(): typeof unitI3D; - static get yAxis(): typeof unitJ3D; - static get zAxis(): typeof unitK3D; + /** + * @param origin The origin point of the coordinate system. + */ + constructor(origin: typeof origin3D); + + /** Points registered in this coordinate system. */ + points: Point3D[]; + + /** Origin of the coordinate system. */ + readonly origin: Point3D; + + /** + * Adds a point to the coordinate system. + * @param point The point to add. + */ + addPoint(point: Point3D): void; + + /** + * Adds a vector to the coordinate system from a starting point. + * @param vector The vector to add. + * @param start The start point (defaults to origin). + */ + addVector(vector: Vector3D, start?: Point3D): void; + + /** The unit vector along the x-axis. */ + static get xAxis(): typeof unitI3D; + + /** The unit vector along the y-axis. */ + static get yAxis(): typeof unitJ3D; + + /** The unit vector along the z-axis. */ + static get zAxis(): typeof unitK3D; } + +/** + * A matrix that includes vector comparison capabilities. + * @template V Vector2D subtype + */ interface ComparatorMatrix extends Matrix, Comparator {} + +/** + * Computes the dot product between two vectors. + * @param v1 First vector. + * @param v2 Second vector. + * @returns A scalar projection as a vector. + */ declare function dotProduct(v1: V, v2: V): V; + +/** + * Computes the cross product of two 3D vectors. + * @param v1 First vector. + * @param v2 Second vector. + * @returns A new 3D vector perpendicular to both. + */ declare function crossProduct(v1: Vector3D, v2: Vector3D): Vector3D; + +/** + * Maps a 2D vector to a 3D vector (z = 0). + * @param v Input 2D vector. + * @returns A 3D vector. + */ declare function mapTo3D(v: Vector2D): Vector3D; + +/** + * Converts a 2D point to polar coordinates. + * @param point A 2D point. + * @returns Polar representation of the point. + */ declare function toPolarCoordinate(point: Point2D): PolarCoordinate; + +/** + * Converts a 3D point to cylindrical coordinates. + * @param point A 3D point. + * @returns Cylindrical representation. + */ declare function toCylindricalCoordinate(point: Point3D): CylindricalCoordinate; + +/** + * Converts a 3D point to spherical coordinates. + * @param point A 3D point. + * @returns Spherical representation. + */ declare function toSphericalCoordinate(point: Point3D): SphericalCoordinate; + +/** + * Converts cylindrical coordinates to spherical coordinates. + * @param point Cylindrical coordinate. + * @returns Spherical representation. + */ declare function toSphericalCoordinate(point: CylindricalCoordinate): SphericalCoordinate; + +/** Unit vector in 2D x-direction. */ export declare const unitI2D: Vector2D; +/** Unit vector in 2D y-direction. */ export declare const unitJ2D: Vector2D; +/** Unit vector in 3D x-direction. */ export declare const unitI3D: Vector3D; +/** Unit vector in 3D y-direction. */ export declare const unitJ3D: Vector3D; +/** Unit vector in 3D z-direction. */ export declare const unitK3D: Vector3D; + export { - origin, origin3D, dotProduct, crossProduct, mapTo3D, - TransformerMatrix, ComparatorMatrix -} + origin, + origin3D, + dotProduct, + crossProduct, + mapTo3D, + TransformerMatrix, + ComparatorMatrix, +}; + export { - PolarCoordinate as PolarPoint, - CylindricalCoordinate as CylindricalPoint, - SphericalCoordinate as SphericalPoint, - toPolarCoordinate, - toSphericalCoordinate, - toCylindricalCoordinate -} + PolarCoordinate as PolarPoint, + CylindricalCoordinate as CylindricalPoint, + SphericalCoordinate as SphericalPoint, + toPolarCoordinate, + toSphericalCoordinate, + toCylindricalCoordinate, +}; diff --git a/web_generator/test/integration/interop_gen/project/input/c.d.ts b/web_generator/test/integration/interop_gen/project/input/c.d.ts index 63dcadab..5c63ec4f 100644 --- a/web_generator/test/integration/interop_gen/project/input/c.d.ts +++ b/web_generator/test/integration/interop_gen/project/input/c.d.ts @@ -1,49 +1,169 @@ +/** + * Represents a basic logger interface with optional flush capability. + * @interface + */ export interface ILogger { - readonly name: string; - level?: "debug" | "info" | "warn" | "error"; - log(message: string): void; - error(message: string): void; - flush?(): Promise; + /** Name of the logger (e.g., subsystem or module). */ + readonly name: string; + + /** Logging level. Defaults to "info" if unspecified. */ + level?: "debug" | "info" | "warn" | "error"; + + /** + * Logs a message at the current level. + * @param message - The message to log. + */ + log(message: string): void; + + /** + * Logs an error message. + * @param message - The error message. + */ + error(message: string): void; + + /** + * Flushes any buffered logs. + * @returns A promise that resolves when flushing is complete. + */ + flush?(): Promise; } + +/** + * A key-value map of strings. + * @interface + */ export interface Dictionary { - [key: string]: string; + [key: string]: string; } + +/** + * A function that compares two items and returns a number: + * - `< 0` if `a < b` + * - `0` if `a === b` + * - `> 0` if `a > b` + * @typeParam T - The type to compare. + */ export interface Comparator { - (a: T, b: T): number; + (a: T, b: T): number; } + +/** + * A simple repository abstraction. + * @typeParam T - The type of the entity. + */ export interface Repository { - findById(id: string): T; - save(entity: T): void; + /** + * Finds an entity by its ID. + * @param id - The unique identifier. + */ + findById(id: string): T; + + /** + * Saves an entity. + * @param entity - The entity to persist. + */ + save(entity: T): void; } + +/** + * A constructor that accepts an array of string arguments. + * @deprecated Prefer factory functions or specific constructors. + */ export interface RepoConstructor { - new (args: string[]): any; + new (args: string[]): any; } + +/** + * Describes a service with asynchronous operations. + * @experimental This API is under evaluation and may change. + */ export interface AsyncService { - fetchData(url: string): Promise; - updateData(id: string, payload: string): Promise; + /** + * Fetches remote data from a URL. + * @param url - The resource endpoint. + */ + fetchData(url: string): Promise; + + /** + * Updates data on the server. + * @param id - The resource ID. + * @param payload - The update content. + * @returns `true` if update succeeded, otherwise `false`. + */ + updateData(id: string, payload: string): Promise; } + +/** + * Represents a basic user. + */ export interface User { - id: string; - email: string; - describe?(): string; + /** Unique identifier. */ + id: string; + + /** User's email address. */ + email: string; + + /** + * Returns a human-readable description of the user. + */ + describe?(): string; } + +/** + * An administrator user with logging capabilities. + */ export interface Admin extends User, ILogger { - role: string; - grantPermission(permission: string): void; + /** Admin role label. */ + role: string; + + /** + * Grants the given permission. + * @param permission - A named permission string. + */ + grantPermission(permission: string): void; } + +/** + * Configuration environment. + */ export interface Config { - env: string; -} -export interface Config { - debug: boolean; + /** Environment name (e.g., 'production', 'dev'). */ + env: string; + + /** Whether debug mode is enabled. */ + debug: boolean; } + +/** + * Represents a resource that requires authentication. + */ export interface SecureResource { - accessToken: string; - authenticate(): boolean; + /** A token used for authentication. */ + accessToken: string; + + /** Authenticates the resource. */ + authenticate(): boolean; } + +/** + * A basic self-referencing linked list node. + */ interface LinkedList { - next(): this; + /** Returns the next node in the list. */ + next(): this; } + +/** + * A global dictionary instance. + */ export declare const dict: Dictionary; + +/** + * Root node of a linked list. + */ export declare const rootList: LinkedList; + +/** + * A numeric comparator for sorting numbers. + */ export declare const compareNumbers: Comparator; diff --git a/web_generator/test/integration/interop_gen/project/output/a.dart b/web_generator/test/integration/interop_gen/project/output/a.dart index 91ffd510..914d7dc9 100644 --- a/web_generator/test/integration/interop_gen/project/output/a.dart +++ b/web_generator/test/integration/interop_gen/project/output/a.dart @@ -12,30 +12,62 @@ import 'c.dart' as _i3; external _i2.Point2D get origin; @_i1.JS() external _i2.Point3D get origin3D; + +/// Computes the dot product between two vectors. +/// - [v1]: First vector. +/// - [v2]: Second vector. +/// +/// Returns A scalar projection as a vector. @_i1.JS() external V dotProduct( V v1, V v2, ); + +/// Computes the cross product of two 3D vectors. +/// - [v1]: First vector. +/// - [v2]: Second vector. +/// +/// Returns A new 3D vector perpendicular to both. @_i1.JS() external _i2.Vector3D crossProduct( _i2.Vector3D v1, _i2.Vector3D v2, ); + +/// Maps a 2D vector to a 3D vector (z = 0). +/// - [v]: Input 2D vector. +/// +/// Returns A 3D vector. @_i1.JS() external _i2.Vector3D mapTo3D(_i2.Vector2D v); + +/// A transformation matrix that acts as a function on 2D vectors. +/// Type Name [V]: Vector2D subtype extension type TransformerMatrix._(_i1.JSObject _) implements Matrix { external V call(V v); } + +/// A matrix that includes vector comparison capabilities. +/// Type Name [V]: Vector2D subtype extension type ComparatorMatrix._(_i1.JSObject _) implements Matrix, _i3.Comparator {} + +/// Represents a point in 2D space using polar coordinates. +/// - `magnitude`: radial distance from the origin. +/// - `angle`: angle in radians from the positive x-axis. @_i1.JS('PolarPoint') extension type PolarCoordinate._(_i1.JSObject _) implements _i1.JSObject { external double magnitude; external double angle; } + +/// Represents a point in 3D space using cylindrical coordinates. +/// - `radius`: radial distance from the z-axis. +/// - `angle`: angle in radians from the x-axis. +/// - `z`: height along the z-axis. @_i1.JS('CylindricalPoint') extension type CylindricalCoordinate._(_i1.JSObject _) implements _i1.JSObject { external double radius; @@ -44,6 +76,11 @@ extension type CylindricalCoordinate._(_i1.JSObject _) implements _i1.JSObject { external double z; } + +/// Represents a point in 3D space using spherical coordinates. +/// - `magnitude`: radial distance from the origin. +/// - `theta`: inclination angle from the z-axis. +/// - `tau`: azimuthal angle from the x-axis in the xy-plane. @_i1.JS('SphericalPoint') extension type SphericalCoordinate._(_i1.JSObject _) implements _i1.JSObject { external double magnitude; @@ -52,60 +89,135 @@ extension type SphericalCoordinate._(_i1.JSObject _) implements _i1.JSObject { external double tau; } + +/// Converts a 2D point to polar coordinates. +/// - [point]: A 2D point. +/// +/// Returns Polar representation of the point. @_i1.JS() external PolarCoordinate toPolarCoordinate(_i2.Point2D point); + +/// Converts a 3D point to spherical coordinates. +/// Converts cylindrical coordinates to spherical coordinates. +/// - [point]: A 3D point. +/// +/// Returns Spherical representation. +/// - [point]: Cylindrical coordinate. +/// +/// Returns Spherical representation. @_i1.JS() external SphericalCoordinate toSphericalCoordinate(_i2.Point3D point); + +/// Converts a 3D point to spherical coordinates. +/// Converts cylindrical coordinates to spherical coordinates. +/// - [point]: A 3D point. +/// +/// Returns Spherical representation. +/// - [point]: Cylindrical coordinate. +/// +/// Returns Spherical representation. @_i1.JS('toSphericalCoordinate') external SphericalCoordinate toSphericalCoordinate$1( CylindricalCoordinate point); + +/// Converts a 3D point to cylindrical coordinates. +/// - [point]: A 3D point. +/// +/// Returns Cylindrical representation. @_i1.JS() external CylindricalCoordinate toCylindricalCoordinate(_i2.Point3D point); + +/// Unit vector in 2D x-direction. @_i1.JS() external _i2.Vector2D get unitI2D; + +/// Unit vector in 2D y-direction. @_i1.JS() external _i2.Vector2D get unitJ2D; + +/// A 2D coordinate system with vector and point operations. extension type CoordinateSystem2D._(_i1.JSObject _) implements _i2.CoordinateSystem<_i2.Point2D> { external CoordinateSystem2D(_i2.Point2D origin); + /// Points registered in this coordinate system. external _i1.JSArray<_i2.Point2D> points; + /// Origin of the coordinate system. @_i4.redeclare external _i2.Point2D get origin; + + /// Adds a point to the coordinate system. + /// - [point]: The point to add. @_i4.redeclare external void addPoint(_i2.Point2D point); + + /// Adds a vector to the coordinate system from a starting point. + /// - [vector]: The vector to add. + /// - [start]: The start point (defaults to origin). external void addVector( _i2.Vector2D vector, [ _i2.Point2D? start, ]); + + /// The unit vector along the x-axis. external _i2.Vector2D get xAxis; + + /// The unit vector along the y-axis. external _i2.Vector2D get yAxis; } + +/// Unit vector in 3D x-direction. @_i1.JS() external _i2.Vector3D get unitI3D; + +/// Unit vector in 3D y-direction. @_i1.JS() external _i2.Vector3D get unitJ3D; + +/// Unit vector in 3D z-direction. @_i1.JS() external _i2.Vector3D get unitK3D; + +/// A 3D coordinate system with vector and point operations. extension type CoordinateSystem3D._(_i1.JSObject _) implements _i2.CoordinateSystem<_i2.Point3D> { external CoordinateSystem3D(_i2.Point3D origin); + /// Points registered in this coordinate system. external _i1.JSArray<_i2.Point3D> points; + /// Origin of the coordinate system. @_i4.redeclare external _i2.Point3D get origin; + + /// Adds a point to the coordinate system. + /// - [point]: The point to add. @_i4.redeclare external void addPoint(_i2.Point3D point); + + /// Adds a vector to the coordinate system from a starting point. + /// - [vector]: The vector to add. + /// - [start]: The start point (defaults to origin). external void addVector( _i2.Vector3D vector, [ _i2.Point3D? start, ]); + + /// The unit vector along the x-axis. external _i2.Vector3D get xAxis; + + /// The unit vector along the y-axis. external _i2.Vector3D get yAxis; + + /// The unit vector along the z-axis. external _i2.Vector3D get zAxis; } + +/// Represents a mathematical matrix. +/// - `rows`: number of rows. +/// - `columns`: number of columns. +/// - Numeric index maps to an array of numbers (row data). extension type Matrix._(_i1.JSObject _) implements _i1.JSObject { external double rows; diff --git a/web_generator/test/integration/interop_gen/project/output/b.dart b/web_generator/test/integration/interop_gen/project/output/b.dart index 46e3a010..fee72b9e 100644 --- a/web_generator/test/integration/interop_gen/project/output/b.dart +++ b/web_generator/test/integration/interop_gen/project/output/b.dart @@ -268,6 +268,8 @@ extension type EpahsImpl._(_i1.JSObject _) @_i1.JS('area') external String area$1(AnonymousUnion unit); external static EpahsImpl getById(String id); + + /// Returns a string representation of an object. @_i1.JS('toString') external String toString$(); } diff --git a/web_generator/test/integration/interop_gen/project/output/c.dart b/web_generator/test/integration/interop_gen/project/output/c.dart index 5179b4be..ba70d515 100644 --- a/web_generator/test/integration/interop_gen/project/output/c.dart +++ b/web_generator/test/integration/interop_gen/project/output/c.dart @@ -3,6 +3,12 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:js_interop' as _i1; +import 'package:meta/meta.dart' as _i2; + +/// A function that compares two items and returns a number: +/// - `< 0` if `a < b` +/// - `0` if `a === b` +/// - `> 0` if `a > b` extension type Comparator._(_i1.JSObject _) implements _i1.JSObject { external double call( @@ -10,60 +16,120 @@ extension type Comparator._(_i1.JSObject _) T b, ); } + +/// Represents a basic logger interface with optional flush capability. extension type ILogger._(_i1.JSObject _) implements _i1.JSObject { + /// Logging level. Defaults to "info" if unspecified. external AnonymousUnion? level; + /// Name of the logger (e.g., subsystem or module). external String get name; + + /// Logs a message at the current level. + /// - [message]: - The message to log. external void log(String message); + + /// Logs an error message. + /// - [message]: - The error message. external void error(String message); + + /// Flushes any buffered logs. + /// + /// Returns A promise that resolves when flushing is complete. external _i1.JSFunction? get flush; } + +/// A key-value map of strings. extension type Dictionary._(_i1.JSObject _) implements _i1.JSObject { external String operator [](String key); } + +/// A simple repository abstraction. extension type Repository._(_i1.JSObject _) implements _i1.JSObject { + /// Finds an entity by its ID. + /// - [id]: - The unique identifier. external T findById(String id); + + /// Saves an entity. + /// - [entity]: - The entity to persist. external void save(T entity); } + +/// A constructor that accepts an array of string arguments. +@Deprecated('Prefer factory functions or specific constructors.') extension type RepoConstructor._(_i1.JSObject _) implements _i1.JSObject { external RepoConstructor(_i1.JSArray<_i1.JSString> args); } + +/// Describes a service with asynchronous operations. +/// **EXPERIMENTAL**: This API is under evaluation and may change. +@_i2.experimental extension type AsyncService._(_i1.JSObject _) implements _i1.JSObject { + /// Fetches remote data from a URL. + /// - [url]: - The resource endpoint. external _i1.JSPromise<_i1.JSAny?> fetchData(String url); + + /// Updates data on the server. + /// - [id]: - The resource ID. + /// - [payload]: - The update content. + /// + /// Returns `true` if update succeeded, otherwise `false`. external _i1.JSPromise<_i1.JSBoolean> updateData( String id, String payload, ); } + +/// Represents a basic user. extension type User._(_i1.JSObject _) implements _i1.JSObject { + /// Unique identifier. external String id; + /// User's email address. external String email; + /// Returns a human-readable description of the user. external _i1.JSFunction? get describe; } + +/// An administrator user with logging capabilities. extension type Admin._(_i1.JSObject _) implements User, ILogger { + /// Admin role label. external String role; + /// Grants the given permission. + /// - [permission]: - A named permission string. external void grantPermission(String permission); } + +/// Configuration environment. extension type Config._(_i1.JSObject _) implements _i1.JSObject { + /// Environment name (e.g., 'production', 'dev'). external String env; -} -@_i1.JS('Config') -extension type Config$1._(_i1.JSObject _) implements _i1.JSObject { + + /// Whether debug mode is enabled. external bool debug; } + +/// Represents a resource that requires authentication. extension type SecureResource._(_i1.JSObject _) implements _i1.JSObject { + /// A token used for authentication. external String accessToken; + /// Authenticates the resource. external bool authenticate(); } + +/// A global dictionary instance. @_i1.JS() external Dictionary get dict; + +/// Root node of a linked list. @_i1.JS() external LinkedList get rootList; + +/// A numeric comparator for sorting numbers. @_i1.JS() external Comparator<_i1.JSNumber> get compareNumbers; extension type const AnonymousUnion._(String _) { @@ -75,6 +141,9 @@ extension type const AnonymousUnion._(String _) { static const AnonymousUnion error = AnonymousUnion._('error'); } + +/// A basic self-referencing linked list node. extension type LinkedList._(_i1.JSObject _) implements _i1.JSObject { + /// Returns the next node in the list. external LinkedList next(); }