diff --git a/java/src/com/google/template/soy/data/LoggingAdvisingAppendable.java b/java/src/com/google/template/soy/data/LoggingAdvisingAppendable.java index 252f7fe42..9695c8aff 100644 --- a/java/src/com/google/template/soy/data/LoggingAdvisingAppendable.java +++ b/java/src/com/google/template/soy/data/LoggingAdvisingAppendable.java @@ -97,9 +97,13 @@ public abstract LoggingAdvisingAppendable append(CharSequence csq, int start, in public abstract LoggingAdvisingAppendable exitLoggableElement(); /** Flushes all pending logging attributes. */ - public final LoggingAdvisingAppendable flushPendingLoggingAttributes() { - // TODO(b/383661457): Implement this. - return this; + public final LoggingAdvisingAppendable flushPendingLoggingAttributes(boolean isAnchor) + throws IOException { + return appendLoggingFunctionInvocation( + isAnchor + ? LoggingFunctionInvocation.FLUSH_PENDING_ATTRIBUTES_FOR_ANCHOR + : LoggingFunctionInvocation.FLUSH_PENDING_ATTRIBUTES_FOR_NON_ANCHOR, + ImmutableList.of()); } /** diff --git a/java/src/com/google/template/soy/data/LoggingFunctionInvocation.java b/java/src/com/google/template/soy/data/LoggingFunctionInvocation.java index 79750965b..6fd77c0aa 100644 --- a/java/src/com/google/template/soy/data/LoggingFunctionInvocation.java +++ b/java/src/com/google/template/soy/data/LoggingFunctionInvocation.java @@ -16,6 +16,8 @@ package com.google.template.soy.data; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.template.soy.data.restricted.BooleanData; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -24,12 +26,31 @@ /** The result of executing the logging function. */ @AutoValue public abstract class LoggingFunctionInvocation { + static final LoggingFunctionInvocation FLUSH_PENDING_ATTRIBUTES_FOR_ANCHOR = + new AutoValue_LoggingFunctionInvocation( + "$$flushPendingAttributes", + "", + ImmutableList.of(BooleanData.FALSE), + /* isFlushPendingAttributes= */ true, + Optional.>empty()); + + static final LoggingFunctionInvocation FLUSH_PENDING_ATTRIBUTES_FOR_NON_ANCHOR = + new AutoValue_LoggingFunctionInvocation( + "$$flushPendingAttributes", + "", + ImmutableList.of(BooleanData.FALSE), + /* isFlushPendingAttributes= */ true, + Optional.>empty()); @Nonnull public static LoggingFunctionInvocation create( String functionName, String placeholderValue, List args) { return new AutoValue_LoggingFunctionInvocation( - functionName, placeholderValue, args, Optional.>empty()); + functionName, + placeholderValue, + args, + /* isFlushPendingAttributes= */ false, + Optional.>empty()); } /** @@ -46,6 +67,14 @@ public static LoggingFunctionInvocation create( */ public abstract List args(); + /** + * Returns whether the function is a special case that should flush pending attributes. + * + *

This is a special case for the {@code $$flushPendingAttributes} function. User logger + * implementations should never see this set to true. + */ + public abstract boolean isFlushPendingAttributes(); + /** * When set, informs the ultimate logger that the content should be sent to the consumer instead * of the output. @@ -55,11 +84,15 @@ public static LoggingFunctionInvocation create( /** Returns a new invocation that will send the result to the given consumer. */ LoggingFunctionInvocation withResultConsumer(Consumer resultConsumer) { if (resultConsumer().isPresent()) { - // There is no known usecase where multiple consumers are needed. If they are it is trivial - // to compose `Consumer` objects via `Consumer.andThen + // There is no known use case where multiple consumers are needed. If we discover one then we + // can compose `Consumer` objects via `Consumer.andThen`. throw new IllegalStateException("resultConsumer already set"); } return new AutoValue_LoggingFunctionInvocation( - functionName(), placeholderValue(), args(), Optional.of(resultConsumer)); + functionName(), + placeholderValue(), + args(), + isFlushPendingAttributes(), + Optional.of(resultConsumer)); } } diff --git a/java/src/com/google/template/soy/jbcsrc/AppendableExpression.java b/java/src/com/google/template/soy/jbcsrc/AppendableExpression.java index c0684ff72..f8cb494a2 100644 --- a/java/src/com/google/template/soy/jbcsrc/AppendableExpression.java +++ b/java/src/com/google/template/soy/jbcsrc/AppendableExpression.java @@ -164,7 +164,8 @@ static Statement concat(List statements) { MethodRef.createNonPure(LoggingAdvisingAppendable.class, "flushBuffers", int.class); private static final MethodRef FLUSH_PENDING_LOGGING_ATTRBIUTES = - MethodRef.createNonPure(LoggingAdvisingAppendable.class, "flushPendingLoggingAttributes"); + MethodRef.createNonPure( + LoggingAdvisingAppendable.class, "flushPendingLoggingAttributes", boolean.class); static AppendableExpression forExpression(Expression delegate) { return new AppendableExpression(delegate, e -> e, /* supportsSoftLimiting= */ true); @@ -271,8 +272,8 @@ AppendableExpression setSanitizedContentKindAndDirectionality(SanitizedContentKi BytecodeUtils.constantSanitizedContentKindAsContentKind(kind))); } - AppendableExpression flushPendingLoggingAttributes() { - return withNewDelegate(e -> e.invoke(FLUSH_PENDING_LOGGING_ATTRBIUTES)); + AppendableExpression flushPendingLoggingAttributes(boolean isAnchor) { + return withNewDelegate(e -> e.invoke(FLUSH_PENDING_LOGGING_ATTRBIUTES, constant(isAnchor))); } Statement flushBuffers(int depth) { diff --git a/java/src/com/google/template/soy/jbcsrc/SoyNodeCompiler.java b/java/src/com/google/template/soy/jbcsrc/SoyNodeCompiler.java index 22ffe0ca1..3ba0b9aa3 100644 --- a/java/src/com/google/template/soy/jbcsrc/SoyNodeCompiler.java +++ b/java/src/com/google/template/soy/jbcsrc/SoyNodeCompiler.java @@ -638,7 +638,7 @@ private Optional tryCompileSwitchToSwitchInstruction( asSwitchableInt(switchExpr, casesByKey.navigableKeySet()), casesByKey, defaultBlock)); } else { - // Otherwise we need more complex matching logic that we outsource to an invoke dyanmic + // Otherwise we need more complex matching logic that we outsource to an invoke dynamic // bootstrap. Create a fake key for each case and then rely on the bootstrap to figure it // out. // update the map with the pseudo keys, so that the loops below can find them @@ -843,7 +843,9 @@ protected Statement visitPrintNode(PrintNode node) { return visitLoggingFunction(node, fn, (LoggingFunction) fn.getSoyFunction()); } if (fn.getSoyFunction() == BuiltinFunction.FLUSH_PENDING_LOGGING_ATTRIBUTES) { - return appendableExpression.flushPendingLoggingAttributes().toStatement(); + return appendableExpression + .flushPendingLoggingAttributes(((BooleanNode) fn.getParams().get(0)).getValue()) + .toStatement(); } } // First check our special case where all print directives are streamable and an expression that @@ -1049,7 +1051,7 @@ protected Statement visitRawTextNode(RawTextNode node) { @Override protected Statement visitDebuggerNode(DebuggerNode node) { - // Call JbcSrcRuntime.debuggger. This logs a stack trace by default and is an obvious place to + // Call JbcSrcRuntime.debugger. This logs a stack trace by default and is an obvious place to // put a breakpoint. return MethodRefs.RUNTIME_DEBUGGER.invokeVoid( constant(node.getSourceLocation().getFilePath().path()), diff --git a/java/src/com/google/template/soy/jbcsrc/api/OutputAppendable.java b/java/src/com/google/template/soy/jbcsrc/api/OutputAppendable.java index 6226a8522..06a52357e 100644 --- a/java/src/com/google/template/soy/jbcsrc/api/OutputAppendable.java +++ b/java/src/com/google/template/soy/jbcsrc/api/OutputAppendable.java @@ -103,8 +103,18 @@ public LoggingAdvisingAppendable appendLoggingFunctionInvocation( LoggingFunctionInvocation funCall, ImmutableList> escapers) throws IOException { if (!isLogOnly()) { - String value = - logger == null ? funCall.placeholderValue() : logger.evalLoggingFunction(funCall); + String value; + if (logger == null) { + value = funCall.placeholderValue(); + } else { + if (funCall.isFlushPendingAttributes()) { + // For now, just no-op these calls. + // TODO-b/383661457: implement this. + value = ""; + } else { + value = logger.evalLoggingFunction(funCall); + } + } for (Function directive : escapers) { value = directive.apply(value); } @@ -159,10 +169,6 @@ public LoggingAdvisingAppendable exitLoggableElement() { return this; } - @Override - public void flushBuffers(int depth) { - throw new AssertionError("shouldn't be called"); - } private void appendDebugOutput(Optional veDebugOutput) { if (veDebugOutput.isPresent()) { diff --git a/java/src/com/google/template/soy/passes/AddFlushPendingLoggingAttributesPass.java b/java/src/com/google/template/soy/passes/AddFlushPendingLoggingAttributesPass.java index e00d18c59..5173bf846 100644 --- a/java/src/com/google/template/soy/passes/AddFlushPendingLoggingAttributesPass.java +++ b/java/src/com/google/template/soy/passes/AddFlushPendingLoggingAttributesPass.java @@ -20,6 +20,8 @@ import com.google.template.soy.base.internal.IdGenerator; import com.google.template.soy.base.internal.Identifier; import com.google.template.soy.error.ErrorReporter; +import com.google.template.soy.exprtree.BooleanNode; +import com.google.template.soy.exprtree.ExprNode; import com.google.template.soy.exprtree.FunctionNode; import com.google.template.soy.shared.internal.BuiltinFunction; import com.google.template.soy.soytree.HtmlAttributeNode; @@ -92,6 +94,7 @@ private void instrumentNode(IdGenerator nodeIdGen, HtmlOpenTagNode openTag) { BuiltinFunction.FLUSH_PENDING_LOGGING_ATTRIBUTES.name(), SourceLocation.UNKNOWN), BuiltinFunction.FLUSH_PENDING_LOGGING_ATTRIBUTES, SourceLocation.UNKNOWN); + functionCall.addChild(getTagIsAnchorNode(openTag)); functionCall.setType(AttributesType.getInstance()); var printNode = new PrintNode( @@ -111,4 +114,12 @@ private void instrumentNode(IdGenerator nodeIdGen, HtmlOpenTagNode openTag) { attributeNode.addChild(printNode); openTag.addChild(attributeNode); } + + private static ExprNode getTagIsAnchorNode(HtmlOpenTagNode openTag) { + var tagName = openTag.getTagName(); + return new BooleanNode( + // If the tag is dynamic we assume it isn't an anchor + tagName.isStatic() && tagName.getStaticTagNameAsLowerCase().equals("a"), + tagName.getTagLocation()); + } } diff --git a/java/src/com/google/template/soy/shared/internal/BuiltinFunction.java b/java/src/com/google/template/soy/shared/internal/BuiltinFunction.java index 11bb23a9b..ac704313e 100644 --- a/java/src/com/google/template/soy/shared/internal/BuiltinFunction.java +++ b/java/src/com/google/template/soy/shared/internal/BuiltinFunction.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSet; import com.google.template.soy.shared.restricted.SoyFunction; import com.google.template.soy.types.AnyType; +import com.google.template.soy.types.BoolType; import com.google.template.soy.types.IterableType; import com.google.template.soy.types.NullType; import com.google.template.soy.types.SanitizedType; @@ -93,7 +94,6 @@ public Set getValidArgsSizes() { case IS_PRIMARY_MSG_IN_USE: return ImmutableSet.of(3); case DEBUG_SOY_TEMPLATE_INFO: - case FLUSH_PENDING_LOGGING_ATTRIBUTES: return ImmutableSet.of(0); case SOY_SERVER_KEY: case CHECK_NOT_NULL: @@ -110,6 +110,7 @@ public Set getValidArgsSizes() { case IS_TRUTHY_NON_EMPTY: case HAS_CONTENT: case NEW_SET: + case FLUSH_PENDING_LOGGING_ATTRIBUTES: return ImmutableSet.of(1); case PROTO_INIT: throw new UnsupportedOperationException(); @@ -135,6 +136,8 @@ public Optional> getValidArgTypes() { StringType.getInstance(), NullType.getInstance(), UndefinedType.getInstance()))); + case FLUSH_PENDING_LOGGING_ATTRIBUTES: + return Optional.of(ImmutableList.of(BoolType.getInstance())); case NEW_SET: // This is further constrained in ResolveExpressionTypesPass. return Optional.of(ImmutableList.of(IterableType.of(AnyType.getInstance()))); diff --git a/java/tests/com/google/template/soy/jbcsrc/StreamingPrintDirectivesTest.java b/java/tests/com/google/template/soy/jbcsrc/StreamingPrintDirectivesTest.java index ce25c3e20..97cfae515 100644 --- a/java/tests/com/google/template/soy/jbcsrc/StreamingPrintDirectivesTest.java +++ b/java/tests/com/google/template/soy/jbcsrc/StreamingPrintDirectivesTest.java @@ -37,7 +37,6 @@ import com.google.template.soy.data.SoyRecord; import com.google.template.soy.data.SoyValueConverter; import com.google.template.soy.data.SoyValueConverterUtility; -import com.google.template.soy.data.SoyValueProvider; import com.google.template.soy.data.internal.ParamStore; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.jbcsrc.restricted.BytecodeUtils;