js_interop_gen: many fixes moving towards supporting large, complex D.TS files#549
Conversation
kevmoo
commented
May 13, 2026
- fix: resolve namespace flattening type resolution mismatch in js_interop_gen
- fix(js_interop_gen): traverse generic type arguments in ReferredType dependency resolver
- refactor(js_interop_gen): eliminate GlobalOptions mutable state in favor of options propagation
- refactor(js_interop_gen): decompose transformer.dart by extracting DependencyWalker
- refactor(js_interop_gen): decompose transformer.dart by extracting NamespaceFlattener
- fix(js_interop_gen): prevent self-import and prefixing when importUrl matches file
- fix(js_interop_gen): deduplicate collected generic parameters by name in unions
- fix(js_interop_gen): distinguish generic builtins by type arguments and cast distinct union array getters
- fix(js_interop_gen): map void generic type arguments to JSAny to conform to JSAny? bounds
- feat(js_interop_gen): support string-literal module declarations
- feat(js_interop_gen): prevent infinite recursion crashes on mutually recursive types
- feat(js_interop_gen): support generic type parameter resolution inside module namespaces
- feat(js_interop_gen): resolve enum member type references to parent EnumDeclaration
- feat(js_interop_gen): add formatter try-catch recovery and document next steps
- feat(js_interop_gen): resolve literal union casts and refactor AST Node type safety
- chore: move type resolver into its own file
- feat: cache transformed AST nodes to resolve mismatched unique-suffix references
- fix: deduplicate types in union/intersection declarations to resolve duplicate definition errors
- fix: resolve unchecked use of nullable value errors for JSAny and void in union getters
- fix: uniquely identify generic type references in dependency walking to prevent missing generic unions
- fix: pad generic type arguments in ReferredType emit to match declaration bounds count
- fix: prevent parent nullability leaking into generic type arguments during referred type emit
- fix: always emit Deprecated annotation with a default message argument
- fix: dynamically resolve and enforce non-JSObject representation types for interfaces
- fix: resolve all static analysis lints and line-length limits in lib/
- fix: automatically detect and redeclare conflicting inherited members inside intersection types
- fix: cast generic ReferredTypes using intermediate JSAny cast in union getters
- test: replace global Object inheritance with custom Parent interface in representation type test to avoid TS warning
- fix: support object literal types in intersection member conflict resolution
- fix: recursively unwrap and walk inside type parameters of BuiltinTypes inside DependencyWalker
- fix: pass typeArg: true when resolving type parameters of referred BuiltinTypes and PackageWebTypes
- fix: propagate typeArg and parameter flags recursively inside UnionType and IntersectionType transformations
- fix: dynamically propagate and resolve specific type parameter bounds constraints inside ReferredType and getGenericTypes
- feat: add lines_longer_than_80_chars ignore rule to all generated interop files
- fix: escape JS properties/methods starting with _ to public Dart names starting with $
…rop_gen - Introduce a static lookup map `declarationToEmittedName` in `ReferredType` to map every parsed AST `Declaration` to its final merged/unique declaration name assigned by the namer. - Populate the lookup map in `processAndReturn()` within `transformer.dart` during the merging/rescoping phase. - Consult the lookup map in `ReferredType.emit()` to emit the correct final/unique names for type references, resolving name conflicts and flattening resolution mismatches. - Add a comprehensive integration test `vscode_flattening` verifying that Namespaced/Flattened Type Reference Resolution compiles with zero static errors. - Clean up minor formatting in `analysis_options.yaml` and wrap code to comply with standard 80-character limits and unused cast warnings.
…dependency resolver
…vor of options propagation
…nd cast distinct union array getters
…orm to JSAny? bounds
…e module namespaces
…de type safety - Fix invalid runtime casts in union getters for literal types (e.g., true, string, int, double) by correctly looking up their JS alternatives and generating safe .toDart / .toDartInt / .toDartDouble conversions. - Resolve mixed union LCA (Lowest Common Ancestor) resolution to properly widen to JSAny by correcting _getSharedPrimitiveTypeIfAny to fall through to the full topological sorting pass. - Refactor Node in base.dart to be generic (Node<T>) to enforce compile-time type safety for AST emit() return types. - Update ConstructorDeclaration to implement Node<Constructor> and update merger generic helper methods to T extends Node. - Expand integration test suite (boolean_literal_union) to comprehensively cover literal type conversion.
…duplicate definition errors
…d in union getters
…to prevent missing generic unions
…tion bounds count
…uring referred type emit
# Conflicts: # js_interop_gen/lib/src/ast/documentation.dart
… inside intersection types
…in representation type test to avoid TS warning
…es inside DependencyWalker
There was a problem hiding this comment.
Code Review
This pull request refactors the js_interop_gen tool to improve type resolution, namespace handling, and code generation consistency. It replaces global configuration with a scoped Options system and introduces modular components like TypeResolver, NamespaceFlattener, and DependencyWalker. Enhancements include signature-based member deduplication during interface merging, improved handling of generic type constraints, and better support for union types and deprecated annotations. Review feedback suggests enhancing type safety by avoiding dynamic member access in NamedDeclaration, reducing global state in ReferredType, and simplifying redundant type checks in union getter logic.
nikeokoronkwo
left a comment
There was a problem hiding this comment.
This is awesome. Thank you!
A few general notes:
- I noticed a lot of conflict resolution handling (i.e. deduping) in your code: technically we should not need to do most of that, either because at the point in the codebase it should be handled, or the TypeScript compiler should not allow such code to be generated or successfully pass through the parser anyways. We are not expecting people to write these
.d.tsfiles so we should expect correct inputs.
cc @srujzs if you have the time to.
|
@nikeokoronkwo PTAL 😄 |
nikeokoronkwo
left a comment
There was a problem hiding this comment.
Some more things...
Another issue is I ran this:
export interface SharedItem {
value: number;
}
export declare namespace API {
interface Container {
item: SharedItem;
}
}
// Re-export the same symbol that was referenced inside the namespace.
export { SharedItem as ReshapedItem };And the generator did not expose SharedItem but instead only ReshapedItem (due to the export statement) and the API namespace.
- Map aliased re-exports to type alias (typedef Alias = Original) instead of mutating AST declarations in place. - Copy and propagate custom @js annotations to conflicting dynamic getters/setters in intersections. - Resolve property member conflicts in intersections (primitives simplify to bottom type never/JSAny). - Map TS generic default type parameter values to padding fallbacks using default$. - Fix ReferredDeclarationType constructor nullability runtime exceptions under web targets. - Format and wrap comments to conform to 80-character line limits, resolving analyze warnings. - Update and expand integration test suites for generic padding, intersections, and re-export aliases.
|
take another look! |
|
Can gemini review one last time? |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors the js_interop_gen package by replacing global configuration options with instance-based options, improving type resolution, and introducing helper classes such as DependencyWalker, NamespaceFlattener, and TypeResolver to handle namespace flattening and dependency resolution. The review feedback highlights several critical issues and improvement opportunities: ensuring GenericType.id uses a stable identifier to prevent duplicate member definitions during interface merging, replacing an unsafe .reduce call with .fold in TypeResolver to avoid runtime StateErrors on empty iterables, removing a redundant cache check, and adding an explicit null check for the nullable symbol parameter in getTypeFromSymbol to prevent potential runtime null pointer exceptions.
- Enhance GenericType.id stability by using parent qualifiedName (or name) instead of namer-suffixed parent.id. This guarantees identical IDs for the same type parameters across separate declarations of a merged generic interface, resolving signature-based member deduplication failures. - Protect the type resolver against StateError on empty declarations maps by replacing the unsafe .reduce iterable call with a robust, high-performance .expand().toList() flattener. - Remove redundant manual transformedCache lookups before calling transformAndReturn (which already checks the cache internally). - Prevent NullThrownError during symbol resolution by inserting a descriptive null-safety diagnostic check at the start of getTypeFromSymbol, enabling static type promotion of the symbol.
Add integration test case under `generic_interface_merge_input.d.ts` and its generated expected output `generic_interface_merge_expected.dart` to verify that overlapping generic property declarations inside merged generic interfaces are successfully and robustly deduplicated under `GenericType.id` name-invariant lookup mapping.
…xample Relocate the ErrorReporter test case interface containing a never-returning method to `interfaces_input.d.ts` (and its expected golden `interfaces_expected.dart`) from `functions_input.d.ts`. This addresses Nike's nit by keeping the functions test suite strictly focused on top-level function declarations, moving interface mappings to their dedicated test file.
|
Thank you @nikeokoronkwo ! |