/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.template.soy.jssrc.dsl;
import static com.google.template.soy.jssrc.dsl.CodeChunk.WithValue.LITERAL_EMPTY_STRING;
import static com.google.template.soy.jssrc.dsl.CodeChunk.id;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.jssrc.dsl.CodeChunk.RequiresCollector;
import com.google.template.soy.jssrc.restricted.JsExprUtils;
import java.util.List;
import java.util.regex.Pattern;
/** Utility methods for working with CodeChunks. */
public final class CodeChunkUtils {
/**
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Variables
*
* <p>This incorrectly allows keywords, but that's not a big problem because doing so would cause
* JSCompiler to crash. This also incorrectly disallows Unicode in identifiers, but that's not a
* big problem because the JS backend generally names identifiers after Soy identifiers, which
* don't allow Unicode either.
*/
private static final Pattern ID = Pattern.compile("[A-Za-z_$][\\w$]*");
private CodeChunkUtils() {}
/** Validates that the given string is a valid javascript identifier. */
static void checkId(String id) {
if (!ID.matcher(id).matches()) {
throw new IllegalArgumentException(String.format("not a valid js identifier: %s", id));
}
}
/**
* Builds a {@link CodeChunk.WithValue} that represents the concatenation of the given code
* chunks. The {@code +} operator is used for concatenation.
*
* <p>The resulting chunk is not guaranteed to be string-valued if the first two operands do not
* produce strings when combined with the plus operator; e.g. 2+2 might be 4 instead of '22'.
*
* <p>This is a port of {@link JsExprUtils#concatJsExprs}, which should eventually go away.
* TODO(user): make that go away.
*/
public static CodeChunk.WithValue concatChunks(List<? extends CodeChunk.WithValue> chunks) {
if (chunks.isEmpty()) {
return LITERAL_EMPTY_STRING;
}
CodeChunk.WithValue accum = chunks.get(0);
for (CodeChunk.WithValue chunk : chunks.subList(1, chunks.size())) {
accum = accum.plus(chunk);
}
return accum;
}
/**
* Builds a {@link CodeChunk.WithValue} that represents the concatenation of the given code
* chunks. This doesn't assume the values represented by the inputs are necessarily strings, but
* guarantees that the value represented by the output is a string.
*/
public static CodeChunk.WithValue concatChunksForceString(
List<? extends CodeChunk.WithValue> chunks) {
if (!chunks.isEmpty()
&& chunks.get(0).isRepresentableAsSingleExpression()
&& JsExprUtils.isStringLiteral(
chunks.get(0).assertExprAndCollectRequires(RequiresCollector.NULL))) {
return concatChunks(chunks);
} else if (chunks.size() > 1
&& chunks.get(1).isRepresentableAsSingleExpression()
&& JsExprUtils.isStringLiteral(
chunks.get(1).assertExprAndCollectRequires(RequiresCollector.NULL))) {
return concatChunks(chunks);
} else {
return concatChunks(
ImmutableList.<CodeChunk.WithValue>builder()
.add(LITERAL_EMPTY_STRING)
.addAll(chunks)
.build());
}
}
}