| // 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 'dart:collection'; |
| import 'dart:io'; |
| import 'dart:isolate'; |
| import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:io/ansi.dart' as ansi; |
| |
| import 'package:path/path.dart' as p; |
| |
| final bindingsGeneratorPath = p.fromUri( |
| Isolate.resolvePackageUriSync(Uri.parse('package:web_generator/src')), |
| ); |
| |
| Future<void> compileDartMain({String? langVersion, String? dir}) async { |
| await runProc(Platform.executable, [ |
| 'compile', |
| 'js', |
| '--enable-asserts', |
| '--server-mode', |
| if (langVersion != null) '-DlanguageVersion=$langVersion', |
| 'dart_main.dart', |
| '-o', |
| 'dart_main.js', |
| ], workingDirectory: dir ?? bindingsGeneratorPath); |
| } |
| |
| Future<Process> runProcWithResult( |
| String executable, |
| List<String> arguments, { |
| required String workingDirectory, |
| }) async { |
| print(ansi.styleBold.wrap(['*', executable, ...arguments].join(' '))); |
| return Process.start( |
| executable, |
| arguments, |
| runInShell: Platform.isWindows, |
| workingDirectory: workingDirectory, |
| ); |
| } |
| |
| Future<void> runProc( |
| String executable, |
| List<String> arguments, { |
| required String workingDirectory, |
| bool detached = false, |
| }) async { |
| print(ansi.styleBold.wrap(['*', executable, ...arguments].join(' '))); |
| final proc = await Process.start( |
| executable, |
| arguments, |
| mode: detached ? ProcessStartMode.detached : ProcessStartMode.inheritStdio, |
| runInShell: Platform.isWindows, |
| workingDirectory: workingDirectory, |
| ); |
| final procExit = await proc.exitCode; |
| if (procExit != 0) { |
| throw ProcessException(executable, arguments, 'Process failed', procExit); |
| } |
| } |
| |
| Future<File> createJsTypeSupertypeContext() async { |
| final contextFile = await File( |
| p.join(bindingsGeneratorPath, '_js_supertypes_src.dart'), |
| ).create(); |
| await contextFile.writeAsString(''' |
| import 'dart:js_interop'; |
| |
| @JS() |
| external JSPromise get promise; |
| '''); |
| return contextFile; |
| } |
| |
| /// Generates a map of the JS type hierarchy defined in `dart:js_interop` that's |
| /// used by both translators. |
| Future<void> generateJsTypeSupertypes(String contextFile) async { |
| // Use a file that uses `dart:js_interop` for analysis. |
| final contextCollection = AnalysisContextCollection( |
| includedPaths: [contextFile], |
| ); |
| final dartJsInterop = |
| (await contextCollection.contexts.single.currentSession.getLibraryByUri( |
| 'dart:js_interop', |
| ) |
| as LibraryElementResult) |
| .element; |
| final definedNames = dartJsInterop.exportNamespace.definedNames2; |
| // `SplayTreeMap` to avoid moving types around in `dart:js_interop` affecting |
| // the code generation. |
| final jsTypeSupertypes = SplayTreeMap<String, String?>(); |
| for (final name in definedNames.keys) { |
| final element = definedNames[name]; |
| if (element is ExtensionTypeElement) { |
| // JS types are any extension type that starts with 'JS' in |
| // `dart:js_interop`. |
| bool isJSType(InterfaceElement element) => |
| element is ExtensionTypeElement && |
| element.library == dartJsInterop && |
| element.name!.startsWith('JS'); |
| if (!isJSType(element)) continue; |
| |
| String? parentJsType; |
| final supertype = element.supertype; |
| final immediateSupertypes = <InterfaceType>[ |
| if (supertype != null) supertype, |
| ...element.interfaces, |
| ]..removeWhere((supertype) => supertype.isDartCoreObject); |
| // We should have at most one non-trivial supertype. |
| assert(immediateSupertypes.length <= 1); |
| for (final supertype in immediateSupertypes) { |
| if (isJSType(supertype.element)) { |
| parentJsType = "'${supertype.element.name!}'"; |
| } |
| } |
| // Ensure that the hierarchy forms a tree. |
| assert((parentJsType == null) == (name == 'JSAny')); |
| jsTypeSupertypes["'$name'"] = parentJsType; |
| } |
| } |
| |
| final jsTypeSupertypesScript = |
| ''' |
| // Copyright (c) 2023, 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. |
| |
| // Generated code. Do not modify by hand. |
| |
| const Map<String, String?> jsTypeSupertypes = { |
| ${jsTypeSupertypes.entries.map((e) => " ${e.key}: ${e.value},").join('\n')} |
| }; |
| '''; |
| final jsTypeSupertypesPath = p.join( |
| bindingsGeneratorPath, |
| 'js_type_supertypes.dart', |
| ); |
| await File(jsTypeSupertypesPath).writeAsString(jsTypeSupertypesScript); |
| } |