blob: 1d824f6b043fabba93c2e86b1b41bc546a3fd898 [file] [log] [blame]
// 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);
}