bazel syntax: make Node.lnt final; pass LineNumberTable as ctor param

Also:
- make the field package-private (again). Unlike protected, this does not
  expose the existence of the field in the Javadoc.
- rename 'LineNumberTable lnt' to 'FileLocations locs'.
- change Comment.text to include the leading # (to match its start-end range).
PiperOrigin-RevId: 305897033
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Argument.java b/src/main/java/com/google/devtools/build/lib/syntax/Argument.java
index 37d2738..b80a2fe 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Argument.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Argument.java
@@ -26,7 +26,8 @@
 
   protected final Expression value;
 
-  Argument(Expression value) {
+  Argument(FileLocations locs, Expression value) {
+    super(locs);
     this.value = Preconditions.checkNotNull(value);
   }
 
@@ -47,8 +48,8 @@
 
   /** Syntax node for a positional argument, {@code f(expr)}. */
   public static final class Positional extends Argument {
-    Positional(Expression value) {
-      super(value);
+    Positional(FileLocations locs, Expression value) {
+      super(locs, value);
     }
 
     @Override
@@ -65,8 +66,8 @@
 
     final Identifier id;
 
-    Keyword(Identifier id, Expression value) {
-      super(value);
+    Keyword(FileLocations locs, Identifier id, Expression value) {
+      super(locs, value);
       this.id = id;
     }
 
@@ -89,8 +90,8 @@
   public static final class Star extends Argument {
     private final int starOffset;
 
-    Star(int starOffset, Expression value) {
-      super(value);
+    Star(FileLocations locs, int starOffset, Expression value) {
+      super(locs, value);
       this.starOffset = starOffset;
     }
 
@@ -104,8 +105,8 @@
   public static final class StarStar extends Argument {
     private final int starStarOffset;
 
-    StarStar(int starStarOffset, Expression value) {
-      super(value);
+    StarStar(FileLocations locs, int starStarOffset, Expression value) {
+      super(locs, value);
       this.starStarOffset = starStarOffset;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java
index 2b56c7c..edefa52 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java
@@ -33,7 +33,9 @@
    * {@code (e, ...)}, where x, i, and e are arbitrary expressions. For an augmented assignment, the
    * list and tuple forms are disallowed.
    */
-  AssignmentStatement(Expression lhs, @Nullable TokenKind op, int opOffset, Expression rhs) {
+  AssignmentStatement(
+      FileLocations locs, Expression lhs, @Nullable TokenKind op, int opOffset, Expression rhs) {
+    super(locs);
     this.lhs = lhs;
     this.op = op;
     this.opOffset = opOffset;
@@ -53,7 +55,7 @@
 
   /** Returns the location of the assignment operator. */
   public Location getOperatorLocation() {
-    return lnt.getLocation(opOffset);
+    return locs.getLocation(opOffset);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BUILD b/src/main/java/com/google/devtools/build/lib/syntax/BUILD
index 9833bde..972c5b4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BUILD
@@ -41,6 +41,7 @@
         "DotExpression.java",
         "Expression.java",
         "ExpressionStatement.java",
+        "FileLocations.java",
         "FileOptions.java",
         "FlowStatement.java",
         "ForStatement.java",
@@ -50,7 +51,6 @@
         "IndexExpression.java",
         "IntegerLiteral.java",
         "Lexer.java",
-        "LineNumberTable.java",
         "ListExpression.java",
         "LoadStatement.java",
         "Location.java",
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index aa0de06..066e908 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -44,7 +44,9 @@
           TokenKind.PIPE,
           TokenKind.STAR);
 
-  BinaryOperatorExpression(Expression x, TokenKind op, int opOffset, Expression y) {
+  BinaryOperatorExpression(
+      FileLocations locs, Expression x, TokenKind op, int opOffset, Expression y) {
+    super(locs);
     this.x = x;
     this.op = op;
     this.opOffset = opOffset;
@@ -62,7 +64,7 @@
   }
 
   public Location getOperatorLocation() {
-    return lnt.getLocation(opOffset);
+    return locs.getLocation(opOffset);
   }
 
   /** Returns the right operand. */
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/CallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/CallExpression.java
index 64af972..128bd5f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/CallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/CallExpression.java
@@ -28,10 +28,12 @@
   private final int numPositionalArgs;
 
   CallExpression(
+      FileLocations locs,
       Expression function,
       Location lparenLocation,
       ImmutableList<Argument> arguments,
       int rparenOffset) {
+    super(locs);
     this.function = Preconditions.checkNotNull(function);
     this.lparenLocation = lparenLocation;
     this.arguments = arguments;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Comment.java b/src/main/java/com/google/devtools/build/lib/syntax/Comment.java
index ee7d6a7..2af98f9 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Comment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Comment.java
@@ -20,11 +20,13 @@
   private final int offset;
   private final String text;
 
-  Comment(int offset, String text) {
+  Comment(FileLocations locs, int offset, String text) {
+    super(locs);
     this.offset = offset;
     this.text = text;
   }
 
+  /** Returns the text of the comment, including the leading '#' but not the trailing newline. */
   public String getText() {
     return text;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Comprehension.java b/src/main/java/com/google/devtools/build/lib/syntax/Comprehension.java
index 4549bbc..3444930 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Comprehension.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Comprehension.java
@@ -37,7 +37,11 @@
 public final class Comprehension extends Expression {
 
   /** For or If */
-  public abstract static class Clause extends Node {}
+  public abstract static class Clause extends Node {
+    Clause(FileLocations locs) {
+      super(locs);
+    }
+  }
 
   /** A for clause in a comprehension, e.g. "for a in b" in the example above. */
   public static final class For extends Clause {
@@ -45,7 +49,8 @@
     private final Expression vars;
     private final Expression iterable;
 
-    For(int forOffset, Expression vars, Expression iterable) {
+    For(FileLocations locs, int forOffset, Expression vars, Expression iterable) {
+      super(locs);
       this.forOffset = forOffset;
       this.vars = vars;
       this.iterable = iterable;
@@ -80,7 +85,8 @@
     private final int ifOffset;
     private final Expression condition;
 
-    If(int ifOffset, Expression condition) {
+    If(FileLocations locs, int ifOffset, Expression condition) {
+      super(locs);
       this.ifOffset = ifOffset;
       this.condition = condition;
     }
@@ -112,11 +118,13 @@
   private final int rbracketOffset;
 
   Comprehension(
+      FileLocations locs,
       boolean isDict,
       int lbracketOffset,
       Node body,
       ImmutableList<Clause> clauses,
       int rbracketOffset) {
+    super(locs);
     this.isDict = isDict;
     this.lbracketOffset = lbracketOffset;
     this.body = body;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java
index 8085d55..973eedc 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ConditionalExpression.java
@@ -33,7 +33,8 @@
   }
 
   /** Constructor for a conditional expression */
-  ConditionalExpression(Expression t, Expression cond, Expression f) {
+  ConditionalExpression(FileLocations locs, Expression t, Expression cond, Expression f) {
+    super(locs);
     this.t = t;
     this.cond = cond;
     this.f = f;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DefStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/DefStatement.java
index d128c0e..342f0ac 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DefStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DefStatement.java
@@ -26,11 +26,13 @@
   private final ImmutableList<Parameter> parameters;
 
   DefStatement(
+      FileLocations locs,
       int defOffset,
       Identifier identifier,
       ImmutableList<Parameter> parameters,
       FunctionSignature signature,
       ImmutableList<Statement> body) {
+    super(locs);
     this.defOffset = defOffset;
     this.identifier = identifier;
     this.parameters = Preconditions.checkNotNull(parameters);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DictExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DictExpression.java
index c144d4a7..fdb5346 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DictExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DictExpression.java
@@ -26,7 +26,8 @@
     private final int colonOffset;
     private final Expression value;
 
-    Entry(Expression key, int colonOffset, Expression value) {
+    Entry(FileLocations locs, Expression key, int colonOffset, Expression value) {
+      super(locs);
       this.key = key;
       this.colonOffset = colonOffset;
       this.value = value;
@@ -51,7 +52,7 @@
     }
 
     public Location getColonLocation() {
-      return lnt.getLocation(colonOffset);
+      return locs.getLocation(colonOffset);
     }
 
     @Override
@@ -64,7 +65,8 @@
   private final ImmutableList<Entry> entries;
   private final int rbraceOffset;
 
-  DictExpression(int lbraceOffset, List<Entry> entries, int rbraceOffset) {
+  DictExpression(FileLocations locs, int lbraceOffset, List<Entry> entries, int rbraceOffset) {
+    super(locs);
     this.lbraceOffset = lbraceOffset;
     this.entries = ImmutableList.copyOf(entries);
     this.rbraceOffset = rbraceOffset;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
index 65b2048..9e7a541 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
@@ -21,7 +21,8 @@
   private final int dotOffset;
   private final Identifier field;
 
-  DotExpression(Expression object, int dotOffset, Identifier field) {
+  DotExpression(FileLocations locs, Expression object, int dotOffset, Identifier field) {
+    super(locs);
     this.object = object;
     this.dotOffset = dotOffset;
     this.field = field;
@@ -46,7 +47,7 @@
   }
 
   public Location getDotLocation() {
-    return lnt.getLocation(dotOffset);
+    return locs.getLocation(dotOffset);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Expression.java b/src/main/java/com/google/devtools/build/lib/syntax/Expression.java
index cb1cb3d..2206d79 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Expression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Expression.java
@@ -45,6 +45,10 @@
     UNARY_OPERATOR,
   }
 
+  Expression(FileLocations locs) {
+    super(locs);
+  }
+
   /**
    * Kind of the expression. This is similar to using instanceof, except that it's more efficient
    * and can be used in a switch/case.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ExpressionStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/ExpressionStatement.java
index 8e54ff0..32d3b26 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ExpressionStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ExpressionStatement.java
@@ -19,7 +19,8 @@
 
   private final Expression expression;
 
-  ExpressionStatement(Expression expression) {
+  ExpressionStatement(FileLocations locs, Expression expression) {
+    super(locs);
     this.expression = expression;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java b/src/main/java/com/google/devtools/build/lib/syntax/FileLocations.java
similarity index 75%
rename from src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java
rename to src/main/java/com/google/devtools/build/lib/syntax/FileLocations.java
index 9c89581..5dd1735 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FileLocations.java
@@ -22,40 +22,32 @@
 import java.util.Objects;
 
 /**
- * A LineNumberTable maps each UTF-16 index within a source file to its (file, line, column) triple.
- * An offset is valid if {@code 0 <= offset <= size}.
+ * FileLocations maps each source offset within a file to a Location. An offset is a (UTF-16) char
+ * index such that {@code 0 <= offset <= size}. A Location is a (file, line, column) triple.
  */
 @AutoCodec
 @Immutable
-// TODO(adonovan): rename to FileLocations.
-final class LineNumberTable {
+final class FileLocations {
 
-  private static final Interner<LineNumberTable> LINE_NUMBER_TABLE_INTERNER =
-      BlazeInterners.newWeakInterner();
+  private static final Interner<FileLocations> INTERNER = BlazeInterners.newWeakInterner();
 
-  /** A mapping from line number (line >= 1) to character offset into the file. */
-  private final int[] linestart;
-
+  private final int[] linestart; // maps line number (line >= 1) to char offset
   private final String file;
   private final int size; // size of file in chars
 
-  private LineNumberTable(char[] buffer, String file) {
-    this(computeLinestart(buffer), file, buffer.length);
-  }
-
-  private LineNumberTable(int[] linestart, String file, int size) {
+  private FileLocations(int[] linestart, String file, int size) {
     this.linestart = linestart;
     this.file = file;
     this.size = size;
   }
 
   @AutoCodec.Instantiator
-  static LineNumberTable createForSerialization(int[] linestart, String file, int size) {
-    return LINE_NUMBER_TABLE_INTERNER.intern(new LineNumberTable(linestart, file, size));
+  static FileLocations createForSerialization(int[] linestart, String file, int size) {
+    return INTERNER.intern(new FileLocations(linestart, file, size));
   }
 
-  static LineNumberTable create(char[] buffer, String file) {
-    return new LineNumberTable(buffer, file);
+  static FileLocations create(char[] buffer, String file) {
+    return new FileLocations(computeLinestart(buffer), file, buffer.length);
   }
 
   private int getLineAt(int offset) {
@@ -98,10 +90,10 @@
 
   @Override
   public boolean equals(Object other) {
-    if (!(other instanceof LineNumberTable)) {
+    if (!(other instanceof FileLocations)) {
       return false;
     }
-    LineNumberTable that = (LineNumberTable) other;
+    FileLocations that = (FileLocations) other;
     return this.size == that.size
         && Arrays.equals(this.linestart, that.linestart)
         && this.file.equals(that.file);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FlowStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/FlowStatement.java
index 1637934..4013668 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FlowStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FlowStatement.java
@@ -21,7 +21,8 @@
   private final int offset;
 
   /** @param kind The label of the statement (break, continue, or pass) */
-  FlowStatement(TokenKind kind, int offset) {
+  FlowStatement(FileLocations locs, TokenKind kind, int offset) {
+    super(locs);
     this.kind = kind;
     this.offset = offset;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
index 9b3fdb9..525379f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
@@ -26,7 +26,13 @@
   private final ImmutableList<Statement> block; // non-empty if well formed
 
   /** Constructs a for loop statement. */
-  ForStatement(int forOffset, Expression lhs, Expression collection, List<Statement> block) {
+  ForStatement(
+      FileLocations locs,
+      int forOffset,
+      Expression lhs,
+      Expression collection,
+      List<Statement> block) {
+    super(locs);
     this.forOffset = forOffset;
     this.lhs = Preconditions.checkNotNull(lhs);
     this.collection = Preconditions.checkNotNull(collection);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
index 70af64e..a253c9d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
@@ -33,7 +33,8 @@
   // ValidationEnvironment.
   @Nullable private ValidationEnvironment.Scope scope;
 
-  Identifier(String name, int nameOffset) {
+  Identifier(FileLocations locs, String name, int nameOffset) {
+    super(locs);
     this.name = name;
     this.nameOffset = nameOffset;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/IfStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/IfStatement.java
index 5769057..931d12c 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/IfStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/IfStatement.java
@@ -27,7 +27,13 @@
   private final ImmutableList<Statement> thenBlock; // non-empty
   @Nullable ImmutableList<Statement> elseBlock; // non-empty if non-null; set after construction
 
-  IfStatement(TokenKind token, int ifOffset, Expression condition, List<Statement> thenBlock) {
+  IfStatement(
+      FileLocations locs,
+      TokenKind token,
+      int ifOffset,
+      Expression condition,
+      List<Statement> thenBlock) {
+    super(locs);
     this.token = token;
     this.ifOffset = ifOffset;
     this.condition = condition;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/IndexExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/IndexExpression.java
index c5c9c8c..6baf1b5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/IndexExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/IndexExpression.java
@@ -26,7 +26,13 @@
   private final Expression key;
   private final int rbracketOffset;
 
-  IndexExpression(Expression object, int lbracketOffset, Expression key, int rbracketOffset) {
+  IndexExpression(
+      FileLocations locs,
+      Expression object,
+      int lbracketOffset,
+      Expression key,
+      int rbracketOffset) {
+    super(locs);
     this.object = object;
     this.lbracketOffset = lbracketOffset;
     this.key = key;
@@ -52,7 +58,7 @@
   }
 
   public Location getLbracketLocation() {
-    return lnt.getLocation(lbracketOffset);
+    return locs.getLocation(lbracketOffset);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/IntegerLiteral.java b/src/main/java/com/google/devtools/build/lib/syntax/IntegerLiteral.java
index d5ae4ec..415d8d2 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/IntegerLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/IntegerLiteral.java
@@ -20,7 +20,8 @@
   private final int tokenOffset;
   private final int value;
 
-  IntegerLiteral(String raw, int tokenOffset, int value) {
+  IntegerLiteral(FileLocations locs, String raw, int tokenOffset, int value) {
+    super(locs);
     this.raw = raw;
     this.tokenOffset = tokenOffset;
     this.value = value;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
index 9ec6be3..9a87e7a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
@@ -28,7 +28,7 @@
   // --- These fields are accessed directly by the parser: ---
 
   // Mapping from file offsets to Locations.
-  final LineNumberTable lnt;
+  final FileLocations locs;
 
   // Information about current token. Updated by nextToken.
   // raw and value are defined only for STRING, INT, IDENTIFIER, and COMMENT.
@@ -87,7 +87,7 @@
   // Constructs a lexer which tokenizes the parser input.
   // Errors are appended to errors.
   Lexer(ParserInput input, FileOptions options, List<SyntaxError> errors) {
-    this.lnt = LineNumberTable.create(input.getContent(), input.getFile());
+    this.locs = FileLocations.create(input.getContent(), input.getFile());
     this.options = options;
     this.buffer = input.getContent();
     this.pos = 0;
@@ -128,7 +128,7 @@
   }
 
   private void error(String message, int pos) {
-    errors.add(new SyntaxError(lnt.getLocation(pos), message));
+    errors.add(new SyntaxError(locs.getLocation(pos), message));
   }
 
   private void setToken(TokenKind kind, int left, int right) {
@@ -867,8 +867,6 @@
   // TODO(adonovan): don't retain comments unconditionally.
   private void addComment(int start, int end) {
     String content = bufferSlice(start, end);
-    Comment c = new Comment(start, content);
-    c.lnt = lnt;
-    comments.add(c);
+    comments.add(new Comment(locs, start, content));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ListExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/ListExpression.java
index 7a4ef85..7704259 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ListExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ListExpression.java
@@ -29,7 +29,12 @@
   private final int rbracketOffset; // -1 => unparenthesized non-empty tuple
 
   ListExpression(
-      boolean isTuple, int lbracketOffset, List<Expression> elements, int rbracketOffset) {
+      FileLocations locs,
+      boolean isTuple,
+      int lbracketOffset,
+      List<Expression> elements,
+      int rbracketOffset) {
+    super(locs);
     // An unparenthesized tuple must be non-empty.
     Preconditions.checkArgument(
         !elements.isEmpty() || (lbracketOffset >= 0 && rbracketOffset >= 0));
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
index 765f923..3d5d135 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
@@ -47,7 +47,12 @@
   private final int rparenOffset;
 
   LoadStatement(
-      int loadOffset, StringLiteral module, ImmutableList<Binding> bindings, int rparenOffset) {
+      FileLocations locs,
+      int loadOffset,
+      StringLiteral module,
+      ImmutableList<Binding> bindings,
+      int rparenOffset) {
+    super(locs);
     this.loadOffset = loadOffset;
     this.module = module;
     this.bindings = bindings;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Node.java b/src/main/java/com/google/devtools/build/lib/syntax/Node.java
index b761590..e89bc1a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Node.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Node.java
@@ -14,6 +14,8 @@
 
 package com.google.devtools.build.lib.syntax;
 
+import com.google.common.base.Preconditions;
+
 /**
  * A Node is a node in a Starlark syntax tree.
  *
@@ -59,13 +61,14 @@
   //   1.0% Comprehension
   //   6  % all others
 
-  // The LNT holds the file name and a compressed mapping from
-  // token char offsets to Locations. It is shared by all nodes
-  // from the same file. For convenience of the parser, it is
-  // set immediately after construction.
-  protected LineNumberTable lnt;
+  // The FileLocations table holds the file name and a compressed
+  // mapping from token char offsets to Locations.
+  // It is shared by all nodes from the same file.
+  final FileLocations locs;
 
-  Node() {}
+  Node(FileLocations locs) {
+    this.locs = Preconditions.checkNotNull(locs);
+  }
 
   /**
    * Returns the node's start offset, as a char index (zero-based count of UTF-16 codes) from the
@@ -75,7 +78,7 @@
 
   /** Returns the location of the start of this syntax node. */
   public final Location getStartLocation() {
-    return lnt.getLocation(getStartOffset());
+    return locs.getLocation(getStartOffset());
   }
 
   /** Returns the char offset of the source position immediately after this syntax node. */
@@ -83,7 +86,7 @@
 
   /** Returns the location of the source position immediately after this syntax node. */
   public final Location getEndLocation() {
-    return lnt.getLocation(getEndOffset());
+    return locs.getLocation(getEndOffset());
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/NodePrinter.java b/src/main/java/com/google/devtools/build/lib/syntax/NodePrinter.java
index 934d3b2..ef8d9ae 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/NodePrinter.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/NodePrinter.java
@@ -55,7 +55,6 @@
       // of StarlarkFile. So don't bother word-wrapping and just print
       // it on a single line.
       printIndent();
-      buf.append("# ");
       buf.append(comment.getText());
 
     } else if (n instanceof Argument) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parameter.java b/src/main/java/com/google/devtools/build/lib/syntax/Parameter.java
index 21422d5..5c92f0c 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parameter.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parameter.java
@@ -31,7 +31,8 @@
 
   @Nullable private final Identifier id;
 
-  private Parameter(@Nullable Identifier id) {
+  private Parameter(FileLocations locs, @Nullable Identifier id) {
+    super(locs);
     this.id = id;
   }
 
@@ -55,8 +56,8 @@
    * depending on its position.
    */
   public static final class Mandatory extends Parameter {
-    Mandatory(Identifier id) {
-      super(id);
+    Mandatory(FileLocations locs, Identifier id) {
+      super(locs, id);
     }
 
     @Override
@@ -78,8 +79,8 @@
 
     public final Expression defaultValue;
 
-    Optional(Identifier id, @Nullable Expression defaultValue) {
-      super(id);
+    Optional(FileLocations locs, Identifier id, @Nullable Expression defaultValue) {
+      super(locs, id);
       this.defaultValue = defaultValue;
     }
 
@@ -109,8 +110,8 @@
   public static final class Star extends Parameter {
     private final int starOffset;
 
-    Star(int starOffset, @Nullable Identifier id) {
-      super(id);
+    Star(FileLocations locs, int starOffset, @Nullable Identifier id) {
+      super(locs, id);
       this.starOffset = starOffset;
     }
 
@@ -129,8 +130,8 @@
   public static final class StarStar extends Parameter {
     private final int starStarOffset;
 
-    StarStar(int starStarOffset, Identifier id) {
-      super(id);
+    StarStar(FileLocations locs, int starStarOffset, Identifier id) {
+      super(locs, id);
       this.starStarOffset = starStarOffset;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index 62e2e7e..8eb169e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -33,7 +33,7 @@
   /** Combines the parser result into a single value object. */
   static final class ParseResult {
     // Maps char offsets in the file to Locations.
-    final LineNumberTable lnt;
+    final FileLocations locs;
 
     /** The statements (rules, basically) from the parsed file. */
     final List<Statement> statements;
@@ -46,11 +46,11 @@
     final List<SyntaxError> errors;
 
     ParseResult(
-        LineNumberTable lnt,
+        FileLocations locs,
         List<Statement> statements,
         List<Comment> comments,
         List<SyntaxError> errors) {
-      this.lnt = lnt;
+      this.locs = locs;
       // No need to copy here; when the object is created, the parser instance is just about to go
       // out of scope and be garbage collected.
       this.statements = Preconditions.checkNotNull(statements);
@@ -97,6 +97,7 @@
   private static final boolean DEBUGGING = false;
 
   private final Lexer lexer;
+  private final FileLocations locs;
   private final List<SyntaxError> errors;
 
   // TODO(adonovan): opt: compute this by subtraction.
@@ -148,6 +149,7 @@
 
   private Parser(Lexer lexer, List<SyntaxError> errors) {
     this.lexer = lexer;
+    this.locs = lexer.locs;
     this.errors = errors;
     this.token = lexer;
     nextToken();
@@ -175,7 +177,7 @@
         Profiler.instance().profile(ProfilerTask.STARLARK_PARSER, input.getFile())) {
       statements = parser.parseFileInput();
     }
-    return new ParseResult(lexer.lnt, statements, lexer.getComments(), errors);
+    return new ParseResult(lexer.locs, statements, lexer.getComments(), errors);
   }
 
   // stmt = simple_stmt
@@ -228,14 +230,14 @@
     List<Expression> elems = new ArrayList<>();
     elems.add(e);
     parseExprList(elems, /*trailingCommaAllowed=*/ false);
-    return setLNT(new ListExpression(/*isTuple=*/ true, -1, elems, -1));
+    return new ListExpression(locs, /*isTuple=*/ true, -1, elems, -1);
   }
 
   private void reportError(int offset, String message) {
     errorsCount++;
     // Limit the number of reported errors to avoid spamming output.
     if (errorsCount <= 5) {
-      Location location = lexer.lnt.getLocation(offset);
+      Location location = locs.getLocation(offset);
       errors.add(new SyntaxError(location, message));
     }
   }
@@ -367,16 +369,9 @@
     // It's tempting to define a dedicated BadExpression type,
     // but it is convenient for parseIdent to return an Identifier
     // even when it fails.
-    return setLNT(new Identifier(lexer.bufferSlice(start, end), start));
+    return new Identifier(locs, lexer.bufferSlice(start, end), start);
   }
 
-  // setLNT sets the LineNumberTable associated with a newly created Node.
-  // TODO(adonovan): experiment with making this an explicit parameter
-  // to all Node subclass constructors.
-  private <N extends Node> N setLNT(N n) {
-    n.lnt = lexer.lnt;
-    return n;
-  }
 
   // arg = IDENTIFIER '=' test
   //     | expr
@@ -389,14 +384,14 @@
     if (token.kind == TokenKind.STAR_STAR) {
       int starStarOffset = nextToken();
       expr = parseTest();
-      return setLNT(new Argument.StarStar(starStarOffset, expr));
+      return new Argument.StarStar(locs, starStarOffset, expr);
     }
 
     // parse *expr
     if (token.kind == TokenKind.STAR) {
       int starOffset = nextToken();
       expr = parseTest();
-      return setLNT(new Argument.Star(starOffset, expr));
+      return new Argument.Star(locs, starOffset, expr);
     }
 
     // IDENTIFIER  or  IDENTIFIER = test
@@ -407,12 +402,12 @@
       if (token.kind == TokenKind.EQUALS) {
         nextToken();
         Expression arg = parseTest();
-        return setLNT(new Argument.Keyword(id, arg));
+        return new Argument.Keyword(locs, id, arg);
       }
     }
 
     // parse a positional argument
-    return setLNT(new Argument.Positional(expr));
+    return new Argument.Positional(locs, expr);
   }
 
   // arg = IDENTIFIER '=' test
@@ -422,7 +417,7 @@
     if (token.kind == TokenKind.STAR_STAR) {
       int starStarOffset = nextToken();
       Identifier id = parseIdent();
-      return setLNT(new Parameter.StarStar(starStarOffset, id));
+      return new Parameter.StarStar(locs, starStarOffset, id);
     }
 
     // * or *args
@@ -430,9 +425,9 @@
       int starOffset = nextToken();
       if (token.kind == TokenKind.IDENTIFIER) {
         Identifier id = parseIdent();
-        return setLNT(new Parameter.Star(starOffset, id));
+        return new Parameter.Star(locs, starOffset, id);
       }
-      return setLNT(new Parameter.Star(starOffset, null));
+      return new Parameter.Star(locs, starOffset, null);
     }
 
     // name=default
@@ -440,11 +435,11 @@
     if (token.kind == TokenKind.EQUALS) {
       nextToken(); // TODO: save token pos?
       Expression expr = parseTest();
-      return setLNT(new Parameter.Optional(id, expr));
+      return new Parameter.Optional(locs, id, expr);
     }
 
     // name
-    return setLNT(new Parameter.Mandatory(id));
+    return new Parameter.Mandatory(locs, id);
   }
 
   // call_suffix = '(' arg_list? ')'
@@ -455,7 +450,7 @@
       args = parseArguments(); // (includes optional trailing comma)
     }
     int rparenOffset = expect(TokenKind.RPAREN);
-    return setLNT(new CallExpression(fn, lexer.lnt.getLocation(lparenOffset), args, rparenOffset));
+    return new CallExpression(locs, fn, locs.getLocation(lparenOffset), args, rparenOffset);
   }
 
   // Parse a list of call arguments.
@@ -548,7 +543,7 @@
     int dotOffset = expect(TokenKind.DOT);
     if (token.kind == TokenKind.IDENTIFIER) {
       Identifier id = parseIdent();
-      return setLNT(new DotExpression(e, dotOffset, id));
+      return new DotExpression(locs, e, dotOffset, id);
     }
 
     syntaxError("expected identifier after dot");
@@ -595,14 +590,14 @@
     Expression key = parseTest();
     int colonOffset = expect(TokenKind.COLON);
     Expression value = parseTest();
-    return setLNT(new DictExpression.Entry(key, colonOffset, value));
+    return new DictExpression.Entry(locs, key, colonOffset, value);
   }
 
   // expr = STRING
   private StringLiteral parseStringLiteral() {
     Preconditions.checkState(token.kind == TokenKind.STRING);
     StringLiteral literal =
-        setLNT(new StringLiteral(token.left, intern((String) token.value), token.right));
+        new StringLiteral(locs, token.left, intern((String) token.value), token.right);
     nextToken();
     if (token.kind == TokenKind.STRING) {
       reportError(token.left, "Implicit string concatenation is forbidden, use the + operator");
@@ -623,7 +618,7 @@
       case INT:
         {
           IntegerLiteral literal =
-              setLNT(new IntegerLiteral(token.raw, token.left, (Integer) token.value));
+              new IntegerLiteral(locs, token.raw, token.left, (Integer) token.value);
           nextToken();
           return literal;
         }
@@ -647,8 +642,8 @@
           // empty tuple: ()
           if (token.kind == TokenKind.RPAREN) {
             int rparen = nextToken();
-            return setLNT(
-                new ListExpression(/*isTuple=*/ true, lparenOffset, ImmutableList.of(), rparen));
+            return new ListExpression(
+                locs, /*isTuple=*/ true, lparenOffset, ImmutableList.of(), rparen);
           }
 
           Expression e = parseTest();
@@ -666,7 +661,7 @@
             elems.add(e);
             parseExprList(elems, /*trailingCommaAllowed=*/ true);
             int rparenOffset = expect(TokenKind.RPAREN);
-            return setLNT(new ListExpression(/*isTuple=*/ true, lparenOffset, elems, rparenOffset));
+            return new ListExpression(locs, /*isTuple=*/ true, lparenOffset, elems, rparenOffset);
           }
 
           expect(TokenKind.RPAREN);
@@ -681,7 +676,7 @@
           TokenKind op = token.kind;
           int offset = nextToken();
           Expression x = parsePrimaryWithSuffix();
-          return setLNT(new UnaryOperatorExpression(op, offset, x));
+          return new UnaryOperatorExpression(locs, op, offset, x);
         }
 
       default:
@@ -725,7 +720,7 @@
       // index x[i]
       if (token.kind == TokenKind.RBRACKET) {
         int rbracketOffset = expect(TokenKind.RBRACKET);
-        return setLNT(new IndexExpression(e, lbracketOffset, start, rbracketOffset));
+        return new IndexExpression(locs, e, lbracketOffset, start, rbracketOffset);
       }
     }
 
@@ -741,7 +736,7 @@
       }
     }
     int rbracketOffset = expect(TokenKind.RBRACKET);
-    return setLNT(new SliceExpression(e, lbracketOffset, start, end, step, rbracketOffset));
+    return new SliceExpression(locs, e, lbracketOffset, start, end, step, rbracketOffset);
   }
 
   // Equivalent to 'exprlist' rule in Python grammar.
@@ -764,7 +759,7 @@
       }
       elems.add(parsePrimaryWithSuffix());
     }
-    return setLNT(new ListExpression(/*isTuple=*/ true, -1, elems, -1));
+    return new ListExpression(locs, /*isTuple=*/ true, -1, elems, -1);
   }
 
   // comprehension_suffix = 'FOR' loop_variables 'IN' expr comprehension_suffix
@@ -780,13 +775,13 @@
         // The expression cannot be a ternary expression ('x if y else z') due to
         // conflicts in Python grammar ('if' is used by the comprehension).
         Expression seq = parseTest(0);
-        clauses.add(setLNT(new Comprehension.For(forOffset, vars, seq)));
+        clauses.add(new Comprehension.For(locs, forOffset, vars, seq));
       } else if (token.kind == TokenKind.IF) {
         int ifOffset = nextToken();
         // [x for x in li if 1, 2]  # parse error
         // [x for x in li if (1, 2)]  # ok
         Expression cond = parseTest(0);
-        clauses.add(setLNT(new Comprehension.If(ifOffset, cond)));
+        clauses.add(new Comprehension.If(locs, ifOffset, cond));
       } else if (token.kind == closingBracket) {
         break;
       } else {
@@ -798,7 +793,7 @@
 
     boolean isDict = closingBracket == TokenKind.RBRACE;
     int roffset = expect(closingBracket);
-    return setLNT(new Comprehension(isDict, loffset, body, clauses.build(), roffset));
+    return new Comprehension(locs, isDict, loffset, body, clauses.build(), roffset);
   }
 
   // list_maker = '[' ']'
@@ -809,9 +804,8 @@
     int lbracketOffset = expect(TokenKind.LBRACKET);
     if (token.kind == TokenKind.RBRACKET) { // empty List
       int rbracketOffset = nextToken();
-      return setLNT(
-          new ListExpression(
-              /*isTuple=*/ false, lbracketOffset, ImmutableList.of(), rbracketOffset));
+      return new ListExpression(
+          locs, /*isTuple=*/ false, lbracketOffset, ImmutableList.of(), rbracketOffset);
     }
 
     Expression expression = parseTest();
@@ -820,12 +814,12 @@
         // [e], singleton list
         {
           int rbracketOffset = nextToken();
-          return setLNT(
-              new ListExpression(
-                  /*isTuple=*/ false,
-                  lbracketOffset,
-                  ImmutableList.of(expression),
-                  rbracketOffset));
+          return new ListExpression(
+              locs,
+              /*isTuple=*/ false,
+              lbracketOffset,
+              ImmutableList.of(expression),
+              rbracketOffset);
         }
 
       case FOR:
@@ -840,8 +834,8 @@
           parseExprList(elems, /*trailingCommaAllowed=*/ true);
           if (token.kind == TokenKind.RBRACKET) {
             int rbracketOffset = nextToken();
-            return setLNT(
-                new ListExpression(/*isTuple=*/ false, lbracketOffset, elems, rbracketOffset));
+            return new ListExpression(
+                locs, /*isTuple=*/ false, lbracketOffset, elems, rbracketOffset);
           }
 
           expect(TokenKind.RBRACKET);
@@ -865,7 +859,7 @@
     int lbraceOffset = expect(TokenKind.LBRACE);
     if (token.kind == TokenKind.RBRACE) { // empty Dict
       int rbraceOffset = nextToken();
-      return setLNT(new DictExpression(lbraceOffset, ImmutableList.of(), rbraceOffset));
+      return new DictExpression(locs, lbraceOffset, ImmutableList.of(), rbraceOffset);
     }
 
     DictExpression.Entry entry = parseDictEntry();
@@ -882,7 +876,7 @@
     }
     if (token.kind == TokenKind.RBRACE) {
       int rbraceOffset = nextToken();
-      return setLNT(new DictExpression(lbraceOffset, entries, rbraceOffset));
+      return new DictExpression(locs, lbraceOffset, entries, rbraceOffset);
     }
 
     expect(TokenKind.RBRACE);
@@ -899,7 +893,7 @@
 
     String name = (String) token.value;
     int offset = nextToken();
-    return setLNT(new Identifier(name, offset));
+    return new Identifier(locs, name, offset);
   }
 
   // binop_expression = binop_expression OP binop_expression
@@ -949,13 +943,13 @@
   private Expression optimizeBinOpExpression(
       Expression x, TokenKind op, int opOffset, Expression y) {
     if (op == TokenKind.PLUS && x instanceof StringLiteral && y instanceof StringLiteral) {
-      return setLNT(
-          new StringLiteral(
-              x.getStartOffset(),
-              intern(((StringLiteral) x).getValue() + ((StringLiteral) y).getValue()),
-              y.getEndOffset()));
+      return new StringLiteral(
+          locs,
+          x.getStartOffset(),
+          intern(((StringLiteral) x).getValue() + ((StringLiteral) y).getValue()),
+          y.getEndOffset());
     }
-    return setLNT(new BinaryOperatorExpression(x, op, opOffset, y));
+    return new BinaryOperatorExpression(locs, x, op, opOffset, y);
   }
 
   // Parses a non-tuple expression ("test" in Python terminology).
@@ -968,7 +962,7 @@
       if (token.kind == TokenKind.ELSE) {
         nextToken();
         Expression elseClause = parseTest();
-        return setLNT(new ConditionalExpression(expr, condition, elseClause));
+        return new ConditionalExpression(locs, expr, condition, elseClause);
       } else {
         reportError(start, "missing else clause in conditional expression or semicolon before if");
         return expr; // Try to recover from error: drop the if and the expression after it. Ouch.
@@ -991,7 +985,7 @@
   private Expression parseNotExpression(int prec) {
     int notOffset = expect(TokenKind.NOT);
     Expression x = parseTest(prec);
-    return setLNT(new UnaryOperatorExpression(TokenKind.NOT, notOffset, x));
+    return new UnaryOperatorExpression(locs, TokenKind.NOT, notOffset, x);
   }
 
   // file_input = ('\n' | stmt)* EOF
@@ -1018,15 +1012,15 @@
     expect(TokenKind.LPAREN);
     if (token.kind != TokenKind.STRING) {
       // error: module is not a string literal.
-      StringLiteral module = setLNT(new StringLiteral(token.left, "", token.right));
+      StringLiteral module = new StringLiteral(locs, token.left, "", token.right);
       expect(TokenKind.STRING);
-      return setLNT(new LoadStatement(loadOffset, module, ImmutableList.of(), token.right));
+      return new LoadStatement(locs, loadOffset, module, ImmutableList.of(), token.right);
     }
 
     StringLiteral module = parseStringLiteral();
     if (token.kind == TokenKind.RPAREN) {
       syntaxError("expected at least one symbol to load");
-      return setLNT(new LoadStatement(loadOffset, module, ImmutableList.of(), token.right));
+      return new LoadStatement(locs, loadOffset, module, ImmutableList.of(), token.right);
     }
     expect(TokenKind.COMMA);
 
@@ -1043,7 +1037,7 @@
     }
 
     int rparen = expect(TokenKind.RPAREN);
-    return setLNT(new LoadStatement(loadOffset, module, bindings.build(), rparen));
+    return new LoadStatement(locs, loadOffset, module, bindings.build(), rparen);
   }
 
   /**
@@ -1061,7 +1055,7 @@
 
     String name = (String) token.value;
     int nameOffset = token.left + (token.kind == TokenKind.STRING ? 1 : 0);
-    Identifier local = setLNT(new Identifier(name, nameOffset));
+    Identifier local = new Identifier(locs, name, nameOffset);
 
     Identifier original;
     if (token.kind == TokenKind.STRING) {
@@ -1078,7 +1072,7 @@
         syntaxError("expected string");
         return;
       }
-      original = setLNT(new Identifier((String) token.value, token.left + 1));
+      original = new Identifier(locs, (String) token.value, token.left + 1);
     }
     nextToken();
     symbols.add(new LoadStatement.Binding(local, original));
@@ -1119,7 +1113,7 @@
         || token.kind == TokenKind.PASS) {
       TokenKind kind = token.kind;
       int offset = nextToken();
-      return setLNT(new FlowStatement(kind, offset));
+      return new FlowStatement(locs, kind, offset);
     }
 
     // load
@@ -1135,9 +1129,9 @@
       int opOffset = nextToken();
       Expression rhs = parseExpression();
       // op == null for ordinary assignment. TODO(adonovan): represent as EQUALS.
-      return setLNT(new AssignmentStatement(lhs, op, opOffset, rhs));
+      return new AssignmentStatement(locs, lhs, op, opOffset, rhs);
     } else {
-      return setLNT(new ExpressionStatement(lhs));
+      return new ExpressionStatement(locs, lhs);
     }
   }
 
@@ -1147,14 +1141,14 @@
     Expression cond = parseTest();
     expect(TokenKind.COLON);
     List<Statement> body = parseSuite();
-    IfStatement ifStmt = setLNT(new IfStatement(TokenKind.IF, ifOffset, cond, body));
+    IfStatement ifStmt = new IfStatement(locs, TokenKind.IF, ifOffset, cond, body);
     IfStatement tail = ifStmt;
     while (token.kind == TokenKind.ELIF) {
       int elifOffset = expect(TokenKind.ELIF);
       cond = parseTest();
       expect(TokenKind.COLON);
       body = parseSuite();
-      IfStatement elif = setLNT(new IfStatement(TokenKind.ELIF, elifOffset, cond, body));
+      IfStatement elif = new IfStatement(locs, TokenKind.ELIF, elifOffset, cond, body);
       tail.setElseBlock(ImmutableList.of(elif));
       tail = elif;
     }
@@ -1175,7 +1169,7 @@
     Expression collection = parseExpression();
     expect(TokenKind.COLON);
     List<Statement> block = parseSuite();
-    return setLNT(new ForStatement(forOffset, lhs, collection, block));
+    return new ForStatement(locs, forOffset, lhs, collection, block);
   }
 
   // def_stmt = DEF IDENTIFIER '(' arguments ')' ':' suite
@@ -1197,7 +1191,7 @@
     expect(TokenKind.RPAREN);
     expect(TokenKind.COLON);
     ImmutableList<Statement> block = ImmutableList.copyOf(parseSuite());
-    return setLNT(new DefStatement(defOffset, ident, params, signature, block));
+    return new DefStatement(locs, defOffset, ident, params, signature, block);
   }
 
   // Parse a list of function parameters.
@@ -1264,6 +1258,6 @@
     if (!STATEMENT_TERMINATOR_SET.contains(token.kind)) {
       result = parseExpression();
     }
-    return setLNT(new ReturnStatement(returnOffset, result));
+    return new ReturnStatement(locs, returnOffset, result);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ReturnStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/ReturnStatement.java
index 3ba54ae..46b6dd4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ReturnStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ReturnStatement.java
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.lib.syntax;
 
-import com.google.common.base.Preconditions;
 import javax.annotation.Nullable;
 
 /** A syntax node for return statements. */
@@ -22,7 +21,8 @@
   private final int returnOffset;
   @Nullable private final Expression result;
 
-  ReturnStatement(int returnOffset, @Nullable Expression result) {
+  ReturnStatement(FileLocations locs, int returnOffset, @Nullable Expression result) {
+    super(locs);
     this.returnOffset = returnOffset;
     this.result = result;
   }
@@ -33,9 +33,7 @@
    * compiled representation.
    */
   public static ReturnStatement make(Expression expr) {
-    ReturnStatement stmt = new ReturnStatement(0, expr);
-    stmt.lnt = Preconditions.checkNotNull(expr.lnt);
-    return stmt;
+    return new ReturnStatement(expr.locs, 0, expr);
   }
 
   // TODO(adonovan): rename to getResult.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SliceExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/SliceExpression.java
index d78a850..29f6d70 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SliceExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SliceExpression.java
@@ -26,12 +26,14 @@
   private final int rbracketOffset;
 
   SliceExpression(
+      FileLocations locs,
       Expression object,
       int lbracketOffset,
       Expression start,
       Expression stop,
       Expression step,
       int rbracketOffset) {
+    super(locs);
     this.object = object;
     this.lbracketOffset = lbracketOffset;
     this.start = start;
@@ -70,7 +72,7 @@
   }
 
   public Location getLbracketLocation() {
-    return lnt.getLocation(lbracketOffset);
+    return locs.getLocation(lbracketOffset);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkFile.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkFile.java
index 237cd19..8850cfd 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkFile.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkFile.java
@@ -41,15 +41,17 @@
 
   @Override
   public int getEndOffset() {
-    return lnt.size();
+    return locs.size();
   }
 
   private StarlarkFile(
+      FileLocations locs,
       ImmutableList<Statement> statements,
       FileOptions options,
       ImmutableList<Comment> comments,
       List<SyntaxError> errors,
       String contentHashCode) {
+    super(locs);
     this.statements = statements;
     this.options = options;
     this.comments = comments;
@@ -60,35 +62,30 @@
   // Creates a StarlarkFile from the given effective list of statements,
   // which may include the prelude.
   private static StarlarkFile create(
-      LineNumberTable lnt,
+      FileLocations locs,
       ImmutableList<Statement> statements,
       FileOptions options,
       Parser.ParseResult result,
       String contentHashCode) {
-    StarlarkFile file =
-        new StarlarkFile(
-            statements,
-            options,
-            ImmutableList.copyOf(result.comments),
-            result.errors,
-            contentHashCode);
-    file.lnt = lnt;
-    return file;
+    return new StarlarkFile(
+        locs,
+        statements,
+        options,
+        ImmutableList.copyOf(result.comments),
+        result.errors,
+        contentHashCode);
   }
 
   /** Extract a subtree containing only statements from i (included) to j (excluded). */
   public StarlarkFile subTree(int i, int j) {
-    StarlarkFile file =
-        new StarlarkFile(
-            this.statements.subList(i, j),
-            this.options,
-            /*comments=*/ ImmutableList.of(),
-            errors,
-            /*contentHashCode=*/ null);
-    file.lnt = this.lnt;
-    return file;
+    return new StarlarkFile(
+        this.locs,
+        this.statements.subList(i, j),
+        this.options,
+        /*comments=*/ ImmutableList.of(),
+        errors,
+        /*contentHashCode=*/ null);
   }
-
   /**
    * Returns an unmodifiable view of the list of scanner, parser, and (perhaps) resolver errors
    * accumulated in this Starlark file.
@@ -134,7 +131,7 @@
     stmts.addAll(prelude);
     stmts.addAll(result.statements);
 
-    return create(result.lnt, stmts.build(), options, result, /*contentHashCode=*/ null);
+    return create(result.locs, stmts.build(), options, result, /*contentHashCode=*/ null);
   }
 
   // TODO(adonovan): make the digest publicly settable, and delete this.
@@ -142,7 +139,7 @@
       throws IOException {
     Parser.ParseResult result = Parser.parseFile(input, options);
     return create(
-        result.lnt,
+        result.locs,
         ImmutableList.copyOf(result.statements),
         options,
         result,
@@ -166,7 +163,7 @@
   public static StarlarkFile parse(ParserInput input, FileOptions options) {
     Parser.ParseResult result = Parser.parseFile(input, options);
     return create(
-        result.lnt,
+        result.locs,
         ImmutableList.copyOf(result.statements),
         options,
         result,
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Statement.java b/src/main/java/com/google/devtools/build/lib/syntax/Statement.java
index 27d3123..e3633b4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Statement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Statement.java
@@ -31,6 +31,10 @@
     RETURN,
   }
 
+  Statement(FileLocations locs) {
+    super(locs);
+  }
+
   /**
    * Kind of the statement. This is similar to using instanceof, except that it's more efficient and
    * can be used in a switch/case.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java b/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java
index 8ca6f1c..bbde6a2 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java
@@ -28,7 +28,8 @@
   private final String value;
   private final int endOffset;
 
-  StringLiteral(int startOffset, String value, int endOffset) {
+  StringLiteral(FileLocations locs, int startOffset, String value, int endOffset) {
+    super(locs);
     this.startOffset = startOffset;
     this.value = value;
     this.endOffset = endOffset;
@@ -40,7 +41,7 @@
   }
 
   public Location getLocation() {
-    return lnt.getLocation(startOffset);
+    return locs.getLocation(startOffset);
   }
 
   @Override
@@ -83,7 +84,7 @@
       out.writeInt32NoTag(lit.startOffset);
       out.writeInt32NoTag(lit.endOffset);
       context.serializeWithAdHocMemoizationStrategy(
-          lit.lnt, MemoizationStrategy.MEMOIZE_AFTER, out);
+          lit.locs, MemoizationStrategy.MEMOIZE_AFTER, out);
     }
 
     @Override
@@ -93,10 +94,9 @@
           context.deserializeWithAdHocMemoizationStrategy(in, MemoizationStrategy.MEMOIZE_AFTER);
       int startOffset = in.readInt32();
       int endOffset = in.readInt32();
-      StringLiteral lit = new StringLiteral(startOffset, value, endOffset);
-      lit.lnt =
+      FileLocations locs =
           context.deserializeWithAdHocMemoizationStrategy(in, MemoizationStrategy.MEMOIZE_AFTER);
-      return lit;
+      return new StringLiteral(locs, startOffset, value, endOffset);
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java
index 59d387a..4b1e9f4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java
@@ -21,7 +21,8 @@
   private final int opOffset;
   private final Expression x;
 
-  UnaryOperatorExpression(TokenKind op, int opOffset, Expression x) {
+  UnaryOperatorExpression(FileLocations locs, TokenKind op, int opOffset, Expression x) {
+    super(locs);
     this.op = op;
     this.opOffset = opOffset;
     this.x = x;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
index 8e42109..4eae9f4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
@@ -90,7 +90,15 @@
     String getUndeclaredNameError(String name);
   }
 
-  private static final Identifier PREDECLARED = new Identifier("", 0);
+  private static final Identifier PREDECLARED; // sentinel for predeclared names
+
+  static {
+    try {
+      PREDECLARED = (Identifier) Expression.parse(ParserInput.fromLines("PREDECLARED"));
+    } catch (SyntaxError.Exception ex) {
+      throw new IllegalStateException(ex); // can't happen
+    }
+  }
 
   private static class Block {
     private final Map<String, Identifier> variables = new HashMap<>();
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/LineNumberTableTest.java b/src/test/java/com/google/devtools/build/lib/syntax/FileLocationsTest.java
similarity index 84%
rename from src/test/java/com/google/devtools/build/lib/syntax/LineNumberTableTest.java
rename to src/test/java/com/google/devtools/build/lib/syntax/FileLocationsTest.java
index 6116743..c672b25 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/LineNumberTableTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/FileLocationsTest.java
@@ -19,17 +19,17 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Tests for {@link LineNumberTable}. */
+/** Tests for {@link FileLocations}. */
 // TODO(adonovan): express this test in terms of the public API.
 @RunWith(JUnit4.class)
-public class LineNumberTableTest {
+public class FileLocationsTest {
 
-  private static LineNumberTable create(String buffer) {
-    return LineNumberTable.create(buffer.toCharArray(), "/fake/file");
+  private static FileLocations create(String buffer) {
+    return FileLocations.create(buffer.toCharArray(), "/fake/file");
   }
 
   // Asserts that the specified offset results in a line/column pair of the form "1:2".
-  private static void checkOffset(LineNumberTable table, int offset, String wantLineCol) {
+  private static void checkOffset(FileLocations table, int offset, String wantLineCol) {
     Location loc = table.getLocation(offset);
     String got = String.format("%d:%d", loc.line(), loc.column());
     if (!got.equals(wantLineCol)) {
@@ -40,20 +40,20 @@
 
   @Test
   public void testEmpty() {
-    LineNumberTable table = create("");
+    FileLocations table = create("");
     checkOffset(table, 0, "1:1");
   }
 
   @Test
   public void testNewline() {
-    LineNumberTable table = create("\n");
+    FileLocations table = create("\n");
     checkOffset(table, 0, "1:1");
     checkOffset(table, 1, "2:1"); // EOF
   }
 
   @Test
   public void testOneLiner() {
-    LineNumberTable table = create("foo");
+    FileLocations table = create("foo");
     checkOffset(table, 0, "1:1");
     checkOffset(table, 1, "1:2");
     checkOffset(table, 2, "1:3");
@@ -62,7 +62,7 @@
 
   @Test
   public void testMultiLiner() {
-    LineNumberTable table = create("\ntwo\nthree\n\nfive\n");
+    FileLocations table = create("\ntwo\nthree\n\nfive\n");
 
     // \n
     checkOffset(table, 0, "1:1");
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/LexerTest.java b/src/test/java/com/google/devtools/build/lib/syntax/LexerTest.java
index 616a85c..be15a86 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/LexerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/LexerTest.java
@@ -83,8 +83,8 @@
   }
 
   /**
-   * Lexes the specified input string, and returns a string containing just the
-   * linenumbers of each token.
+   * Lexes the specified input string, and returns a string containing just the line numbers of each
+   * token.
    */
   private String linenums(String input) {
     Lexer lexer = createLexer(input);
@@ -93,7 +93,7 @@
       if (buf.length() > 0) {
         buf.append(' ');
       }
-      int line = lexer.lnt.getLocation(tok.left).line();
+      int line = lexer.locs.getLocation(tok.left).line();
       buf.append(line);
     }
     return buf.toString();
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/PrettyPrintTest.java b/src/test/java/com/google/devtools/build/lib/syntax/PrettyPrintTest.java
index 6293505..dfbe0f74 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/PrettyPrintTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/PrettyPrintTest.java
@@ -23,6 +23,7 @@
 
 /** Tests the {@code toString} and pretty printing methods for {@link Node} subclasses. */
 @RunWith(JUnit4.class)
+// TODO(adonovan): rename to NodePrinterTest.
 public final class PrettyPrintTest {
 
   private static StarlarkFile parseFile(String... lines) throws SyntaxError.Exception {
@@ -393,9 +394,17 @@
 
   @Test
   public void comment() throws SyntaxError.Exception {
-    Comment node = new Comment(0, "foo");
-    assertIndentedPrettyMatches(node, "  # foo");
-    assertTostringMatches(node, "foo");
+    ParserInput input =
+        ParserInput.fromLines(
+            "# foo", //
+            "expr # bar");
+    Parser.ParseResult r = Parser.parseFile(input, FileOptions.DEFAULT);
+    Comment c0 = r.comments.get(0);
+    assertIndentedPrettyMatches(c0, "  # foo");
+    assertTostringMatches(c0, "# foo");
+    Comment c1 = r.comments.get(1);
+    assertIndentedPrettyMatches(c1, "  # bar");
+    assertTostringMatches(c1, "# bar");
   }
 
   /* Not tested explicitly because they're covered implicitly by tests for other nodes: