blob: 2e24fe10779e746ef7d61b45c0f74bd446ade101 [file] [log] [blame]
// Copyright (c) 2021, 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.
// @dart = 2.10
import 'package:js_ast/src/precedence.dart' as js show PRIMARY;
import 'package:front_end/src/api_unstable/dart2js.dart' show $A;
import '../common/elements.dart' show JCommonElements;
import '../elements/entities.dart';
import '../js/js.dart' as js;
import '../serialization/serialization.dart';
import '../util/util.dart';
import '../js_emitter/model.dart';
import '../constants/values.dart' show ConstantValue;
import 'namer.dart';
// TODO(joshualitt): Figure out how to subsume more of the modular naming
// framework into this approach. For example, we are still creating ModularNames
// for the entity referenced in the DeferredHolderExpression.
enum DeferredHolderExpressionKind {
globalObjectForStaticState,
globalObjectForConstant,
globalObjectForInterceptors,
globalObjectForClass,
globalObjectForMember,
}
/// A [DeferredHolderExpression] is a deferred JavaScript expression determined
/// by the finalization of holders. It is the injection point for data or
/// code to related to holders. The actual [Expression] contained within the
/// [DeferredHolderExpression] is determined by the
/// [DeferredHolderExpressionKind], eventually, most will be a [PropertyAccess]
/// but currently all are [VariableUse]s.
class DeferredHolderExpression extends js.DeferredExpression
implements js.AstContainer {
static const String tag = 'deferred-holder-expression';
final DeferredHolderExpressionKind kind;
final Object data;
js.Expression _value;
@override
final js.JavaScriptNodeSourceInformation sourceInformation;
DeferredHolderExpression(this.kind, this.data) : sourceInformation = null;
DeferredHolderExpression._(
this.kind, this.data, this._value, this.sourceInformation);
factory DeferredHolderExpression.forInterceptors() {
return DeferredHolderExpression(
DeferredHolderExpressionKind.globalObjectForInterceptors, null);
}
factory DeferredHolderExpression.forStaticState() {
return DeferredHolderExpression(
DeferredHolderExpressionKind.globalObjectForStaticState, null);
}
factory DeferredHolderExpression.readFromDataSource(DataSourceReader source) {
source.begin(tag);
var kind = source.readEnum(DeferredHolderExpressionKind.values);
Object data;
switch (kind) {
case DeferredHolderExpressionKind.globalObjectForClass:
data = source.readClass();
break;
case DeferredHolderExpressionKind.globalObjectForMember:
data = source.readMember();
break;
case DeferredHolderExpressionKind.globalObjectForConstant:
data = source.readConstant();
break;
case DeferredHolderExpressionKind.globalObjectForInterceptors:
case DeferredHolderExpressionKind.globalObjectForStaticState:
// no entity.
break;
}
source.end(tag);
return DeferredHolderExpression(kind, data);
}
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeEnum(kind);
switch (kind) {
case DeferredHolderExpressionKind.globalObjectForClass:
sink.writeClass(data);
break;
case DeferredHolderExpressionKind.globalObjectForMember:
sink.writeMember(data);
break;
case DeferredHolderExpressionKind.globalObjectForConstant:
sink.writeConstant(data);
break;
case DeferredHolderExpressionKind.globalObjectForInterceptors:
case DeferredHolderExpressionKind.globalObjectForStaticState:
// no entity.
break;
}
sink.end(tag);
}
set value(js.Expression value) {
assert(!isFinalized && value != null);
_value = value;
}
@override
js.Expression get value {
assert(isFinalized, '$this is unassigned');
return _value /*!*/;
}
@override
bool get isFinalized => _value != null;
@override
DeferredHolderExpression withSourceInformation(
js.JavaScriptNodeSourceInformation newSourceInformation) {
if (newSourceInformation == sourceInformation) return this;
if (newSourceInformation == null) return this;
return DeferredHolderExpression._(kind, data, _value, newSourceInformation);
}
@override
int get precedenceLevel => _value?.precedenceLevel ?? js.PRIMARY;
@override
int get hashCode {
return Hashing.objectsHash(kind, data);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DeferredHolderExpression &&
kind == other.kind &&
data == other.data;
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('DeferredHolderExpression(kind=$kind,data=$data,');
sb.write('value=$_value)');
return sb.toString();
}
@override
String nonfinalizedDebugText() {
switch (kind) {
case DeferredHolderExpressionKind.globalObjectForClass:
return 'Holder"${_className(data)}"';
case DeferredHolderExpressionKind.globalObjectForMember:
return 'Holder"${_qualifiedStaticName(data)}"';
case DeferredHolderExpressionKind.globalObjectForInterceptors:
return 'J';
case DeferredHolderExpressionKind.globalObjectForConstant:
return 'Holder"constants"';
case DeferredHolderExpressionKind.globalObjectForStaticState:
return r'$';
}
return super.nonfinalizedDebugText();
}
String _className(ClassEntity cls) => cls.name.replaceAll('&', '_');
String _qualifiedStaticName(MemberEntity member) {
if (member.isConstructor || member.isStatic) {
return '${_className(member.enclosingClass)}.${member.name}';
}
return member.name;
}
@override
Iterable<js.Node> get containedNodes => isFinalized ? [value] : const [];
}
/// A [DeferredHolderParameter] is a deferred JavaScript expression determined
/// by the finalization of holders. It is the injection point for data or
/// code to related to holders. This class does not support serialization.
/// TODO(joshualitt): Today this exists just for the static state holder.
/// Ideally we'd be able to treat the static state holder like other holders.
class DeferredHolderParameter extends js.Expression implements js.Parameter {
String _name;
@override
final bool allowRename = false;
@override
final js.JavaScriptNodeSourceInformation sourceInformation;
DeferredHolderParameter() : sourceInformation = null;
DeferredHolderParameter._(this._name, this.sourceInformation);
set name(String name) {
assert(!isFinalized && name != null);
_name = name;
}
@override
String get name {
assert(isFinalized, '$this is unassigned');
return _name;
}
@override
bool get isFinalized => _name != null;
@override
DeferredHolderParameter withSourceInformation(
js.JavaScriptNodeSourceInformation newSourceInformation) {
if (newSourceInformation == sourceInformation) return this;
if (newSourceInformation == null) return this;
return DeferredHolderParameter._(_name, newSourceInformation);
}
@override
int get precedenceLevel => js.PRIMARY;
@override
T accept<T>(js.NodeVisitor<T> visitor) => visitor.visitParameter(this);
@override
R accept1<R, A>(js.NodeVisitor1<R, A> visitor, A arg) =>
visitor.visitParameter(this, arg);
@override
void visitChildren<T>(js.NodeVisitor<T> visitor) {}
@override
void visitChildren1<R, A>(js.NodeVisitor1<R, A> visitor, A arg) {}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('DeferredHolderParameter(name=$_name)');
return sb.toString();
}
}
enum DeferredHolderResourceKind {
mainFragment,
deferredFragment,
}
/// A [DeferredHolderResource] is a deferred JavaScript statement determined by
/// the finalization of holders. Each fragment contains one
/// [DeferredHolderResource]. The actual [Statement] contained with the
/// [DeferredHolderResource] will be determined by the
/// [DeferredHolderResourceKind]. These [Statement]s differ considerably
/// depending on where they are used in the AST. This class is created by the
/// fragment emitter so does not need to support serialization.
class DeferredHolderResource extends js.DeferredStatement
implements js.AstContainer {
DeferredHolderResourceKind kind;
// Each resource has a distinct name.
String name;
List<Fragment> fragments;
Map<Entity, List<js.Property>> holderCode;
js.Statement _statement;
@override
final js.JavaScriptNodeSourceInformation sourceInformation;
DeferredHolderResource(this.kind, this.name, this.fragments, this.holderCode)
: sourceInformation = null;
DeferredHolderResource._(this.kind, this.name, this.fragments,
this.holderCode, this._statement, this.sourceInformation);
bool get isMainFragment => kind == DeferredHolderResourceKind.mainFragment;
set statement(js.Statement statement) {
assert(!isFinalized && statement != null);
_statement = statement;
}
@override
js.Statement get statement {
assert(isFinalized, 'DeferredHolderResource is unassigned');
return _statement;
}
@override
bool get isFinalized => _statement != null;
@override
DeferredHolderResource withSourceInformation(
js.JavaScriptNodeSourceInformation newSourceInformation) {
if (newSourceInformation == sourceInformation) return this;
if (newSourceInformation == null) return this;
return DeferredHolderResource._(kind, this.name, this.fragments, holderCode,
_statement, newSourceInformation);
}
@override
Iterable<js.Node> get containedNodes => isFinalized ? [_statement] : const [];
@override
void visitChildren<T>(js.NodeVisitor<T> visitor) {
_statement?.accept<T>(visitor);
}
@override
void visitChildren1<R, A>(js.NodeVisitor1<R, A> visitor, A arg) {
_statement?.accept1<R, A>(visitor, arg);
}
}
const String mainResourceName = 'MAIN';
abstract class DeferredHolderExpressionFinalizer {
/// Collects DeferredHolderExpressions from the JavaScript
/// AST [code] and associates it with [resourceName].
void addCode(String resourceName, js.Node code);
/// Performs analysis on all collected DeferredHolderExpression nodes
/// finalizes the values to expressions to access the holders.
void finalize();
/// The below registration functions are for use only by the visitor.
void registerDeferredHolderExpression(
String resourceName, DeferredHolderExpression node);
void registerDeferredHolderResource(DeferredHolderResource node);
void registerDeferredHolderParameter(DeferredHolderParameter node);
}
/// An abstraction representing a [Holder] object, which will contain some
/// portion of the programs code.
class Holder {
final String key;
final Map<String, int> refCountPerResource = {};
final Map<String, String> localNames = {};
final Map<String, List<js.Property>> propertiesPerResource = {};
int _index;
int _hashCode;
Holder(this.key);
int refCount(String resource) {
assert(refCountPerResource.containsKey(resource));
return refCountPerResource[resource];
}
String localName(String resource) {
assert(localNames.containsKey(resource));
return localNames[resource];
}
void setLocalName(String resource, String name) {
assert(!localNames.containsKey(resource));
localNames[resource] = name;
}
void registerUse(String resource) {
refCountPerResource.update(resource, (count) => count + 1,
ifAbsent: () => 0);
}
void registerUpdate(String resource, List<js.Property> properties) {
(propertiesPerResource[resource] ??= []).addAll(properties);
registerUse(resource);
}
int get index {
assert(_index != null);
return _index;
}
set index(int newIndex) {
assert(_index == null);
_index = newIndex;
}
@override
bool operator ==(that) {
return that is Holder && key == that.key;
}
@override
int get hashCode {
return _hashCode ??= Hashing.objectsHash(key);
}
}
/// [DeferredHolderExpressionFinalizerImpl] finalizes
/// [DeferredHolderExpression]s, [DeferredHolderParameter]s,
/// [DeferredHolderResource]s, [DeferredHolderResourceExpression]s.
class DeferredHolderExpressionFinalizerImpl
implements DeferredHolderExpressionFinalizer {
_DeferredHolderExpressionCollectorVisitor _visitor;
final Map<String, List<DeferredHolderExpression>> holderReferences = {};
final List<DeferredHolderParameter> holderParameters = [];
final List<DeferredHolderResource> holderResources = [];
final Map<String, Set<Holder>> holdersPerResource = {};
final JCommonElements _commonElements;
final bool enableMinification;
final Holder globalObjectForStaticState =
Holder(globalObjectNameForStaticState());
final Holder globalObjectForInterceptors =
Holder(globalObjectNameForInterceptors());
final Set<Holder> allHolders = {};
DeferredHolderResource mainHolderResource;
Holder mainHolder;
Holder mainConstantHolder;
/// Maps of various object types to the holders they ended up in.
final Map<ClassEntity, Holder> classEntityMap = {};
final Map<ConstantValue, Holder> constantValueMap = {};
final Map<MemberEntity, Holder> memberEntityMap = {};
DeferredHolderExpressionFinalizerImpl(this._commonElements,
{this.enableMinification = true}) {
_visitor = _DeferredHolderExpressionCollectorVisitor(this);
}
@override
void addCode(String resourceName, js.Node code) {
_visitor.setResourceNameAndVisit(resourceName, code);
}
Holder _lookup<T>(T data, LibraryEntity library, Map<T, Holder> map) {
if (library == _commonElements.interceptorsLibrary) {
return globalObjectForInterceptors;
}
// See the below note on globalObjectForConstants.
return map[data] ?? mainHolder;
}
/// Returns true if [element] is stored in the static state holder
/// ([staticStateHolder]). We intend to store only mutable static state
/// there, whereas constants are stored in 'C'. Functions, accessors,
/// classes, etc. are stored in one of the other objects in
/// [reservedGlobalObjectNames].
bool _isPropertyOfStaticStateHolder(MemberEntity element) {
// TODO(ahe): Make sure this method's documentation is always true and
// remove the word "intend".
return element.isField;
}
Holder globalObjectForMember(MemberEntity entity) {
if (_isPropertyOfStaticStateHolder(entity)) {
return globalObjectForStaticState;
} else {
return _lookup(entity, entity.library, memberEntityMap);
}
}
Holder globalObjectForClass(ClassEntity entity) {
return _lookup(entity, entity.library, classEntityMap);
}
static String globalObjectNameForStaticState() => r'$';
static String globalObjectNameForInterceptors() => 'J';
Holder globalObjectForConstant(ConstantValue constant) {
// TODO(46009): There is a bug where constants are referenced without being
// emitted. However, in practice it may not matter because these constants
// may not be used. Until this bug is fixed, we say these constants are in
// the [mainHolder] even though they aren't in the code at all.
return constantValueMap[constant] ?? mainConstantHolder;
}
Holder globalObjectForEntity(Entity entity) {
if (entity is MemberEntity) {
return globalObjectForMember(entity);
} else if (entity is ClassEntity) {
return globalObjectForClass(entity);
} else {
assert((entity as LibraryEntity) == _commonElements.interceptorsLibrary);
return globalObjectForInterceptors;
}
}
/// Registers a [holder] use within a given [resource], if [properties] are
/// provided then it is assumed this is an update to a holder.
void registerHolderUseOrUpdate(String resourceName, Holder holder,
{List<js.Property> properties}) {
if (properties == null) {
holder.registerUse(resourceName);
} else {
holder.registerUpdate(resourceName, properties);
}
allHolders.add(holder);
(holdersPerResource[resourceName] ??= {}).add(holder);
}
/// Returns a global object for a given [Object] based on the
/// [DeferredHolderExpressionKind].
Holder kindToHolder(DeferredHolderExpressionKind kind, Object data) {
switch (kind) {
case DeferredHolderExpressionKind.globalObjectForInterceptors:
return globalObjectForInterceptors;
case DeferredHolderExpressionKind.globalObjectForClass:
return globalObjectForClass(data);
case DeferredHolderExpressionKind.globalObjectForMember:
return globalObjectForMember(data);
case DeferredHolderExpressionKind.globalObjectForConstant:
return globalObjectForConstant(data);
case DeferredHolderExpressionKind.globalObjectForStaticState:
return globalObjectForStaticState;
}
throw UnsupportedError("Unreachable");
}
/// Finalizes [DeferredHolderParameter]s.
void finalizeParameters() {
for (var parameter in holderParameters) {
if (parameter.isFinalized) continue;
parameter.name = globalObjectNameForStaticState();
}
}
/// Finalizes all of the [DeferredHolderExpression]s associated with a
/// [DeferredHolderResource].
void finalizeReferences(DeferredHolderResource resource) {
var resourceName = resource.name;
if (!holderReferences.containsKey(resourceName)) return;
for (var reference in holderReferences[resourceName]) {
if (reference.isFinalized) continue;
var holder = kindToHolder(reference.kind, reference.data);
js.Expression value = js.VariableUse(holder.localName(resourceName));
reference.value =
value.withSourceInformation(reference.sourceInformation);
}
}
/// Registers all of the holders used in the entire program.
void registerHolders() {
// Register all holders used in all [DeferredHolderResource]s.
for (var resource in holderResources) {
resource.holderCode.forEach((entity, properties) {
Holder holder = globalObjectForEntity(entity);
registerHolderUseOrUpdate(resource.name, holder,
properties: properties);
});
}
// Register all holders used in [DeferredHolderReference]s.
holderReferences.forEach((resource, references) {
for (var reference in references) {
var holder = kindToHolder(reference.kind, reference.data);
registerHolderUseOrUpdate(resource, holder);
}
});
// Finally, because all holders are needed in the main holder, we register
// their use here.
for (var holder in allHolders) {
registerHolderUseOrUpdate(mainHolderResource.name, holder);
}
}
/// Returns an [Iterable<Holder>] containing all of the holders used within a
/// given [DeferredHolderResource] except the static state holder (if any).
Iterable<Holder> nonStaticStateHolders(DeferredHolderResource resource) {
if (!holdersPerResource.containsKey(resource.name)) return [];
return holdersPerResource[resource.name]
.where((holder) => holder != globalObjectForStaticState);
}
/// Generates code to declare holders for a given [resourceName].
HolderInitCode declareHolders(String resourceName, Iterable<Holder> holders,
{bool initializeEmptyHolders = false}) {
// Create holder initialization code. If there are no properties
// associated with a given holder in this specific [DeferredHolderResource]
// then it will be omitted. However, in some cases, i.e. the main output
// unit, we still want to declare the holder with an empty object literal
// which will be filled in later by another [DeferredHolderResource], i.e.
// in a specific deferred fragment. The generated code looks like this:
//
// {
// var H = {...}, ..., G = {...};
// }
List<Holder> activeHolders = [];
List<js.VariableInitialization> holderInitializations = [];
for (var holder in holders) {
var holderName = holder.localName(resourceName);
List<js.Property> properties =
holder.propertiesPerResource[resourceName] ?? [];
if (properties.isEmpty) {
holderInitializations.add(js.VariableInitialization(
js.VariableDeclaration(holderName, allowRename: false),
initializeEmptyHolders ? js.ObjectInitializer(properties) : null));
} else {
activeHolders.add(holder);
holderInitializations.add(js.VariableInitialization(
js.VariableDeclaration(holderName, allowRename: false),
js.ObjectInitializer(properties)));
}
}
// Create statement to initialize holders.
var initStatement = js.ExpressionStatement(
js.VariableDeclarationList(holderInitializations, indentSplits: false));
return HolderInitCode(holders, activeHolders, initStatement);
}
/// Finalizes [resource] to code that updates holders. [resource] must be in
/// the AST of a deferred fragment.
void updateHolders(DeferredHolderResource resource) {
var resourceName = resource.name;
final holderCode =
declareHolders(resourceName, nonStaticStateHolders(resource));
// Update holder assignments.
List<js.Statement> updateHolderAssignments = [
if (holderCode.allHolders.isNotEmpty) holderCode.statement,
];
for (var holder in holderCode.allHolders) {
var holderName = holder.localName(resourceName);
var holderIndex = js.number(holder.index);
if (holderCode.activeHolders.contains(holder)) {
updateHolderAssignments.add(js.js.statement(
'#holder = hunkHelpers.updateHolder(holdersList[#index], #holder)',
{'index': holderIndex, 'holder': js.VariableUse(holderName)}));
} else {
// TODO(sra): Change declaration followed by assignments to declarations
// with initialization.
updateHolderAssignments.add(js.js.statement(
'#holder = holdersList[#index]',
{'index': holderIndex, 'holder': js.VariableUse(holderName)}));
}
}
// Create a single block of all statements.
resource.statement = js.Block(updateHolderAssignments);
}
/// Declares all holders in the [DeferredHolderResource] representing the main
/// fragment.
void declareHoldersInMainResource() {
// Declare holders in main output unit.
var holders = nonStaticStateHolders(mainHolderResource);
var mainHolderResourceName = mainHolderResource.name;
var holderCode = declareHolders(mainHolderResourceName, holders,
initializeEmptyHolders: true);
// Create holder uses and init holder indices.
List<js.VariableUse> holderUses = [];
int i = 0;
for (var holder in holders) {
holder.index = i++;
holderUses.add(js.VariableUse(holder.localName(mainHolderResourceName)));
}
// Create holders array statement.
// {
// var holders = [ H, ..., G ];
// }
var holderArray =
js.js.statement('var holders = #', js.ArrayInitializer(holderUses));
mainHolderResource.statement =
js.Block([holderCode.statement, holderArray]);
}
/// Initializes local names for [Holder] objects, and also performs frequency
/// based renaming if requested.
void setLocalHolderNames() {
bool shouldMinify(Holder holder) {
// We minify all holders if minification is enabled, except for holders
// which are already minified.
return enableMinification &&
holder != globalObjectForStaticState &&
holder != globalObjectForInterceptors;
}
holdersPerResource.forEach((resource, holders) {
// Sort holders by reference count within this resource.
var sortedHolders = holders.toList(growable: false);
sortedHolders.sort((a, b) {
return b.refCount(resource).compareTo(a.refCount(resource));
});
// Assign names based on frequency. This will be ignored unless
// minification is enabled.
var reservedNames = Namer.reservedCapitalizedGlobalSymbols
.union({globalObjectNameForInterceptors()});
var namer = TokenScope(initialChar: $A, illegalNames: reservedNames);
for (var holder in sortedHolders) {
// We will use minified local names for all holders, unless minification
// is disabled or the holder is the static state holder.
String localHolderName;
if (shouldMinify(holder)) {
localHolderName = namer.getNextName();
} else {
localHolderName = holder.key;
}
holder.setLocalName(resource, localHolderName);
}
});
}
/// Initializes [Holder] objects with their default names and sets up maps of
/// [Entity] / [ConstantValue] to [Holder].
void initializeHolders() {
void _addMembers(Holder holder, List<Method> methods) {
for (var method in methods) {
memberEntityMap[method.element] = holder;
if (method is DartMethod) {
_addMembers(holder, method.parameterStubs);
}
}
}
void _addClass(Holder holder, Class cls) {
classEntityMap[cls.element] = holder;
_addMembers(holder, cls.methods);
_addMembers(holder, cls.isChecks);
_addMembers(holder, cls.checkedSetters);
_addMembers(holder, cls.gettersSetters);
_addMembers(holder, cls.callStubs);
_addMembers(holder, cls.noSuchMethodStubs);
if (cls.nativeExtensions != null) {
for (var extClass in cls.nativeExtensions) {
_addClass(holder, extClass);
}
}
}
for (var resource in holderResources) {
// Our default names are either 'MAIN,' 'PART<N>', or '<NAME>_C'.
var holderName =
resource.isMainFragment ? mainResourceName : 'part${resource.name}';
holderName = holderName.toUpperCase();
var holder = Holder(holderName);
// Constant properties are not unique globally and must live in their own
// holder.
var constantHolder = Holder('${holderName}_C');
// Initialize the [mainHolder] and [mainConstantHolder].
if (resource.isMainFragment) {
mainHolder = holder;
mainConstantHolder = constantHolder;
}
for (var fragment in resource.fragments) {
for (var constant in fragment.constants) {
constantValueMap[constant.value] = constantHolder;
}
for (var library in fragment.libraries) {
for (var cls in library.classes) {
_addClass(holder, cls);
}
for (var staticMethod in library.statics) {
memberEntityMap[staticMethod.element] = holder;
}
}
}
}
}
/// Allocates all [DeferredHolderResource]s and finalizes the associated
/// [DeferredHolderExpression]s.
void allocateResourcesAndFinalizeReferences() {
// First finalize all holders in the main output unit.
declareHoldersInMainResource();
// Next finalize all [DeferredHolderResource]s.
for (var resource in holderResources) {
switch (resource.kind) {
case DeferredHolderResourceKind.mainFragment:
// There should only be one main resource and at this point it
// should have already been finalized.
assert(mainHolderResource == resource && resource.isFinalized);
break;
case DeferredHolderResourceKind.deferredFragment:
updateHolders(resource);
break;
}
finalizeReferences(resource);
}
}
@override
void finalize() {
initializeHolders();
registerHolders();
setLocalHolderNames();
finalizeParameters();
allocateResourcesAndFinalizeReferences();
}
@override
void registerDeferredHolderExpression(
String resourceName, DeferredHolderExpression node) {
(holderReferences[resourceName] ??= []).add(node);
}
@override
void registerDeferredHolderResource(DeferredHolderResource node) {
if (node.isMainFragment) {
assert(mainHolderResource == null);
mainHolderResource = node;
}
holderResources.add(node);
}
@override
void registerDeferredHolderParameter(DeferredHolderParameter node) {
holderParameters.add(node);
}
}
/// Scans a JavaScript AST to collect all the [DeferredHolderExpression],
/// [DeferredHolderParameter], [DeferredHolderResource], and
/// [DeferredHolderResourceExpression] nodes.
///
/// The state is kept in the finalizer so that this scan could be extended to
/// look for other deferred expressions in one pass.
class _DeferredHolderExpressionCollectorVisitor extends js.BaseVisitorVoid {
String resourceName;
final DeferredHolderExpressionFinalizer _finalizer;
_DeferredHolderExpressionCollectorVisitor(this._finalizer);
void setResourceNameAndVisit(String resourceName, js.Node code) {
this.resourceName = resourceName;
code.accept(this);
this.resourceName = null;
}
@override
void visitNode(js.Node node) {
assert(node is! DeferredHolderExpression);
if (node is js.AstContainer) {
for (js.Node element in node.containedNodes) {
element.accept(this);
}
} else {
super.visitNode(node);
}
}
@override
void visitDeferredExpression(js.DeferredExpression node) {
if (node is DeferredHolderExpression) {
assert(resourceName != null);
_finalizer.registerDeferredHolderExpression(resourceName, node);
} else {
visitNode(node);
}
}
@override
void visitDeferredStatement(js.DeferredStatement node) {
if (node is DeferredHolderResource) {
_finalizer.registerDeferredHolderResource(node);
} else {
visitNode(node);
}
}
@override
void visitParameter(js.Parameter node) {
if (node is DeferredHolderParameter) {
_finalizer.registerDeferredHolderParameter(node);
} else {
visitNode(node);
}
}
}
class HolderInitCode {
final Iterable<Holder> allHolders;
final List<Holder> activeHolders;
final js.Statement statement;
HolderInitCode(this.allHolders, this.activeHolders, this.statement);
}