All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.SourceFile Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2009 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.io.CharStreams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe;
import com.google.javascript.jscomp.serialization.SourceFileProto;
import com.google.javascript.jscomp.serialization.SourceFileProto.FileOnDisk;
import com.google.javascript.jscomp.serialization.SourceFileProto.ZipEntryOnDisk;
import com.google.javascript.rhino.StaticSourceFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.jspecify.nullness.Nullable;

/**
 * An abstract representation of a source file that provides access to language-neutral features.
 *
 * 

The source file can be loaded from various locations, such as from disk or from a preloaded * string. Loading is done as lazily as possible to minimize IO delays and memory cost of source * text. */ public final class SourceFile implements StaticSourceFile { private static final String UTF8_BOM = "\uFEFF"; private static final String BANG_SLASH = "!" + Platform.getFileSeperator(); /** Number of lines in the region returned by {@link #getRegion(int)}. This length must be odd. */ private static final int SOURCE_EXCERPT_REGION_LENGTH = 5; /** * The file name of the source file. * *

It does not necessarily need to correspond to a real path. But it should be unique. Will * appear in warning messages emitted by the compiler. */ private final String fileName; private SourceKind kind; private final CodeLoader loader; // Source Line Information private int @Nullable [] lineOffsets = null; private volatile @Nullable String code = null; // Code statistics used for metrics. // For both of these, a negative value means we haven't counted yet. private int numLines = -1; private int numBytes = -1; private SourceFile(CodeLoader loader, String fileName, SourceKind kind) { if (isNullOrEmpty(fileName)) { throw new IllegalArgumentException("a source must have a name"); } if (!"/".equals(Platform.getFileSeperator())) { fileName = fileName.replace(Platform.getFileSeperator(), "/"); } this.loader = loader; this.fileName = fileName; this.kind = kind; } @Override public int getLineOffset(int lineno) { findLineOffsets(); if (lineno < 1 || lineno > lineOffsets.length) { throw new IllegalArgumentException( "Expected line number between 1 and " + lineOffsets.length + "\nActual: " + lineno); } return lineOffsets[lineno - 1]; } /** * Returns the number of lines in the source file. * *

NOTE: this may be stale if the SourceFile has been {@link #clearCachedSource cleared} and * the file has changed on disk. If being fully accurate is required call {@link #getCode} and * then call this method. */ int getNumLines() { if (numLines < 0) { // A negative value means we need to read in the code and calculate this information. // Otherwise, assume the file hasn't changed since we read it. try { var unused = getCode(); } catch (IOException e) { // this is consistent with old behavior of this method. return 0; } } return numLines; } /** * Returns the number of bytes in the source file * *

Oddly, bytes is defined as 'length of code as a Java String' this is accurate assuming files * only use Latin1 characters but may be inaccurate outside of that. * *

NOTE: this may be stale if the SourceFile has been {@link #clearCachedSource cleared} and * the file has changed on disk. If being fully accurate is required call {@link #getCode} and * then call this method. */ int getNumBytes() { if (numBytes < 0) { // A negative value means we need to read in the code and calculate this information. // Otherwise, assume the file hasn't changed since we read it. try { var unused = getCode(); } catch (IOException e) { // this is consistent with old behavior of this method. return 0; } } return numBytes; } private void findLineOffsets() { if (this.lineOffsets != null) { return; } String localCode = this.code; if (localCode == null) { try { localCode = this.getCode(); } catch (IOException e) { localCode = ""; this.lineOffsets = new int[1]; return; } } // getCode() updates numLines, so this is always in sync with the code. int[] offsets = new int[this.numLines]; int index = 1; // start at 1 since the offset for line 0 is always at byte 0 int offset = 0; while ((offset = localCode.indexOf('\n', offset)) != -1) { // +1 because this is the offset of the next line which is one past the newline offset++; offsets[index++] = offset; } checkState(index == offsets.length); this.lineOffsets = offsets; } /** Gets all the code in this source file. */ public final String getCode() throws IOException { if (this.code == null) { // Only synchronize if we need to synchronized (this) { // Make sure another thread hasn't loaded the code while we waited. if (this.code == null) { this.setCodeAndDoBookkeeping(this.loader.loadUncachedCode()); } } } return this.code; } @Deprecated final void setCodeDeprecated(String code) { this.setCodeAndDoBookkeeping(code); } /** Gets a reader for the code in this source file. */ @GwtIncompatible("java.io.Reader") public Reader getCodeReader() throws IOException { // Only synchronize if we need to if (this.code == null) { synchronized (this) { // Make sure another thread hasn't loaded the code while we waited. if (this.code == null) { Reader uncachedReader = this.loader.openUncachedReader(); if (uncachedReader != null) { return uncachedReader; } } } } return new StringReader(this.getCode()); } private void setCodeAndDoBookkeeping(@Nullable String sourceCode) { this.code = null; // Force recalculation of all of these values when they are requested. this.lineOffsets = null; if (sourceCode != null) { if (sourceCode.startsWith(UTF8_BOM)) { sourceCode = sourceCode.substring(UTF8_BOM.length()); } this.code = sourceCode; // Update numLines and numBytes // NOTE: In some edit/refresh development workflows, we may end up re-reading the file // and getting different numbers than we got last time we read it, so we should // not have checkState() calls here to assert they have not changed. // Misleading variable name. This really stores the 'number of utf16' code points which is // not the same as number of bytes. this.numBytes = sourceCode.length(); int numLines = 1; // there is always at least one line int index = 0; while ((index = sourceCode.indexOf('\n', index)) != -1) { index++; numLines++; } this.numLines = numLines; } } /** * @deprecated alias of {@link #getName()}. Use that instead */ @InlineMe(replacement = "this.getName()") @Deprecated public String getOriginalPath() { return this.getName(); } /** * For SourceFile types which cache source code that can be regenerated easily, flush the cache. * *

We maintain the cache mostly to speed up generating source when displaying error messages, * so dumping the file contents after the compile is a fine thing to do. */ public void clearCachedSource() { this.setCodeAndDoBookkeeping(null); } boolean hasSourceInMemory() { return code != null; } /** * Returns a unique name for the source file. * *

This name is not required to be an actual file path on disk. */ @Override public String getName() { return fileName; } /** Returns the source kind. */ @Override public SourceKind getKind() { return kind; } /** Sets the source kind. */ public void setKind(SourceKind kind) { this.kind = kind; } @Override public int getLineOfOffset(int offset) { findLineOffsets(); int search = Arrays.binarySearch(lineOffsets, offset); if (search >= 0) { return search + 1; // lines are 1-based. } else { int insertionPoint = -1 * (search + 1); return min(insertionPoint - 1, lineOffsets.length - 1) + 1; } } @Override public int getColumnOfOffset(int offset) { int line = getLineOfOffset(offset); return offset - lineOffsets[line - 1]; } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end of the file. Returns {@code * null} if it does not exist, or if there was an IO exception. */ public @Nullable String getLine(int lineNumber) { String js; try { js = getCode(); } catch (IOException e) { return null; } findLineOffsets(); if (lineNumber > lineOffsets.length) { return null; } if (lineNumber < 1) { lineNumber = 1; } int pos = lineOffsets[lineNumber - 1]; if (js.indexOf('\n', pos) == -1) { // If next new line cannot be found, there are two cases // 1. pos already reaches the end of file, then null should be returned // 2. otherwise, return the contents between pos and the end of file. if (pos >= js.length()) { return null; } else { return js.substring(pos); } } else { return js.substring(pos, js.indexOf('\n', pos)); } } /** * Gets the source lines starting at `lineNumber` and continuing until `length`. Omits any * trailing newlines. * * @param lineNumber the line number, 1 being the first line of the file. * @param length the number of characters desired, starting at the 0th character of the specified * line. If negative or 0, returns a single line. * @return The line(s) indicated. Returns {@code null} if it does not exist or if there was an IO * exception. */ public @Nullable Region getLines(int lineNumber, int length) { String js; try { js = getCode(); } catch (IOException e) { return null; } findLineOffsets(); if (lineNumber > lineOffsets.length) { return null; } if (lineNumber < 1) { lineNumber = 1; } if (length <= 0) { length = 1; } int pos = lineOffsets[lineNumber - 1]; if (pos == js.length()) { return new SimpleRegion( lineNumber, lineNumber, ""); // Happens when asking for the last empty line in a file. } int endChar = pos; int endLine = lineNumber; // go through lines until we've reached the end of the file or met the specified length. for (; endChar < pos + length && endLine <= lineOffsets.length; endLine++) { endChar = (endLine < lineOffsets.length) ? lineOffsets[endLine] : js.length(); } if (js.charAt(endChar - 1) == '\n') { return new SimpleRegion(lineNumber, endLine, js.substring(pos, endChar - 1)); } return new SimpleRegion(lineNumber, endLine, js.substring(pos, endChar)); } /** * Get a region around the indicated line number. The exact definition of a region is * implementation specific, but it must contain the line indicated by the line number. A region * must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, or if there was an IO * exception. */ public @Nullable Region getRegion(int lineNumber) { String js = ""; try { js = getCode(); } catch (IOException e) { return null; } int pos = 0; int startLine = max(1, lineNumber - (SOURCE_EXCERPT_REGION_LENGTH + 1) / 2 + 1); for (int n = 1; n < startLine; n++) { int nextpos = js.indexOf('\n', pos); if (nextpos == -1) { break; } pos = nextpos + 1; } int end = pos; int endLine = startLine; for (int n = 0; n < SOURCE_EXCERPT_REGION_LENGTH; n++, endLine++) { end = js.indexOf('\n', end); if (end == -1) { break; } end++; } if (lineNumber >= endLine) { return null; } if (end == -1) { int last = js.length() - 1; if (js.charAt(last) == '\n') { return new SimpleRegion(startLine, endLine, js.substring(pos, last)); } else { return new SimpleRegion(startLine, endLine, js.substring(pos)); } } else { return new SimpleRegion(startLine, endLine, js.substring(pos, end)); } } @Override public String toString() { return fileName; } @GwtIncompatible("fromZipInput") public static List fromZipFile(String zipName, Charset inputCharset) throws IOException { try (InputStream input = new FileInputStream(zipName)) { return fromZipInput(zipName, input, inputCharset); } } @GwtIncompatible("java.util.zip.ZipInputStream") public static List fromZipInput( String zipName, InputStream input, Charset inputCharset) throws IOException { final String absoluteZipPath = new File(zipName).getAbsolutePath(); List sourceFiles = new ArrayList<>(); try (ZipInputStream in = new ZipInputStream(input, inputCharset)) { ZipEntry zipEntry; while ((zipEntry = in.getNextEntry()) != null) { String entryName = zipEntry.getName(); if (!entryName.endsWith(".js")) { // Only accept js files continue; } sourceFiles.add( builder() .withCharset(inputCharset) .withOriginalPath(zipName + BANG_SLASH + entryName) .withZipEntryPath(absoluteZipPath, entryName) .build()); } } return sourceFiles; } @GwtIncompatible("java.io.File") public static SourceFile fromFile(String fileName, Charset charset) { return builder().withPath(fileName).withCharset(charset).build(); } @GwtIncompatible("java.io.File") public static SourceFile fromFile(String fileName) { return builder().withPath(fileName).build(); } @GwtIncompatible("java.io.File") public static SourceFile fromPath(Path path, Charset charset) { return builder().withPath(path).withCharset(charset).build(); } public static SourceFile fromCode(String fileName, String code, SourceKind kind) { return builder().withPath(fileName).withKind(kind).withContent(code).build(); } public static SourceFile fromCode(String fileName, String code) { return builder().withPath(fileName).withContent(code).build(); } /** * Reconciles serialized state in a {@link SourceFileProto} with the existing state in this file. * *

This should be called whenever initializing a compilation based on TypedAST protos. For * these compilations, the compiler initalization methods require creating SourceFiles before * deserializing TypedAST protos, so we sometimes get two copies of the same SourceFile.) */ public void restoreCachedStateFrom(SourceFileProto protoSourceFile) { checkState( protoSourceFile.getFilename().equals(this.getName()), "Cannot restore state for %s from %s", this.getName(), protoSourceFile.getFilename()); // TypedAST proto information is expected to be more accurate for: // 1) whether a SourceFile contains an @extern annotation or not. In non-TypedAST // builds, we allow passing @extern files under the --js flag. For TypedAST builds, we // could support this, but it's an uncommon pattern and trickier to support than ban. // 2) tracking some extra state that is lazily computed in a SourceFile, like the number of // lines and bytes in a file. SourceFile::restoreCachedStateFrom handles this case. // Note: the state in the proto might be incorrect in other cases, since some state cannot be // computed during library-level typechecking (e.g. what files are in the weak chunk) if (protoSourceFile.getSourceKind() == SourceFileProto.SourceKind.EXTERN) { checkState( this.getKind() == SourceKind.EXTERN, "TypedAST compilations must pass all extern files as externs, not js, but found %s", this.getName()); } // Restore the number of lines/bytes from the proto, unless we already have cached // numLines and numBytes. Offset by 1. the proto "unset" value is 0, where as in SourceFile we // use "-1" int protoNumLines = protoSourceFile.getNumLinesPlusOne() - 1; int protoNumBytes = protoSourceFile.getNumBytesPlusOne() - 1; // It's possible that this.numLines and protoNumLines are both not "-1" but contain // conflicting values. This would happen if a file changes on disk after TypedAST // proto serialization. We choose the proto value over this.numLines because this // data is intended for metrics recording, which should care most about the size of a // file when compilation began and parsing ran. this.numLines = protoNumLines != -1 ? protoNumLines : this.numLines; this.numBytes = protoNumBytes != -1 ? protoNumBytes : this.numBytes; } @GwtIncompatible("java.io.Reader") public static SourceFile fromProto(SourceFileProto protoSourceFile) { SourceKind sourceKind = getSourceKindFromProto(protoSourceFile); SourceFile sourceFile = fromProto(protoSourceFile, sourceKind); // Restore the number of lines/bytes, which are offset by 1 in the proto. sourceFile.numLines = protoSourceFile.getNumLinesPlusOne() - 1; sourceFile.numBytes = protoSourceFile.getNumBytesPlusOne() - 1; return sourceFile; } private static SourceFile fromProto(SourceFileProto protoSourceFile, SourceKind sourceKind) { switch (protoSourceFile.getLoaderCase()) { case PRELOADED_CONTENTS: return SourceFile.fromCode( protoSourceFile.getFilename(), protoSourceFile.getPreloadedContents(), sourceKind); case FILE_ON_DISK: String pathOnDisk = protoSourceFile.getFileOnDisk().getActualPath().isEmpty() ? protoSourceFile.getFilename() : protoSourceFile.getFileOnDisk().getActualPath(); return SourceFile.builder() .withCharset(toCharset(protoSourceFile.getFileOnDisk().getCharset())) .withOriginalPath(protoSourceFile.getFilename()) .withKind(sourceKind) .withPath(pathOnDisk) .build(); case ZIP_ENTRY: { SourceFileProto.ZipEntryOnDisk zipEntry = protoSourceFile.getZipEntry(); return SourceFile.builder() .withKind(sourceKind) .withOriginalPath(protoSourceFile.getFilename()) .withCharset(toCharset(zipEntry.getCharset())) .withZipEntryPath(zipEntry.getZipPath(), zipEntry.getEntryName()) .build(); } case LOADER_NOT_SET: break; } throw new AssertionError(); } private static SourceKind getSourceKindFromProto(SourceFileProto protoSourceFile) { switch (protoSourceFile.getSourceKind()) { case EXTERN: return SourceKind.EXTERN; case CODE: return SourceKind.STRONG; case NOT_SPECIFIED: case UNRECOGNIZED: break; } throw new AssertionError(); } private static Charset toCharset(String protoCharset) { if (protoCharset.isEmpty()) { return UTF_8; } return Charset.forName(protoCharset); } /** Create a new builder for source files. */ public static Builder builder() { return new Builder(); } /** * A builder interface for source files. * *

Allows users to customize the Charset, and the original path of the source file (if it * differs from the path on disk). */ public static final class Builder { private SourceKind kind = SourceKind.STRONG; private Charset charset = UTF_8; private @Nullable String originalPath = null; private @Nullable String path = null; private @Nullable Path pathWithFilesystem = null; private @Nullable String zipEntryPath = null; private @Nullable Supplier lazyContent = null; private Builder() {} /** Set the source kind. */ @CanIgnoreReturnValue public Builder withKind(SourceKind kind) { this.kind = kind; return this; } /** Set the charset to use when reading from an input stream or file. */ @CanIgnoreReturnValue public Builder withCharset(Charset charset) { this.charset = charset; return this; } public Builder withPath(String path) { return this.withPathInternal(path, null); } public Builder withPath(Path path) { return this.withPathInternal(path.toString(), path); } @CanIgnoreReturnValue public Builder withContent(String x) { this.lazyContent = x::toString; return this; } @CanIgnoreReturnValue @GwtIncompatible public Builder withContent(InputStream x) { this.lazyContent = () -> { checkState(this.charset != null); try { return CharStreams.toString(new InputStreamReader(x, this.charset)); } catch (IOException e) { throw new RuntimeException(e); } }; return this; } @CanIgnoreReturnValue public Builder withZipEntryPath(String zipPath, String entryPath) { this.path = zipPath; this.zipEntryPath = entryPath; return this; } /** * Sets a name for this source file that does not need to correspond to a path on disk. * *

Allow passing a reasonable human-readable name in cases like for zip files and for * generated files with unstable artifact prefixes. * *

The name must still be unique. */ @CanIgnoreReturnValue public Builder withOriginalPath(String originalPath) { this.originalPath = originalPath; return this; } public SourceFile build() { String displayPath = (this.originalPath != null) ? this.originalPath : ((this.zipEntryPath == null) ? this.path : this.path + BANG_SLASH + this.zipEntryPath); if (this.lazyContent != null) { return new SourceFile( new CodeLoader.Preloaded(this.lazyContent.get()), displayPath, this.kind); } if (this.zipEntryPath != null) { return new SourceFile( new CodeLoader.AtZip(this.path, this.zipEntryPath, this.charset), displayPath, this.kind); } return new SourceFile( new CodeLoader.OnDisk( (this.pathWithFilesystem != null) ? this.pathWithFilesystem : Paths.get(this.path), this.charset), displayPath, this.kind); } private Builder withPathInternal(String path, @Nullable Path pathWithFilesystem) { // Check if this path should be inferred as a ZIP entry path. int bangSlashIndex = path.indexOf(BANG_SLASH); if (bangSlashIndex >= 0) { String zipPath = path.substring(0, bangSlashIndex); String entryPath = path.substring(bangSlashIndex + BANG_SLASH.length()); if (zipPath.endsWith(".zip") && (entryPath.endsWith(".js") || entryPath.endsWith(".js.map"))) { return this.withZipEntryPath(zipPath, entryPath); } } // Path instances have an implicit reference to a FileSystem. Make sure to preserve it. this.path = path; this.pathWithFilesystem = pathWithFilesystem; return this; } } ////////////////////////////////////////////////////////////////////////////// // Implementations private abstract static class CodeLoader implements Serializable { /** * Return the source text of this file from its original storage. * *

The implementation may be a slow operation such as reading from a file. SourceFile * guarantees that this method is only called under synchronization. */ String loadUncachedCode() throws IOException { throw new AssertionError(); } /** * Return a Reader for the source text of this file from its original storage. * *

The implementation may be a slow operation such as reading from a file. SourceFile * guarantees that this method is only called under synchronization. */ Reader openUncachedReader() throws IOException { return null; } /** * Returns a representation of this loader that can be serialized/deserialized to reconstruct * this SourceFile */ abstract SourceFileProto.Builder toProtoLocationBuilder(String fileName); static final class Preloaded extends CodeLoader { private static final long serialVersionUID = 2L; private final String preloadedCode; Preloaded(String preloadedCode) { super(); this.preloadedCode = checkNotNull(preloadedCode); } @Override String loadUncachedCode() { return this.preloadedCode; } @Override SourceFileProto.Builder toProtoLocationBuilder(String fileName) { return SourceFileProto.newBuilder().setPreloadedContents(this.preloadedCode); } } static final class OnDisk extends CodeLoader { private static final long serialVersionUID = 1L; private final String serializableCharset; private final Path relativePath; OnDisk(Path relativePath, Charset c) { super(); this.serializableCharset = c.name(); this.relativePath = relativePath; } @Override @GwtIncompatible String loadUncachedCode() throws IOException { try { return Files.readString(this.relativePath, this.getCharset()); } catch (CharacterCodingException e) { throw new IOException( "Failed to read: " + this.relativePath + ", is this input UTF-8 encoded?", e); } } @Override @GwtIncompatible Reader openUncachedReader() throws IOException { return Files.newBufferedReader(this.relativePath, this.getCharset()); } private Charset getCharset() { return Charset.forName(this.serializableCharset); } @Override SourceFileProto.Builder toProtoLocationBuilder(String fileName) { String actualPath = this.relativePath.toString(); return SourceFileProto.newBuilder() .setFileOnDisk( FileOnDisk.newBuilder() .setActualPath( // to save space, don't serialize the path if equal to the fileName. fileName.equals(actualPath) ? "" : actualPath) // save space by not serializing UTF_8 (the default charset) .setCharset(this.getCharset().equals(UTF_8) ? "" : this.serializableCharset)); } } static final class AtZip extends CodeLoader { private static final long serialVersionUID = 1L; private final String zipName; private final String entryName; private final String serializableCharset; AtZip(String zipName, String entryName, Charset c) { super(); this.zipName = zipName; this.entryName = entryName; this.serializableCharset = c.name(); } @Override @GwtIncompatible String loadUncachedCode() throws IOException { return CharStreams.toString(this.openUncachedReader()); } @Override @GwtIncompatible Reader openUncachedReader() throws IOException { return new InputStreamReader( JSCompZipFileCache.getEntryStream(this.zipName, this.entryName), this.getCharset()); } private Charset getCharset() { return Charset.forName(this.serializableCharset); } @Override SourceFileProto.Builder toProtoLocationBuilder(String fileName) { return SourceFileProto.newBuilder() .setFilename(fileName) .setZipEntry( ZipEntryOnDisk.newBuilder() .setEntryName(this.entryName) .setZipPath(this.zipName) // save space by not serializing UTF_8 (the default charset) .setCharset(this.getCharset().equals(UTF_8) ? "" : this.serializableCharset) .build()); } } } public SourceFileProto getProto() { return this.loader .toProtoLocationBuilder(this.getName()) .setFilename(this.getName()) .setSourceKind(sourceKindToProto(this.getKind())) .setNumLinesPlusOne(this.numLines + 1) .setNumBytesPlusOne(this.numBytes + 1) .build(); } private static SourceFileProto.SourceKind sourceKindToProto(SourceKind sourceKind) { switch (sourceKind) { case EXTERN: return SourceFileProto.SourceKind.EXTERN; case STRONG: case WEAK: return SourceFileProto.SourceKind.CODE; case NON_CODE: break; } throw new AssertionError(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy