[interop] Default to `JSAny` for unsupported type and skip unsupported declarations (#450)

* [interop] Default to `JSAny` for unsupported type and skip unsupported declarations

* added bypass as cmdline option only

* implemented testing

* re-added never type case
diff --git a/web_generator/bin/gen_interop_bindings.dart b/web_generator/bin/gen_interop_bindings.dart
index 6d2df55..129a5bb 100644
--- a/web_generator/bin/gen_interop_bindings.dart
+++ b/web_generator/bin/gen_interop_bindings.dart
@@ -89,6 +89,7 @@
       if (relativeConfigFile case final config?) '--config=$config',
       if (argResult.wasParsed('ignore-errors')) '--ignore-errors',
       if (argResult.wasParsed('generate-all')) '--generate-all',
+      if (argResult.wasParsed('strict-unsupported')) '--strict-unsupported',
     ],
     workingDirectory: bindingsGeneratorPath,
   );
@@ -120,6 +121,11 @@
       help: 'Generate all declarations '
           '(including private declarations)',
       negatable: false)
+  ..addFlag('strict-unsupported',
+      help:
+          'Treat unsupported declarations/types as errors. Only used for development of the generator',
+      negatable: false,
+      hide: true)
   ..addOption('config',
       hide: true,
       abbr: 'c',
diff --git a/web_generator/lib/src/config.dart b/web_generator/lib/src/config.dart
index a20227a..1d83d0e 100644
--- a/web_generator/lib/src/config.dart
+++ b/web_generator/lib/src/config.dart
@@ -66,6 +66,12 @@
   /// declarations
   bool get generateAll;
 
+  /// Treat any unsupported/unimplemented types/declarations as errors
+  ///
+  /// This is to be used for development purposes only and is not supported
+  /// as a config option in the configuration file
+  bool get strictUnsupported;
+
   factory Config(
       {required List<String> input,
       required String output,
@@ -75,7 +81,8 @@
       List<String> includedDeclarations,
       bool generateAll,
       bool ignoreErrors,
-      String? tsConfigFile}) = ConfigImpl._;
+      String? tsConfigFile,
+      bool strictUnsupported}) = ConfigImpl._;
 }
 
 class ConfigImpl implements Config {
@@ -118,6 +125,9 @@
   @override
   bool generateAll;
 
+  @override
+  bool strictUnsupported;
+
   ConfigImpl._(
       {required this.input,
       required this.output,
@@ -127,7 +137,8 @@
       this.includedDeclarations = const [],
       this.ignoreErrors = false,
       this.generateAll = false,
-      this.tsConfigFile});
+      this.tsConfigFile,
+      this.strictUnsupported = false});
 
   @override
   bool get singleFileOutput => input.length == 1;
@@ -176,6 +187,9 @@
   @override
   bool generateAll;
 
+  @override
+  bool get strictUnsupported => false;
+
   YamlConfig._(
       {required this.filename,
       required this.input,
diff --git a/web_generator/lib/src/dart_main.dart b/web_generator/lib/src/dart_main.dart
index 2f5a8ef..ce5bd4e 100644
--- a/web_generator/lib/src/dart_main.dart
+++ b/web_generator/lib/src/dart_main.dart
@@ -62,7 +62,8 @@
           languageVersion: Version.parse(languageVersionString),
           tsConfigFile: tsConfigFile,
           ignoreErrors: argResult.wasParsed('ignore-errors'),
-          generateAll: argResult['generate-all'] as bool);
+          generateAll: argResult['generate-all'] as bool,
+          strictUnsupported: argResult.wasParsed('strict-unsupported'));
     }
 
     await generateJSInteropBindings(config);
@@ -202,5 +203,11 @@
       help: '[TS Declarations] The input file to read and generate types for')
   ..addFlag('ignore-errors',
       help: '[TS Declarations] Ignore Generator Errors', negatable: false)
+  ..addFlag(
+    'strict-unsupported',
+    help:
+        '[TS Declarations] Treat unsupported declarations/types as errors. Only used for development of the generator',
+    negatable: false,
+  )
   ..addOption('config',
       abbr: 'c', hide: true, valueHelp: '[file].yaml', help: 'Configuration');
diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart
index 87d5d67..f1e5861 100644
--- a/web_generator/lib/src/interop_gen/transform.dart
+++ b/web_generator/lib/src/interop_gen/transform.dart
@@ -181,8 +181,12 @@
 
   final bool generateAll;
 
+  final bool strictUnsupported;
+
   ProgramMap(this.program, List<String> files,
-      {this.filterDeclSet = const [], bool? generateAll})
+      {this.filterDeclSet = const [],
+      bool? generateAll,
+      this.strictUnsupported = false})
       : typeChecker = program.getTypeChecker(),
         generateAll = generateAll ?? false,
         files = p.PathSet.of(files);
@@ -323,7 +327,8 @@
   TransformerManager.fromParsedResults(ParserResult result, {Config? config})
       : programMap = ProgramMap(result.program, result.files.toList(),
             filterDeclSet: config?.includedDeclarations ?? [],
-            generateAll: config?.generateAll);
+            generateAll: config?.generateAll,
+            strictUnsupported: config?.strictUnsupported ?? false);
 
   TransformResult transform() {
     final outputNodeMap = <String, NodeMap>{};
diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart
index aac412e..87f6ce3 100644
--- a/web_generator/lib/src/interop_gen/transform/transformer.dart
+++ b/web_generator/lib/src/interop_gen/transform/transformer.dart
@@ -86,6 +86,8 @@
 
   bool get generateAll => programMap.generateAll;
 
+  bool get errorIfUnsupported => programMap.strictUnsupported;
+
   Transformer(this.programMap, this._sourceFile,
       {Set<String> exportSet = const {}, String? file})
       : exportSet = exportSet.map((e) => ExportReference(e, as: e)).toSet(),
@@ -152,7 +154,12 @@
               (node.name as TSIdentifier).text != 'global':
         return [_transformNamespace(node, namer: namer, parent: parent)];
       default:
-        throw Exception('Unsupported Declaration Kind: ${node.kind}');
+        if (errorIfUnsupported) {
+          throw Exception('Unsupported Declaration Kind: ${node.kind}');
+        } else {
+          print('WARN: Unsupported Declaration Kind: ${node.kind}');
+          return [];
+        }
     }
   }
 
