blob: 90a47f2034955e0f36f6b61d0eda02bc1b31847a [file] [edit]
// Copyright (c) 2026, 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 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'js/util.dart' show JsInteropMemberData;
import 'translator.dart' show TranslatorOptions;
import 'util.dart';
/// Provides wasm import/export names for a module.
///
/// Each instance of [Namer] defines a namespace. Uniqueness is only guaranteed
/// within a namespace. Names passed to [getName] must themselves be unique for
/// a given namespace.
///
/// If [jsSafeNames] is true, the returned names will be safe to use as a
/// JavaScript identifier. This is useful if the name will be used as a key in a
/// JS object. Names only referenced from wasm modules can be any ASCII string.
///
/// Each name should appear exactly once in the wasm module (defining the
/// import/export) and JS code so we don't consider frequency when naming.
class Namer {
final bool minify;
final Set<String> _usedNames = {};
int _nextId = 0;
Namer({this.minify = false});
/// Mark a name as reserved in case the name might collide with a
/// pre-specified name such as an export.
void reserveName(String name) {
if (!minify) return;
final added = _usedNames.add(name);
assert(added, "Name '$name' is already reserved");
}
static String _jsSanitizeName(String name) {
return name.replaceAll(RegExp(r'[^a-zA-Z0-9_$]'), '_');
}
/// Get an assigned name for the given name.
///
String getName(String name, {bool jsSafeName = false}) {
if (!minify) {
String basename = name = jsSafeName ? _jsSanitizeName(name) : name;
int counter = 0;
while (!_usedNames.add(name)) {
name = '${basename}_${counter++}';
}
return name;
}
do {
name = jsSafeName
? intToMinJsSafeString(_nextId++)
: intToMinString(_nextId++);
} while (!_usedNames.add(name));
return name;
}
}
/// Manages naming for external interop members.
///
/// Members annotated with `@pragma('wasm:import', '...')` or
/// `@pragma('wasm:export', '...')` or `@pragma('wasm:weak-export', '...')`
/// are considered external members. These members are visible from outside the
/// Dart program and are therefore not minified. We do make sure that other
/// names do not collide with these names.
class _ExternalMemberNamer {
final Namer _exportNamer;
final CoreTypes coreTypes;
_ExternalMemberNamer(this.coreTypes, this._exportNamer);
ImportName? getImportName(Member member) {
return getWasmImportPragma(coreTypes, member);
}
String? getExportName(Member member) {
// External export names should be registered already so we don't have to
// reserve them with the namer here.
return getWasmExportPragma(coreTypes, member) ??
getWasmWeakExportPragma(coreTypes, member);
}
bool registerExportName(Member member) {
final name = getWasmExportPragma(coreTypes, member);
if (name != null) {
_exportNamer.reserveName(name);
return true;
}
final weakName = getWasmWeakExportPragma(coreTypes, member);
if (weakName != null) {
_exportNamer.reserveName(weakName);
}
return false;
}
}
/// Manages naming for internal interop helper members.
///
/// These members may import code from the support JS modules but are not
/// visible from outside the Dart program and are only used by the Dart runtime.
/// Their names can therefore be minified.
class _InteropHelperMemberNamer {
final Namer _interopHelperNamer;
final Namer _exportNamer;
final CoreTypes coreTypes;
final String interopModuleName;
final String thisModuleSetterName;
final Map<Member, String> interopMemberNames = {};
_InteropHelperMemberNamer(
this._exportNamer,
this.coreTypes,
TranslatorOptions options,
) : interopModuleName = options.minify ? '_' : 'dart2wasm',
_interopHelperNamer = Namer(
minify: options.minify || options.minifyInteropNames,
),
thisModuleSetterName = _exportNamer.getName(
'\$setThisModule',
jsSafeName: true,
);
ImportName? getImportName(Member member) {
final annotationInfo = JsInteropMemberData.fromMember(member, coreTypes);
if (annotationInfo == null) return null;
if (annotationInfo.isImport) {
return ImportName(
interopModuleName,
interopMemberNames[member] ??= _interopHelperNamer.getName(
member.name.text,
jsSafeName: true,
),
);
}
return null;
}
String? getExportName(Member member) {
final annotationInfo = JsInteropMemberData.fromMember(member, coreTypes);
if (annotationInfo == null) return null;
if (annotationInfo.isWeakExport) {
return interopMemberNames[member] ??= _exportNamer.getName(
member.name.text,
jsSafeName: true,
);
}
return null;
}
}
/// Manages naming for [Member]s associated with JS interop.
///
/// Some of these members are visible from JS outside of the Dart program and
/// are therefore not minified. Others are only helpers used by the Dart runtime
/// and can be minified.
class InteropMemberNamer {
final _ExternalMemberNamer _externalMemberNamer;
final _InteropHelperMemberNamer _interopHelperMemberNamer;
InteropMemberNamer(
CoreTypes coreTypes,
Namer exportNamer,
TranslatorOptions options,
) : _externalMemberNamer = _ExternalMemberNamer(coreTypes, exportNamer),
_interopHelperMemberNamer = _InteropHelperMemberNamer(
exportNamer,
coreTypes,
options,
);
String get interopHelperModuleName =>
_interopHelperMemberNamer.interopModuleName;
String get thisModuleSetterName =>
_interopHelperMemberNamer.thisModuleSetterName;
/// Returns the import name for the given member.
///
/// Returns null if the member is not an import. Checks both external and
/// interop helper imports.
ImportName? getImportName(Member member) {
return _externalMemberNamer.getImportName(member) ??
_interopHelperMemberNamer.getImportName(member);
}
/// Returns the export name for the given member.
///
/// Returns null if the member is not an export. Checks both external and
/// interop helper exports.
String? getExportName(Member member) {
final externalName = _externalMemberNamer.getExportName(member);
if (externalName != null) return externalName;
return _interopHelperMemberNamer.getExportName(member);
}
/// Registers the export name for the given member with the [Namer].
///
/// Returns true if the member is a strong external export.
bool registerExternalExportName(Member member) {
return _externalMemberNamer.registerExportName(member);
}
}