blob: 5a494224ea070ec06ec5de63386309c27d5bb948 [file] [edit]
// 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.
import 'dart:convert' show json;
import 'package:_js_interop_checks/src/js_interop.dart'
show calculateTransitiveImportsOfJsInteropIfUsed;
import 'package:_js_interop_checks/src/transformations/static_interop_class_eraser.dart';
import 'package:collection/collection.dart' show compareNatural;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import '../namer.dart';
import 'interop_transformer.dart';
import 'runtime_blob.dart';
import 'util.dart';
void _performJSInteropTransformations(
CoreTypes coreTypes,
ClassHierarchy classHierarchy,
Set<Library> interopDependentLibraries,
) {
// Transform kernel and generate JS methods.
final transformer = InteropTransformer(coreTypes, classHierarchy);
for (final library in interopDependentLibraries) {
transformer.visitLibrary(library);
}
// We want static types to help us specialize methods based on receivers.
// Therefore, erasure must come after the lowering.
final jsValueClass = coreTypes.index.getClass('dart:_js_helper', 'JSValue');
final staticInteropClassEraser = StaticInteropClassEraser(
coreTypes,
eraseStaticInteropType: (staticInteropType) =>
InterfaceType(jsValueClass, staticInteropType.declaredNullability),
additionalCoreLibraries: {
'_js_helper',
'_js_string_convert',
'_js_types',
'_string',
'convert',
'js_interop',
'js_interop_unsafe',
},
);
for (Library library in interopDependentLibraries) {
staticInteropClassEraser.visitLibrary(library);
}
}
class RuntimeFinalizer {
static String escape(String s) => json.encode(s);
final CoreTypes _coreTypes;
final InteropMemberNamer _interopMemberNamer;
RuntimeFinalizer(this._coreTypes, this._interopMemberNamer);
String generateJsMethods(Iterable<Procedure> translatedProcedures) {
Set<Procedure> usedProcedures = {};
final usedJSMethods = <({String importName, String jsCode})>[];
for (Procedure p in translatedProcedures) {
if (!usedProcedures.add(p)) continue;
final annotationInfo = JsInteropMemberData.fromMember(p, _coreTypes);
if (annotationInfo == null) continue;
switch (annotationInfo) {
case JsCodeData(:final jsCode):
final importName = _interopMemberNamer.getImportName(p)!.itemName;
usedJSMethods.add((importName: importName, jsCode: jsCode));
case JsTrampolineWrapperData():
final importName = _interopMemberNamer.getImportName(p)!.itemName;
usedJSMethods.add((
importName: importName,
jsCode: annotationInfo.jsCode(),
));
case JsTrampolineData():
// do nothing
}
}
// Sort so _9 comes before _11 (for example)
usedJSMethods.sort((a, b) => compareNatural(a.importName, b.importName));
final jsMethods = StringBuffer();
for (final (:importName, :jsCode) in usedJSMethods) {
jsMethods.write(' ');
jsMethods.write(importName);
jsMethods.write(': ');
final lines = _unindentJsCode(jsCode);
for (int i = 0; i < lines.length; ++i) {
if (i != 0) {
jsMethods.write(' ');
}
jsMethods.write(lines[i]);
if (i < (lines.length - 1)) {
jsMethods.writeln();
} else {
jsMethods.writeln(',');
}
}
}
return jsMethods.toString();
}
String _generateInternalizedStrings(
bool requireJsBuiltin,
List<String> constantStrings,
) {
final sb = StringBuffer();
String indent = '';
if (constantStrings.isNotEmpty) {
sb.writeln('s: [');
indent = ' ';
for (final c in constantStrings) {
sb.writeln('$indent ${escape(c)},');
}
sb.writeln('$indent],');
}
if (!requireJsBuiltin) {
sb.writeln(
'$indent"": new Proxy({}, { get(_, prop) { return prop; } }),',
);
}
return '$sb';
}
String generate(
String mainModuleName,
Iterable<Procedure> translatedProcedures,
List<String> constantStrings,
bool requireJsBuiltin,
bool supportsAdditionalModuleLoading,
) {
final jsMethods = generateJsMethods(translatedProcedures);
final builtins = [
'builtins: [\'js-string\']',
if (requireJsBuiltin) 'importedStringConstants: \'\'',
];
String internalizedStrings = _generateInternalizedStrings(
requireJsBuiltin,
constantStrings,
);
final jsStringBuiltinPolyfillImportVars = {
'JS_POLYFILL_IMPORT': requireJsBuiltin
? ''
: '"wasm:js-string": jsStringPolyfill,',
};
final moduleLoadingImportVars = {
'MODULE_LOADING_IMPORT': supportsAdditionalModuleLoading
? '"moduleLoadingHelper": moduleLoadingHelper,'
: '',
};
final moduleLoadingHelperMethods = supportsAdditionalModuleLoading
? moduleLoadingHelperTemplate.instantiate({
...jsStringBuiltinPolyfillImportVars,
'MAIN_MODULE_NAME': mainModuleName,
})
: '';
return jsRuntimeBlobTemplate.instantiate({
...jsStringBuiltinPolyfillImportVars,
...moduleLoadingImportVars,
'BUILTINS_MAP_BODY': builtins.join(', '),
'JS_METHODS': jsMethods,
'INTERNAL_IMPORTS_MODULE_NAME':
_interopMemberNamer.interopHelperModuleName,
'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings,
'JS_STRING_POLYFILL_METHODS': requireJsBuiltin ? '' : jsPolyFillMethods,
'DEFERRED_LIBRARY_HELPER_METHODS': moduleLoadingHelperMethods,
});
}
}
void performJSInteropTransformations(
List<Library> libraries,
CoreTypes coreTypes,
ClassHierarchy classHierarchy,
) {
Set<Library> transitiveImportingJSInterop = {
...calculateTransitiveImportsOfJsInteropIfUsed(
libraries,
Uri.parse("dart:_js_helper"),
),
...calculateTransitiveImportsOfJsInteropIfUsed(
libraries,
Uri.parse("dart:js_interop"),
),
};
return _performJSInteropTransformations(
coreTypes,
classHierarchy,
transitiveImportingJSInterop,
);
}
// Removes indentation common among all lines of [block] (except for first one)
List<String> _unindentJsCode(String block) {
block = block.trim();
final lines = block.split('\n');
if (lines.length == 1) return lines;
int minSpaces = -1;
for (int i = 1; i < lines.length; ++i) {
final line = lines[i];
final currentSpaces = _countLeadingSpaces(line);
if (currentSpaces == line.length) continue; // empty line
if (minSpaces == -1 || currentSpaces < minSpaces) {
minSpaces = currentSpaces;
}
}
if (minSpaces > 0) {
for (int i = 1; i < lines.length; ++i) {
final line = lines[i];
if (line.length <= minSpaces) {
lines[i] = '';
} else {
lines[i] = lines[i].substring(minSpaces);
}
}
}
return lines;
}
int _countLeadingSpaces(String line) {
int spaces = 0;
for (int i = 0; i < line.length; ++i) {
if (line.codeUnitAt(i) != ' '.codeUnitAt(0)) break;
spaces++;
}
return spaces;
}