@@ -1460,13 +1467,23 @@
           TSSyntaxKind.BigIntKeyword => PrimitiveType.bigint,
           TSSyntaxKind.SymbolKeyword => PrimitiveType.symbol,
           TSSyntaxKind.NeverKeyword => PrimitiveType.never,
-          _ => throw UnsupportedError(
-              'The given type with kind ${type.kind} is not supported yet')
+          _ => null
         };
 
-        return BuiltinType.primitiveType(primitiveType,
-            shouldEmitJsType: typeArg ? true : null,
-            isNullable: primitiveType == PrimitiveType.any ? true : isNullable);
+        if (primitiveType != null) {
+          return BuiltinType.primitiveType(primitiveType,
+              shouldEmitJsType: typeArg ? true : null,
+              isNullable:
+                  primitiveType == PrimitiveType.any ? true : isNullable);
+        } else if (errorIfUnsupported) {
+          throw UnsupportedError(
+              'The given type with kind ${type.kind} is not supported yet');
+        } else {
+          print('WARN: The given type with kind ${type.kind} is '
+              'not supported yet');
+          return BuiltinType.primitiveType(PrimitiveType.any,
+              isNullable: isNullable);
+        }
     }
   }
 
@@ -2160,10 +2177,6 @@
     void updateFilteredDeclsForDecl(Node? decl, NodeMap filteredDeclarations) {
       switch (decl) {
         case final VariableDeclaration v:
-          print((
-            v.name,
-            v.type is TupleType ? (v.type as TupleType).name : null
-          ));
           filteredDeclarations.add(v.type);
           break;
         case final CallableDeclaration f:
diff --git a/web_generator/test/assets/unsupported_test.d.ts b/web_generator/test/assets/unsupported_test.d.ts
new file mode 100644
index 0000000..a430bee
--- /dev/null
+++ b/web_generator/test/assets/unsupported_test.d.ts
@@ -0,0 +1,8 @@
+export interface Foo {
+    name: string;
+    length: number;
+    raw: ArrayBuffer;
+}
+export interface Fee {
+    raw: Foo['raw']
+}
\ No newline at end of file
diff --git a/web_generator/test/assets/unsupported_test_expected.dart b/web_generator/test/assets/unsupported_test_expected.dart
new file mode 100644
index 0000000..5d227d0
--- /dev/null
+++ b/web_generator/test/assets/unsupported_test_expected.dart
@@ -0,0 +1,15 @@
+// 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;
+
+extension type Foo._(_i1.JSObject _) implements _i1.JSObject {
+  external String name;
+
+  external double length;
+
+  external _i1.JSArrayBuffer raw;
+}
+extension type Fee._(_i1.JSObject _) implements _i1.JSObject {
+  external _i1.JSAny raw;
+}
diff --git a/web_generator/test/unsupported_test.dart b/web_generator/test/unsupported_test.dart
new file mode 100644
index 0000000..74f3dd2
--- /dev/null
+++ b/web_generator/test/unsupported_test.dart
@@ -0,0 +1,74 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:web_generator/src/cli.dart';
+
+/// Actual test output can be found in `.dart_tool/idl`
+void main() {
+  final bindingsGenPath = p.join('lib', 'src');
+  group('Interop Gen Unsupported Test', () {
+    final testFile = p.join('test', 'assets', 'unsupported_test.d.ts');
+    final outputFile = p.join('.dart_tool', 'unsupported_test.dart');
+    final expectedFile =
+        p.join('test', 'assets', 'unsupported_test_expected.dart');
+
+    setUpAll(() async {
+      // set up npm
+      await runProc('npm', ['install'], workingDirectory: bindingsGenPath);
+
+      // compile file
+      await compileDartMain(dir: bindingsGenPath);
+    });
+
+    test('Strict Unsupported', () async {
+      final inputFilePath = p.relative(testFile, from: bindingsGenPath);
+      final outputFilePath = p.relative(outputFile, from: bindingsGenPath);
+
+      final process = await runProcWithResult(
+          'node',
+          [
+            'main.mjs',
+            '--input=$inputFilePath',
+            '--output=$outputFilePath',
+            '--strict-unsupported',
+            '--declaration'
+          ],
+          workingDirectory: bindingsGenPath);
+
+      final stderr = await process.stderr.transform(utf8.decoder).toList();
+
+      expect(stderr, isNotEmpty);
+      expect(await process.exitCode, isNot(0));
+    });
+
+    test('Not Strict Unsupported', () async {
+      final inputFilePath = p.relative(testFile, from: bindingsGenPath);
+      final outputFilePath = p.relative(outputFile, from: bindingsGenPath);
+
+      await runProc(
+          'node',
+          [
+            'main.mjs',
+            '--input=$inputFilePath',
+            '--output=$outputFilePath',
+            '--declaration'
+          ],
+          workingDirectory: bindingsGenPath);
+
+      // read files
+      final expectedOutput = await File(expectedFile).readAsString();
+      final actualOutput = await File(outputFile).readAsString();
+
+      expect(actualOutput, expectedOutput);
+    });
+  });
+}