[interop] Add Support for Function Declarations (#392)

* Interop Gen: Support Function Declarations
Fixes #389

* code resolution

* changed primitive types from `enum` to `static const` types

* minor changes

* formatting

* resolved code issues, added test cases, and refactored primitive type handling

* Comment Resolution:

- enum switch expression refactoring
- other fixes

* completed comment resolution
diff --git a/web_generator/lib/src/ast.dart b/web_generator/lib/src/ast.dart
deleted file mode 100644
index 14a43a6..0000000
--- a/web_generator/lib/src/ast.dart
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
-// 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 'package:code_builder/code_builder.dart';
-
-import 'interop_gen/generate.dart';
-import 'interop_gen/namer.dart';
-
-sealed class Node {
-  abstract final String? name;
-  abstract final ID id;
-  final String? dartName;
-
-  Node() : dartName = null;
-}
-
-abstract class Declaration extends Node {
-  @override
-  abstract final String name;
-
-  Spec emit();
-}
-
-abstract class NamedDeclaration extends Declaration {
-  ReferredType asReferredType([List<Type>? typeArgs]) =>
-      ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []);
-}
-
-abstract interface class ExportableDeclaration extends Declaration {
-  /// Whether this declaration is exported.
-  bool get exported;
-}
-
-abstract class Type extends Node {
-  Reference emit();
-}
-
-enum PrimitiveType implements Type {
-  string('string'),
-  any('any'),
-  object('object'),
-  number('number'),
-  boolean('boolean'),
-  undefined('undefined'),
-  unknown('unknown');
-
-  const PrimitiveType(this.name);
-
-  @override
-  final String name;
-
-  @override
-  ID get id => ID(type: 'type', name: name);
-
-  // TODO(https://github.com/dart-lang/web/pull/386): Configuration options: double and num
-  @override
-  Reference emit() {
-    return switch (this) {
-      PrimitiveType.string => refer('String'),
-      PrimitiveType.any => refer('JSAny', 'dart:js_interop'),
-      PrimitiveType.object => refer('JSObject', 'dart:js_interop'),
-      PrimitiveType.number => refer('int'),
-      PrimitiveType.boolean => refer('bool'),
-      PrimitiveType.undefined => TypeReference((t) => t
-        ..symbol = 'JSAny'
-        ..url = 'dart:js_interop'
-        ..isNullable = true),
-      PrimitiveType.unknown => TypeReference((t) => t
-        ..symbol = 'JSAny'
-        ..url = 'dart:js_interop'
-        ..isNullable = true)
-    };
-  }
-
-  @override
-  String? get dartName => null;
-}
-
-// TODO(): Refactor name - not all types can be referred to
-//  (only specific types) Instead change this
-//  to represent `typeof` declarations.
-// TODO(): Create a shared type for such types that
-//  can be referred to (i.e namespace, interface, class)
-//  as a type `ReferrableDeclaration`.
-class ReferredType<T extends Declaration> extends Type {
-  @override
-  String name;
-
-  @override
-  ID get id => ID(type: 'type', name: name);
-
-  T declaration;
-
-  List<Type> typeParams;
-
-  ReferredType(
-      {required this.name,
-      required this.declaration,
-      this.typeParams = const []});
-
-  @override
-  Reference emit() {
-    // TODO: implement emit
-    throw UnimplementedError();
-  }
-}
-
-// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`)
-class UnionType extends Type {
-  List<Type> types;
-
-  UnionType({required this.types});
-
-  @override
-  ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|'));
-
-  @override
-  Reference emit() {
-    throw UnimplementedError();
-  }
-
-  @override
-  String? get name => null;
-}
-
-class VariableDeclaration extends NamedDeclaration
-    implements ExportableDeclaration {
-  /// The variable modifier, as represented in TypeScript
-  VariableModifier modifier;
-
-  @override
-  String name;
-
-  Type type;
-
-  @override
-  bool exported;
-
-  VariableDeclaration(
-      {required this.name,
-      required this.type,
-      required this.modifier,
-      required this.exported});
-
-  @override
-  ID get id => ID(type: 'var', name: name);
-
-  @override
-  Spec emit() {
-    if (modifier == VariableModifier.$const) {
-      return Method((m) => m
-        ..name = name
-        ..type = MethodType.getter
-        ..annotations.add(generateJSAnnotation())
-        ..external = true
-        ..returns = type.emit());
-    } else {
-      // getter and setter -> single variable
-      return Field((f) => f
-        ..external = true
-        ..name = name
-        ..type = type.emit()
-        ..annotations.add(generateJSAnnotation()));
-    }
-  }
-}
-
-enum VariableModifier { let, $const, $var }
diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart
new file mode 100644
index 0000000..6ebc13c
--- /dev/null
+++ b/web_generator/lib/src/ast/base.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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 'package:code_builder/code_builder.dart';
+
+import '../interop_gen/namer.dart';
+import 'types.dart';
+
+class GlobalOptions {
+  static int variardicArgsCount = 4;
+  static bool shouldEmitJsTypes = false;
+}
+
+class Options {}
+
+// TODO(nikeokoronkwo): Remove this once we address isNullable
+class DeclarationOptions extends Options {
+  DeclarationOptions();
+
+  TypeOptions toTypeOptions({bool nullable = false}) =>
+      TypeOptions(nullable: nullable);
+}
+
+class TypeOptions extends Options {
+  bool nullable;
+
+  TypeOptions({this.nullable = false});
+}
+
+class ASTOptions {
+  bool parameter;
+  bool emitJSTypes;
+  int variardicArgsCount;
+
+  ASTOptions(
+      {this.parameter = false,
+      this.variardicArgsCount = 4,
+      this.emitJSTypes = false});
+}
+
+sealed class Node {
+  abstract final String? name;
+  abstract final ID id;
+  String? get dartName;
+
+  Spec emit([Options? options]);
+
+  Node();
+}
+
+abstract class Declaration extends Node {
+  @override
+  abstract final String name;
+
+  @override
+  Spec emit([covariant DeclarationOptions? options]);
+}
+
+abstract class NamedDeclaration extends Declaration {
+  ReferredType asReferredType([List<Type>? typeArgs]) =>
+      ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []);
+}
+
+abstract interface class ExportableDeclaration extends Declaration {
+  /// Whether this declaration is exported.
+  bool get exported;
+}
+
+abstract class Type extends Node {
+  @override
+  String? dartName;
+
+  @override
+  Reference emit([covariant TypeOptions? options]);
+}
diff --git a/web_generator/lib/src/ast/builtin.dart b/web_generator/lib/src/ast/builtin.dart
new file mode 100644
index 0000000..563572c
--- /dev/null
+++ b/web_generator/lib/src/ast/builtin.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// ignore_for_file: non_constant_identifier_names
+
+import 'package:code_builder/code_builder.dart';
+
+import '../interop_gen/namer.dart';
+import 'base.dart';
+
+/// A built in type supported by `dart:js_interop` or by this library
+/// (with generated declarations)
+class BuiltinType extends Type {
+  @override
+  final String name;
+
+  final List<Type> typeParams;
+
+  /// Whether the given type is present in "dart:js_interop"
+  final bool fromDartJSInterop;
+
+  // TODO(nikeokoronkwo): Types in general should have an `isNullable`
+  //  property on them to indicate nullability for Dart generated code.
+  final bool? isNullable;
+
+  BuiltinType(
+      {required this.name,
+      this.typeParams = const [],
+      this.fromDartJSInterop = false,
+      this.isNullable});
+
+  @override
+  ID get id => ID(type: 'type', name: name);
+
+  @override
+  String? get dartName => null;
+
+  @override
+  Reference emit([TypeOptions? options]) {
+    options ??= TypeOptions();
+
+    return TypeReference((t) => t
+      ..symbol = name
+      ..types.addAll(typeParams
+          // if there is only one type param, and it is void, ignore
+          .where((p) => typeParams.length != 1 || p != $voidType)
+          .map((p) => p.emit(TypeOptions())))
+      ..url = fromDartJSInterop ? 'dart:js_interop' : null
+      ..isNullable = isNullable ?? options!.nullable);
+  }
+
+  static final BuiltinType $voidType = BuiltinType(name: 'void');
+  static final BuiltinType anyType =
+      BuiltinType(name: 'JSAny', fromDartJSInterop: true, isNullable: true);
+
+  static BuiltinType primitiveType(PrimitiveType typeIdentifier,
+      {bool? shouldEmitJsType,
+      bool? isNullable,
+      List<Type> typeParams = const []}) {
+    shouldEmitJsType ??= GlobalOptions.shouldEmitJsTypes;
+    return switch (typeIdentifier) {
+      PrimitiveType.int ||
+      PrimitiveType.num ||
+      PrimitiveType.double when shouldEmitJsType =>
+        BuiltinType(
+            name: 'JSNumber', fromDartJSInterop: true, isNullable: isNullable),
+      PrimitiveType.int => BuiltinType(name: 'int', isNullable: isNullable),
+      PrimitiveType.num => BuiltinType(name: 'num', isNullable: isNullable),
+      PrimitiveType.double =>
+        BuiltinType(name: 'double', isNullable: isNullable),
+      PrimitiveType.boolean => shouldEmitJsType
+          ? BuiltinType(
+              name: 'JSBoolean',
+              fromDartJSInterop: true,
+              isNullable: isNullable)
+          : BuiltinType(name: 'bool', isNullable: isNullable),
+      PrimitiveType.string => shouldEmitJsType
+          ? BuiltinType(
+              name: 'JSString', fromDartJSInterop: true, isNullable: isNullable)
+          : BuiltinType(name: 'String', isNullable: isNullable),
+      PrimitiveType.$void || PrimitiveType.undefined => $voidType,
+      PrimitiveType.any || PrimitiveType.unknown => anyType,
+      PrimitiveType.object => BuiltinType(
+          name: 'JSObject', fromDartJSInterop: true, isNullable: isNullable),
+      PrimitiveType.array => BuiltinType(
+          name: 'JSArray',
+          typeParams: [typeParams.single],
+          fromDartJSInterop: true,
+          isNullable: isNullable),
+      PrimitiveType.promise => BuiltinType(
+          name: 'JSPromise',
+          typeParams: [typeParams.single],
+          fromDartJSInterop: true,
+          isNullable: isNullable),
+      PrimitiveType.function => BuiltinType(
+          name: 'JSFunction', fromDartJSInterop: true, isNullable: isNullable),
+    };
+  }
+}
+
+enum PrimitiveType {
+  int,
+  num,
+  double,
+  boolean,
+  string,
+  $void,
+  any,
+  object,
+  unknown,
+  undefined,
+  array,
+  promise,
+  function
+}
diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart
new file mode 100644
index 0000000..1b02774
--- /dev/null
+++ b/web_generator/lib/src/ast/declarations.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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 'package:code_builder/code_builder.dart';
+
+import '../interop_gen/namer.dart';
+import 'base.dart';
+import 'helpers.dart';
+import 'types.dart';
+
+class VariableDeclaration extends NamedDeclaration
+    implements ExportableDeclaration {
+  /// The variable modifier, as represented in TypeScript
+  VariableModifier modifier;
+
+  @override
+  String name;
+
+  Type type;
+
+  @override
+  bool exported;
+
+  VariableDeclaration(
+      {required this.name,
+      required this.type,
+      required this.modifier,
+      required this.exported});
+
+  @override
+  ID get id => ID(type: 'var', name: name);
+
+  @override
+  Spec emit([DeclarationOptions? options]) {
+    if (modifier == VariableModifier.$const) {
+      return Method((m) => m
+        ..name = name
+        ..type = MethodType.getter
+        ..annotations.add(generateJSAnnotation())
+        ..external = true
+        ..returns = type.emit());
+    } else {
+      // getter and setter -> single variable
+      return Field((f) => f
+        ..external = true
+        ..name = name
+        ..type = type.emit()
+        ..annotations.add(generateJSAnnotation()));
+    }
+  }
+
+  @override
+  String? get dartName => null;
+}
+
+enum VariableModifier { let, $const, $var }
+
+class FunctionDeclaration extends NamedDeclaration
+    implements ExportableDeclaration {
+  @override
+  final String name;
+
+  @override
+  final String? dartName;
+
+  final List<ParameterDeclaration> parameters;
+
+  final List<GenericType> typeParameters;
+
+  final Type returnType;
+
+  @override
+  bool exported;
+
+  @override
+  ID id;
+
+  FunctionDeclaration(
+      {required this.name,
+      required this.id,
+      this.dartName,
+      this.parameters = const [],
+      this.typeParameters = const [],
+      required this.exported,
+      required this.returnType});
+
+  @override
+  Spec emit([DeclarationOptions? options]) {
+    options ??= DeclarationOptions();
+
+    final requiredParams = <Parameter>[];
+    final optionalParams = <Parameter>[];
+    for (final p in parameters) {
+      if (p.variardic) {
+        optionalParams.addAll(spreadParam(p, GlobalOptions.variardicArgsCount));
+        requiredParams.add(p.emit(options));
+      } else {
+        if (p.optional) {
+          optionalParams.add(p.emit(options));
+        } else {
+          requiredParams.add(p.emit(options));
+        }
+      }
+    }
+
+    return Method((m) => m
+      ..external = true
+      ..name = dartName ?? name
+      ..annotations.add(generateJSAnnotation(
+          dartName == null || dartName == name ? null : name))
+      ..types
+          .addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions())))
+      ..returns = returnType.emit()
+      ..requiredParameters.addAll(requiredParams)
+      ..optionalParameters.addAll(optionalParams));
+  }
+}
+
+class ParameterDeclaration {
+  final String name;
+
+  final bool optional;
+
+  final Type type;
+
+  final bool variardic;
+
+  ParameterDeclaration(
+      {required this.name,
+      this.optional = false,
+      required this.type,
+      this.variardic = false});
+
+  Parameter emit([DeclarationOptions? options]) {
+    return Parameter((p) => p
+      ..name = name
+      ..type = type.emit(TypeOptions(nullable: optional)));
+  }
+}
diff --git a/web_generator/lib/src/ast/helpers.dart b/web_generator/lib/src/ast/helpers.dart
new file mode 100644
index 0000000..e987ccd
--- /dev/null
+++ b/web_generator/lib/src/ast/helpers.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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 'package:code_builder/code_builder.dart';
+
+import 'base.dart';
+import 'builtin.dart';
+import 'declarations.dart';
+
+BuiltinType? getSupportedType(String name, [List<Type> typeParams = const []]) {
+  final type = switch (name) {
+    'Array' => PrimitiveType.array,
+    'Promise' => PrimitiveType.promise,
+    _ => null
+  };
+
+  if (type == null) return null;
+
+  return BuiltinType.primitiveType(type, typeParams: [
+    getJSTypeAlternative(typeParams.singleOrNull ?? BuiltinType.anyType)
+  ]);
+}
+
+Type getJSTypeAlternative(Type type) {
+  if (type is BuiltinType) {
+    if (type.fromDartJSInterop) return type;
+
+    final primitiveType = switch (type.name) {
+      'num' => PrimitiveType.num,
+      'int' => PrimitiveType.int,
+      'double' => PrimitiveType.double,
+      'String' => PrimitiveType.string,
+      'bool' => PrimitiveType.boolean,
+      _ => null
+    };
+
+    if (primitiveType == null) return BuiltinType.anyType;
+
+    return BuiltinType.primitiveType(primitiveType, shouldEmitJsType: true);
+  }
+  return type;
+}
+
+Expression generateJSAnnotation([String? name]) {
+  return refer('JS', 'dart:js_interop')
+      .call([if (name != null) literalString(name)]);
+}
+
+List<Parameter> spreadParam(ParameterDeclaration p, int count) {
+  return List.generate(count - 1, (i) {
+    final paramNumber = i + 2;
+    final paramName = '${p.name}$paramNumber';
+    return ParameterDeclaration(name: paramName, type: p.type).emit();
+  });
+}
diff --git a/web_generator/lib/src/ast/types.dart b/web_generator/lib/src/ast/types.dart
new file mode 100644
index 0000000..e3d789b
--- /dev/null
+++ b/web_generator/lib/src/ast/types.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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 'package:code_builder/code_builder.dart';
+import '../interop_gen/namer.dart';
+import 'base.dart';
+
+class ReferredType<T extends Declaration> extends Type {
+  @override
+  String name;
+
+  @override
+  ID get id => ID(type: 'type', name: name);
+
+  T declaration;
+
+  List<Type> typeParams;
+
+  ReferredType(
+      {required this.name,
+      required this.declaration,
+      this.typeParams = const []});
+
+  @override
+  Reference emit([TypeOptions? options]) {
+    // TODO: implement emit
+    throw UnimplementedError();
+  }
+}
+
+// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`)
+class UnionType extends Type {
+  List<Type> types;
+
+  UnionType({required this.types});
+
+  @override
+  ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|'));
+
+  @override
+  Reference emit([TypeOptions? options]) {
+    throw UnimplementedError('TODO: Implement UnionType.emit');
+  }
+
+  @override
+  String? get name => null;
+}
+
+/// The base class for a type generic (like 'T')
+class GenericType extends Type {
+  @override
+  final String name;
+
+  final Type? constraint;
+
+  final Declaration? parent;
+
+  GenericType({required this.name, this.constraint, this.parent});
+
+  @override
+  Reference emit([TypeOptions? options]) => TypeReference((t) => t
+    ..symbol = name
+    ..bound = constraint?.emit()
+    ..isNullable = options?.nullable);
+
+  @override
+  ID get id =>
+      ID(type: 'generic-type', name: '$name@${parent?.id ?? "(anonymous)"}');
+}
diff --git a/web_generator/lib/src/interop_gen/generate.dart b/web_generator/lib/src/interop_gen/generate.dart
deleted file mode 100644
index 54ce2d3..0000000
--- a/web_generator/lib/src/interop_gen/generate.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
-// 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 'package:code_builder/code_builder.dart';
-
-Expression generateJSAnnotation([String? name]) {
-  return refer('JS', 'dart:js_interop')
-      .call([if (name != null) literalString(name)]);
-}
diff --git a/web_generator/lib/src/interop_gen/namer.dart b/web_generator/lib/src/interop_gen/namer.dart
index 3725c11..dd07115 100644
--- a/web_generator/lib/src/interop_gen/namer.dart
+++ b/web_generator/lib/src/interop_gen/namer.dart
@@ -2,6 +2,8 @@
 // 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 '../banned_names.dart';
+
 class ID {
   final String type;
   final String name;
@@ -21,10 +23,44 @@
   UniqueNamer([Iterable<String> used = const <String>[]])
       : _usedNames = used.toSet();
 
+  /// Creates a unique name and ID for a given declaration to prevent
+  /// name collisions in Dart applications
+  ///
+  /// (Dart does not support operator overloading)
+  ({ID id, String name}) makeUnique(String name, String type) {
+    // nested structures (and anonymous structures) may not have a name
+    if (name.isEmpty) {
+      name = 'unnamed';
+    }
+
+    var newName = name;
+    if (keywords.contains(newName)) {
+      newName = '$newName\$';
+    }
+
+    var i = 0;
+    while (_usedNames.contains(newName)) {
+      ++i;
+      newName = '$name\$$i';
+    }
+
+    markUsed(newName);
+    return (
+      id: ID(type: type, name: name, index: i == 0 ? null : i),
+      name: newName
+    );
+  }
+
   static ID parse(String id) {
     String? index;
-    final [type, name, ...ids] = id.split('#');
-    if (ids.isEmpty) index = ids.single;
+    String name;
+    final [type, ...parts] = id.split('#');
+    if (parts.isEmpty) {
+      throw Exception('Invalid ID: $id');
+    } else {
+      name = parts[0];
+      if (parts.length > 1) index = parts[1];
+    }
 
     return ID(
         type: type, name: name, index: index == null ? null : int.parse(index));
diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart
index f6c0a78..663786b 100644
--- a/web_generator/lib/src/interop_gen/transform.dart
+++ b/web_generator/lib/src/interop_gen/transform.dart
@@ -7,7 +7,7 @@
 import 'package:code_builder/code_builder.dart';
 import 'package:dart_style/dart_style.dart';
 
-import '../ast.dart';
+import '../ast/base.dart';
 import '../js/typescript.dart' as ts;
 import '../js/typescript.types.dart';
 import 'namer.dart';
@@ -29,9 +29,9 @@
       final specs = declMap.decls.values.map((d) {
         return switch (d) {
           final Declaration n => n.emit(),
-          final Type t => t.emit(),
+          final Type _ => null,
         };
-      });
+      }).whereType<Spec>();
       final lib = Library((l) => l..body.addAll(specs));
       return MapEntry(file, formatter.format('${lib.accept(emitter)}'));
     });
diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart
index 0da0e79..404c299 100644
--- a/web_generator/lib/src/interop_gen/transform/transformer.dart
+++ b/web_generator/lib/src/interop_gen/transform/transformer.dart
@@ -3,7 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:js_interop';
-import '../../ast.dart';
+import '../../ast/base.dart';
+import '../../ast/builtin.dart';
+import '../../ast/declarations.dart';
+import '../../ast/helpers.dart';
+import '../../ast/types.dart';
 import '../../js/typescript.dart' as ts;
 import '../../js/typescript.types.dart';
 import '../namer.dart';
@@ -41,6 +45,8 @@
         nodeMap.addAll({for (final d in decs) d.id.toString(): d});
       default:
         final Declaration decl = switch (node.kind) {
+          TSSyntaxKind.FunctionDeclaration =>
+            _transformFunction(node as TSFunctionDeclaration),
           _ => throw Exception('Unsupported Declaration Kind: ${node.kind}')
         };
         // ignore: dead_code This line will not be dead in future decl additions
@@ -50,6 +56,180 @@
     nodes.add(node);
   }
 
+  List<Declaration> _transformVariable(TSVariableStatement variable) {
+    // get the modifier of the declaration
+    final modifiers = variable.modifiers.toDart;
+    final isExported = modifiers.any((m) {
+      return m.kind == TSSyntaxKind.ExportKeyword;
+    });
+
+    var modifier = VariableModifier.$var;
+
+    if ((variable.declarationList.flags & TSNodeFlags.Const) != 0) {
+      modifier = VariableModifier.$const;
+    } else if ((variable.declarationList.flags & TSNodeFlags.Let) != 0) {
+      modifier = VariableModifier.let;
+    }
+
+    return variable.declarationList.declarations.toDart.map((d) {
+      namer.markUsed(d.name.text);
+      return VariableDeclaration(
+          name: d.name.text,
+          type: d.type == null ? BuiltinType.anyType : _transformType(d.type!),
+          modifier: modifier,
+          exported: isExported);
+    }).toList();
+  }
+
+  TSNode? _getDeclarationByName(TSIdentifier name) {
+    final symbol = typeChecker.getSymbolAtLocation(name);
+
+    final declarations = symbol?.getDeclarations();
+    // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file,
+    //  and may be from an import statement
+    //  We should be able to handle these
+    return declarations?.toDart.first;
+  }
+
+  FunctionDeclaration _transformFunction(TSFunctionDeclaration function) {
+    final name = function.name.text;
+
+    final modifiers = function.modifiers.toDart;
+    final isExported = modifiers.any((m) {
+      return m.kind == TSSyntaxKind.ExportKeyword;
+    });
+
+    final params = function.parameters.toDart;
+
+    final typeParams = function.typeParameters?.toDart;
+
+    final (id: id, name: uniqueName) = namer.makeUnique(name, 'fun');
+
+    return FunctionDeclaration(
+        name: name,
+        id: id,
+        dartName: uniqueName,
+        exported: isExported,
+        parameters: params.map(_transformParameter).toList(),
+        typeParameters:
+            typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
+        returnType: function.type != null
+            ? _transformType(function.type!)
+            : BuiltinType.anyType);
+  }
+
+  ParameterDeclaration _transformParameter(TSParameterDeclaration parameter) {
+    final type = parameter.type != null
+        ? _transformType(parameter.type!, parameter: true)
+        : BuiltinType.anyType;
+    final isOptional = parameter.questionToken != null;
+    final isVariardic = parameter.dotDotDotToken != null;
+
+    // what kind of parameter is this
+    switch (parameter.name.kind) {
+      case TSSyntaxKind.Identifier:
+        return ParameterDeclaration(
+            name: (parameter.name as TSIdentifier).text,
+            type: type,
+            variardic: isVariardic,
+            optional: isOptional);
+      default:
+        // TODO: Support Destructured Object Parameters
+        //  and Destructured Array Parameters
+        throw Exception('Unsupported Parameter Name kind ${parameter.kind}');
+    }
+  }
+
+  GenericType _transformTypeParamDeclaration(
+      TSTypeParameterDeclaration typeParam) {
+    return GenericType(
+        name: typeParam.name.text,
+        constraint: typeParam.constraint == null
+            ? BuiltinType.anyType
+            : _transformType(typeParam.constraint!));
+  }
+
+  /// Parses the type
+  ///
+  /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`)
+  /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types
+  Type _transformType(TSTypeNode type, {bool parameter = false}) {
+    if (type.kind == TSSyntaxKind.UnionType) {
+      final unionType = type as TSUnionTypeNode;
+      return UnionType(
+          types: unionType.types.toDart.map<Type>(_transformType).toList());
+    }
+
+    if (type.kind == TSSyntaxKind.TypeReference) {
+      final refType = type as TSTypeReferenceNode;
+
+      final name = refType.typeName.text;
+      final typeArguments = refType.typeArguments?.toDart;
+
+      var declarationsMatching = nodeMap.findByName(name);
+
+      if (declarationsMatching.isEmpty) {
+        // check if builtin
+        // TODO(https://github.com/dart-lang/web/issues/380): A better name
+        //  for this, and adding support for "supported declarations"
+        //  (also a better name for that)
+        final supportedType = getSupportedType(
+            name, (typeArguments ?? []).map(_transformType).toList());
+        if (supportedType != null) {
+          return supportedType;
+        }
+
+        // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
+        final declaration = _getDeclarationByName(refType.typeName);
+
+        if (declaration == null) {
+          throw Exception('Found no declaration matching $name');
+        }
+
+        if (declaration.kind == TSSyntaxKind.TypeParameter) {
+          return GenericType(name: name);
+        }
+
+        transform(declaration);
+
+        declarationsMatching = nodeMap.findByName(name);
+      }
+
+      // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
+      final firstNode =
+          declarationsMatching.whereType<NamedDeclaration>().first;
+
+      return firstNode.asReferredType(
+        (typeArguments ?? []).map(_transformType).toList(),
+      );
+    }
+
+    if (type.kind == TSSyntaxKind.ArrayType) {
+      return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [
+        getJSTypeAlternative(
+            _transformType((type as TSArrayTypeNode).elementType))
+      ]);
+    }
+
+    // check for primitive type via its kind
+    final primitiveType = switch (type.kind) {
+      TSSyntaxKind.ArrayType => PrimitiveType.array,
+      TSSyntaxKind.StringKeyword => PrimitiveType.string,
+      TSSyntaxKind.AnyKeyword => PrimitiveType.any,
+      TSSyntaxKind.ObjectKeyword => PrimitiveType.object,
+      TSSyntaxKind.NumberKeyword =>
+        (parameter ? PrimitiveType.num : PrimitiveType.double),
+      TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined,
+      TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown,
+      TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean,
+      TSSyntaxKind.VoidKeyword => PrimitiveType.$void,
+      _ => throw UnsupportedError(
+          'The given type with kind ${type.kind} is not supported yet')
+    };
+
+    return BuiltinType.primitiveType(primitiveType);
+  }
+
   NodeMap filter() {
     final filteredDeclarations = NodeMap();
 
@@ -66,7 +246,7 @@
             filteredDeclarations.add(e);
           }
           break;
-        case final PrimitiveType _:
+        case final BuiltinType _:
           // primitive types are generated by default
           break;
         case Type():
@@ -94,15 +274,29 @@
 
     switch (decl) {
       case final VariableDeclaration v:
-        if (v.type is! PrimitiveType) filteredDeclarations.add(v.type);
+        if (v.type is! BuiltinType) filteredDeclarations.add(v.type);
+        break;
+      case final FunctionDeclaration f:
+        if (f.returnType is! BuiltinType) {
+          filteredDeclarations.add(f.returnType);
+        }
+        filteredDeclarations.addAll({
+          for (final node in f.parameters.map((p) => p.type))
+            node.id.toString(): node
+        });
+        filteredDeclarations.addAll({
+          for (final node
+              in f.typeParameters.map((p) => p.constraint).whereType<Type>())
+            node.id.toString(): node
+        });
         break;
       case final UnionType u:
         filteredDeclarations.addAll({
-          for (final t in u.types.where((t) => t is! PrimitiveType))
+          for (final t in u.types.where((t) => t is! BuiltinType))
             t.id.toString(): t
         });
         break;
-      case final PrimitiveType _:
+      case final BuiltinType _:
         // primitive types are generated by default
         break;
       default:
@@ -121,95 +315,4 @@
 
     return filteredDeclarations;
   }
-
-  List<Declaration> _transformVariable(TSVariableStatement variable) {
-    // get the modifier of the declaration
-    final modifiers = variable.modifiers.toDart;
-    final isExported = modifiers.any((m) {
-      return m.kind == TSSyntaxKind.ExportKeyword;
-    });
-
-    var modifier = VariableModifier.$var;
-
-    if ((variable.declarationList.flags & TSNodeFlags.Const) != 0) {
-      modifier = VariableModifier.$const;
-    } else if ((variable.declarationList.flags & TSNodeFlags.Let) != 0) {
-      modifier = VariableModifier.let;
-    }
-
-    return variable.declarationList.declarations.toDart.map((d) {
-      namer.markUsed(d.name.text);
-      return VariableDeclaration(
-          name: d.name.text,
-          type: d.type == null ? PrimitiveType.any : _transformType(d.type!),
-          modifier: modifier,
-          exported: isExported);
-    }).toList();
-  }
-
-  TSNode? _getDeclarationByName(TSIdentifier name) {
-    final symbol = typeChecker.getSymbolAtLocation(name);
-
-    final declarations = symbol?.getDeclarations();
-    // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file,
-    //  and may be from an import statement
-    //  We should be able to handle these
-    return declarations?.toDart.first;
-  }
-
-  /// Parses the type
-  ///
-  /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`)
-  /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types
-  Type _transformType(TSTypeNode type) {
-    if (type.kind == TSSyntaxKind.UnionType) {
-      final unionType = type as TSUnionTypeNode;
-      // parse union type
-      return UnionType(
-          types: unionType.types.toDart.map<Type>(_transformType).toList());
-    }
-
-    if (type.kind == TSSyntaxKind.TypeReference) {
-      // reference type
-      final refType = type as TSTypeReferenceNode;
-
-      final name = refType.typeName.text;
-      final typeArguments = refType.typeArguments?.toDart;
-
-      var declarationsMatching = nodeMap.findByName(name);
-      if (declarationsMatching.isEmpty) {
-        // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
-        final declaration = _getDeclarationByName(refType.typeName);
-
-        if (declaration == null) {
-          throw Exception('Found no declaration matching $name');
-        }
-
-        transform(declaration);
-
-        declarationsMatching = nodeMap.findByName(name);
-      }
-
-      // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
-      final firstNode =
-          declarationsMatching.whereType<NamedDeclaration>().first;
-
-      return firstNode.asReferredType(
-        (typeArguments ?? []).map(_transformType).toList(),
-      );
-    }
-
-    // check for its kind
-    return switch (type.kind) {
-      TSSyntaxKind.StringKeyword => PrimitiveType.string,
-      TSSyntaxKind.AnyKeyword => PrimitiveType.any,
-      TSSyntaxKind.ObjectKeyword => PrimitiveType.object,
-      TSSyntaxKind.NumberKeyword => PrimitiveType.number,
-      TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined,
-      TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown,
-      TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean,
-      _ => throw UnsupportedError(
-          'The given type with kind ${type.kind} is not supported yet')
-    };
-  }
 }
diff --git a/web_generator/lib/src/js/typescript.dart b/web_generator/lib/src/js/typescript.dart
index bc9a73d..f098057 100644
--- a/web_generator/lib/src/js/typescript.dart
+++ b/web_generator/lib/src/js/typescript.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
 @JS('ts')
 library;
 
diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart
index e6d31d3..ed8855d 100644
--- a/web_generator/lib/src/js/typescript.types.dart
+++ b/web_generator/lib/src/js/typescript.types.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
 // ignore_for_file: constant_identifier_names
 
 @JS('ts')
@@ -7,6 +11,8 @@
 
 import 'package:meta/meta.dart';
 
+import 'typescript.dart';
+
 extension type const TSSyntaxKind._(num _) {
   /// To be ignored
   static const TSSyntaxKind EndOfFileToken = TSSyntaxKind._(1);
@@ -18,6 +24,7 @@
   static const TSSyntaxKind InterfaceDeclaration = TSSyntaxKind._(264);
   static const TSSyntaxKind FunctionDeclaration = TSSyntaxKind._(262);
   static const TSSyntaxKind ExportDeclaration = TSSyntaxKind._(278);
+  static const TSSyntaxKind Parameter = TSSyntaxKind._(169);
 
   /// keywords
   static const TSSyntaxKind ExportKeyword = TSSyntaxKind._(95);
@@ -34,13 +41,17 @@
   static const TSSyntaxKind UndefinedKeyword = TSSyntaxKind._(157);
   static const TSSyntaxKind SetKeyword = TSSyntaxKind._(153);
   static const TSSyntaxKind UnknownKeyword = TSSyntaxKind._(159);
+  static const TSSyntaxKind VoidKeyword = TSSyntaxKind._(116);
 
   // types
   static const TSSyntaxKind UnionType = TSSyntaxKind._(192);
   static const TSSyntaxKind TypeReference = TSSyntaxKind._(183);
+  static const TSSyntaxKind ArrayType = TSSyntaxKind._(188);
 
   /// Other
   static const TSSyntaxKind Identifier = TSSyntaxKind._(80);
+  static const TSSyntaxKind ObjectBindingPattern = TSSyntaxKind._(206);
+  static const TSSyntaxKind ArrayBindingPattern = TSSyntaxKind._(207);
   static const TSSyntaxKind TypeParameter = TSSyntaxKind._(168);
   static const TSSyntaxKind HeritageClause = TSSyntaxKind._(298);
   static const TSSyntaxKind ExpressionWithTypeArguments = TSSyntaxKind._(233);
@@ -57,11 +68,20 @@
   external TSSyntaxKind get kind;
   external TSNode get parent;
   external TSNodeFlags get flags;
+  external String getText([TSSourceFile? sourceFile]);
+  external String getFullText([TSSourceFile? sourceFile]);
 }
 
 @JS('TypeNode')
 extension type TSTypeNode._(JSObject _) implements TSNode {}
 
