[interop] Add Support for JSDoc Documentation (#435)
* wip: docs
* completed doc integration
* made fields final and added const for performance
* resolved issues
* refactored `generateFromDocumentation` and redundant annotation transform
refactored `generateFromDocumentation` for nullable docs and removed unnecessary add for annotations in `transformer.dart`
diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart
index c158715..54158a6 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 @@
@override
abstract String name;
+ abstract Documentation? documentation;
+
ReferredType asReferredType([List<Type>? 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 2c7367b..6fc2357 100644
--- a/web_generator/lib/src/ast/declarations.dart
+++ b/web_generator/lib/src/ast/declarations.dart
@@ -8,6 +8,7 @@
import '../js/typescript.types.dart';
import 'base.dart';
import 'builtin.dart';
+import 'documentation.dart';
import 'helpers.dart';
import 'types.dart';
@@ -52,6 +53,9 @@
final List<ConstructorDeclaration> constructors;
+ @override
+ Documentation? documentation;
+
TypeDeclaration(
{required this.name,
this.dartName,
@@ -61,7 +65,8 @@
this.properties = const [],
this.operators = const [],
this.constructors = const [],
- this.parent});
+ this.parent,
+ this.documentation});
ExtensionType _emit(
[covariant DeclarationOptions? options,
@@ -72,6 +77,8 @@
final hierarchy = getMemberHierarchy(this);
+ final (doc, annotations) = generateFromDocumentation(documentation);
+
final fieldDecs = <Field>[];
final methodDecs = <Method>[];
@@ -98,6 +105,8 @@
: 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 @@
@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 @@
@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 @@
} 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 @@
@override
ID id;
+ @override
+ Documentation? documentation;
+
FunctionDeclaration(
{required this.name,
required this.id,
@@ -224,16 +245,20 @@
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 @@
@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 @@
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 @@
@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 @@
@override
Set<TSNode> nodes = {};
+ @override
+ Documentation? documentation;
+
NamespaceDeclaration(
{required this.name,
this.exported = true,
@@ -430,13 +478,17 @@
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 = <Method>[];
final fields = <Field>[];
@@ -525,6 +577,8 @@
// put them together...
return ExtensionType((eType) => eType
+ ..docs.addAll([...doc])
+ ..annotations.addAll([...annotations])
..name = completedDartName
..annotations.addAll([
if (parent != null)
@@ -565,7 +619,8 @@
super.constructors,
required super.methods,
required super.properties,
- super.operators});
+ super.operators,
+ super.documentation});
@override
ExtensionType emit([covariant DeclarationOptions? options]) {
@@ -602,7 +657,8 @@
super.methods,
super.properties,
super.operators,
- super.constructors})
+ super.constructors,
+ super.documentation})
: _id = id;
@override
@@ -642,6 +698,9 @@
@override
Type type;
+ @override
+ Documentation? documentation;
+
PropertyDeclaration(
{required this.name,
this.dartName,
@@ -650,15 +709,20 @@
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 @@
..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 @@
final bool isNullable;
+ @override
+ Documentation? documentation;
+
MethodDeclaration(
{required this.name,
this.dartName,
@@ -722,12 +791,15 @@
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 @@
if (isNullable) {
return Method((m) => m
+ ..docs.addAll([...doc])
+ ..annotations.addAll([...annotations])
..external = true
..name = dartName ?? name
..type = MethodType.getter
@@ -753,6 +827,8 @@
}
return Method((m) => m
+ ..docs.addAll([...doc])
+ ..annotations.addAll([...annotations])
..external = true
..name = dartName ?? name
..type = switch (kind) {
@@ -804,12 +880,15 @@
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 @@
Constructor emit([covariant DeclarationOptions? options]) {
options ??= DeclarationOptions();
+ final (doc, annotations) = generateFromDocumentation(documentation);
final (requiredParams, optionalParams) =
emitParameters(parameters, options);
@@ -826,6 +906,8 @@
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 @@
final bool static;
+ @override
+ Documentation? documentation;
+
OperatorDeclaration(
{required this.kind,
this.dartName,
@@ -876,12 +961,15 @@
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 = <Parameter>[];
final optionalParams = <Parameter>[];
for (final p in parameters) {
@@ -896,6 +984,8 @@
}
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 0000000..3f9ebb8
--- /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<Annotation> 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 = <Expression>[];
+ final namedArgs = <String, Expression>{};
+
+ 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 acd3c82..472ade6 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 @@
}
}
+(List<String>, List<Expression>) 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<Parameter>, List<Parameter>) emitParameters(
List<ParameterDeclaration> 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 d209f4d..e2e3634 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 '../../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 @@
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 @@
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 @@
methods: [],
properties: [],
operators: [],
- constructors: [])
+ constructors: [],
+ documentation: _parseAndTransformDocumentation(typeDecl))
: ClassDeclaration(
name: name,
dartName: dartName,
@@ -423,7 +427,8 @@
constructors: [],
methods: [],
properties: [],
- operators: []);
+ operators: [],
+ documentation: _parseAndTransformDocumentation(typeDecl));
final typeNamer = ScopedUniqueNamer({'get', 'set'});
@@ -528,7 +533,8 @@
: _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 @@
? _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 @@
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 @@
returnType: methodType ??
(callSignature.type != null
? _transformType(callSignature.type!)
- : BuiltinType.anyType));
+ : BuiltinType.anyType),
+ documentation: _parseAndTransformDocumentation(callSignature));
methodDeclaration.parent = parent;
return methodDeclaration;
}
@@ -686,6 +695,8 @@
indexerType = parent.asReferredType(parent.typeParameters);
}
+ final doc = _parseAndTransformDocumentation(indexSignature);
+
final getOperatorDeclaration = OperatorDeclaration(
kind: OperatorKind.squareBracket,
parameters: params.map(_transformParameter).toList(),
@@ -693,7 +704,8 @@
scope: scope,
typeParameters:
typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
- static: isStatic);
+ static: isStatic,
+ documentation: doc);
final setOperatorDeclaration = isReadonly
? OperatorDeclaration(
kind: OperatorKind.squareBracketSet,
@@ -702,7 +714,8 @@
scope: scope,
typeParameters:
typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
- static: isStatic)
+ static: isStatic,
+ documentation: doc)
: null;
getOperatorDeclaration.parent = parent;
@@ -748,7 +761,8 @@
returnType: methodType ??
(getter.type != null
? _transformType(getter.type!)
- : BuiltinType.anyType));
+ : BuiltinType.anyType),
+ documentation: _parseAndTransformDocumentation(getter));
methodDeclaration.parent = parent;
return methodDeclaration;
}
@@ -791,7 +805,8 @@
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 @@
typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
returnType: function.type != null
? _transformType(function.type!)
- : BuiltinType.anyType);
+ : BuiltinType.anyType,
+ documentation: _parseAndTransformDocumentation(function));
}
List<VariableDeclaration> _transformVariable(TSVariableStatement variable,
@@ -866,7 +882,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
type: _transformType(type),
typeParameters:
typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
- exported: isExported);
+ exported: isExported,
+ documentation: _parseAndTransformDocumentation(typealias));
}
ParameterDeclaration _transformParameter(TSParameterDeclaration parameter,
@@ -1601,6 +1623,138 @@
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<TSSymbolDisplayPart> topLevelDocParts,
+ List<JSDocTagInfo> jsDocTags) {
+ final docBuffer = StringBuffer();
+ final annotations = <Annotation>[];
+
+ 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 ?? <TSSymbolDisplayPart>[]) {
+ 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 ?? <TSSymbolDisplayPart>[]) {
+ 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 ?? <TSSymbolDisplayPart>[]) {
+ 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 ?? <TSSymbolDisplayPart>[]) {
+ 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 d43e0a5..b0c51b6 100644
--- a/web_generator/lib/src/js/typescript.types.dart
+++ b/web_generator/lib/src/js/typescript.types.dart
@@ -234,7 +234,7 @@
}
@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 @@
/// A common API for Classes and Interfaces
extension type TSObjectDeclaration<T extends TSDeclaration>._(JSObject _)
- implements TSDeclaration, TSStatement {
+ implements TSDeclarationStatement {
// TODO: May be undefined for classes in default exports
external TSIdentifier get name;
external TSNodeArray<TSNode>? get modifiers;
@@ -422,7 +422,7 @@
external TSNodeArray<TSTypeNode>? get typeArguments;
}
-extension type TSPropertyEntity._(JSObject _) implements TSDeclaration {
+extension type TSPropertyEntity._(JSObject _) implements TSNamedDeclaration {
external TSNodeArray<TSNode>? get modifiers;
external TSIdentifier get name;
external TSToken? get questionToken;
@@ -441,7 +441,7 @@
}
@JS('ClassElement')
-extension type TSClassElement._(JSObject _) implements TSDeclaration {
+extension type TSClassElement._(JSObject _) implements TSNamedDeclaration {
external TSIdentifier? get name;
}
@@ -471,7 +471,7 @@
}
@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 @@
@JS('TypeAliasDeclaration')
extension type TSTypeAliasDeclaration._(JSObject _)
- implements TSDeclaration, TSStatement {
+ implements TSDeclarationStatement, TSStatement {
external TSNodeArray<TSNode>? get modifiers;
external TSNodeArray<TSTypeParameterDeclaration>? get typeParameters;
external TSIdentifier get name;
@@ -556,14 +556,14 @@
@JS('EnumDeclaration')
extension type TSEnumDeclaration._(JSObject _)
- implements TSDeclaration, TSStatement {
+ implements TSDeclarationStatement, TSStatement {
external TSIdentifier get name;
external TSNodeArray<TSNode>? get modifiers;
external TSNodeArray<TSEnumMember> 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 TSSymbol._(JSObject _) implements JSObject {
external String get name;
external JSArray<TSDeclaration>? getDeclarations();
+ external JSArray<TSSymbolDisplayPart> getDocumentationComment(
+ TSTypeChecker? typeChecker);
+ external JSArray<JSDocTagInfo> getJsDocTags([TSTypeChecker checker]);
external TSSymbolTable? get exports;
}
typedef TSSymbolTable = JSMap<JSString, TSSymbol>;
+@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<TSSymbolDisplayPart>? 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 244db76..49d010f 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 @@
@_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 d36a423..f1923dd 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 @@
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 @@
export declare class EpahsImpl<TMeta = any> implements Epahs<TMeta> {
readonly id: string;
name: string;
- /* other decls in Shape */
metadata?: TMeta;
constructor(name: string, type?: 'circle' | 'rectangle' | 'polygon');
onUpdate?(prev: Epahs<TMeta>): 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 0000000..1d088ed
--- /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<User> 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<User> 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 0000000..702b302
--- /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<User>} A promise that resolves to a user object or null.
+ */
+export declare function getUserById(userId: string): Promise<User>;
+/**
+ * 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<User>} The created user with a generated ID.
+ */
+export declare function registerUser(newUser: User): Promise<User>;
+/**
+ * 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<void>}
+ */
+export declare function initializeSystem(): Promise<void>;
+/**
+ * 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<void>}
+ */
+export declare function shutdownSystem(): Promise<void>;
+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 0194efb..8227441 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 @@
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 @@
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 @@
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<T extends _i1.JSAny?>._(_i1.JSObject _)
implements _i1.JSObject {
@@ -241,11 +250,21 @@
@_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 9445ac4..0e0e19b 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<V extends Vector2D> 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<Point2D> {
- 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<Point3D> {
- 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<V extends Vector2D> extends Matrix, Comparator<V> {}
+
+/**
+ * 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<V extends Vector>(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 63dcada..5c63ec4 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<void>;
+ /** 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<void>;
}
+
+/**
+ * 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<T> {
- (a: T, b: T): number;
+ (a: T, b: T): number;
}
+
+/**
+ * A simple repository abstraction.
+ * @typeParam T - The type of the entity.
+ */
export interface Repository<T> {
- 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<any>;
- updateData(id: string, payload: string): Promise<boolean>;
+ /**
+ * Fetches remote data from a URL.
+ * @param url - The resource endpoint.
+ */
+ fetchData(url: string): Promise<any>;
+
+ /**
+ * 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<boolean>;
}
+
+/**
+ * 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;
+ /** Environment name (e.g., 'production', 'dev'). */
+ env: string;
+
+ /** Whether debug mode is enabled. */
+ debug: boolean;
}
-export interface Config {
- 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<number>;
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 91ffd51..914d7dc 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 @@
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 extends _i2.Vector>(
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<V extends _i2.Vector2D>._(_i1.JSObject _)
implements Matrix {
external V call(V v);
}
+
+/// A matrix that includes vector comparison capabilities.
+/// Type Name [V]: Vector2D subtype
extension type ComparatorMatrix<V extends _i2.Vector2D>._(_i1.JSObject _)
implements Matrix, _i3.Comparator<V> {}
+
+/// 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 @@
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 @@
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 46e3a01..fee72b9 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 @@
@_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 5179b4b..ba70d51 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<T extends _i1.JSAny?>._(_i1.JSObject _)
implements _i1.JSObject {
external double call(
@@ -10,60 +16,120 @@
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<T extends _i1.JSAny?>._(_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 @@
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();
}