void _writePropertyMap()

in tool/mustachio/codegen_runtime_renderer.dart [403:595]


  void _writePropertyMap(_RendererInfo renderer) {
    var contextClass = renderer._contextClass;
    var generics = renderer._typeParametersStringWith(
        '$_contextTypeVariable extends ${renderer._typeName}');
    // It would be simplest if [propertyMap] were just a getter, but it must be
    // parameterized on `CT_`, so it is a static method. Due to the possibly
    // extensive amount of spreading (supertypes, mixins) and object
    // construction (lots of [Property] objects with function literals), we
    // cache the construction of each one, keyed to the `CT_` value. Each cache
    // should not have many entries, as there are probably not many values for
    // each type variable, `CT_`, typically one.
    _buffer.writeln('''
    static final Map<Type, Object> _propertyMapCache = {};
    static Map<String, Property<$_contextTypeVariable>> propertyMap$generics() =>
        _propertyMapCache.putIfAbsent($_contextTypeVariable, () => {''');
    var supertype = contextClass.supertype;
    if (supertype != null) {
      var superclassRendererName = _typeToRendererClassName[supertype.element];
      if (superclassRendererName != null) {
        var superMapName = '$superclassRendererName.propertyMap';
        var generics = asGenerics([
          ...supertype.typeArguments
              .map((e) => e.getDisplayString(withNullability: false)),
          _contextTypeVariable
        ]);
        _buffer.writeln('    ...$superMapName$generics(),');
      }
    }
    // Mixins are spread into the property map _after_ the super class, so
    // that they override any values which need to be overridden. Superclass
    // and mixins override from left to right, as do spreads:
    // `class C extends E with M, N` first takes members from N, then M, then
    // E. Similarly, `{...a, ...b, ...c}` will feature elements from `c` which
    // override `b` and `a`.
    for (var mixin in contextClass.mixins) {
      var mixinRendererName = _typeToRendererClassName[mixin.element];
      if (mixinRendererName != null) {
        var mixinMapName = '$mixinRendererName.propertyMap';
        var generics = asGenerics([
          ...mixin.typeArguments
              .map((e) => e.getDisplayString(withNullability: false)),
          _contextTypeVariable
        ]);
        _buffer.writeln('    ...$mixinMapName$generics(),');
      }
    }
    for (var property in [...contextClass.accessors]
      ..sort((a, b) => a.name.compareTo(b.name))) {
      var returnType = property.type.returnType;
      if (returnType is InterfaceType) {
        _writeProperty(renderer, property, returnType);
      } else if (returnType is TypeParameterType &&
          !returnType.bound.isDynamic) {
        _writeProperty(renderer, property, returnType.bound as InterfaceType);
      }
    }
    _buffer.writeln('}) as Map<String, Property<$_contextTypeVariable>>;');
    _buffer.writeln('');
  }

  void _writeProperty(_RendererInfo renderer, PropertyAccessorElement property,
      InterfaceType getterType) {
    if (getterType == _typeProvider.typeType) {
      // The [Type] type is the first case of a type we don't want to traverse.
      return;
    }

    if (property.isPrivate || property.isStatic || property.isSetter) return;
    if (property.hasProtected || property.hasVisibleForTesting) return;
    _buffer.writeln("'${property.name}': Property(");
    _buffer
        .writeln('getValue: ($_contextTypeVariable c) => c.${property.name},');

    var getterName = property.name;
    var getterTypeString = getterType.getDisplayString(withNullability: false);
    // Only add a `getProperties` function, which returns the property map for
    // [getterType], if [getterType] is a renderable type.
    if (_typeToRendererClassName.containsKey(getterType.element)) {
      var rendererClassName = _typeToRendererClassName[getterType.element];
      _buffer.writeln('''
renderVariable:
    ($_contextTypeVariable c, Property<$_contextTypeVariable> self, List<String> remainingNames) {
  if (remainingNames.isEmpty) {
    return self.getValue(c).toString();
  }
  var name = remainingNames.first;
  var nextProperty = $rendererClassName.propertyMap().getValue(name);
  return nextProperty.renderVariable(
      self.getValue(c) as $getterTypeString, nextProperty, [...remainingNames.skip(1)]);
},
''');
    } else {
      // [getterType] does not have a full renderer, so we just render a simple
      // variable, with no opportunity to access fields on [getterType].

      _buffer.writeln('''
renderVariable:
    ($_contextTypeVariable c, Property<CT_> self, List<String> remainingNames) =>
        self.renderSimpleVariable(c, remainingNames, '$getterTypeString'),
''');
    }

    if (getterType.isDartCoreBool) {
      _buffer.writeln(
          'getBool: ($_contextTypeVariable c) => c.$getterName == true,');
    } else if (_typeSystem.isAssignableTo(
        getterType, _typeProvider.iterableDynamicType)) {
      var iterableElement = _typeProvider.iterableElement;
      var iterableType = getterType.asInstanceOf(iterableElement);
      // Not sure why [iterableType] would be null... unresolved type?
      if (iterableType != null) {
        var innerType = iterableType.typeArguments.first;
        // Don't add Iterable functions for a generic type, for example
        // `List<E>.reversed` has inner type `E`, which we don't have a specific
        // renderer for.
        // TODO(srawlins): Find a solution for this. We can track all of the
        // concrete types substituted for `E` for example.
        if (innerType is! TypeParameterType) {
          var renderFunctionName = _typeToRenderFunctionName[innerType.element];
          String renderCall;
          if (renderFunctionName == null) {
            var typeName = innerType.element!.name!;
            if (innerType is InterfaceType) {
              _invisibleGetters.putIfAbsent(
                  typeName, () => innerType.element.allAccessorNames);
            }
            renderCall = 'renderSimple(e, ast, r.template, sink, parent: r, '
                "getters: _invisibleGetters['$typeName']!)";
          } else {
            var bang = _typeSystem.isPotentiallyNullable(innerType) ? '!' : '';
            renderCall =
                '$renderFunctionName(e$bang, ast, r.template, sink, parent: r)';
          }
          _buffer.writeln('''
renderIterable:
    ($_contextTypeVariable c, RendererBase<$_contextTypeVariable> r,
     List<MustachioNode> ast, StringSink sink) {
  return c.$getterName.map((e) => $renderCall);
},
''');
        }
      }
    } else {
      // Don't add Iterable functions for a generic type, for example
      // `List<E>.first` has type `E`, which we don't have a specific
      // renderer for.
      // TODO(srawlins): Find a solution for this. We can track all of the
      // concrete types substituted for `E` for example.
      if (getterName is! TypeParameterType) {
        var renderFunctionName = _typeToRenderFunctionName[getterType.element];
        String renderCall;
        if (renderFunctionName == null) {
          var typeName = getterType.element.name;
          _invisibleGetters.putIfAbsent(
              typeName, () => getterType.element.allAccessorNames);
          renderCall =
              'renderSimple(c.$getterName, ast, r.template, sink, parent: r, '
              "getters: _invisibleGetters['$typeName']!)";
        } else {
          var bang = _typeSystem.isPotentiallyNullable(getterType) ? '!' : '';
          renderCall =
              '$renderFunctionName(c.$getterName$bang, ast, r.template, sink, parent: r)';
        }
        var nullValueGetter =
            getterType.nullabilitySuffix == NullabilitySuffix.none
                ? 'false'
                : 'c.$getterName == null';
        _buffer.writeln('''
isNullValue: ($_contextTypeVariable c) => $nullValueGetter,

renderValue:
    ($_contextTypeVariable c, RendererBase<$_contextTypeVariable> r,
     List<MustachioNode> ast, StringSink sink) {
  $renderCall;
},
''');
      }
    }
    _buffer.writeln('),');
  }

  /// Writes the mapping of invisible getters, used to report simple renderer
  /// errors.
  void _writeInvisibleGetters() {
    _buffer.write('const _invisibleGetters = {');
    for (var class_ in _invisibleGetters.keys.toList()..sort()) {
      _buffer.write("'$class_':");
      var getters = _invisibleGetters[class_]!.toList()..sort();
      _buffer.write('{${getters.map((e) => "'$e'").join(', ')}},');
    }
    _buffer.write('};');
  }
}