+@JS('ArrayTypeNode')
+extension type TSArrayTypeNode._(JSObject _) implements TSTypeNode {
+  @redeclare
+  TSSyntaxKind get kind => TSSyntaxKind.ArrayType;
+  external TSTypeNode get elementType;
+}
+
 @JS('UnionTypeNode')
 extension type TSUnionTypeNode._(JSObject _) implements TSTypeNode {
   @redeclare
@@ -89,21 +109,47 @@
   external String get text;
 }
 
-@JS('VariableDeclaration')
+@JS('VariableStatement')
 extension type TSVariableStatement._(JSObject _) implements TSStatement {
   external TSVariableDeclarationList get declarationList;
   external TSNodeArray<TSNode> get modifiers;
 }
 
+@JS('VariableDeclarationList')
+extension type TSVariableDeclarationList._(JSObject _) implements TSNode {
+  external TSNodeArray<TSVariableDeclaration> get declarations;
+}
+
 @JS('VariableDeclaration')
 extension type TSVariableDeclaration._(JSObject _) implements TSDeclaration {
   external TSIdentifier get name;
   external TSTypeNode? get type;
 }
 
-@JS('VariableDeclarationList')
-extension type TSVariableDeclarationList._(JSObject _) implements TSNode {
-  external TSNodeArray<TSVariableDeclaration> get declarations;
+@JS('FunctionDeclaration')
+extension type TSFunctionDeclaration._(JSObject _) implements TSDeclaration {
+  external TSIdentifier get name;
+  external TSTypeNode? get type;
+  external TSNode? get asteriskToken;
+  external TSNodeArray<TSParameterDeclaration> get parameters;
+  external TSNodeArray<TSTypeParameterDeclaration>? get typeParameters;
+  external TSNodeArray<TSNode> get modifiers;
+}
+
+@JS('ParameterDeclaration')
+extension type TSParameterDeclaration._(JSObject _) implements TSDeclaration {
+  external TSNode get name;
+  external TSTypeNode? get type;
+  external TSNodeArray<TSNode>? get modifiers;
+  external TSNode? get questionToken;
+  external TSNode? get dotDotDotToken;
+}
+
+@JS('TypeParameterDeclaration')
+extension type TSTypeParameterDeclaration._(JSObject _)
+    implements TSDeclaration {
+  external TSIdentifier get name;
+  external TSTypeNode? get constraint;
 }
 
 @JS('NodeArray')
diff --git a/web_generator/lib/src/js/typescript_extensions.dart b/web_generator/lib/src/js/typescript_extensions.dart
deleted file mode 100644
index 603d62d..0000000
--- a/web_generator/lib/src/js/typescript_extensions.dart
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'typescript.types.dart';
-
-extension Names on TSSyntaxKind {
-  String get name {
-    return switch (this) {
-      TSSyntaxKind.DeclareKeyword => 'declare',
-      TSSyntaxKind.ExportKeyword => 'export',
-      TSSyntaxKind.ExtendsKeyword => 'extends',
-      TSSyntaxKind.ImplementsKeyword => 'implements',
-      TSSyntaxKind.VariableDeclaration => 'variable',
-      _ => throw UnsupportedError('The keyword is not supported at the moment')
-    };
-  }
-}
diff --git a/web_generator/test/integration/interop_gen/functions_expected.dart b/web_generator/test/integration/interop_gen/functions_expected.dart
new file mode 100644
index 0000000..4c8c2f8
--- /dev/null
+++ b/web_generator/test/integration/interop_gen/functions_expected.dart
@@ -0,0 +1,47 @@
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:js_interop' as _i1;
+
+@_i1.JS()
+external String greetUser(String name);
+@_i1.JS()
+external void logMessages(
+  _i1.JSArray<_i1.JSString> messages, [
+  _i1.JSArray<_i1.JSString> messages2,
+  _i1.JSArray<_i1.JSString> messages3,
+  _i1.JSArray<_i1.JSString> messages4,
+]);
+@_i1.JS()
+external _i1.JSPromise<U> delay<U extends _i1.JSAny?>(num ms, [U? returnValue]);
+@_i1.JS()
+external _i1.JSArray<_i1.JSNumber> toArray(num a);
+@_i1.JS()
+external double square(num a);
+@_i1.JS()
+external double pow(num a);
+@_i1.JS('pow')
+external double pow$1(num a, num power);
+@_i1.JS('toArray')
+external _i1.JSArray<_i1.JSString> toArray$1(String a);
+@_i1.JS()
+external _i1.JSObject createUser(String name, [num? age, String? role]);
+@_i1.JS()
+external T firstElement<T extends _i1.JSAny?>(_i1.JSArray<T> arr);
+@_i1.JS()
+external void throwError(String msg);
+@_i1.JS()
+external _i1.JSArray<T> wrapInArray<T extends _i1.JSAny?>(T value);
+@_i1.JS()
+external T identity<T extends _i1.JSAny?>(T value);
+@_i1.JS()
+external void someFunction<A extends _i1.JSAny?>(_i1.JSArray<A> arr);
+@_i1.JS('someFunction')
+external B someFunction$1<A extends _i1.JSAny?, B extends _i1.JSAny?>(
+  _i1.JSArray<A> arr,
+);
+@_i1.JS()
+external T logTuple<T extends _i1.JSArray<_i1.JSAny?>>(
+  T args, [
+  T args2,
+  T args3,
+  T args4,
+]);
diff --git a/web_generator/test/integration/interop_gen/functions_input.d.ts b/web_generator/test/integration/interop_gen/functions_input.d.ts
new file mode 100644
index 0000000..6fc8665
--- /dev/null
+++ b/web_generator/test/integration/interop_gen/functions_input.d.ts
@@ -0,0 +1,16 @@
+export declare function greetUser(name: string): string;
+export declare function logMessages(...messages: string[]): void;
+export declare function delay<U>(ms: number, returnValue?: U): Promise<U>;
+export declare function toArray(a: number): number[];
+export declare function square(a: number): number;
+export declare function pow(a: number): number;
+export declare function pow(a: number, power: number): number;
+export declare function toArray(a: string): string[];
+export declare function createUser(name: string, age?: number, role?: string): object;
+export declare function firstElement<T>(arr: T[]): T;
+export declare function throwError(msg: string): void;
+export declare function wrapInArray<T>(value: T): T[];
+export declare function identity<T = string>(value: T): T;
+export declare function someFunction<A>(arr: A[]): undefined;
+export declare function someFunction<A, B>(arr: A[]): B;
+export declare function logTuple<T extends any[]>(...args: T): T;
diff --git a/web_generator/test/integration/interop_gen/variables_expected.dart b/web_generator/test/integration/interop_gen/variables_expected.dart
index 2609c67..1df172a 100644
--- a/web_generator/test/integration/interop_gen/variables_expected.dart
+++ b/web_generator/test/integration/interop_gen/variables_expected.dart
@@ -2,11 +2,11 @@
 import 'dart:js_interop' as _i1;
 
 @_i1.JS()
-external int counter;
+external double counter;
 @_i1.JS()
 external String get appName;
 @_i1.JS()
-external int globalCounter;
+external double globalCounter;
 @_i1.JS()
 external _i1.JSObject globalObject;
 @_i1.JS()
@@ -14,18 +14,22 @@
 @_i1.JS()
 external String username;
 @_i1.JS()
-external int get foo;
+external double get foo;
 @_i1.JS()
-external int get bar;
+external double get bar;
 @_i1.JS()
-external int free;
+external double free;
 @_i1.JS()
-external int dom;
+external double dom;
 @_i1.JS()
 external String fred;
 @_i1.JS()
 external String doctor;
 @_i1.JS()
-external _i1.JSAny something;
+external _i1.JSAny? something;
 @_i1.JS()
 external _i1.JSAny? get maybeValue;
+@_i1.JS()
+external _i1.JSArray<_i1.JSString> get names;
+@_i1.JS()
+external _i1.JSArray<_i1.JSString> get newNames;
diff --git a/web_generator/test/integration/interop_gen/variables_input.d.ts b/web_generator/test/integration/interop_gen/variables_input.d.ts
index 933449b..1bc7d05 100644
--- a/web_generator/test/integration/interop_gen/variables_input.d.ts
+++ b/web_generator/test/integration/interop_gen/variables_input.d.ts
@@ -9,3 +9,5 @@
 export declare let fred: string, doctor: string;
 export declare let something: any;
 export declare const maybeValue: unknown;
+export declare const names: string[];
+export declare const newNames: Array<string>;