Version 1.6.0-dev.8.0

svn merge -r 38828:38964 https://dart.googlecode.com/svn/branches/bleeding_edge trunk

git-svn-id: http://dart.googlecode.com/svn/trunk@38967 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
new file mode 100644
index 0000000..e8226e6
--- /dev/null
+++ b/pkg/analysis_server/doc/api.html
@@ -0,0 +1,2938 @@
+<html><head>
+    <meta charset="UTF-8">
+    <title>Analysis Server API Specification</title>
+  <style>h1 {
+  text-align: center;
+}
+pre {
+  margin: 0px;
+}
+div.box {
+  border: 1px solid rgb(0, 0, 0);
+  background-color: rgb(207, 226, 243);
+  padding: 0.5em;
+}
+dt {
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+</style></head>
+  <body>
+    <h1>Analysis Server API Specification</h1>
+    <h1 style="color:#999999">WORKING DRAFT - Version 0.3</h1>
+    <p>
+      This document contains a specification of the API provided by
+      the analysis server. The API in this document is currently under
+      development and should be expected to change. In some cases
+      those changes will be substantial.
+    </p>
+    <h2>Overview</h2>
+    <p>
+      The analysis server API is a bi-directional client-server
+      API. The API is independent of the transport mechanism used, but
+      is heavily influenced by a model in which sockets or character
+      streams are used to transport JSON-RPC encoded information.
+    </p>
+    <h3>Transport Mechanism</h3>
+    <p>
+      The characters passed to the server are expected to be encoded
+      using UTF-8.
+    </p>
+    <p>
+      When character streams are used as the transport, messages are
+      delineated by newlines. This means, in particular, that the JSON
+      encoding process must not introduce newlines within a
+      message. Note however that newlines are used in this document
+      for readability.
+    </p>
+    <p>
+      To ease interoperability with Lisp-based clients (which may not
+      be able to easily distinguish between empty lists, empty maps,
+      and null), client-to-server communication is allowed to replace
+      any instance of “<tt>{}</tt>” or “<tt>[]</tt>” with null. The
+      server will always properly represent empty lists as
+      “<tt>[]</tt>” and empty maps as “<tt>{}</tt>”.
+    </p>
+    <h3>Communication Structure</h3>
+    <p>
+      Clients can make a request of the server and the server will
+      provide a response for each request that it receives. While many
+      of the requests that can be made by a client are informational
+      in nature, we have chosen to always return a response so that
+      clients can know whether the request was received and was
+      correct.
+    </p>
+    <p>
+      There is no guarantee concerning the order in which responses
+      will be returned, but there is a guarantee that the server will
+      process requests in the order in which they are sent as long as
+      the transport mechanism also makes this guarantee. Responses can
+      be returned in an order that is different from the order in
+      which the requests were received because some requests take
+      longer to process than others.
+    </p>
+    <p>
+      Every request is required to have two fields and may have an
+      optional third field. The first required field is the ‘id’
+      field, which is only used by the server to associate a response
+      with the request that generated the response. The second
+      required field is the ‘method’ field, which is used to determine
+      what the server is being requested to do. The optional field is
+      the ‘params’ field, whose structure is dependent on the method
+      being requested. The structure of this field is described with
+      each request for which it is required.
+    </p>
+    <p>
+      Every response has up to three fields. The first field is the
+      ‘id’ field, which is always present and whose value is the
+      identifier that was passed to the request that generated the
+      response. The second field is the ‘error’ field, which is only
+      present if an error was encountered while processing the
+      request. The third field is the ‘result’ field, whose structure
+      is dependent on the method being responded to, and is described
+      with each request that will produce it.
+    </p>
+    <p>
+      The server can also communicate to the clients by sending a
+      notification. The purpose of these notifications is to provide
+      information to clients as it becomes available rather than to
+      require that clients poll for it. Unless explicitly stated, all
+      notifications are designed to return the complete information
+      available at the time the notification is sent; clients are not
+      required to update previously communicated
+      results. Consequently, the server can and should return partial
+      results before all results are available. For example, the
+      syntactic errors for a file can be returned as soon as the
+      syntactic analysis is complete, and both syntactic and semantic
+      errors can be returned together at a later time.
+    </p>
+    <p>
+      Each notification has two fields. The first field is the ‘event’
+      field, which identifies the kind of notification. The second
+      field is the ‘params’ field, whose structure is dependent on the
+      kind of notification being sent. The structure of this field is
+      described with each notification.
+    </p>
+    <h3>Eventual Consistency</h3>
+    <p>
+      TBD
+    </p>
+    <h3>Domains</h3>
+    <p>
+      For convenience, the API is divided into domains. Each domain is
+      specified in a separate section below:
+    </p>
+    <ul>
+      <li><a href="#domain_server">Server</a></li>
+      <li><a href="#domain_analysis">Analysis</a></li>
+      <li><a href="#domain_completion">Code Completion</a></li>
+      <li><a href="#domain_search">Search</a></li>
+      <li><a href="#domain_edit">Edit</a></li>
+      <li><a href="#domain_debug">Debugging</a></li>
+    </ul>
+    <p>
+      The specifications of the API’s refer to data structures beyond
+      the standard JSON primitives. These data structures are
+      documented in the section titled <a href="#types">Types</a>.
+    </p>
+    <h3>Command-line Arguments</h3>
+    <p>
+      The command-line arguments that can be passed to the server.
+    </p>
+    <h4>Options</h4>
+    <dl>
+      <dt>--no-error-notification</dt>
+      <dd></dd>
+    </dl>
+    <p>
+      Disable notifications about errors (see analysis.error). If this
+      flag is not specified then notifications will be sent for all
+      errors produced for all files in the actual analysis roots.
+    </p>
+    <h2><a name="domain_server">Domain: server</a></h2>
+      <p>
+        The server domain contains API’s related to the execution of
+        the server.
+      </p>
+      
+      
+      
+      
+      
+      
+    <h3>Requests</h3><dl><dt class="request">server.getVersion</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "server.getVersion"
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>version</b>": String
+  }
+}</pre></div>
+        <p>Return the version number of the analysis server.</p>
+        
+      <h4>Returns</h4><dl><dt class="field"><b><i>version ( String )</i></b></dt><dd>
+            
+            <p>The version number of the analysis server.</p>
+          </dd></dl></dd><dt class="request">server.shutdown</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "server.shutdown"
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Cleanly shutdown the analysis server. Requests that are
+          received after this request will not be processed. Requests
+          that were received before this request, but for which a
+          response has not yet been sent, will not be responded to. No
+          further responses or notifications will be sent after the
+          response to this request has been sent.
+        </p>
+      </dd><dt class="request">server.setSubscriptions</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "server.setSubscriptions"
+  "params": {
+    "<b>subscriptions</b>": List&lt;<a href="#type_ServerService">ServerService</a>&gt;
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Subscribe for services. All previous subscriptions are
+          replaced by the given set of services.
+        </p>
+        <p>
+          It is an error if any of the elements in the list are not
+          valid services. If there is an error, then the current
+          subscriptions will remain unchanged.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>subscriptions ( List&lt;<a href="#type_ServerService">ServerService</a>&gt; )</i></b></dt><dd>
+            
+            <p>A list of the services being subscribed to.</p>
+          </dd></dl></dd></dl><h3>Notifications</h3><dl><dt class="notification">server.connected</dt><dd><div class="box"><pre>notification: {
+  "event": "server.connected"
+}</pre></div>
+        <p>
+          Reports that the server is running. This notification is
+          issued once after the server has started running but before
+          any requests are processed to let the client know that it
+          started correctly.
+        </p>
+        <p>
+          It is not possible to subscribe to or unsubscribe from this
+          notification.
+        </p>
+      </dd><dt class="notification">server.error</dt><dd><div class="box"><pre>notification: {
+  "event": "server.error"
+  "params": {
+    "<b>fatal</b>": bool
+    "<b>message</b>": String
+    "<b>stackTrace</b>": String
+  }
+}</pre></div>
+        <p>
+          Reports that an unexpected error has occurred while
+          executing the server. This notification is not used for
+          problems with specific requests (which are returned as part
+          of the response) but is used for exceptions that occur while
+          performing other tasks, such as analysis or preparing
+          notifications.
+        </p>
+        <p>
+          It is not possible to subscribe to or unsubscribe from this
+          notification.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>fatal ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if the error is a fatal error, meaning that the
+              server will shutdown automatically after sending this
+              notification.
+            </p>
+          </dd><dt class="field"><b><i>message ( String )</i></b></dt><dd>
+            
+            <p>
+              The error message indicating what kind of error was
+              encountered.
+            </p>
+          </dd><dt class="field"><b><i>stackTrace ( String )</i></b></dt><dd>
+            
+            <p>
+              The stack trace associated with the generation of the
+              error, used for debugging the server.
+            </p>
+          </dd></dl></dd><dt class="notification">server.status</dt><dd><div class="box"><pre>notification: {
+  "event": "server.status"
+  "params": {
+    "<b>analysis</b>": <span style="color:#999999">optional</span> <a href="#type_AnalysisStatus">AnalysisStatus</a>
+  }
+}</pre></div>
+        <p>
+          Reports the current status of the server. Parameters are
+          omitted if there has been no change in the status
+          represented by that parameter.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"STATUS"</tt> in
+          the list of services passed in a server.setSubscriptions
+          request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>analysis ( <span style="color:#999999">optional</span> <a href="#type_AnalysisStatus">AnalysisStatus</a> )</i></b></dt><dd>
+            
+            <p>
+              The current status of analysis, including whether
+              analysis is being performed and if so what is being
+              analyzed.
+            </p>
+          </dd></dl></dd></dl>
+    <h2><a name="domain_analysis">Domain: analysis</a></h2>
+      <p>
+        The analysis domain contains API’s related to the analysis of
+        files.
+      </p>
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+    <h3>Requests</h3><dl><dt class="request">analysis.getErrors</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.getErrors"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>errors</b>": List&lt;<a href="#type_AnalysisError">AnalysisError</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Return the errors associated with the given file. If the
+          errors for the given file have not yet been computed, or the
+          most recently computed errors for the given file are out of
+          date, then the response for this request will be delayed
+          until they have been computed. If some or all of the errors
+          for the file cannot be computed, then the subset of the
+          errors that can be computed will be returned and the
+          response will contain an error to indicate why the errors
+          could not be computed.
+        </p>
+        <p>
+          This request is intended to be used by clients that cannot
+          asynchronously apply updated error information. Clients that
+          <b>can</b> apply error information as it becomes available
+          should use the information provided by the 'analysis.errors'
+          notification.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file for which errors are being requested.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>errors ( List&lt;<a href="#type_AnalysisError">AnalysisError</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The errors associated with the file.
+            </p>
+          </dd></dl></dd><dt class="request">analysis.getHover</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.getHover"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>hovers</b>": List&lt;<a href="#type_HoverInformation">HoverInformation</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Return the hover information associate with the given
+          location. If some or all of the hover information is not
+          available at the time this request is processed the
+          information will be omitted from the response.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file in which hover information is being
+              requested.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset for which hover information is being
+              requested.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>hovers ( List&lt;<a href="#type_HoverInformation">HoverInformation</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The hover information associated with the
+              location. The list will be empty if no information
+              could be determined for the location. The list can
+              contain multiple items if the file is being analyzed
+              in multiple contexts in conflicting ways (such as a
+              part that is included in multiple libraries).
+            </p>
+          </dd></dl></dd><dt class="request">analysis.reanalyze</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.reanalyze"
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Force the re-analysis of everything contained in the
+          existing analysis roots. This will cause all previously
+          computed analysis results to be discarded and recomputed,
+          and will cause all subscribed notifications to be re-sent.
+        </p>
+      </dd><dt class="request">analysis.setAnalysisRoots</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.setAnalysisRoots"
+  "params": {
+    "<b>included</b>": List&lt;<a href="#type_FilePath">FilePath</a>&gt;
+    "<b>excluded</b>": List&lt;<a href="#type_FilePath">FilePath</a>&gt;
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Sets the root paths used to determine which files to
+          analyze. The set of files to be analyzed are all of the
+          files in one of the root paths that are not also in one of
+          the excluded paths.
+        </p>
+        <p>
+          Note that this request determines the set of requested
+          analysis roots. The actual set of analysis roots at any
+          given time is the intersection of this set with the set of
+          files and directories actually present on the
+          filesystem. When the filesystem changes, the actual set of
+          analysis roots is automatically updated, but the set of
+          requested analysis roots is unchanged. This means that if
+          the client sets an analysis root before the root becomes
+          visible to server in the filesystem, there is no error; once
+          the server sees the root in the filesystem it will start
+          analyzing it. Similarly, server will stop analyzing files
+          that are removed from the file system but they will remain
+          in the set of requested roots.
+        </p>
+        <p>
+          If an included path represents a file, then server will look
+          in the directory containing the file for a pubspec.yaml
+          file. If none is found, then the parents of the directory
+          will be searched until such a file is found or the root of
+          the file system is reached. If such a file is found, it will
+          be used to resolve package: URI’s within the file.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>included ( List&lt;<a href="#type_FilePath">FilePath</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the files and directories that should be
+              analyzed.
+            </p>
+          </dd><dt class="field"><b><i>excluded ( List&lt;<a href="#type_FilePath">FilePath</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the files and directories within the
+              included directories that should not be analyzed.
+            </p>
+          </dd></dl></dd><dt class="request">analysis.setPriorityFiles</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.setPriorityFiles"
+  "params": {
+    "<b>files</b>": List&lt;<a href="#type_FilePath">FilePath</a>&gt;
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Set the priority files to the files in the given list. A
+          priority file is a file that is given priority when
+          scheduling which analysis work to do first. The list
+          typically contains those files that are visible to the user
+          and those for which analysis results will have the biggest
+          impact on the user experience. The order of the files within
+          the list is significant: the first file will be given higher
+          priority than the second, the second higher priority than
+          the third, and so on.
+        </p>
+        <p>
+          Note that this request determines the set of requested
+          priority files. The actual set of priority files is the
+          intersection of the requested set of priority files with the
+          set of files currently subject to analysis. (See
+          analysis.setSubscriptions for a description of files that
+          are subject to analysis.)
+        </p>
+        <p>
+          If a requested priority file is a directory it is ignored,
+          but remains in the set of requested priority files so that
+          if it later becomes a file it can be included in the set of
+          actual priority files.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>files ( List&lt;<a href="#type_FilePath">FilePath</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The files that are to be a priority for analysis.
+            </p>
+          </dd></dl></dd><dt class="request">analysis.setSubscriptions</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.setSubscriptions"
+  "params": {
+    "<b>subscriptions</b>": Map&lt;<a href="#type_AnalysisService">AnalysisService</a>, List&lt;<a href="#type_FilePath">FilePath</a>&gt;&gt;
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Subscribe for services. All previous subscriptions are
+          replaced by the current set of subscriptions. If a given
+          service is not included as a key in the map then no files
+          will be subscribed to the service, exactly as if the service
+          had been included in the map with an explicit empty list of
+          files.
+        </p>
+        <p>
+          Note that this request determines the set of requested
+          subscriptions. The actual set of subscriptions at any given
+          time is the intersection of this set with the set of files
+          currently subject to analysis. The files currently subject
+          to analysis are the set of files contained within an actual
+          analysis root but not excluded, plus all of the files
+          transitively reachable from those files via import, export
+          and part directives. (See analysis.setAnalysisRoots for an
+          explanation of how the actual analysis roots are
+          determined.) When the actual analysis roots change, the
+          actual set of subscriptions is automatically updated, but
+          the set of requested subscriptions is unchanged.
+        </p>
+        <p>
+          If a requested subscription is a directory it is ignored,
+          but remains in the set of requested subscriptions so that if
+          it later becomes a file it can be included in the set of
+          actual subscriptions.
+        </p>
+        <p>
+          It is an error if any of the keys in the map are not valid
+          services. If there is an error, then the existing
+          subscriptions will remain unchanged.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>subscriptions ( Map&lt;<a href="#type_AnalysisService">AnalysisService</a>, List&lt;<a href="#type_FilePath">FilePath</a>&gt;&gt; )</i></b></dt><dd>
+            
+            <p>
+              A table mapping services to a list of the files being
+              subscribed to the service.
+            </p>
+          </dd></dl></dd><dt class="request">analysis.updateContent</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.updateContent"
+  "params": {
+    "<b>files</b>": Map&lt;<a href="#type_FilePath">FilePath</a>, <a href="#type_ContentChange">ContentChange</a>&gt;
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Update the content of one or more files. Files that were
+          previously updated but not included in this update remain
+          unchanged. This effectively represents an overlay of the
+          filesystem. The files whose content is overridden are
+          therefore seen by server as being files with the given
+          content, even if the files do not exist on the filesystem or
+          if the file path represents the path to a directory on the
+          filesystem.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>files ( Map&lt;<a href="#type_FilePath">FilePath</a>, <a href="#type_ContentChange">ContentChange</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A table mapping the files whose content has changed to
+              a description of the content.
+            </p>
+          </dd></dl></dd><dt class="request">analysis.updateOptions</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "analysis.updateOptions"
+  "params": {
+    "<b>options</b>": <a href="#type_AnalysisOptions">AnalysisOptions</a>
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Update the options controlling analysis based on the given
+          set of options. Any options that are not included in the
+          analysis options will not be changed. If there are options
+          in the analysis options that are not valid an error will be
+          reported but the values of the valid options will still be
+          updated.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>options ( <a href="#type_AnalysisOptions">AnalysisOptions</a> )</i></b></dt><dd>
+            
+            <p>
+              The options that are to be used to control analysis.
+            </p>
+          </dd></dl></dd></dl><h3>Notifications</h3><dl><dt class="notification">analysis.errors</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.errors"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>errors</b>": List&lt;<a href="#type_AnalysisError">AnalysisError</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports the errors associated with a given file. The set of
+          errors included in the notification is always a complete
+          list that supersedes any previously reported errors.
+        </p>
+        <p>
+          It is only possible to unsubscribe from this notification by
+          using the command-line flag --no-error-notification.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the errors.
+            </p>
+          </dd><dt class="field"><b><i>errors ( List&lt;<a href="#type_AnalysisError">AnalysisError</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The errors contained in the file.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.flushResults</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.flushResults"
+  "params": {
+    "<b>files</b>": List&lt;<a href="#type_FilePath">FilePath</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports that any analysis results that were previously
+          associated with the given files should be considered to be
+          invalid because those files are no longer being analyzed,
+          either because the analysis root that contained it is no
+          longer being analyzed or because the file no longer exists.
+        </p>
+        <p>
+          If a file is included in this notification and at some later
+          time a notification with results for the file is received,
+          clients should assume that the file is once again being
+          analyzed and the information should be processed.
+        </p>
+        <p>
+          It is not possible to subscribe to or unsubscribe from this
+          notification.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>files ( List&lt;<a href="#type_FilePath">FilePath</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The files that are no longer being analyzed.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.folding</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.folding"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>regions</b>": List&lt;<a href="#type_FoldingRegion">FoldingRegion</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports the folding regions associated with a given
+          file. Folding regions can be nested, but will not be
+          overlapping. Nesting occurs when a foldable element, such as
+          a method, is nested inside another foldable element such as
+          a class.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"FOLDING"</tt> in
+          the list of services passed in an analysis.setSubscriptions
+          request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the folding regions.
+            </p>
+          </dd><dt class="field"><b><i>regions ( List&lt;<a href="#type_FoldingRegion">FoldingRegion</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The folding regions contained in the file.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.highlights</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.highlights"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>regions</b>": List&lt;<a href="#type_HighlightRegion">HighlightRegion</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports the highlight regions associated with a given file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"HIGHLIGHTS"</tt>
+          in the list of services passed in an
+          analysis.setSubscriptions request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the highlight regions.
+            </p>
+          </dd><dt class="field"><b><i>regions ( List&lt;<a href="#type_HighlightRegion">HighlightRegion</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The highlight regions contained in the file. Each
+              highlight region represents a particular syntactic or
+              semantic meaning associated with some range. Note that
+              the highlight regions that are returned can overlap
+              other highlight regions if there is more than one
+              meaning associated with a particular region.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.navigation</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.navigation"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>regions</b>": List&lt;<a href="#type_NavigationRegion">NavigationRegion</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports the navigation targets associated with a given file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"NAVIGATION"</tt>
+          in the list of services passed in an
+          analysis.setSubscriptions request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the navigation regions.
+            </p>
+          </dd><dt class="field"><b><i>regions ( List&lt;<a href="#type_NavigationRegion">NavigationRegion</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The navigation regions contained in the file. Each
+              navigation region represents a list of targets
+              associated with some range. The lists will usually
+              contain a single target, but can contain more in the
+              case of a part that is included in multiple libraries
+              or in Dart code that is compiled against multiple
+              versions of a package. Note that the navigation
+              regions that are returned do not overlap other
+              navigation regions.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.occurrences</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.occurrences"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>occurrences</b>": List&lt;<a href="#type_Occurrences">Occurrences</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports the occurrences of references to elements within a
+          single file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"OCCURRENCES"</tt>
+          in the list of services passed in an
+          analysis.setSubscriptions request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file in which the references occur.
+            </p>
+          </dd><dt class="field"><b><i>occurrences ( List&lt;<a href="#type_Occurrences">Occurrences</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The occurrences of references to elements within the
+              file.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.outline</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.outline"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>outline</b>": <a href="#type_Outline">Outline</a>
+  }
+}</pre></div>
+        <p>
+          Reports the outline associated with a single file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"OUTLINE"</tt> in
+          the list of services passed in an analysis.setSubscriptions
+          request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file with which the outline is associated.
+            </p>
+          </dd><dt class="field"><b><i>outline ( <a href="#type_Outline">Outline</a> )</i></b></dt><dd>
+            
+            <p>
+              The outline associated with the file.
+            </p>
+          </dd></dl></dd><dt class="notification">analysis.overrides</dt><dd><div class="box"><pre>notification: {
+  "event": "analysis.overrides"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>overrides</b>": List&lt;<a href="#type_Override">Override</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Reports the overridding members in a file. This notification
+          currently includes only members that override a member from
+          a superclass. In particular, it does not include members
+          that override members from interfaces.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"OVERRIDES"</tt> in
+          the list of services passed in an analysis.setSubscriptions
+          request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file with which the overrides are associated.
+            </p>
+          </dd><dt class="field"><b><i>overrides ( List&lt;<a href="#type_Override">Override</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The overrides associated with the file.
+            </p>
+          </dd></dl></dd></dl>
+    <h2><a name="domain_completion">Domain: completion</a></h2>
+      <p>
+        The code completion domain contains commands related to
+        getting code completion suggestions.
+      </p>
+      
+      
+    <h3>Requests</h3><dl><dt class="request">completion.getSuggestions</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "completion.getSuggestions"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+  }
+}</pre><br><pre>response: {
+  "<b>id</b>": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>id</b>": <a href="#type_CompletionId">CompletionId</a>
+  }
+}</pre></div>
+        <p>
+          Request that completion suggestions for the given offset in
+          the given file be returned.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the point at which suggestions are
+              to be made.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset within the file at which suggestions are to
+              be made.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>id ( <a href="#type_CompletionId">CompletionId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier used to associate results with this
+              completion request.
+            </p>
+          </dd></dl></dd></dl><h3>Notifications</h3><dl><dt class="notification">completion.results</dt><dd><div class="box"><pre>notification: {
+  "event": "completion.results"
+  "params": {
+    "<b>id</b>": <a href="#type_CompletionId">CompletionId</a>
+    "<b>replacementOffset</b>": int
+    "<b>replacementLength</b>": int
+    "<b>results</b>": List&lt;<a href="#type_CompletionSuggestion">CompletionSuggestion</a>&gt;
+    "<b>last</b>": bool
+  }
+}</pre></div>
+        <p>
+          Reports the completion suggestions that should be presented
+          to the user. The set of suggestions included in the
+          notification is always a complete list that supersedes any
+          previously reported suggestions.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>id ( <a href="#type_CompletionId">CompletionId</a> )</i></b></dt><dd>
+            
+            <p>
+              The id associated with the completion.
+            </p>
+          </dd><dt class="field"><b><i>replacementOffset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the start of the text to be
+              replaced. This will be different than the offset used
+              to request the completion suggestions if there was a
+              portion of an identifier before the original
+              offset. In particular, the replacementOffset will be
+              the offset of the beginning of said identifier.
+            </p>
+          </dd><dt class="field"><b><i>replacementLength ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the text to be replaced if the remainder
+              of the identifier containing the cursor is to be
+              replaced when the suggestion is applied (that is, the
+              number of characters in the existing identifier).
+            </p>
+          </dd><dt class="field"><b><i>results ( List&lt;<a href="#type_CompletionSuggestion">CompletionSuggestion</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The completion suggestions being reported.
+            </p>
+          </dd><dt class="field"><b><i>last ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if this is that last set of results that will be
+              returned for the indicated completion.
+            </p>
+          </dd></dl></dd></dl>
+    <h2><a name="domain_search">Domain: search</a></h2>
+      <p>
+        The search domain contains commands related to searches that
+        can be performed against the code base.
+      </p>
+      
+      
+      
+      
+      
+      
+    <h3>Requests</h3><dl><dt class="request">search.findElementReferences</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "search.findElementReferences"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+    "<b>includePotential</b>": bool
+  }
+}</pre><br><pre>response: {
+  "<b>id</b>": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>id</b>": <a href="#type_SearchId">SearchId</a>
+    "<b>element</b>": <a href="#type_Element">Element</a>
+  }
+}</pre></div>
+        <p>
+          Perform a search for references to the element defined or
+          referenced at the given offset in the given file.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the declaration of or reference to
+              the element used to define the search.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset within the file of the declaration of or
+              reference to the element.
+            </p>
+          </dd><dt class="field"><b><i>includePotential ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if potential matches are to be included in the
+              results.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>id ( <a href="#type_SearchId">SearchId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </dd><dt class="field"><b><i>element ( <a href="#type_Element">Element</a> )</i></b></dt><dd>
+            
+            <p>
+              The element referenced or defined at the given offset
+              and whose references will be returned in the search
+              results.
+            </p>
+          </dd></dl></dd><dt class="request">search.findMemberDeclarations</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "search.findMemberDeclarations"
+  "params": {
+    "<b>name</b>": String
+  }
+}</pre><br><pre>response: {
+  "<b>id</b>": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>id</b>": <a href="#type_SearchId">SearchId</a>
+  }
+}</pre></div>
+        <p>
+          Perform a search for declarations of members whose name is
+          equal to the given name.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>name ( String )</i></b></dt><dd>
+            
+            <p>
+              The name of the declarations to be found.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>id ( <a href="#type_SearchId">SearchId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </dd></dl></dd><dt class="request">search.findMemberReferences</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "search.findMemberReferences"
+  "params": {
+    "<b>name</b>": String
+  }
+}</pre><br><pre>response: {
+  "<b>id</b>": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>id</b>": <a href="#type_SearchId">SearchId</a>
+  }
+}</pre></div>
+        <p>
+          Perform a search for references to members whose name is
+          equal to the given name. This search does not check to see
+          that there is a member defined with the given name, so it is
+          able to find references to undefined members as well.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>name ( String )</i></b></dt><dd>
+            
+            <p>
+              The name of the references to be found.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>id ( <a href="#type_SearchId">SearchId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </dd></dl></dd><dt class="request">search.findTopLevelDeclarations</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "search.findTopLevelDeclarations"
+  "params": {
+    "<b>pattern</b>": String
+  }
+}</pre><br><pre>response: {
+  "<b>id</b>": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>id</b>": <a href="#type_SearchId">SearchId</a>
+  }
+}</pre></div>
+        <p>
+          Perform a search for declarations of top-level elements
+          (classes, typedefs, getters, setters, functions and fields)
+          whose name matches the given pattern.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>pattern ( String )</i></b></dt><dd>
+            
+            <p>
+              The regular expression used to match the names of the
+              declarations to be found.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>id ( <a href="#type_SearchId">SearchId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </dd></dl></dd><dt class="request">search.getTypeHierarchy</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "search.getTypeHierarchy"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>hierarchyItems</b>": List&lt;<a href="#type_TypeHierarchyItem">TypeHierarchyItem</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Return the type hierarchy of the class declared or
+          referenced at the given location.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the declaration or reference to the
+              type for which a hierarchy is being requested.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the name of the type within the file.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>hierarchyItems ( List&lt;<a href="#type_TypeHierarchyItem">TypeHierarchyItem</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the types in the requested hierarchy. The
+              first element of the list is the item representing the
+              type for which the hierarchy was requested. The index of
+              other elements of the list is unspecified, but
+              correspond to the integers used to reference supertype
+              and subtype items within the items.
+            </p>
+          </dd></dl></dd></dl><h3>Notifications</h3><dl><dt class="notification">search.results</dt><dd><div class="box"><pre>notification: {
+  "event": "search.results"
+  "params": {
+    "<b>id</b>": <a href="#type_SearchId">SearchId</a>
+    "<b>results</b>": List&lt;<a href="#type_SearchResult">SearchResult</a>&gt;
+    "<b>last</b>": bool
+  }
+}</pre></div>
+        <p>
+          Reports some or all of the results of performing a requested
+          search. Unlike other notifications, this notification
+          contains search results that should be added to any
+          previously received search results associated with the same
+          search id.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>id ( <a href="#type_SearchId">SearchId</a> )</i></b></dt><dd>
+            
+            <p>
+              The id associated with the search.
+            </p>
+          </dd><dt class="field"><b><i>results ( List&lt;<a href="#type_SearchResult">SearchResult</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The search results being reported.
+            </p>
+          </dd><dt class="field"><b><i>last ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if this is that last set of results that will be
+              returned for the indicated search.
+            </p>
+          </dd></dl></dd></dl>
+    <h2><a name="domain_edit">Domain: edit</a></h2>
+      <p>
+        The edit domain contains commands related to edits that can be
+        applied to the code.
+      </p>
+      
+      
+      
+      
+    <h3>Requests</h3><dl><dt class="request">edit.getAssists</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "edit.getAssists"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+    "<b>length</b>": int
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>assists</b>": List&lt;<a href="#type_SourceChange">SourceChange</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Return the set of assists that are available at the given
+          location. An assist is distinguished from a refactoring
+          primarily by the fact that it affects a single file and does
+          not require user input in order to be performed.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the code for which assists are being
+              requested.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the code for which assists are being
+              requested.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the code for which assists are being
+              requested.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>assists ( List&lt;<a href="#type_SourceChange">SourceChange</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The assists that are available at the given location.
+            </p>
+          </dd></dl></dd><dt class="request">edit.getAvailableRefactorings</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "edit.getAvailableRefactorings"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+    "<b>length</b>": int
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>kinds</b>": List&lt;<a href="#type_RefactoringKind">RefactoringKind</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Get a list of the kinds of refactorings that are valid for
+          the given selection in the given file.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the code on which the refactoring
+              would be based.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the code on which the refactoring would be
+              based.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the code on which the refactoring would be
+              based.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>kinds ( List&lt;<a href="#type_RefactoringKind">RefactoringKind</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The kinds of refactorings that are valid for the given
+              selection.
+            </p>
+          </dd></dl></dd><dt class="request">edit.getFixes</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "edit.getFixes"
+  "params": {
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>fixes</b>": List&lt;<a href="#type_ErrorFixes">ErrorFixes</a>&gt;
+  }
+}</pre></div>
+        <p>
+          Return the set of fixes that are available for the errors at
+          a given offset in a given file.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the errors for which fixes are being
+              requested.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset used to select the errors for which fixes
+              will be returned.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>fixes ( List&lt;<a href="#type_ErrorFixes">ErrorFixes</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The fixes that are available for each of the analysis
+              errors. There is a one-to-one correspondence between the
+              analysis errors in the request and the lists of changes
+              in the response. In particular, it is always the case
+              that errors.length == fixes.length and that fixes[i] is
+              the list of fixes for the error in errors[i]. The list
+              of changes corresponding to an error can be empty if
+              there are no fixes available for that error.
+            </p>
+          </dd></dl></dd><dt class="request">edit.getRefactoring</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "edit.getRefactoring"
+  "params": {
+    "<b>kindId</b>": <a href="#type_RefactoringKind">RefactoringKind</a>
+    "<b>file</b>": <a href="#type_FilePath">FilePath</a>
+    "<b>offset</b>": int
+    "<b>length</b>": int
+    "<b>validateOnly</b>": bool
+    "<b>options</b>": <span style="color:#999999">optional</span> object
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>status</b>": List&lt;<a href="#type_RefactoringProblem">RefactoringProblem</a>&gt;
+    "<b>feedback</b>": <span style="color:#999999">optional</span> object
+    "<b>change</b>": <span style="color:#999999">optional</span> <a href="#type_SourceChange">SourceChange</a>
+  }
+}</pre></div>
+        <p>
+          Get the changes required to perform a refactoring.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>kindId ( <a href="#type_RefactoringKind">RefactoringKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier of the kind of refactoring to be
+              performed.
+            </p>
+          </dd><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the code involved in the
+              refactoring.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region involved in the refactoring.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region involved in the refactoring.
+            </p>
+          </dd><dt class="field"><b><i>validateOnly ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if the client is only requesting that the values of
+              the options be validated and no change be generated.
+            </p>
+          </dd><dt class="field"><b><i>options ( <span style="color:#999999">optional</span> object )</i></b></dt><dd>
+            
+            <p>
+              Data used to provide values provided by the user. The
+              structure of the data is dependent on the kind of
+              refactoring being performed. The data that is expected is
+              documented in the section titled <a href="#refactorings">Refactorings</a>, labeled as
+              “Options”. This field can be omitted if the refactoring
+              does not require any options or if the values of those
+              options are not known.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>status ( List&lt;<a href="#type_RefactoringProblem">RefactoringProblem</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The status of the refactoring. The array will be empty
+              if there are no known problems.
+            </p>
+          </dd><dt class="field"><b><i>feedback ( <span style="color:#999999">optional</span> object )</i></b></dt><dd>
+            
+            <p>
+              Data used to provide feedback to the user. The structure
+              of the data is dependent on the kind of refactoring
+              being created. The data that is returned is documented
+              in the section titled <a href="#refactorings">Refactorings</a>, labeled as
+              “Feedback”.
+            </p>
+          </dd><dt class="field"><b><i>change ( <span style="color:#999999">optional</span> <a href="#type_SourceChange">SourceChange</a> )</i></b></dt><dd>
+            
+            <p>
+              The changes that are to be applied to affect the
+              refactoring. This field will be omitted if there are
+              problems that prevent a set of changed from being
+              computed, such as having no options specified for a
+              refactoring that requires them, or if only validation
+              was requested.
+            </p>
+          </dd></dl></dd></dl>
+    <h2><a name="domain_debug">Domain: debug</a></h2>
+      <p>
+        The debugging domain contains commands related to providing a
+        debugging experience.
+      </p>
+      
+      
+      
+      
+      
+    <h3>Requests</h3><dl><dt class="request">debug.createContext</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "debug.createContext"
+  "params": {
+    "<b>contextRoot</b>": <a href="#type_FilePath">FilePath</a>
+  }
+}</pre><br><pre>response: {
+  "<b>id</b>": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>id</b>": <a href="#type_DebugContextId">DebugContextId</a>
+  }
+}</pre></div>
+        <p>
+          Create a debugging context for the executable file with the
+          given path. The context that is created will persist until
+          debug.deleteContext is used to delete it. Clients,
+          therefore, are responsible for managing the lifetime of
+          debugging contexts.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>contextRoot ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The path of the Dart or HTML file that will be launched.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>id ( <a href="#type_DebugContextId">DebugContextId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier used to refer to the debugging context
+              that was created.
+            </p>
+          </dd></dl></dd><dt class="request">debug.deleteContext</dt><dd><div class="box"><pre>request: {
+  "<b>id</b>": String
+  "method": "debug.deleteContext"
+  "params": {
+    "<b>id</b>": <a href="#type_DebugContextId">DebugContextId</a>
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Delete the debugging context with the given identifier. The
+          context id is no longer valid after this command. The server
+          is allowed to re-use ids when they are no longer valid.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>id ( <a href="#type_DebugContextId">DebugContextId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier of the debugging context that is to be
+              deleted.
+            </p>
+          </dd></dl></dd><dt class="request">debug.mapUri</dt><dd><div class="box"><pre>request: {
+  "<b>id</b>": String
+  "method": "debug.mapUri"
+  "params": {
+    "<b>id</b>": <a href="#type_DebugContextId">DebugContextId</a>
+    "<b>file</b>": <span style="color:#999999">optional</span> <a href="#type_FilePath">FilePath</a>
+    "<b>uri</b>": <span style="color:#999999">optional</span> String
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+  "result": {
+    "<b>file</b>": <span style="color:#999999">optional</span> <a href="#type_FilePath">FilePath</a>
+    "<b>uri</b>": <span style="color:#999999">optional</span> String
+  }
+}</pre></div>
+        <p>
+          Map a URI from the debugging context to the file that it
+          corresponds to, or map a file to the URI that it corresponds
+          to in the debugging context.
+        </p>
+        <p>
+          Exactly one of the file and uri fields must be provided.
+        </p>
+        
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>id ( <a href="#type_DebugContextId">DebugContextId</a> )</i></b></dt><dd>
+            
+            <p>
+              The identifier of the debugging context in which the URI
+              is to be mapped.
+            </p>
+          </dd><dt class="field"><b><i>file ( <span style="color:#999999">optional</span> <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The path of the file to be mapped into a URI.
+            </p>
+          </dd><dt class="field"><b><i>uri ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The URI to be mapped into a file path.
+            </p>
+          </dd></dl><h4>Returns</h4><dl><dt class="field"><b><i>file ( <span style="color:#999999">optional</span> <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file to which the URI was mapped. This field is
+              omitted if the uri field was not given in the request.
+            </p>
+          </dd><dt class="field"><b><i>uri ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The URI to which the file path was mapped. This field is
+              omitted if the file field was not given in the request.
+            </p>
+          </dd></dl></dd><dt class="request">debug.setSubscriptions</dt><dd><div class="box"><pre>request: {
+  "id": String
+  "method": "debug.setSubscriptions"
+  "params": {
+    "<b>subscriptions</b>": List&lt;<a href="#type_DebugService">DebugService</a>&gt;
+  }
+}</pre><br><pre>response: {
+  "id": String
+  "error": <span style="color:#999999">optional</span> <a href="#type_Error">Error</a>
+}</pre></div>
+        <p>
+          Subscribe for services. All previous subscriptions are
+          replaced by the given set of services.
+        </p>
+        <p>
+          It is an error if any of the elements in the list are not
+          valid services. If there is an error, then the current
+          subscriptions will remain unchanged.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>subscriptions ( List&lt;<a href="#type_DebugService">DebugService</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the services being subscribed to.
+            </p>
+          </dd></dl></dd></dl><h3>Notifications</h3><dl><dt class="notification">debug.launchData</dt><dd><div class="box"><pre>notification: {
+  "event": "debug.launchData"
+  "params": {
+    "<b>executables</b>": List&lt;<a href="#type_ExecutableFile">ExecutableFile</a>&gt;
+    "<b>dartToHtml</b>": Map&lt;<a href="#type_FilePath">FilePath</a>, List&lt;<a href="#type_FilePath">FilePath</a>&gt;&gt;
+    "<b>htmlToDart</b>": Map&lt;<a href="#type_FilePath">FilePath</a>, List&lt;<a href="#type_FilePath">FilePath</a>&gt;&gt;
+  }
+}</pre></div>
+        <p>
+          Reports information needed to allow applications within the
+          given context to be launched.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value "LAUNCH_DATA" in the
+          list of services passed in a debug.setSubscriptions request.
+        </p>
+        
+      <h4>Parameters</h4><dl><dt class="field"><b><i>executables ( List&lt;<a href="#type_ExecutableFile">ExecutableFile</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the files that are executable in the given
+              context. This list replaces any previous list provided
+              for the given context.
+            </p>
+          </dd><dt class="field"><b><i>dartToHtml ( Map&lt;<a href="#type_FilePath">FilePath</a>, List&lt;<a href="#type_FilePath">FilePath</a>&gt;&gt; )</i></b></dt><dd>
+            
+            <p>
+              A mapping from the paths of Dart files that are
+              referenced by HTML files to a list of the HTML files
+              that reference the Dart files.
+            </p>
+          </dd><dt class="field"><b><i>htmlToDart ( Map&lt;<a href="#type_FilePath">FilePath</a>, List&lt;<a href="#type_FilePath">FilePath</a>&gt;&gt; )</i></b></dt><dd>
+            
+            <p>
+              A mapping from the paths of HTML files that reference
+              Dart files to a list of the Dart files they reference.
+            </p>
+          </dd></dl></dd></dl>
+    
+      <h2><a name="types">Types</a></h2>
+      <p>
+        This section contains descriptions of the data types referenced
+        in the API’s of the various domains.
+      </p>
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+      
+    <dl><dt class="typeDefinition"><a name="type_AnalysisError">AnalysisError: object</a></dt><dd>
+        <p>
+          An indication of an error, warning, or hint that was produced
+          by the analysis.
+        </p>
+        
+      <dl><dt class="field"><b><i>severity ( <a href="#type_ErrorSeverity">ErrorSeverity</a> )</i></b></dt><dd>
+            
+            <p>
+              The severity of the error.
+            </p>
+          </dd><dt class="field"><b><i>type ( <a href="#type_ErrorType">ErrorType</a> )</i></b></dt><dd>
+            
+            <p>
+              The type of the error.
+            </p>
+          </dd><dt class="field"><b><i>location ( <a href="#type_Location">Location</a> )</i></b></dt><dd>
+            
+            <p>
+              The location associated with the error.
+            </p>
+          </dd><dt class="field"><b><i>message ( String )</i></b></dt><dd>
+            
+            <p>
+              The message to be displayed for this error. The message
+              should indicate what is wrong with the code and why it is
+              wrong.
+            </p>
+          </dd><dt class="field"><b><i>correction ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The correction message to be displayed for this error. The
+              correction message should indicate how the user can fix
+              the error. The field is omitted if there is no correction
+              message associated with the error code.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_AnalysisOptions">AnalysisOptions: object</a></dt><dd>
+        <p>
+          A set of options controlling what kind of analysis is to be
+          performed. If the value of a field is omitted the value of the
+          option will not be changed.
+        </p>
+        <p>
+          NOTE: These options need to change.
+        </p>
+        
+      <dl><dt class="field"><b><i>analyzeAngular ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True if the client wants Angular code to be analyzed.
+            </p>
+          </dd><dt class="field"><b><i>analyzePolymer ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True if the client wants Polymer code to be analyzed.
+            </p>
+          </dd><dt class="field"><b><i>enableAsync ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True if the client wants to enable support for the
+              proposed async feature.
+            </p>
+          </dd><dt class="field"><b><i>enableDeferredLoading ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True if the client wants to enable support for the
+              proposed deferred loading feature.
+            </p>
+          </dd><dt class="field"><b><i>enableEnums ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True if the client wants to enable support for the
+              proposed enum feature.
+            </p>
+          </dd><dt class="field"><b><i>generateDart2jsHints ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True if hints that are specific to dart2js should be
+              generated. This option is ignored if either provideErrors
+              or generateHints is false.
+            </p>
+          </dd><dt class="field"><b><i>generateHints ( <span style="color:#999999">optional</span> bool )</i></b></dt><dd>
+            
+            <p>
+              True is hints should be generated as part of generating
+              errors and warnings. This option is ignored if
+              provideErrors is false.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_AnalysisService">AnalysisService: String</a></dt><dd>
+        <p>
+          An enumeration of the services provided by the analysis
+          domain.
+        </p>
+        
+      <dl><dt class="value">FOLDING</dt><dt class="value">HIGHLIGHTS</dt><dt class="value">NAVIGATION</dt><dt class="value">OCCURRENCES</dt><dt class="value">OUTLINE</dt><dt class="value">OVERRIDES</dt></dl></dd><dt class="typeDefinition"><a name="type_AnalysisStatus">AnalysisStatus: object</a></dt><dd>
+        <p>
+          An indication of the current state of analysis.
+        </p>
+        
+      <dl><dt class="field"><b><i>analyzing ( bool )</i></b></dt><dd>
+            
+            <p>True if analysis is currently being performed.</p>
+          </dd><dt class="field"><b><i>analysisTarget ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The name of the current target of analysis. This field is
+              omitted if analyzing is false.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_CompletionId">CompletionId: String</a></dt><dd>
+        
+        <p>
+          An identifier used to associate completion results with a
+          completion request.
+        </p>
+      </dd><dt class="typeDefinition"><a name="type_CompletionRelevance">CompletionRelevance: String</a></dt><dd>
+        <p>
+          An enumeration of the relevance of a completion
+          suggestion.
+        </p>
+        
+      <dl><dt class="value">LOW</dt><dt class="value">DEFAULT</dt><dt class="value">HIGH</dt></dl></dd><dt class="typeDefinition"><a name="type_CompletionSuggestion">CompletionSuggestion: object</a></dt><dd>
+        <p>
+          A suggestion for how to complete partially entered text. Many
+          of the fields are optional, depending on the kind of element
+          being suggested.
+        </p>
+        
+      <dl><dt class="field"><b><i>kind ( <a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The kind of element being suggested.
+            </p>
+          </dd><dt class="field"><b><i>relevance ( <a href="#type_CompletionRelevance">CompletionRelevance</a> )</i></b></dt><dd>
+            
+            <p>
+              The relevance of this completion suggestion.
+            </p>
+          </dd><dt class="field"><b><i>completion ( String )</i></b></dt><dd>
+            
+            <p>
+              The identifier to be inserted if the suggestion is
+              selected. If the suggestion is for a method or function,
+              the client might want to additionally insert a template
+              for the parameters. The information required in order to
+              do so is contained in other fields.
+            </p>
+          </dd><dt class="field"><b><i>selectionOffset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset, relative to the beginning of the completion,
+              of where the selection should be placed after insertion.
+            </p>
+          </dd><dt class="field"><b><i>selectionLength ( int )</i></b></dt><dd>
+            
+            <p>
+              The number of characters that should be selected after
+              insertion.
+            </p>
+          </dd><dt class="field"><b><i>isDeprecated ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if the suggested element is deprecated.
+            </p>
+          </dd><dt class="field"><b><i>isPotential ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if the element is not known to be valid for the
+              target. This happens if the type of the target is dynamic.
+            </p>
+          </dd><dt class="field"><b><i>docSummary ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              An abbreviated version of the Dartdoc associated with the
+              element being suggested, This field is omitted if there is
+              no Dartdoc associated with the element.
+            </p>
+          </dd><dt class="field"><b><i>docComplete ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The Dartdoc associated with the element being suggested,
+              This field is omitted if there is no Dartdoc associated
+              with the element.
+            </p>
+          </dd><dt class="field"><b><i>declaringType ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The class that declares the element being suggested. This
+              field is omitted if the suggested element is not a member
+              of a class.
+            </p>
+          </dd><dt class="field"><b><i>returnType ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The return type of the getter, function or method being
+              suggested. This field is omitted if the suggested element
+              is not a getter, function or method.
+            </p>
+          </dd><dt class="field"><b><i>parameterNames ( <span style="color:#999999">optional</span> List&lt;String&gt; )</i></b></dt><dd>
+            
+            <p>
+              The names of the parameters of the function or method
+              being suggested. This field is omitted if the suggested
+              element is not a setter, function or method.
+            </p>
+          </dd><dt class="field"><b><i>parameterTypes ( <span style="color:#999999">optional</span> List&lt;String&gt; )</i></b></dt><dd>
+            
+            <p>
+              The types of the parameters of the function or method
+              being suggested. This field is omitted if the
+              parameterNames field is omitted.
+            </p>
+          </dd><dt class="field"><b><i>requiredParameterCount ( <span style="color:#999999">optional</span> int )</i></b></dt><dd>
+            
+            <p>
+              The number of required parameters for the function or
+              method being suggested. This field is omitted if the
+              parameterNames field is omitted.
+            </p>
+          </dd><dt class="field"><b><i>positionalParameterCount ( <span style="color:#999999">optional</span> int )</i></b></dt><dd>
+            
+            <p>
+              The number of positional parameters for the function or
+              method being suggested. This field is omitted if the
+              parameterNames field is omitted.
+            </p>
+          </dd><dt class="field"><b><i>parameterName ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The name of the optional parameter being suggested. This
+              field is omitted if the suggestion is not the addition of
+              an optional argument within an argument list.
+            </p>
+          </dd><dt class="field"><b><i>parameterType ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The type of the options parameter being suggested. This
+              field is omitted if the parameterName field is omitted.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_CompletionSuggestionKind">CompletionSuggestionKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of elements that can be included
+          in a completion suggestion.
+        </p>
+        
+      <dl><dt class="value">ARGUMENT_LIST</dt><dt class="value">CLASS</dt><dt class="value">CLASS_ALIAS</dt><dt class="value">CONSTRUCTOR</dt><dt class="value">FIELD</dt><dt class="value">FUNCTION</dt><dt class="value">FUNCTION_TYPE_ALIAS</dt><dt class="value">GETTER</dt><dt class="value">IMPORT</dt><dt class="value">LIBRARY_PREFIX</dt><dt class="value">LOCAL_VARIABLE</dt><dt class="value">METHOD</dt><dt class="value">METHOD_NAME</dt><dt class="value">NAMED_ARGUMENT</dt><dt class="value">OPTIONAL_ARGUMENT</dt><dt class="value">PARAMETER</dt><dt class="value">SETTER</dt><dt class="value">TOP_LEVEL_VARIABLE</dt><dt class="value">TYPE_PARAMETER</dt></dl></dd><dt class="typeDefinition"><a name="type_ContentChange">ContentChange: object</a></dt><dd>
+        <p>
+          A description of the change to the content of a file. The
+          optional fields are omitted if there is no single range of
+          characters that was modified. If any of the optional fields
+          are provided then all of the optional fields must be provided.
+        </p>
+        
+      <dl><dt class="field"><b><i>content ( String )</i></b></dt><dd>
+            
+            <p>
+              The new content of the file, or null if the content of the
+              file should be read from disk.
+            </p>
+          </dd><dt class="field"><b><i>offset ( <span style="color:#999999">optional</span> int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region that was modified.
+            </p>
+          </dd><dt class="field"><b><i>oldLength ( <span style="color:#999999">optional</span> int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region that was removed.
+            </p>
+          </dd><dt class="field"><b><i>newLength ( <span style="color:#999999">optional</span> int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region that was added.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_DebugContextId">DebugContextId: String</a></dt><dd>
+        
+        <p>
+          The identifier for a debug context.
+        </p>
+      </dd><dt class="typeDefinition"><a name="type_DebugService">DebugService: String</a></dt><dd>
+        <p>
+          An enumeration of the services provided by the debug
+          domain.
+        </p>
+        
+      <dl><dt class="value">LAUNCH_DATA</dt></dl></dd><dt class="typeDefinition"><a name="type_Element">Element: object</a></dt><dd>
+        <p>
+          Information about an element (something that can be declared
+          in code).
+        </p>
+        
+      <dl><dt class="field"><b><i>kind ( <a href="#type_ElementKind">ElementKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The kind of the element.
+            </p>
+          </dd><dt class="field"><b><i>name ( String )</i></b></dt><dd>
+            
+            <p>
+              The name of the element. This is typically used as the
+              label in the outline.
+            </p>
+          </dd><dt class="field"><b><i>location ( <span style="color:#999999">optional</span> <a href="#type_Location">Location</a> )</i></b></dt><dd>
+            
+            <p>
+              The location of the name in the declaration of the
+              element.
+            </p>
+          </dd><dt class="field"><b><i>flags ( int )</i></b></dt><dd>
+            
+            <p>
+              A bit-map containing the following flags:
+            </p>
+            <ul>
+              <li>0x01 - set if the element is explicitly or implicitly abstract</li>
+              <li>0x02 - set if the element was declared to be ‘const’</li>
+              <li>0x04 - set if the element was declared to be ‘final’</li>
+              <li>0x08 - set if the element is a static member of a class or is a top-level function or field</li>
+              <li>0x10 - set if the element is private</li>
+              <li>0x20 - set if the element is deprecated</li>
+            </ul>
+          </dd><dt class="field"><b><i>parameters ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The parameter list for the element. If the element is not
+              a method or function this field will not be defined. If
+              the element has zero parameters, this field will have a
+              value of "()".
+            </p>
+          </dd><dt class="field"><b><i>returnType ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The return type of the element. If the element is not a
+              method or function this field will not be defined. If the
+              element does not have a declared return type, this field
+              will contain an empty string.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_ElementKind">ElementKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of elements.
+        </p>
+        
+      <dl><dt class="value">CLASS</dt><dt class="value">CLASS_TYPE_ALIAS</dt><dt class="value">COMPILATION_UNIT</dt><dt class="value">CONSTRUCTOR</dt><dt class="value">GETTER</dt><dt class="value">FIELD</dt><dt class="value">FUNCTION</dt><dt class="value">FUNCTION_TYPE_ALIAS</dt><dt class="value">LIBRARY</dt><dt class="value">LOCAL_VARIABLE</dt><dt class="value">METHOD</dt><dt class="value">SETTER</dt><dt class="value">TOP_LEVEL_VARIABLE</dt><dt class="value">TYPE_PARAMETER</dt><dt class="value">UNKNOWN</dt><dt class="value">UNIT_TEST_GROUP</dt><dt class="value">UNIT_TEST_TEST</dt></dl></dd><dt class="typeDefinition"><a name="type_Error">Error: object</a></dt><dd>
+        <p>
+          An indication of a problem with the execution of the server,
+          typically in response to a request. The error codes that can
+          be returned are documented in the section titled Errors.
+        </p>
+        
+      <dl><dt class="field"><b><i>code ( String )</i></b></dt><dd>
+            
+            <p>
+              A code that uniquely identifies the error that occurred.
+            </p>
+          </dd><dt class="field"><b><i>message ( String )</i></b></dt><dd>
+            
+            <p>
+              A short description of the error.
+            </p>
+          </dd><dt class="field"><b><i>data ( <span style="color:#999999">optional</span> object )</i></b></dt><dd>
+            
+            <p>
+              Additional data related to the error. This field is
+              omitted if there is no additional data available.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_ErrorFixes">ErrorFixes: object</a></dt><dd>
+        <p>
+          A list of fixes associated with a specific error
+        </p>
+        
+      <dl><dt class="field"><b><i>error ( <a href="#type_AnalysisError">AnalysisError</a> )</i></b></dt><dd>
+            
+            <p>
+              The error with which the fixes are associated.
+            </p>
+          </dd><dt class="field"><b><i>fixes ( List&lt;<a href="#type_SourceChange">SourceChange</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The fixes associated with the error.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_ErrorSeverity">ErrorSeverity: String</a></dt><dd>
+        <p>
+          An enumeration of the possible severities of analysis
+          errors.
+        </p>
+        
+      <dl><dt class="value">INFO</dt><dt class="value">WARNING</dt><dt class="value">ERROR</dt></dl></dd><dt class="typeDefinition"><a name="type_ErrorType">ErrorType: String</a></dt><dd>
+        <p>
+          An enumeration of the possible types of analysis errors.
+        </p>
+        
+      <dl><dt class="value">COMPILE_TIME_ERROR</dt><dt class="value">HINT</dt><dt class="value">STATIC_TYPE_WARNING</dt><dt class="value">STATIC_WARNING</dt><dt class="value">SYNTACTIC_ERROR</dt><dt class="value">TODO</dt></dl></dd><dt class="typeDefinition"><a name="type_ExecutableFile">ExecutableFile: object</a></dt><dd>
+        <p>
+          A description of an executable file.
+        </p>
+        
+      <dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The path of the executable file.
+            </p>
+          </dd><dt class="field"><b><i>offset ( <a href="#type_ExecutableKind">ExecutableKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region to be highlighted.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_ExecutableKind">ExecutableKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of executable files.
+        </p>
+        
+      <dl><dt class="value">CLIENT</dt><dt class="value">EITHER</dt><dt class="value">SERVER</dt></dl></dd><dt class="typeDefinition"><a name="type_FilePath">FilePath: String</a></dt><dd>
+        
+        <p>
+          The absolute path of a file.
+        </p>
+      </dd><dt class="typeDefinition"><a name="type_FoldingKind">FoldingKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of folding regions.
+        </p>
+        
+      <dl><dt class="value">COMMENT</dt><dt class="value">CLASS_MEMBER</dt><dt class="value">DIRECTIVES</dt><dt class="value">DOCUMENTATION_COMMENT</dt><dt class="value">TOP_LEVEL_DECLARATION</dt></dl></dd><dt class="typeDefinition"><a name="type_FoldingRegion">FoldingRegion: object</a></dt><dd>
+        <p>
+          A description of a region that can be folded.
+        </p>
+        
+      <dl><dt class="field"><b><i>kind ( <a href="#type_FoldingKind">FoldingKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The kind of the region.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region to be folded.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region to be folded.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_HighlightRegion">HighlightRegion: object</a></dt><dd>
+        <p>
+          A description of a region that could have special highlighting
+          associated with it.
+        </p>
+        
+      <dl><dt class="field"><b><i>type ( <a href="#type_HighlightRegionType">HighlightRegionType</a> )</i></b></dt><dd>
+            
+            <p>
+              The type of highlight associated with the region.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region to be highlighted.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region to be highlighted.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_HighlightRegionType">HighlightRegionType: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of highlighting that can be
+          applied to files.
+        </p>
+        
+      <dl><dt class="value">ANNOTATION</dt><dt class="value">BUILT_IN</dt><dt class="value">CLASS</dt><dt class="value">COMMENT_BLOCK</dt><dt class="value">COMMENT_DOCUMENTATION</dt><dt class="value">COMMENT_END_OF_LINE</dt><dt class="value">CONSTRUCTOR</dt><dt class="value">DIRECTIVE</dt><dt class="value">DYNAMIC_TYPE</dt><dt class="value">FIELD</dt><dt class="value">FIELD_STATIC</dt><dt class="value">FUNCTION_DECLARATION</dt><dt class="value">FUNCTION</dt><dt class="value">FUNCTION_TYPE_ALIAS</dt><dt class="value">GETTER_DECLARATION</dt><dt class="value">KEYWORD</dt><dt class="value">IDENTIFIER_DEFAULT</dt><dt class="value">IMPORT_PREFIX</dt><dt class="value">LITERAL_BOOLEAN</dt><dt class="value">LITERAL_DOUBLE</dt><dt class="value">LITERAL_INTEGER</dt><dt class="value">LITERAL_LIST</dt><dt class="value">LITERAL_MAP</dt><dt class="value">LITERAL_STRING</dt><dt class="value">LOCAL_VARIABLE_DECLARATION</dt><dt class="value">LOCAL_VARIABLE</dt><dt class="value">METHOD_DECLARATION</dt><dt class="value">METHOD_DECLARATION_STATIC</dt><dt class="value">METHOD</dt><dt class="value">METHOD_STATIC</dt><dt class="value">PARAMETER</dt><dt class="value">SETTER_DECLARATION</dt><dt class="value">TOP_LEVEL_VARIABLE</dt><dt class="value">TYPE_NAME_DYNAMIC</dt><dt class="value">TYPE_PARAMETER</dt></dl></dd><dt class="typeDefinition"><a name="type_HoverInformation">HoverInformation: object</a></dt><dd>
+        <p>
+          The hover information associated with a specific location.
+        </p>
+        
+      <dl><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the range of characters that encompases the
+              cursor position and has the same hover information as the
+              cursor position.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the range of characters that encompases the
+              cursor position and has the same hover information as the
+              cursor position.
+            </p>
+          </dd><dt class="field"><b><i>containingLibraryPath ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The path to the defining compilation unit of the library
+              in which the referenced element is declared. This data is
+              omitted if there is no referenced element.
+            </p>
+          </dd><dt class="field"><b><i>containingLibraryName ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The name of the library in which the referenced element is
+              declared. This data is omitted if there is no referenced
+              element.
+            </p>
+          </dd><dt class="field"><b><i>dartdoc ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The dartdoc associated with the referenced element. Other
+              than the removal of the comment delimiters, including
+              leading asterisks in the case of a block comment, the
+              dartdoc is unprocessed markdown. This data is omitted if
+              there is no referenced element.
+            </p>
+          </dd><dt class="field"><b><i>elementDescription ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              A human-readable description of the element being
+              referenced. This data is omitted if there is no referenced
+              element.
+            </p>
+          </dd><dt class="field"><b><i>elementKind ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              A human-readable description of the kind of element being
+              referenced (such as “class” or “function type
+              alias”). This data is omitted if there is no referenced
+              element.
+            </p>
+          </dd><dt class="field"><b><i>parameter ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              A human-readable description of the parameter
+              corresponding to the expression being hovered over. This
+              data is omitted if the location is not in an argument to a
+              function.
+            </p>
+          </dd><dt class="field"><b><i>propagatedType ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The name of the propagated type of the expression. This
+              data is omitted if the location does not correspond to an
+              expression or if there is no propagated type information.
+            </p>
+          </dd><dt class="field"><b><i>staticType ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The name of the static type of the expression. This data
+              is omitted if the location does not correspond to an
+              expression.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_LinkedEditGroup">LinkedEditGroup: object</a></dt><dd>
+        <p>
+          A collection of positions that should be linked (edited
+          simultaneously) for the purposes of updating code after a
+          source change. For example, if a set of edits introduced a
+          new variable name, the group would contain all of the
+          positions of the variable name so that if the client wanted
+          to let the user edit the variable name after the operation,
+          all occurrences of the name could be edited simultaneously.
+        </p>
+        
+      <dl><dt class="field"><b><i>positions ( List&lt;<a href="#type_Position">Position</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The positions of the regions that should be edited
+              simultaneously.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the regions that should be edited
+              simultaneously.
+            </p>
+          </dd><dt class="field"><b><i>suggestions ( List&lt;<a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              Pre-computed suggestions for what every region might
+              want to be changed to.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_LinkedEditSuggestion">LinkedEditSuggestion: object</a></dt><dd>
+        <p>
+          A suggestion of a value that could be used to replace all of
+          the linked edit regions in a LinkedEditGroup.
+        </p>
+        
+      <dl><dt class="field"><b><i>value ( String )</i></b></dt><dd>
+            
+            <p>
+              The value that could be used to replace all of the linked
+              edit regions.
+            </p>
+          </dd><dt class="field"><b><i>kind ( <a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The kind of value being proposed.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_LinkedEditSuggestionKind">LinkedEditSuggestionKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kind of values that can be suggested
+          for a linked edit.
+        </p>
+        
+      <dl><dt class="value">METHOD</dt><dt class="value">PARAMETER</dt><dt class="value">TYPE</dt><dt class="value">VARIABLE</dt></dl></dd><dt class="typeDefinition"><a name="type_Location">Location: object</a></dt><dd>
+        <p>
+          A location (character range) within a file.
+        </p>
+        
+      <dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the range.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the range.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the range.
+            </p>
+          </dd><dt class="field"><b><i>startLine ( int )</i></b></dt><dd>
+            
+            <p>
+              The one-based index of the line containing the first
+              character of the range.
+            </p>
+          </dd><dt class="field"><b><i>startColumn ( int )</i></b></dt><dd>
+            
+            <p>
+              The one-based index of the column containing the first
+              character of the range.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_NavigationRegion">NavigationRegion: object</a></dt><dd>
+        <p>
+          A description of a region from which the user can navigate to
+          the declaration of an element.
+        </p>
+        
+      <dl><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region from which the user can navigate.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region from which the user can navigate.
+            </p>
+          </dd><dt class="field"><b><i>targets ( List&lt;<a href="#type_Element">Element</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The elements to which the given region is bound. By
+              opening the declaration of the elements, clients can
+              implement one form of navigation.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_Occurrences">Occurrences: object</a></dt><dd>
+        <p>
+          A description of the references to a single element within a
+          single file.
+        </p>
+        
+      <dl><dt class="field"><b><i>element ( <a href="#type_Element">Element</a> )</i></b></dt><dd>
+            
+            <p>
+              The element that was referenced.
+            </p>
+          </dd><dt class="field"><b><i>offsets ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The offsets of the name of the referenced element within
+              the file.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the name of the referenced element.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_Outline">Outline: object</a></dt><dd>
+        <p>
+          An node in the outline structure of a file.
+        </p>
+        
+      <dl><dt class="field"><b><i>element ( <a href="#type_Element">Element</a> )</i></b></dt><dd>
+            
+            <p>
+              A description of the element represented by this node.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the first character of the element. This is
+              different than the offset in the Element, which if the
+              offset of the name of the element. It can be used, for
+              example, to map locations in the file back to an outline.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the element.
+            </p>
+          </dd><dt class="field"><b><i>children ( <span style="color:#999999">optional</span> List&lt;<a href="#type_Outline">Outline</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The children of the node. The field will be omitted if the
+              node has no children.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_Override">Override: object</a></dt><dd>
+        <p>
+          A description of a member that overrides an inherited member.
+        </p>
+        
+      <dl><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the name of the overriding member.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the name of the overriding member.
+            </p>
+          </dd><dt class="field"><b><i>superclassMember ( <span style="color:#999999">optional</span> <a href="#type_OverriddenMember">OverriddenMember</a> )</i></b></dt><dd>
+            
+            <p>
+              The member inherited from a superclass that is overridden
+              by the overriding member. The field is omitted if there is
+              no superclass member, in which case there must be at least
+              one interface member.
+            </p>
+          </dd><dt class="field"><b><i>interfaceMembers ( <span style="color:#999999">optional</span> List&lt;<a href="#type_OverriddenMember">OverriddenMember</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The members inherited from interfaces that are overridden
+              by the overriding member. The field is omitted if there
+              are no interface members, in which case there must be a
+              superclass member.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_OverriddenMember">OverriddenMember: object</a></dt><dd>
+        <p>
+          A description of a member that is being overridden.
+        </p>
+        
+      <dl><dt class="field"><b><i>element ( <a href="#type_Element">Element</a> )</i></b></dt><dd>
+            
+            <p>
+              The element that is being overridden.
+            </p>
+          </dd><dt class="field"><b><i>className ( String )</i></b></dt><dd>
+            
+            <p>
+              The name of the class in which the member is defined.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_Parameter">Parameter: object</a></dt><dd>
+        <p>
+          A description of a parameter.
+        </p>
+        
+      <dl><dt class="field"><b><i>type ( String )</i></b></dt><dd>
+            
+            <p>
+              The type that should be given to the parameter.
+            </p>
+          </dd><dt class="field"><b><i>name ( String )</i></b></dt><dd>
+            
+            <p>
+              The name that should be given to the parameter.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_Position">Position: object</a></dt><dd>
+        <p>
+          A position within a file.
+        </p>
+        
+      <dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the position.
+            </p>
+          </dd><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the position.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_RefactoringKind">RefactoringKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of refactorings that can be
+          created.
+        </p>
+        
+      <dl><dt class="value">CONVERT_GETTER_TO_METHOD</dt><dt class="value">CONVERT_METHOD_TO_GETTER</dt><dt class="value">EXTRACT_LOCAL_VARIABLE</dt><dt class="value">EXTRACT_METHOD</dt><dt class="value">INLINE_LOCAL_VARIABLE</dt><dt class="value">INLINE_METHOD</dt><dt class="value">RENAME</dt></dl></dd><dt class="typeDefinition"><a name="type_RefactoringProblem">RefactoringProblem: object</a></dt><dd>
+        <p>
+          A description of a problem related to a refactoring.
+        </p>
+        
+      <dl><dt class="field"><b><i>severity ( <a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a> )</i></b></dt><dd>
+            
+            <p>
+              The severity of the problem being represented.
+            </p>
+          </dd><dt class="field"><b><i>message ( String )</i></b></dt><dd>
+            
+            <p>
+              A human-readable description of the problem being
+              represented.
+            </p>
+          </dd><dt class="field"><b><i>location ( <a href="#type_Location">Location</a> )</i></b></dt><dd>
+            
+            <p>
+              The location of the problem being represented.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_RefactoringProblemSeverity">RefactoringProblemSeverity: String</a></dt><dd>
+        <p>
+          An enumeration of the severities of problems that can be
+          returned by the refactoring requests.
+        </p>
+        
+      <dl><dt class="value">INFO</dt><dt class="value">WARNING</dt><dt class="value">ERROR</dt><dt class="value">FATAL</dt></dl></dd><dt class="typeDefinition"><a name="type_SearchId">SearchId: String</a></dt><dd>
+        
+        <p>
+          An identifier used to associate search results with a search
+          request.
+        </p>
+      </dd><dt class="typeDefinition"><a name="type_SearchResult">SearchResult: object</a></dt><dd>
+        <p>
+          A single result from a search request.
+        </p>
+        
+      <dl><dt class="field"><b><i>location ( <a href="#type_Location">Location</a> )</i></b></dt><dd>
+            
+            <p>
+              The location of the code that matched the search criteria.
+            </p>
+          </dd><dt class="field"><b><i>kind ( <a href="#type_SearchResultKind">SearchResultKind</a> )</i></b></dt><dd>
+            
+            <p>
+              The kind of element that was found or the kind of
+              reference that was found.
+            </p>
+          </dd><dt class="field"><b><i>isPotential ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if the result is a potential match but cannot be
+              confirmed to be a match. For example, if all references to
+              a method m defined in some class were requested, and a
+              reference to a method m from an unknown class were found,
+              it would be marked as being a potential match.
+            </p>
+          </dd><dt class="field"><b><i>path ( List&lt;<a href="#type_Element">Element</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The elements that contain the result, starting with the
+              most immediately enclosing ancestor and ending with the
+              library.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_SearchResultKind">SearchResultKind: String</a></dt><dd>
+        <p>
+          An enumeration of the kinds of search results returned by the
+          search domain.
+        </p>
+        
+      <dl><dt class="value">DECLARATION</dt><dd>
+            
+            <p>
+              The declaration of an element.
+            </p>
+          </dd><dt class="value">INVOCATION</dt><dd>
+            
+            <p>
+              The invocation of a function or method.
+            </p>
+          </dd><dt class="value">READ</dt><dd>
+            
+            <p>
+              A reference to a field, parameter or variable where it is being read.
+            </p>
+          </dd><dt class="value">READ_WRITE</dt><dd>
+            
+            <p>
+              A reference to a field, parameter or variable where it is being read and written.
+            </p>
+          </dd><dt class="value">REFERENCE</dt><dd>
+            
+            <p>
+              A reference to an element.
+            </p>
+          </dd><dt class="value">WRITE</dt><dd>
+            
+            <p>
+              A reference to a field, parameter or variable where it is being written.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_ServerService">ServerService: String</a></dt><dd>
+        <p>
+          An enumeration of the services provided by the server domain.
+        </p>
+        
+      <dl><dt class="value">STATUS</dt></dl></dd><dt class="typeDefinition"><a name="type_SourceChange">SourceChange: object</a></dt><dd>
+        <p>
+          A description of a set of edits that implement a single
+          conceptual change.
+        </p>
+        
+      <dl><dt class="field"><b><i>message ( String )</i></b></dt><dd>
+            
+            <p>
+              A human-readable description of the change to be applied.
+            </p>
+          </dd><dt class="field"><b><i>edits ( List&lt;<a href="#type_SourceFileEdit">SourceFileEdit</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the edits used to effect the change, grouped by
+              file.
+            </p>
+          </dd><dt class="field"><b><i>linkedEditGroups ( List&lt;<a href="#type_LinkedEditGroup">LinkedEditGroup</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the linked editing groups used to customize
+              the changes that were made.
+            </p>
+          </dd><dt class="field"><b><i>selection ( <span style="color:#999999">optional</span> <a href="#type_Position">Position</a> )</i></b></dt><dd>
+            
+            <p>
+              The position that should be selected after the edits
+              have been applied.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_SourceEdit">SourceEdit: object</a></dt><dd>
+        <p>
+          A description of a single change to a single file.
+        </p>
+        
+      <dl><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset of the region to be modified.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the region to be modified.
+            </p>
+          </dd><dt class="field"><b><i>replacement ( String )</i></b></dt><dd>
+            
+            <p>
+              The code that is to replace the specified region in the
+              original code.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_SourceFileEdit">SourceFileEdit: object</a></dt><dd>
+        <p>
+          A description of a set of changes to a single file.
+        </p>
+        
+      <dl><dt class="field"><b><i>file ( <a href="#type_FilePath">FilePath</a> )</i></b></dt><dd>
+            
+            <p>
+              The file containing the code to be modified.
+            </p>
+          </dd><dt class="field"><b><i>edits ( List&lt;<a href="#type_SourceEdit">SourceEdit</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              A list of the edits used to effect the change.
+            </p>
+          </dd></dl></dd><dt class="typeDefinition"><a name="type_TypeHierarchyItem">TypeHierarchyItem: object</a></dt><dd>
+        <p>
+          A representation of a class in a type hierarchy.
+        </p>
+        
+      <dl><dt class="field"><b><i>classElement ( <a href="#type_Element">Element</a> )</i></b></dt><dd>
+            
+            <p>
+              The class element represented by this item.
+            </p>
+          </dd><dt class="field"><b><i>displayName ( <span style="color:#999999">optional</span> String )</i></b></dt><dd>
+            
+            <p>
+              The name to be displayed for the class. This field will be
+              omitted if the display name is the same as the name of the
+              element. The display name is different if there is
+              additional type information to be displayed, such as type
+              arguments.
+            </p>
+          </dd><dt class="field"><b><i>memberElement ( <span style="color:#999999">optional</span> <a href="#type_Element">Element</a> )</i></b></dt><dd>
+            
+            <p>
+              The member in the class corresponding to the member on
+              which the hierarchy was requested. This field will be
+              omitted if the hierarchy was not requested for a member or
+              if the class does not have a corresponding member.
+            </p>
+          </dd><dt class="field"><b><i>superclass ( <span style="color:#999999">optional</span> int )</i></b></dt><dd>
+            
+            <p>
+              The index of the item representing the superclass of
+              this class. This field will be omitted if this item
+              represents the class Object.
+            </p>
+          </dd><dt class="field"><b><i>interfaces ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The indexes of the items representing the interfaces
+              implemented by this class. The list will be empty if
+              there are no implemented interfaces.
+            </p>
+          </dd><dt class="field"><b><i>mixins ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The indexes of the items representing the mixins
+              referenced by this class. The list will be empty if
+              there are no classes mixed in to this class.
+            </p>
+          </dd><dt class="field"><b><i>subclasses ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The indexes of the items representing the subtypes of
+              this class. The list will be empty if there are no
+              subtypes or if this item represents a supertype of the
+              pivot type.
+            </p>
+          </dd></dl></dd></dl>
+    
+      <h2><a name="refactorings">Refactorings</a></h2>
+      <p>
+        This section contains additional information for each kind of
+        refactoring. In addition to a brief description of the
+        refactoring, there is a specification of the feedback that is
+        provided when a refactoring is created using the
+        edit.createRefactoring request (designed to improve the UX)
+        and the options that must be set using the
+        edit.setRefactoringOptions request before the refactoring can
+        be applied.
+      </p>
+      
+      
+      
+      
+      
+      
+      
+    <dl><dt class="refactoring">CONVERT_GETTER_TO_METHOD</dt><dd>
+        <p>
+          Convert a getter into a method by removing the keyword get
+          and adding an empty parameter list.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single getter.
+        </p>
+      <h4>Feedback</h4><p>none</p><h4>Options</h4><p>none</p></dd><dt class="refactoring">CONVERT_METHOD_TO_GETTER</dt><dd>
+        <p>
+          Convert a method into a getter by adding the keyword get and
+          removing the parameter list.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single method or if the method has
+          a non-empty parameter list.
+        </p>
+      <h4>Feedback</h4><p>none</p><h4>Options</h4><p>none</p></dd><dt class="refactoring">EXTRACT_LOCAL_VARIABLE</dt><dd>
+        <p>
+          Create a local variable initialized by a specified
+          expression.
+        </p>
+        <p>
+          It is an error if the range contains anything other than a
+          complete expression (no partial expressions are allowed).
+        </p>
+        
+        
+      <h4>Feedback</h4><dl><dt class="field"><b><i>names ( List&lt;String&gt; )</i></b></dt><dd>
+            
+            <p>
+              The proposed names for the local variable.
+            </p>
+          </dd><dt class="field"><b><i>offsets ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The offsets of the expressions that would be replaced by
+              a reference to the variable.
+            </p>
+          </dd><dt class="field"><b><i>lengths ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The lengths of the expressions that would be replaced by
+              a reference to the variable. The lengths correspond to
+              the offsets. In other words, for a given expression, if
+              the offset of that expression is offsets[i], then the
+              length of that expression is lengths[i].
+            </p>
+          </dd></dl><h4>Options</h4><dl><dt class="field"><b><i>name ( String )</i></b></dt><dd>
+            
+            <p>
+              The name that the local variable should be given.
+            </p>
+          </dd><dt class="field"><b><i>extractAll ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if all occurrences of the expression within the
+              scope in which the variable will be defined should be
+              replaced by a reference to the local variable. The
+              expression used to initiate the refactoring will always
+              be replaced.
+            </p>
+          </dd></dl></dd><dt class="refactoring">EXTRACT_METHOD</dt><dd>
+        <p>
+          Create a method whose body is the specified expression or
+          list of statements, possibly augmented with a return
+          statement.
+        </p>
+        <p>
+          It is an error if the range contains anything other than a
+          complete expression (no partial expressions are allowed) or
+          a complete sequence of statements.
+        </p>
+        
+        
+      <h4>Feedback</h4><dl><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset to the beginning of the expression or
+              statements that will be extracted.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the expression or statements that will be
+              extracted.
+            </p>
+          </dd><dt class="field"><b><i>returnType ( String )</i></b></dt><dd>
+            
+            <p>
+              The proposed return type for the method.
+            </p>
+          </dd><dt class="field"><b><i>names ( List&lt;String&gt; )</i></b></dt><dd>
+            
+            <p>
+              The proposed names for the method.
+            </p>
+          </dd><dt class="field"><b><i>canCreateGetter ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if a getter could be created rather than a method.
+            </p>
+          </dd><dt class="field"><b><i>parameters ( List&lt;<a href="#type_Parameter">Parameter</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The proposed parameters for the method.
+            </p>
+          </dd><dt class="field"><b><i>occurrences ( int )</i></b></dt><dd>
+            
+            <p>
+              The number of times the expression or statements occurs.
+            </p>
+          </dd><dt class="field"><b><i>offsets ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The offsets of the expressions or statements that would
+              be replaced by an invocation of the method.
+            </p>
+          </dd><dt class="field"><b><i>lengths ( List&lt;int&gt; )</i></b></dt><dd>
+            
+            <p>
+              The lengths of the expressions or statements that would
+              be replaced by an invocation of the method. The lengths
+              correspond to the offsets. In other words, for a given
+              expression (or block of statements), if the offset of
+              that expression is offsets[i], then the length of that
+              expression is lengths[i].
+            </p>
+          </dd></dl><h4>Options</h4><dl><dt class="field"><b><i>returnType ( String )</i></b></dt><dd>
+            
+            <p>
+              The return type that should be defined for the method.
+            </p>
+          </dd><dt class="field"><b><i>createGetter ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if a getter should be created rather than a
+              method. It is an error if this field is true and the
+              list of parameters is non-empty.
+            </p>
+          </dd><dt class="field"><b><i>name ( String )</i></b></dt><dd>
+            
+            <p>
+              The name that the method should be given.
+            </p>
+          </dd><dt class="field"><b><i>parameters ( List&lt;<a href="#type_Parameter">Parameter</a>&gt; )</i></b></dt><dd>
+            
+            <p>
+              The parameters that should be defined for the method.
+            </p>
+          </dd><dt class="field"><b><i>extractAll ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if all occurrences of the expression or statements
+              should be replaced by an invocation of the method. The
+              expression or statements used to initiate the
+              refactoring will always be replaced.
+            </p>
+          </dd></dl></dd><dt class="refactoring">INLINE_LOCAL_VARIABLE</dt><dd>
+        <p>
+          Inline the initializer expression of a local variable in
+          place of any references to that variable.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single local variable.
+        </p>
+      <h4>Feedback</h4><p>none</p><h4>Options</h4><p>none</p></dd><dt class="refactoring">INLINE_METHOD</dt><dd>
+        <p>
+          Inline a method in place of one or all references to that
+          method.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single method.
+        </p>
+        
+      <h4>Feedback</h4><p>none</p><h4>Options</h4><dl><dt class="field"><b><i>deleteSource ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if the method being inlined should be removed. It
+              is an error if this field is true and inlineAll is
+              false.
+            </p>
+          </dd><dt class="field"><b><i>inlineAll ( bool )</i></b></dt><dd>
+            
+            <p>
+              True if all invocations of the method should be inlined,
+              or false if only the invocation site used to create this
+              refactoring should be inlined.
+            </p>
+          </dd></dl></dd><dt class="refactoring">RENAME</dt><dd>
+        <p>
+          Rename a given element and all of the references to that
+          element.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single function (including methods,
+          getters and setters), variable (including fields, parameters
+          and local variables), class or function type.
+        </p>
+        
+        
+      <h4>Feedback</h4><dl><dt class="field"><b><i>offset ( int )</i></b></dt><dd>
+            
+            <p>
+              The offset to the beginning of the name selected to be
+              renamed.
+            </p>
+          </dd><dt class="field"><b><i>length ( int )</i></b></dt><dd>
+            
+            <p>
+              The length of the name selected to be renamed.
+            </p>
+          </dd></dl><h4>Options</h4><dl><dt class="field"><b><i>newName ( String )</i></b></dt><dd>
+            
+            <p>
+              The name that the element should have after the
+              refactoring.
+            </p>
+          </dd></dl></dd></dl>
+    <h2>Errors</h2>
+    <p>
+      This section contains a list of all of the errors that are
+      produced by the server and the data that is returned with each.
+    </p>
+    <p>
+      TBD
+    </p>
+  
+
+</body></html>
\ No newline at end of file
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 279884a..e1c992a 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -21,7 +21,6 @@
 import 'package:analyzer/source/package_map_resolver.dart';
 import 'package:analyzer/src/generated/ast.dart';
 import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/error.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source_io.dart';
@@ -30,6 +29,7 @@
 import 'package:analysis_services/index/index.dart';
 import 'package:analysis_services/search/search_engine.dart';
 import 'package:analyzer/src/generated/element.dart';
+import 'package:path/path.dart';
 
 
 class AnalysisServerContextDirectoryManager extends ContextDirectoryManager {
@@ -105,6 +105,11 @@
   final ServerCommunicationChannel channel;
 
   /**
+   * The [ResourceProvider] using which paths are converted into [Resource]s.
+   */
+  final ResourceProvider resourceProvider;
+
+  /**
    * The [Index] for this server.
    */
   final Index index;
@@ -193,7 +198,7 @@
    * exceptions to show up in unit tests, but it should be set to false when
    * running a full analysis server.
    */
-  AnalysisServer(this.channel, ResourceProvider resourceProvider,
+  AnalysisServer(this.channel, this.resourceProvider,
       PackageMapProvider packageMapProvider, this.index, this.defaultSdk,
       {this.rethrowExceptions: true}) {
     searchEngine = createSearchEngine(index);
@@ -371,6 +376,15 @@
   }
 
   /**
+   * Returns `true` if errors should be reported for [file] with the given
+   * absolute path.
+   */
+  bool shouldSendErrorsNotificationFor(String file) {
+    // TODO(scheglov) add support for the "--no-error-notification" flag.
+    return contextDirectoryManager.isInAnalysisRoot(file);
+  }
+
+  /**
    * Returns `true` if the given [AnalysisContext] is a priority one.
    */
   bool isPriorityContext(AnalysisContext context) {
@@ -493,14 +507,6 @@
         if (context == null) {
           continue;
         }
-        // errors
-        if (service == AnalysisService.ERRORS) {
-          LineInfo lineInfo = context.getLineInfo(source);
-          if (lineInfo != null) {
-            List<AnalysisError> errors = context.getErrors(source).errors;
-            sendAnalysisNotificationErrors(this, file, lineInfo, errors);
-          }
-        }
         // Dart unit notifications.
         if (AnalysisEngine.isDartFileName(file)) {
           CompilationUnit dartUnit = getResolvedCompilationUnitToResendNotification(file);
@@ -536,11 +542,22 @@
    * Return `null` if there is no such context.
    */
   AnalysisContext getAnalysisContext(String path) {
+    // try to find a containing context
     for (Folder folder in folderMap.keys) {
       if (path.startsWith(folder.path)) {
         return folderMap[folder];
       }
     }
+    // check if there is a context that analyzed this source
+    {
+      Source source = getSource(path);
+      for (AnalysisContext context in folderMap.values) {
+        SourceKind kind = context.getKindOf(source);
+        if (kind != null) {
+          return context;
+        }
+      }
+    }
     return null;
   }
 
@@ -548,8 +565,17 @@
    * Return the [Source] of the Dart file with the given [path].
    */
   Source getSource(String path) {
-    File file = contextDirectoryManager.resourceProvider.getResource(path);
-    return file.createSource(UriKind.FILE_URI);
+    // try SDK
+    {
+      Uri uri = resourceProvider.pathContext.toUri(path);
+      Source sdkSource = defaultSdk.fromFileUri(uri);
+      if (sdkSource != null) {
+        return sdkSource;
+      }
+    }
+    // file-based source
+    File file = resourceProvider.getResource(path);
+    return file.createSource();
   }
 
   /**
@@ -772,7 +798,6 @@
  * An enumeration of the services provided by the analysis domain.
  */
 class AnalysisService extends Enum2<AnalysisService> {
-  static const ERRORS = const AnalysisService('ERRORS', 0);
   static const HIGHLIGHTS = const AnalysisService('HIGHLIGHTS', 1);
   static const NAVIGATION = const AnalysisService('NAVIGATION', 2);
   static const OCCURRENCES = const AnalysisService('OCCURRENCES', 3);
@@ -780,7 +805,7 @@
   static const OVERRIDES = const AnalysisService('OVERRIDES', 5);
 
   static const List<AnalysisService> VALUES =
-      const [ERRORS, HIGHLIGHTS, NAVIGATION, OCCURRENCES, OUTLINE, OVERRIDES];
+      const [HIGHLIGHTS, NAVIGATION, OCCURRENCES, OUTLINE, OVERRIDES];
 
   const AnalysisService(String name, int ordinal) : super(name, ordinal);
 }
diff --git a/pkg/analysis_server/lib/src/computer/computer_overrides.dart b/pkg/analysis_server/lib/src/computer/computer_overrides.dart
index 429c778..ad92386 100644
--- a/pkg/analysis_server/lib/src/computer/computer_overrides.dart
+++ b/pkg/analysis_server/lib/src/computer/computer_overrides.dart
@@ -67,14 +67,13 @@
     }
     // is there any override?
     if (superEngineElement != null || interfaceEngineElements.isNotEmpty) {
-      Element superElement = superEngineElement != null ?
-          new Element.fromEngine(superEngineElement) : null;
-      List<Element> interfaceElements = interfaceEngineElements.map(
-          (engineElement) {
-        return new Element.fromEngine(engineElement);
-      }).toList();
-      _overrides.add(new Override(offset, length, superElement,
-          interfaceElements));
+      OverriddenMember superMember = superEngineElement != null ?
+          OverriddenMember.fromEngine(superEngineElement) :
+          null;
+      List<OverriddenMember> interfaceMembers =
+          interfaceEngineElements.map(OverriddenMember.fromEngine).toList();
+      _overrides.add(
+          new Override(offset, length, superMember, interfaceMembers));
     }
   }
 
@@ -105,56 +104,82 @@
 }
 
 
+class OverriddenMember implements HasToJson {
+  final Element element;
+  final String className;
+
+  OverriddenMember(this.element, this.className);
+
+  Map<String, Object> toJson() {
+    return {
+      ELEMENT: element.toJson(),
+      CLASS_NAME: className
+    };
+  }
+
+  @override
+  String toString() => toJson().toString();
+
+  static OverriddenMember fromEngine(engine.Element member) {
+    Element element = new Element.fromEngine(member);
+    String className = member.enclosingElement.displayName;
+    return new OverriddenMember(element, className);
+  }
+
+  static OverriddenMember fromJson(Map<String, Object> json) {
+    Map<String, Object> elementJson = json[ELEMENT];
+    Element element = new Element.fromJson(elementJson);
+    String className = json[CLASS_NAME];
+    return new OverriddenMember(element, className);
+  }
+}
+
+
 class Override implements HasToJson {
   final int offset;
   final int length;
-  final Element superclassElement;
-  final List<Element> interfaceElements;
+  final OverriddenMember superclassMember;
+  final List<OverriddenMember> interfaceMembers;
 
-  Override(this.offset, this.length, this.superclassElement,
-      this.interfaceElements);
-
-  factory Override.fromJson(Map<String, Object> map) {
-    int offset = map[OFFSET];
-    int length = map[LENGTH];
-    // super
-    Element superclassElement = null;
-    {
-      Map<String, Object> superJson = map[SUPER_CLASS_ELEMENT];
-      if (superJson != null) {
-        superclassElement = new Element.fromJson(superJson);
-      }
-    }
-    // interfaces
-    List<Element> interfaceElements = null;
-    {
-      List<Map<String, Object>> jsonList = map[INTERFACE_ELEMENTS];
-      if (jsonList != null) {
-        interfaceElements = <Element>[];
-        for (Map<String, Object> json in jsonList) {
-          interfaceElements.add(new Element.fromJson(json));
-        }
-      }
-    }
-    // done
-    return new Override(offset, length, superclassElement, interfaceElements);
-  }
+  Override(this.offset, this.length, this.superclassMember,
+      this.interfaceMembers);
 
   Map<String, Object> toJson() {
     Map<String, Object> json = <String, Object>{};
     json[OFFSET] = offset;
     json[LENGTH] = length;
-    if (superclassElement != null) {
-      json[SUPER_CLASS_ELEMENT] = superclassElement.toJson();
+    if (superclassMember != null) {
+      json[SUPER_CLASS_MEMBER] = superclassMember.toJson();
     }
-    if (interfaceElements != null && interfaceElements.isNotEmpty) {
-      json[INTERFACE_ELEMENTS] = interfaceElements.map((element) {
-        return element.toJson();
-      }).toList();
+    if (interfaceMembers != null && interfaceMembers.isNotEmpty) {
+      json[INTERFACE_MEMBERS] = objectToJson(interfaceMembers);
     }
     return json;
   }
 
   @override
   String toString() => toJson().toString();
+
+  static Override fromJson(Map<String, Object> map) {
+    int offset = map[OFFSET];
+    int length = map[LENGTH];
+    // super
+    OverriddenMember superclassMember = null;
+    {
+      Map<String, Object> superJson = map[SUPER_CLASS_MEMBER];
+      if (superJson != null) {
+        superclassMember = OverriddenMember.fromJson(superJson);
+      }
+    }
+    // interfaces
+    List<OverriddenMember> interfaceElements = null;
+    {
+      List<Map<String, Object>> jsonList = map[INTERFACE_MEMBERS];
+      if (jsonList != null) {
+        interfaceElements = jsonList.map(OverriddenMember.fromJson).toList();
+      }
+    }
+    // done
+    return new Override(offset, length, superclassMember, interfaceElements);
+  }
 }
diff --git a/pkg/analysis_server/lib/src/constants.dart b/pkg/analysis_server/lib/src/constants.dart
index aab2d88..36df52b 100644
--- a/pkg/analysis_server/lib/src/constants.dart
+++ b/pkg/analysis_server/lib/src/constants.dart
@@ -28,7 +28,6 @@
 const String ANALYSIS_SET_SUBSCRIPTIONS = 'analysis.setSubscriptions';
 const String ANALYSIS_UPDATE_CONTENT = 'analysis.updateContent';
 const String ANALYSIS_UPDATE_OPTIONS = 'analysis.updateOptions';
-const String ANALYSIS_UPDATE_SDKS = 'analysis.updateSdks';
 
 //
 // Analysis notifications
diff --git a/pkg/analysis_server/lib/src/context_directory_manager.dart b/pkg/analysis_server/lib/src/context_directory_manager.dart
index fa1970c..a8d024e 100644
--- a/pkg/analysis_server/lib/src/context_directory_manager.dart
+++ b/pkg/analysis_server/lib/src/context_directory_manager.dart
@@ -152,7 +152,7 @@
           File file = resource;
           if (_shouldFileBeAnalyzed(file)) {
             ChangeSet changeSet = new ChangeSet();
-            Source source = file.createSource(UriKind.FILE_URI);
+            Source source = file.createSource();
             changeSet.addedSource(source);
             applyChangesToContext(folder, changeSet);
             info.sources[event.path]= source;
@@ -212,7 +212,7 @@
     for (Resource child in children) {
       if (child is File) {
         if (_shouldFileBeAnalyzed(child)) {
-          Source source = child.createSource(UriKind.FILE_URI);
+          Source source = child.createSource();
           changeSet.addedSource(source);
           info.sources[child.path] = source;
         }
@@ -241,6 +241,20 @@
   }
 
   /**
+   * Returns `true` if the given absolute [path] is in one of the current
+   * root folders and is not excluded.
+   */
+  bool isInAnalysisRoot(String path) {
+    // TODO(scheglov) check for excluded paths
+    for (Folder root in _currentDirectoryInfo.keys) {
+      if (root.contains(path)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
    * Called when a new context needs to be created.
    */
   void addContext(Folder folder, Map<String, List<Folder>> packageMap);
diff --git a/pkg/analysis_server/lib/src/domain_analysis.dart b/pkg/analysis_server/lib/src/domain_analysis.dart
index c67ad63..ed21d0a 100644
--- a/pkg/analysis_server/lib/src/domain_analysis.dart
+++ b/pkg/analysis_server/lib/src/domain_analysis.dart
@@ -108,8 +108,6 @@
         return updateContent(request);
       } else if (requestName == ANALYSIS_UPDATE_OPTIONS) {
         return updateOptions(request);
-      } else if (requestName == ANALYSIS_UPDATE_SDKS) {
-        return updateSdks(request);
       }
     } on RequestFailure catch (exception) {
       return exception.response;
@@ -242,23 +240,6 @@
     server.updateOptions(updaters);
     return new Response(request.id);
   }
-
-  /**
-   * Implement the 'analysis.updateSdks' request.
-   */
-  Response updateSdks(Request request) {
-    // added
-    RequestDatum addedDatum = request.getRequiredParameter(ADDED);
-    List<String> added = addedDatum.asStringList();
-    // removed
-    RequestDatum removedDatum = request.getRequiredParameter(REMOVED);
-    List<String> removed = removedDatum.asStringList();
-    // default
-    RequestDatum defaultDatum = request.getRequiredParameter(DEFAULT);
-    String defaultSdk = defaultDatum.asString();
-    // TODO(scheglov) implement
-    return null;
-  }
 }
 
 
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 7d000c1..e6c9f41 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -7,10 +7,9 @@
 import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/constants.dart';
 import 'package:analysis_server/src/protocol.dart';
-import 'package:analysis_services/completion/completion_suggestion.dart';
 import 'package:analysis_services/completion/completion_computer.dart';
+import 'package:analysis_services/completion/completion_suggestion.dart';
 import 'package:analysis_services/constants.dart';
-import 'package:analysis_services/search/search_engine.dart';
 
 /**
  * Instances of the class [CompletionDomainHandler] implement a [RequestHandler]
@@ -23,11 +22,6 @@
   final AnalysisServer server;
 
   /**
-   * The [SearchEngine] for this server.
-   */
-  SearchEngine searchEngine;
-
-  /**
    * The next completion response id.
    */
   int _nextCompletionId = 0;
@@ -59,16 +53,17 @@
     int offset = request.getRequiredParameter(OFFSET).asInt();
     // schedule completion analysis
     String completionId = (_nextCompletionId++).toString();
-    CompletionComputer.create(server.searchEngine).then((computers) {
-      int count = computers.length;
-      List<CompletionSuggestion> results = new List<CompletionSuggestion>();
-      computers.forEach((CompletionComputer c) {
-        c.compute().then((List<CompletionSuggestion> partialResults) {
-          // send aggregate results as we compute them
-          results.addAll(partialResults);
-          sendCompletionNotification(completionId, --count == 0, results);
-        });
-      });
+    CompletionManager.create(
+        server.getAnalysisContext(file),
+        server.getSource(file),
+        offset,
+        server.searchEngine).results().listen((CompletionResult result) {
+      sendCompletionNotification(
+          completionId,
+          result.replacementOffset,
+          result.replacementLength,
+          result.suggestions,
+          result.last);
     });
     // initial response without results
     return new Response(request.id)..setResult(ID, completionId);
@@ -77,12 +72,14 @@
   /**
    * Send completion notification results.
    */
-  void sendCompletionNotification(String completionId, bool isLast,
-      Iterable<CompletionSuggestion> results) {
+  void sendCompletionNotification(String completionId, int replacementOffset,
+      int replacementLength, Iterable<CompletionSuggestion> results, bool isLast) {
     Notification notification = new Notification(COMPLETION_RESULTS);
     notification.setParameter(ID, completionId);
-    notification.setParameter(LAST, isLast);
+    notification.setParameter(REPLACEMENT_OFFSET, replacementOffset);
+    notification.setParameter(REPLACEMENT_LENGTH, replacementLength);
     notification.setParameter(RESULTS, results);
+    notification.setParameter(LAST, isLast);
     server.sendNotification(notification);
   }
 }
diff --git a/pkg/analysis_server/lib/src/edit/fix.dart b/pkg/analysis_server/lib/src/edit/fix.dart
index 5e765d4..f34c110 100644
--- a/pkg/analysis_server/lib/src/edit/fix.dart
+++ b/pkg/analysis_server/lib/src/edit/fix.dart
@@ -32,13 +32,4 @@
 
   @override
   String toString() => 'ErrorFixes(error=$error, fixes=$fixes)';
-
-  static ErrorFixes fromJson(Map<String, Object> json) {
-    AnalysisError error = AnalysisError.fromJson(json[ERROR]);
-    ErrorFixes errorFixes = new ErrorFixes(error);
-    errorFixes.fixes.addAll((json[FIXES] as List).map((json) {
-      return Change.fromJson(json);
-    }));
-    return errorFixes;
-  }
 }
diff --git a/pkg/analysis_server/lib/src/operation/operation_analysis.dart b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
index 914d5d5..d8b10ec 100644
--- a/pkg/analysis_server/lib/src/operation/operation_analysis.dart
+++ b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
@@ -158,8 +158,7 @@
           sendAnalysisNotificationOverrides(server, file, dartUnit);
         }
       }
-      // TODO(scheglov) use default subscriptions
-      if (!source.isInSystemLibrary) {
+      if (server.shouldSendErrorsNotificationFor(file)) {
         sendAnalysisNotificationErrors(
             server,
             file,
diff --git a/pkg/analysis_server/lib/src/protocol.dart b/pkg/analysis_server/lib/src/protocol.dart
index 03d99cf..71e6191 100644
--- a/pkg/analysis_server/lib/src/protocol.dart
+++ b/pkg/analysis_server/lib/src/protocol.dart
@@ -513,7 +513,7 @@
    * by a [request] referencing a context that does not exist.
    */
   Response.contextDoesNotExist(Request request)
-    : this(request.id, new RequestError(-1, 'Context does not exist'));
+    : this(request.id, new RequestError('NONEXISTENT_CONTEXT', 'Context does not exist'));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
@@ -523,7 +523,7 @@
    * [expectation] is a description of the type of data that was expected.
    */
   Response.invalidParameter(Request request, String path, String expectation)
-      : this(request.id, new RequestError(-2,
+      : this(request.id, new RequestError('INVALID_PARAMETER',
           "Expected parameter $path to $expectation"));
 
   /**
@@ -531,14 +531,14 @@
    * by a malformed request.
    */
   Response.invalidRequestFormat()
-    : this('', new RequestError(-4, 'Invalid request'));
+    : this('', new RequestError('INVALID_REQUEST', 'Invalid request'));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
    * by a [request] that does not have a required parameter.
    */
   Response.missingRequiredParameter(Request request, String parameterName)
-    : this(request.id, new RequestError(-5, 'Missing required parameter: $parameterName'));
+    : this(request.id, new RequestError('MISSING_PARAMETER', 'Missing required parameter: $parameterName'));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
@@ -546,20 +546,20 @@
    * unknown analysis option was provided.
    */
   Response.unknownAnalysisOption(Request request, String optionName)
-    : this(request.id, new RequestError(-6, 'Unknown analysis option: "$optionName"'));
+    : this(request.id, new RequestError('UNKNOWN_ANALYSIS_OPTION', 'Unknown analysis option: "$optionName"'));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
    * by a [request] that cannot be handled by any known handlers.
    */
   Response.unknownRequest(Request request)
-    : this(request.id, new RequestError(-7, 'Unknown request'));
+    : this(request.id, new RequestError('UNKNOWN_REQUEST', 'Unknown request'));
 
   Response.contextAlreadyExists(Request request)
-    : this(request.id, new RequestError(-8, 'Context already exists'));
+    : this(request.id, new RequestError('CONTENT_ALREADY_EXISTS', 'Context already exists'));
 
   Response.unsupportedFeature(String requestId, String message)
-    : this(requestId, new RequestError(-9, message));
+    : this(requestId, new RequestError('UNSUPPORTED_FEATURE', message));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
@@ -567,7 +567,7 @@
    * analysis service name.
    */
   Response.unknownAnalysisService(Request request, String name)
-    : this(request.id, new RequestError(-10, 'Unknown analysis service: "$name"'));
+    : this(request.id, new RequestError('UNKNOWN_ANALYSIS_SERVICE', 'Unknown analysis service: "$name"'));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
@@ -575,7 +575,7 @@
    * that are not being analyzed.
    */
   Response.unanalyzedPriorityFiles(Request request, String fileNames)
-    : this(request.id, new RequestError(-11, "Unanalyzed files cannot be a priority: '$fileNames'"));
+    : this(request.id, new RequestError('UNANALYZED_PRIORITY_FILES', "Unanalyzed files cannot be a priority: '$fileNames'"));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
@@ -583,7 +583,7 @@
    * option.
    */
   Response.unknownOptionName(Request request, String optionName)
-    : this(request.id, new RequestError(-12, 'Unknown analysis option: "$optionName"'));
+    : this(request.id, new RequestError('UNKNOWN_OPTION_NAME', 'Unknown analysis option: "$optionName"'));
 
   /**
    * Initialize a newly created instance to represent an error condition caused
@@ -592,7 +592,7 @@
   Response.getErrorsError(Request request, String message)
     : this(
         request.id,
-        new RequestError(-13, 'Error during `analysis.getErrors`: $message.'));
+        new RequestError('GET_ERRORS_ERROR', 'Error during `analysis.getErrors`: $message.'));
 
   /**
    * Initialize a newly created instance based upon the given JSON data
@@ -679,55 +679,50 @@
    * An error code indicating a parse error. Invalid JSON was received by the
    * server. An error occurred on the server while parsing the JSON text.
    */
-  static const int CODE_PARSE_ERROR = -32700;
+  static const String CODE_PARSE_ERROR = 'PARSE_ERROR';
 
   /**
    * An error code indicating that the analysis server has already been
    * started (and hence won't accept new connections).
    */
-  static const int CODE_SERVER_ALREADY_STARTED = -32701;
+  static const String CODE_SERVER_ALREADY_STARTED = 'SERVER_ALREADY_STARTED';
 
   /**
    * An error code indicating an invalid request. The JSON sent is not a valid
    * [Request] object.
    */
-  static const int CODE_INVALID_REQUEST = -32600;
+  static const String CODE_INVALID_REQUEST = 'INVALID_REQUEST';
 
   /**
    * An error code indicating a method not found. The method does not exist or
    * is not currently available.
    */
-  static const int CODE_METHOD_NOT_FOUND = -32601;
+  static const String CODE_METHOD_NOT_FOUND = 'METHOD_NOT_FOUND';
 
   /**
    * An error code indicating one or more invalid parameters.
    */
-  static const int CODE_INVALID_PARAMS = -32602;
+  static const String CODE_INVALID_PARAMS = 'INVALID_PARAMS';
 
   /**
    * An error code indicating an internal error.
    */
-  static const int CODE_INTERNAL_ERROR = -32603;
+  static const String CODE_INTERNAL_ERROR = 'INTERNAL_ERROR';
 
   /**
    * An error code indicating a problem using the specified Dart SDK.
    */
-  static const int CODE_SDK_ERROR = -32603;
+  static const String CODE_SDK_ERROR = 'SDK_ERROR';
 
   /**
    * An error code indicating a problem during 'analysis.getErrors'.
    */
-  static const int CODE_ANALISYS_GET_ERRORS_ERROR = -32500;
-
-  /*
-   * In addition, codes -32000 to -32099 indicate a server error. They are
-   * reserved for implementation-defined server-errors.
-   */
+  static const String CODE_ANALISYS_GET_ERRORS_ERROR = 'ANALYSIS_GET_ERRORS_ERROR';
 
   /**
    * The code that uniquely identifies the error that occurred.
    */
-  final int code;
+  final String code;
 
   /**
    * A short description of the error.
@@ -786,7 +781,7 @@
    */
   factory RequestError.fromJson(Map<String, Object> json) {
     try {
-      int code = json[RequestError.CODE];
+      String code = json[RequestError.CODE];
       String message = json[RequestError.MESSAGE];
       Map<String, Object> data = json[RequestError.DATA];
       RequestError requestError = new RequestError(code, message);
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart
index e9baeaa..9b35b4c 100644
--- a/pkg/analysis_server/lib/src/search/search_domain.dart
+++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -14,6 +14,7 @@
 import 'package:analysis_server/src/search/search_result.dart';
 import 'package:analysis_server/src/search/type_hierarchy.dart';
 import 'package:analysis_services/constants.dart';
+import 'package:analysis_services/json.dart';
 import 'package:analysis_services/search/search_engine.dart';
 import 'package:analyzer/src/generated/element.dart';
 
@@ -142,9 +143,9 @@
     Element element = elements.first;
     // prepare type hierarchy
     TypeHierarchyComputer computer = new TypeHierarchyComputer(searchEngine);
-    computer.compute(element).then((TypeHierarchyItem item) {
+    computer.compute(element).then((List<TypeHierarchyItem> items) {
       Response response = new Response(request.id);
-      response.setResult(HIERARCHY, item);
+      response.setResult(HIERARCHY_ITEMS, objectToJson(items));
       server.sendResponse(response);
     });
     // delay response
diff --git a/pkg/analysis_server/lib/src/search/type_hierarchy.dart b/pkg/analysis_server/lib/src/search/type_hierarchy.dart
index 5a28bb5..3ec189b 100644
--- a/pkg/analysis_server/lib/src/search/type_hierarchy.dart
+++ b/pkg/analysis_server/lib/src/search/type_hierarchy.dart
@@ -20,15 +20,20 @@
  */
 class TypeHierarchyComputer {
   final SearchEngine _searchEngine;
+
   ElementKind _pivotKind;
   String _pivotName;
 
+  final List<TypeHierarchyItem> _items = <TypeHierarchyItem>[];
+  final Map<Element, TypeHierarchyItem> _elementItemMap =
+      new HashMap<Element, TypeHierarchyItem>();
+
   TypeHierarchyComputer(this._searchEngine);
 
   /**
    * Returns the computed type hierarchy, maybe `null`.
    */
-  Future<TypeHierarchyItem> compute(Element element) {
+  Future<List<TypeHierarchyItem>> compute(Element element) {
     _pivotKind = element.kind;
     _pivotName = element.name;
     if (element is ExecutableElement &&
@@ -36,63 +41,99 @@
       element = element.enclosingElement;
     }
     if (element is ClassElement) {
-      Set<ClassElement> processed = new HashSet<ClassElement>();
       InterfaceType type = element.type;
-      TypeHierarchyItem item = createSuperItem(type, processed);
-      return _createSubclasses(item, type, processed).then((_) {
-        return item;
+      _createSuperItem(type);
+      return _createSubclasses(_items[0], type).then((_) {
+        return new Future.value(_items);
       });
     }
-    return new Future.value(null);
+    return new Future.value([]);
   }
 
-  TypeHierarchyItem createSuperItem(InterfaceType type,
-      Set<ClassElement> processed) {
+  Future _createSubclasses(TypeHierarchyItem item, InterfaceType type) {
+    var future = getDirectSubClasses(_searchEngine, type.element);
+    return future.then((Set<ClassElement> subElements) {
+      List<TypeHierarchyItem> subItems = <TypeHierarchyItem>[];
+      for (ClassElement subElement in subElements) {
+        // check for recursion
+        TypeHierarchyItem subItem = _elementItemMap[subElement];
+        if (subItem != null) {
+          int id = _items.indexOf(subItem);
+          subItem.subclasses.add(id);
+          continue;
+        }
+        // create a subclass item
+        ExecutableElement subMemberElement = _findMemberElement(subElement);
+        subItem = new TypeHierarchyItem(
+            _items.length,
+            subElement,
+            subMemberElement,
+            null,
+            item.id,
+            <int>[],
+            <int>[]);
+        // remember
+        _elementItemMap[subElement] = subItem;
+        _items.add(subItem);
+        // add to hierarchy
+        item.subclasses.add(subItem.id);
+        subItems.add(subItem);
+      }
+      // compute subclasses of subclasses
+      return Future.forEach(subItems, (TypeHierarchyItem subItem) {
+        InterfaceType subType = subItem.classElement.type;
+        return _createSubclasses(subItem, subType);
+      });
+    });
+  }
+
+  int _createSuperItem(InterfaceType type) {
     // check for recursion
-    if (!processed.add(type.element)) {
-      return null;
+    TypeHierarchyItem item = _elementItemMap[type.element];
+    if (item != null) {
+      return _items.indexOf(item);
+    }
+    // create an empty item now
+    {
+      String displayName = null;
+      if (type.typeArguments.isNotEmpty) {
+        displayName = type.toString();
+      }
+      ClassElement classElement = type.element;
+      ExecutableElement memberElement = _findMemberElement(classElement);
+      item = new TypeHierarchyItem(
+          _items.length,
+          classElement,
+          memberElement,
+          displayName,
+          null,
+          <int>[],
+          <int>[]);
+      _elementItemMap[classElement] = item;
+      _items.add(item);
     }
     // superclass
-    TypeHierarchyItem superItem = null;
     {
       InterfaceType superType = type.superclass;
       if (superType != null) {
-        superItem = createSuperItem(superType, processed);
+        item.superclass = _createSuperItem(superType);
       }
     }
     // mixins
-    List<TypeHierarchyItem> mixinsItems;
-    {
-      List<InterfaceType> mixinsTypes = type.mixins;
-      mixinsItems = mixinsTypes.map((InterfaceType type) {
-        return createSuperItem(type, processed);
-      }).toList();
-    }
+    type.mixins.forEach((InterfaceType type) {
+      int id = _createSuperItem(type);
+      item.mixins.add(id);
+    });
     // interfaces
-    List<TypeHierarchyItem> interfacesItems;
-    {
-      List<InterfaceType> interfacesTypes = type.interfaces;
-      interfacesItems = interfacesTypes.map((InterfaceType type) {
-        return createSuperItem(type, processed);
-      }).toList();
-    }
+    type.interfaces.forEach((InterfaceType type) {
+      int id = _createSuperItem(type);
+      item.interfaces.add(id);
+    });
     // done
-    String displayName = null;
-    if (type.typeArguments.isNotEmpty) {
-      displayName = type.toString();
-    }
-    ClassElement classElement = type.element;
-    ExecutableElement memberElement = findMemberElement(classElement);
-    return new TypeHierarchyItem(
-        classElement,
-        memberElement,
-        displayName,
-        superItem,
-        mixinsItems,
-        interfacesItems);
+    return item.id;
   }
 
-  ExecutableElement findMemberElement(ClassElement classElement) {
+  ExecutableElement _findMemberElement(ClassElement classElement) {
     if (_pivotKind == ElementKind.METHOD) {
       return classElement.getMethod(_pivotName);
     }
@@ -104,49 +145,21 @@
     }
     return null;
   }
-
-  Future _createSubclasses(TypeHierarchyItem item, InterfaceType type,
-      Set<ClassElement> processed) {
-    var future = getDirectSubClasses(_searchEngine, type.element);
-    return future.then((Set<ClassElement> subElements) {
-      for (ClassElement subElement in subElements) {
-        // check for recursion
-        if (!processed.add(subElement)) {
-          continue;
-        }
-        // create a subclass item
-        ExecutableElement subMemberElement = findMemberElement(subElement);
-        TypeHierarchyItem subItem =
-            new TypeHierarchyItem(
-                subElement,
-                subMemberElement,
-                null,
-                null,
-                <TypeHierarchyItem>[],
-                <TypeHierarchyItem>[]);
-        item.subclasses.add(subItem);
-      }
-      // compute subclasses of subclasses
-      return Future.forEach(item.subclasses, (TypeHierarchyItem subItem) {
-        InterfaceType subType = subItem.classElement.type;
-        return _createSubclasses(subItem, subType, processed);
-      });
-    });
-  }
 }
 
 
 class TypeHierarchyItem implements HasToJson {
+  final int id;
   final ClassElement classElement;
   final Element memberElement;
   final String displayName;
-  final TypeHierarchyItem superclass;
-  final List<TypeHierarchyItem> mixins;
-  final List<TypeHierarchyItem> interfaces;
-  List<TypeHierarchyItem> subclasses = <TypeHierarchyItem>[];
+  int superclass;
+  final List<int> mixins;
+  final List<int> interfaces;
+  final List<int> subclasses = <int>[];
 
-  TypeHierarchyItem(this.classElement, this.memberElement, this.displayName,
-      this.superclass, this.mixins, this.interfaces);
+  TypeHierarchyItem(this.id, this.classElement, this.memberElement,
+      this.displayName, this.superclass, this.mixins, this.interfaces);
 
   Map<String, Object> toJson() {
     Map<String, Object> json = {};
diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml
index f76d896..abab51a 100644
--- a/pkg/analysis_server/pubspec.yaml
+++ b/pkg/analysis_server/pubspec.yaml
@@ -1,7 +1,7 @@
 name: analysis_server
 version: 0.1.0
 author: Dart Team <misc@dartlang.org>
-description: An HTTP server that performs analysis of Dart code via web sockets.
+description: A server that performs analysis of Dart code over character streams using JSON-RPC encoded information.
 homepage: http://www.dartlang.org
 environment:
   sdk: '>=1.0.0 <2.0.0'
@@ -14,6 +14,7 @@
   watcher: any
 dev_dependencies:
   analysis_testing: '>=0.4.0 <0.5.0'
+  html5lib: any
   mock: '>=0.10.0 <0.11.0'
   typed_mock: '>=0.0.4 <1.0.0'
   unittest: '>=0.10.0 <0.12.0'
diff --git a/pkg/analysis_server/test/analysis/get_errors_test.dart b/pkg/analysis_server/test/analysis/get_errors_test.dart
index 0ca901f..9e2365a 100644
--- a/pkg/analysis_server/test/analysis/get_errors_test.dart
+++ b/pkg/analysis_server/test/analysis/get_errors_test.dart
@@ -115,7 +115,7 @@
       expect(response.getResult(ERRORS), isEmpty);
       RequestError error = response.error;
       expect(error, isNotNull);
-      expect(error.code, -13);
+      expect(error.code, 'GET_ERRORS_ERROR');
     });
   }
 
diff --git a/pkg/analysis_server/test/analysis/notification_errors_test.dart b/pkg/analysis_server/test/analysis/notification_errors_test.dart
new file mode 100644
index 0000000..b7260aa
--- /dev/null
+++ b/pkg/analysis_server/test/analysis/notification_errors_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library test.analysis.notification_errors;
+
+import 'package:analysis_server/src/computer/error.dart';
+import 'package:analysis_server/src/constants.dart';
+import 'package:analysis_server/src/domain_analysis.dart';
+import 'package:analysis_server/src/protocol.dart';
+import 'package:analysis_services/constants.dart';
+import 'package:analysis_testing/reflective_tests.dart';
+import 'package:unittest/unittest.dart';
+
+import '../analysis_abstract.dart';
+
+
+main() {
+  groupSep = ' | ';
+  runReflectiveTests(NotificationErrorsTest);
+}
+
+
+@ReflectiveTestCase()
+class NotificationErrorsTest extends AbstractAnalysisTest {
+  Map<String, List<AnalysisError>> filesErrors = {};
+
+  void processNotification(Notification notification) {
+    if (notification.event == ANALYSIS_ERRORS) {
+      String file = notification.getParameter(FILE);
+      List<Map<String, Object>> errorMaps = notification.getParameter(ERRORS);
+      filesErrors[file] = errorMaps.map(AnalysisError.fromJson).toList();
+    }
+  }
+
+  @override
+  void setUp() {
+    super.setUp();
+    server.handlers = [new AnalysisDomainHandler(server),];
+  }
+
+  test_ParserError() {
+    createProject();
+    addTestFile('library lib');
+    return waitForTasksFinished().then((_) {
+      List<AnalysisError> errors = filesErrors[testFile];
+      expect(errors, hasLength(1));
+      AnalysisError error = errors[0];
+      expect(error.location.file, '/project/bin/test.dart');
+      expect(error.location.offset, isPositive);
+      expect(error.location.length, isNonNegative);
+      expect(error.severity, 'ERROR');
+      expect(error.type, 'SYNTACTIC_ERROR');
+      expect(error.message, isNotNull);
+    });
+  }
+
+  test_StaticWarning() {
+    createProject();
+    addTestFile('''
+main() {
+  print(UNKNOWN);
+}
+''');
+    return waitForTasksFinished().then((_) {
+      List<AnalysisError> errors = filesErrors[testFile];
+      expect(errors, hasLength(1));
+      AnalysisError error = errors[0];
+      expect(error.severity, 'WARNING');
+      expect(error.type, 'STATIC_WARNING');
+    });
+  }
+
+  test_notInAnalysisRoot() {
+    createProject();
+    String otherFile = '/other.dart';
+    addFile(otherFile, 'UnknownType V;');
+    addTestFile('''
+import '/other.dart';
+
+main() {
+  print(V);
+}
+''');
+    return waitForTasksFinished().then((_) {
+      expect(filesErrors[otherFile], isNull);
+    });
+  }
+}
diff --git a/pkg/analysis_server/test/analysis/test_all.dart b/pkg/analysis_server/test/analysis/test_all.dart
index c33c81c..5029a51 100644
--- a/pkg/analysis_server/test/analysis/test_all.dart
+++ b/pkg/analysis_server/test/analysis/test_all.dart
@@ -6,6 +6,7 @@
 import 'package:unittest/unittest.dart';
 
 import 'get_errors_test.dart' as get_errors_test;
+import 'notification_errors_test.dart' as notification_errors_test;
 
 /**
  * Utility for manually running all tests.
@@ -14,5 +15,6 @@
   groupSep = ' | ';
   group('search', () {
     get_errors_test.main();
+    notification_errors_test.main();
   });
 }
diff --git a/pkg/analysis_server/test/analysis_abstract.dart b/pkg/analysis_server/test/analysis_abstract.dart
index 0cf7558..2598194 100644
--- a/pkg/analysis_server/test/analysis_abstract.dart
+++ b/pkg/analysis_server/test/analysis_abstract.dart
@@ -15,7 +15,6 @@
 import 'package:analysis_testing/mock_sdk.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
-import 'package:analyzer/src/generated/source.dart';
 import 'package:unittest/unittest.dart';
 
 import 'mocks.dart';
@@ -107,7 +106,7 @@
    */
   int findFileOffset(String path, String search) {
     File file = resourceProvider.getResource(path) as File;
-    String code = file.createSource(UriKind.FILE_URI).contents.data;
+    String code = file.createSource().contents.data;
     int offset = code.indexOf(search);
     expect(offset, isNot(-1), reason: '"$search" in\n$code');
     return offset;
diff --git a/pkg/analysis_server/test/analysis_notification_overrides_test.dart b/pkg/analysis_server/test/analysis_notification_overrides_test.dart
index 5e59269..873c16e 100644
--- a/pkg/analysis_server/test/analysis_notification_overrides_test.dart
+++ b/pkg/analysis_server/test/analysis_notification_overrides_test.dart
@@ -8,7 +8,6 @@
 
 import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/computer/computer_overrides.dart';
-import 'package:analysis_server/src/computer/element.dart';
 import 'package:analysis_server/src/constants.dart';
 import 'package:analysis_server/src/protocol.dart';
 import 'package:analysis_services/constants.dart';
@@ -30,19 +29,19 @@
   Override override;
 
   /**
-   * Asserts that there is an overridden interface [Element] at the offset of
-   * [search] in [override].
+   * Asserts that there is an overridden interface [OverriddenMember] at the
+   * offset of [search] in [override].
    */
-  void assertHasInterfaceElement(String search) {
+  void assertHasInterfaceMember(String search) {
     int offset = findOffset(search);
-    for (Element element in override.interfaceElements) {
-      if (element.location.offset == offset) {
+    for (OverriddenMember member in override.interfaceMembers) {
+      if (member.element.location.offset == offset) {
         return;
       }
     }
     fail(
-        'Expect to find an overridden interface elements at $offset in '
-            '${override.interfaceElements.join('\n')}');
+        'Expect to find an overridden interface members at $offset in '
+            '${override.interfaceMembers.join('\n')}');
   }
 
   /**
@@ -60,27 +59,27 @@
   }
 
   /**
-   * Asserts that there is an overridden superclass [Element] at the offset of
-   * [search] in [override].
+   * Asserts that there is an overridden superclass [OverriddenMember] at the
+   * offset of [search] in [override].
    */
   void assertHasSuperElement(String search) {
     int offset = findOffset(search);
-    Element element = override.superclassElement;
-    expect(element.location.offset, offset);
+    OverriddenMember member = override.superclassMember;
+    expect(member.element.location.offset, offset);
   }
 
   /**
-   * Asserts that there are no overridden elements from interfaces.
+   * Asserts that there are no overridden members from interfaces.
    */
-  void assertNoInterfaceElements() {
-    expect(override.interfaceElements, isNull);
+  void assertNoInterfaceMembers() {
+    expect(override.interfaceMembers, isNull);
   }
 
   /**
-   * Asserts that there are no overridden elements from the superclass.
+   * Asserts that there are no overridden member from the superclass.
    */
-  void assertNoSuperElement() {
-    expect(override.superclassElement, isNull);
+  void assertNoSuperMember() {
+    expect(override.superclassMember, isNull);
   }
 
   /**
@@ -119,12 +118,9 @@
     if (notification.event == ANALYSIS_OVERRIDES) {
       String file = notification.getParameter(FILE);
       if (file == testFile) {
-        overridesList = <Override>[];
         List<Map<String, Object>> jsonList =
             notification.getParameter(OVERRIDES);
-        for (Map<String, Object> json in jsonList) {
-          overridesList.add(new Override.fromJson(json));
-        }
+        overridesList = jsonList.map(Override.fromJson).toList();
       }
     }
   }
@@ -146,8 +142,8 @@
     return waitForTasksFinished().then((_) {
       return prepareOverrides().then((_) {
         assertHasOverride('m() {} // in B');
-        assertNoSuperElement();
-        assertHasInterfaceElement('m() {} // in A');
+        assertNoSuperMember();
+        assertHasInterfaceMember('m() {} // in A');
       });
     });
   }
@@ -166,9 +162,9 @@
 ''');
     return prepareOverrides().then((_) {
       assertHasOverride('m() {} // in A');
-      assertNoSuperElement();
-      assertHasInterfaceElement('m() {} // in IA');
-      assertHasInterfaceElement('m() {} // in IB');
+      assertNoSuperMember();
+      assertHasInterfaceMember('m() {} // in IA');
+      assertHasInterfaceMember('m() {} // in IB');
     });
   }
 
@@ -183,8 +179,8 @@
 ''');
     return prepareOverrides().then((_) {
       assertHasOverride('m() {} // in B');
-      assertNoSuperElement();
-      assertHasInterfaceElement('m() {} // in A');
+      assertNoSuperMember();
+      assertHasInterfaceMember('m() {} // in A');
     });
   }
 
@@ -201,8 +197,8 @@
 ''');
     return prepareOverrides().then((_) {
       assertHasOverride('m() {} // in C');
-      assertNoSuperElement();
-      assertHasInterfaceElement('m() {} // in A');
+      assertNoSuperMember();
+      assertHasInterfaceMember('m() {} // in A');
     });
   }
 
@@ -218,7 +214,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff; // in B');
       assertHasSuperElement('fff; // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -234,7 +230,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff => 0; // in B');
       assertHasSuperElement('fff; // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -250,7 +246,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff() {} // in B');
       assertHasSuperElement('fff; // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -266,7 +262,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff(x) {} // in B');
       assertHasSuperElement('fff; // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -283,7 +279,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff; // in B');
       assertHasSuperElement('fff => 0; // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -299,7 +295,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff => 0; // in B');
       assertHasSuperElement('fff => 0; // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -315,7 +311,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('m() {} // in B');
       assertHasSuperElement('m() {} // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -333,7 +329,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('m() {} // in C');
       assertHasSuperElement('m() {} // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 
@@ -349,7 +345,7 @@
     return prepareOverrides().then((_) {
       assertHasOverride('fff(x) {} // in B');
       assertHasSuperElement('fff(x) {} // in A');
-      assertNoInterfaceElements();
+      assertNoInterfaceMembers();
     });
   }
 }
diff --git a/pkg/analysis_server/test/context_directory_manager_test.dart b/pkg/analysis_server/test/context_directory_manager_test.dart
index b640e01..f33b2f8 100644
--- a/pkg/analysis_server/test/context_directory_manager_test.dart
+++ b/pkg/analysis_server/test/context_directory_manager_test.dart
@@ -193,6 +193,22 @@
       });
     });
 
+    group('isInAnalysisRoot', () {
+      test('in root', () {
+        String projPath = '/project';
+        resourceProvider.newFolder(projPath);
+        manager.setRoots(<String>[projPath], <String>[]);
+        expect(manager.isInAnalysisRoot('/project/test.dart'), isTrue);
+      });
+
+      test('not in root', () {
+        String projPath = '/project';
+        resourceProvider.newFolder(projPath);
+        manager.setRoots(<String>[projPath], <String>[]);
+        expect(manager.isInAnalysisRoot('/test.dart'), isFalse);
+      });
+    });
+
     group('detect context modifications', () {
       String projPath;
 
diff --git a/pkg/analysis_server/test/domain_analysis_test.dart b/pkg/analysis_server/test/domain_analysis_test.dart
index 58c04db..78676ad 100644
--- a/pkg/analysis_server/test/domain_analysis_test.dart
+++ b/pkg/analysis_server/test/domain_analysis_test.dart
@@ -41,7 +41,6 @@
     handler = new AnalysisDomainHandler(server);
   });
 
-  group('notification.errors', testNotificationErrors);
   group('updateContent', testUpdateContent);
   group('setSubscriptions', test_setSubscriptions);
 
@@ -144,51 +143,6 @@
         expect(options.enableDeferredLoading, equals(enableDeferredLoading));
       });
     });
-
-    test('updateSdks', () {
-      var request = new Request('0', ANALYSIS_UPDATE_SDKS);
-      request.setParameter(ADDED, ['/dart/sdk-1.3', '/dart/sdk-1.4']);
-      request.setParameter(REMOVED, ['/dart/sdk-1.2']);
-      request.setParameter(DEFAULT, '/dart/sdk-1.4');
-      var response = handler.handleRequest(request);
-      // TODO(scheglov) implement
-      expect(response, isNull);
-    });
-  });
-}
-
-
-testNotificationErrors() {
-  AnalysisTestHelper helper;
-
-  setUp(() {
-    helper = new AnalysisTestHelper();
-  });
-
-  test('ParserError', () {
-    helper.createSingleFileProject('library lib');
-    return helper.waitForOperationsFinished().then((_) {
-      List<AnalysisError> errors = helper.getTestErrors();
-      expect(errors, hasLength(1));
-      AnalysisError error = errors[0];
-      expect(error.location.file, '/project/bin/test.dart');
-      expect(error.location.offset, isPositive);
-      expect(error.location.length, isNonNegative);
-      expect(error.severity, 'ERROR');
-      expect(error.type, 'SYNTACTIC_ERROR');
-      expect(error.message, isNotNull);
-    });
-  });
-
-  test('StaticWarning', () {
-    helper.createSingleFileProject(['main() {', '  print(unknown);', '}']);
-    return helper.waitForOperationsFinished().then((_) {
-      List<AnalysisError> errors = helper.getTestErrors();
-      expect(errors, hasLength(1));
-      AnalysisError error = errors[0];
-      expect(error.severity, 'WARNING');
-      expect(error.type, 'STATIC_WARNING');
-    });
   });
 }
 
@@ -320,11 +274,28 @@
     AnalysisTestHelper helper = new AnalysisTestHelper();
     helper.createSingleFileProject('int V = 42;');
     return helper.waitForOperationsFinished().then((_) {
-      String noFile = '/no-such.file.dart';
-      helper.addAnalysisSubscriptionErrors(noFile);
+      String noFile = '/no-such-file.dart';
+      helper.addAnalysisSubscriptionHighlights(noFile);
       return helper.waitForOperationsFinished().then((_) {
-        var errors = helper.getErrors(noFile);
-        expect(errors, isEmpty);
+        var highlights = helper.getHighlights(noFile);
+        expect(highlights, isEmpty);
+      });
+    });
+  });
+
+  test('after analysis, SDK file', () {
+    AnalysisTestHelper helper = new AnalysisTestHelper();
+    helper.createSingleFileProject('''
+main() {
+  print(42);
+}
+''');
+    return helper.waitForOperationsFinished().then((_) {
+      String file = '/lib/core/core.dart';
+      helper.addAnalysisSubscriptionNavigation(file);
+      return helper.waitForOperationsFinished().then((_) {
+        var navigationRegions = helper.getNavigation(file);
+        expect(navigationRegions, isNot(isEmpty));
       });
     });
   });
@@ -404,8 +375,8 @@
     return waitForTasksFinished().then((_) {
       // if 'package:pkgA/libA.dart' was resolved, then there are no errors
       expect(filesErrors[testFile], isEmpty);
-      // packages file also was resolved
-      expect(filesErrors[pkgFile], isEmpty);
+      // errors are not reported for packages
+      expect(filesErrors[pkgFile], isNull);
     });
   }
 }
@@ -469,10 +440,6 @@
     handleSuccessfulRequest(request);
   }
 
-  void addAnalysisSubscriptionErrors(String file) {
-    addAnalysisSubscription(AnalysisService.ERRORS, file);
-  }
-
   void addAnalysisSubscriptionHighlights(String file) {
     addAnalysisSubscription(AnalysisService.HIGHLIGHTS, file);
   }
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index 3c0008b..e9c7863 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -17,6 +17,7 @@
 import 'package:unittest/unittest.dart';
 
 import 'analysis_abstract.dart';
+import 'mocks.dart';
 
 main() {
   groupSep = ' | ';
@@ -27,6 +28,8 @@
 class CompletionTest extends AbstractAnalysisTest {
   String completionId;
   int completionOffset;
+  int replacementOffset;
+  int replacementLength;
   List<CompletionSuggestion> suggestions = [];
   bool suggestionsDone = false;
 
@@ -79,7 +82,9 @@
       Response response = handleSuccessfulRequest(request);
       completionId = response.getResult(ID);
       assertValidId(completionId);
-      return waitForSuggestions();
+      return pumpEventQueue().then((_) {
+        expect(suggestionsDone, isTrue);
+      });
     });
   }
 
@@ -89,6 +94,8 @@
       assertValidId(id);
       if (id == completionId) {
         expect(suggestionsDone, isFalse);
+        replacementOffset = notification.getParameter(REPLACEMENT_OFFSET);
+        replacementLength = notification.getParameter(REPLACEMENT_LENGTH);
         suggestionsDone = notification.getParameter(LAST);
         expect(suggestionsDone, isNotNull);
         for (Map<String, Object> json in notification.getParameter(RESULTS)) {
@@ -106,11 +113,16 @@
     handler = new CompletionDomainHandler(server);
   }
 
-  Future waitForSuggestions() {
-    if (suggestionsDone) {
-      return new Future.value();
-    }
-    return new Future.delayed(Duration.ZERO, waitForSuggestions);
+  test_suggestions_html() {
+    testFile = '/project/web/test.html';
+    addTestFile('''
+      <html>^</html>
+    ''');
+    return getSuggestions().then((_) {
+      expect(replacementOffset, equals(completionOffset));
+      expect(replacementLength, equals(0));
+      expect(suggestions, hasLength(0));
+    });
   }
 
   test_suggestions_importedType() {
@@ -119,6 +131,8 @@
       main() {^}
     ''');
     return getSuggestions().then((_) {
+      expect(replacementOffset, equals(completionOffset));
+      expect(replacementLength, equals(0));
       assertHasResult(CompletionSuggestionKind.CLASS, 'Object');
       assertHasResult(CompletionSuggestionKind.CLASS, 'HtmlElement');
       assertNoResult('test');
@@ -128,10 +142,12 @@
   test_suggestions_topLevel() {
     addTestFile('''
       typedef foo();
-      var test^ = '';
-      main() {test.}
+      var test = '';
+      main() {tes^t}
     ''');
     return getSuggestions().then((_) {
+//      expect(replacementOffset, equals(completionOffset - 3));
+//      expect(replacementLength, equals(4));
       assertHasResult(CompletionSuggestionKind.CLASS, 'Object');
       assertHasResult(CompletionSuggestionKind.TOP_LEVEL_VARIABLE, 'test');
       assertNoResult('HtmlElement');
diff --git a/pkg/analysis_server/test/edit/fix_test.dart b/pkg/analysis_server/test/edit/fix_test.dart
index 20ce526..d3fd7a4 100644
--- a/pkg/analysis_server/test/edit/fix_test.dart
+++ b/pkg/analysis_server/test/edit/fix_test.dart
@@ -4,11 +4,9 @@
 
 library test.edit.fix;
 
-import 'package:analysis_server/src/computer/element.dart';
 import 'package:analysis_server/src/computer/error.dart';
 import 'package:analysis_server/src/edit/fix.dart';
 import 'package:analysis_services/constants.dart';
-import 'package:analysis_services/correction/change.dart';
 import 'package:analysis_services/correction/fix.dart' as services;
 import 'package:analysis_services/index/index.dart' hide Location;
 import 'package:analysis_services/index/local_memory_index.dart';
@@ -41,72 +39,6 @@
     verifyNoTestUnitErrors = false;
   }
 
-  void test_fromJson() {
-    var json = {
-      ERROR: {
-        SEVERITY: 'ERROR',
-        TYPE: 'SYNTACTIC_ERROR',
-        LOCATION: {
-          FILE: '/test.dart',
-          OFFSET: 19,
-          LENGTH: 1,
-          START_LINE: 2,
-          START_COLUMN: 11
-        },
-        MESSAGE: 'Expected to find \';\''
-      },
-      FIXES: [{
-          MESSAGE: 'Insert \';\'',
-          EDITS: [{
-              FILE: '/test.dart',
-              EDITS: [{
-                  OFFSET: 20,
-                  LENGTH: 0,
-                  REPLACEMENT: ';'
-                }]
-            }],
-          LINKED_POSITION_GROUPS: []
-        }]
-    };
-    ErrorFixes errorFixes = ErrorFixes.fromJson(json);
-    {
-      AnalysisError error = errorFixes.error;
-      expect(error.severity, 'ERROR');
-      expect(error.type, 'SYNTACTIC_ERROR');
-      expect(error.message, "Expected to find ';'");
-      {
-        Location location = error.location;
-        expect(location.file, testFile);
-        expect(location.offset, 19);
-        expect(location.length, 1);
-        expect(location.startLine, 2);
-        expect(location.startColumn, 11);
-      }
-    }
-    expect(errorFixes.fixes, hasLength(1));
-    {
-      Change change = errorFixes.fixes[0];
-      expect(change.message, "Insert ';'");
-      expect(change.edits, hasLength(1));
-      {
-        FileEdit fileEdit = change.edits[0];
-        expect(fileEdit.file, testFile);
-        expect(
-            fileEdit.edits.toString(),
-            "[Edit(offset=20, length=0, replacement=:>;<:)]");
-      }
-    }
-    expect(
-        errorFixes.toString(),
-        'ErrorFixes(error=AnalysisError('
-            'location=Location(file=/test.dart; offset=19; length=1; '
-            'startLine=2; startColumn=11) message=Expected to find \';\'; '
-            'severity=ERROR; type=SYNTACTIC_ERROR; correction=null, '
-            'fixes=[Change(message=Insert \';\', '
-            'edits=[FileEdit(file=/test.dart, edits=[Edit(offset=20, length=0, '
-            'replacement=:>;<:)])], linkedPositionGroups=[])])');
-  }
-
   void test_fromService() {
     verifyNoTestUnitErrors = false;
     resolveTestUnit('''
@@ -145,7 +77,7 @@
                   REPLACEMENT: ';'
                 }]
             }],
-          LINKED_POSITION_GROUPS: []
+          LINKED_EDIT_GROUPS: []
         }]
     });
   }
diff --git a/pkg/analysis_server/test/edit/fixes_test.dart b/pkg/analysis_server/test/edit/fixes_test.dart
index a27dd4a..5170038 100644
--- a/pkg/analysis_server/test/edit/fixes_test.dart
+++ b/pkg/analysis_server/test/edit/fixes_test.dart
@@ -6,16 +6,12 @@
 
 import 'dart:async';
 
-import 'package:analysis_server/src/computer/element.dart';
-import 'package:analysis_server/src/computer/error.dart';
 import 'package:analysis_server/src/constants.dart';
 import 'package:analysis_server/src/edit/edit_domain.dart';
-import 'package:analysis_server/src/edit/fix.dart';
 import 'package:analysis_server/src/protocol.dart';
 import 'package:analysis_services/constants.dart';
-import 'package:analysis_services/correction/change.dart';
 import 'package:analysis_testing/reflective_tests.dart';
-import 'package:unittest/unittest.dart';
+import 'package:unittest/unittest.dart' hide ERROR;
 
 import '../analysis_abstract.dart';
 
@@ -47,37 +43,13 @@
       request.setParameter(OFFSET, findOffset('print'));
       Response response = handleSuccessfulRequest(request);
       List<Map<String, Object>> errorFixesJsonList = response.getResult(FIXES);
-      List<ErrorFixes> errorFixesList = errorFixesJsonList.map(ErrorFixes.fromJson).toList();
-      expect(errorFixesList, hasLength(1));
+      expect(errorFixesJsonList, hasLength(1));
       {
-         ErrorFixes errorFixes = errorFixesList[0];
-         {
-           AnalysisError error = errorFixes.error;
-           expect(error.severity, 'ERROR');
-           expect(error.type, 'SYNTACTIC_ERROR');
-           expect(error.message, "Expected to find ';'");
-           {
-             Location location = error.location;
-             expect(location.file, testFile);
-             expect(location.offset, 19);
-             expect(location.length, 1);
-             expect(location.startLine, 2);
-             expect(location.startColumn, 11);
-           }
-         }
-         expect(errorFixes.fixes, hasLength(1));
-         {
-           Change change = errorFixes.fixes[0];
-           expect(change.message, "Insert ';'");
-           expect(change.edits, hasLength(1));
-           {
-             FileEdit fileEdit = change.edits[0];
-             expect(fileEdit.file, testFile);
-             expect(
-                 fileEdit.edits.toString(),
-                 "[Edit(offset=20, length=0, replacement=:>;<:)]");
-           }
-         }
+        Map<String, Object> fixes = errorFixesJsonList[0];
+        Map<String, Object> error = fixes[ERROR];
+        expect(error[SEVERITY], 'ERROR');
+        expect(error[TYPE], 'SYNTACTIC_ERROR');
+        expect(fixes[FIXES], hasLength(1));
       }
     });
   }
diff --git a/pkg/analysis_server/test/integration/analysis_domain_int_test.dart b/pkg/analysis_server/test/integration/analysis_domain_int_test.dart
index f744c1d..a5007d9 100644
--- a/pkg/analysis_server/test/integration/analysis_domain_int_test.dart
+++ b/pkg/analysis_server/test/integration/analysis_domain_int_test.dart
@@ -12,13 +12,13 @@
 import 'package:unittest/unittest.dart';
 
 import 'integration_tests.dart';
+import 'protocol_matchers.dart';
 
 @ReflectiveTestCase()
 class AnalysisDomainIntegrationTest extends
     AbstractAnalysisServerIntegrationTest {
   test_getHover() {
-    String filename = 'test.dart';
-    String pathname = normalizePath(filename);
+    String pathname = sourcePath('test.dart');
     String text =
         r'''
 library lib.test;
@@ -38,8 +38,8 @@
   func(35);
 }
 ''';
-    writeFile(filename, text);
-    setAnalysisRoots(['']);
+    writeFile(pathname, text);
+    standardAnalysisRoot();
 
     testHover(String target, int length, List<String> descriptionRegexps, String
         kind, List<String> staticTypeRegexps, {bool isCore: false, String docRegexp:
@@ -137,15 +137,14 @@
   }
 
   test_getHover_noInfo() {
-    String filename = 'test.dart';
-    String pathname = normalizePath(filename);
+    String pathname = sourcePath('test.dart');
     String text = r'''
 main() {
   // no code
 }
 ''';
-    writeFile(filename, text);
-    setAnalysisRoots(['']);
+    writeFile(pathname, text);
+    standardAnalysisRoot();
 
     // Note: analysis.getHover doesn't wait for analysis to complete--it simply
     // returns the latest results that are available at the time that the
@@ -170,14 +169,13 @@
   }
 
   Future getErrorsTest(bool afterAnalysis) {
-    String filename = 'test.dart';
-    String pathname = normalizePath(filename);
+    String pathname = sourcePath('test.dart');
     String text = r'''
 main() {
   var x // parse error: missing ';'
 }''';
-    writeFile(filename, text);
-    setAnalysisRoots(['']);
+    writeFile(pathname, text);
+    standardAnalysisRoot();
     Future finishTest() {
       return server.send(ANALYSIS_GET_ERRORS, {
         'file': pathname
@@ -202,15 +200,14 @@
   }
 
   Future updateContentTest(bool includeOffsetAndLengths) {
-    String filename = 'test.dart';
-    String pathname = normalizePath(filename);
+    String pathname = sourcePath('test.dart');
     String goodText = r'''
 main() {
   print("Hello, world!");
 }''';
     String badText = goodText.replaceAll(';', '');
-    writeFile(filename, badText);
-    setAnalysisRoots(['']);
+    writeFile(pathname, badText);
+    standardAnalysisRoot();
     return analysisFinished.then((_) {
       // The contents on disk (badText) are missing a semicolon.
       expect(currentAnalysisErrors[pathname], isNot(isEmpty));
diff --git a/pkg/analysis_server/test/integration/analysis_error_int_test.dart b/pkg/analysis_server/test/integration/analysis_error_int_test.dart
index d43090b..e519890 100644
--- a/pkg/analysis_server/test/integration/analysis_error_int_test.dart
+++ b/pkg/analysis_server/test/integration/analysis_error_int_test.dart
@@ -8,24 +8,25 @@
 import 'package:unittest/unittest.dart';
 
 import 'integration_tests.dart';
+import 'protocol_matchers.dart';
 
 @ReflectiveTestCase()
 class AnalysisErrorIntegrationTest extends AbstractAnalysisServerIntegrationTest
     {
   test_detect_simple_error() {
-    writeFile('test.dart',
+    String pathname = sourcePath('test.dart');
+    writeFile(pathname,
         '''
 main() {
   var x // parse error: missing ';'
 }''');
-    setAnalysisRoots(['']);
+    standardAnalysisRoot();
     return analysisFinished.then((_) {
-      String filePath = normalizePath('test.dart');
-      expect(currentAnalysisErrors[filePath], isList);
-      List errors = currentAnalysisErrors[filePath];
+      expect(currentAnalysisErrors[pathname], isList);
+      List errors = currentAnalysisErrors[pathname];
       expect(errors, hasLength(1));
       expect(errors[0], isAnalysisError);
-      expect(errors[0]['location']['file'], equals(filePath));
+      expect(errors[0]['location']['file'], equals(pathname));
     });
   }
 }
diff --git a/pkg/analysis_server/test/integration/completion_domain_int_test.dart b/pkg/analysis_server/test/integration/completion_domain_int_test.dart
index bfb9238..4621abe 100644
--- a/pkg/analysis_server/test/integration/completion_domain_int_test.dart
+++ b/pkg/analysis_server/test/integration/completion_domain_int_test.dart
@@ -17,8 +17,7 @@
     AbstractAnalysisServerIntegrationTest {
   fail_test_getSuggestions_string_var() {
     // See dartbug.com/20188
-    String filename = 'test.dart';
-    String pathname = normalizePath(filename);
+    String pathname = sourcePath('test.dart');
     String text =
         r'''
 var test = '';
@@ -26,8 +25,8 @@
   test.
 }
 ''';
-    writeFile(filename, text);
-    setAnalysisRoots(['']);
+    writeFile(pathname, text);
+    standardAnalysisRoot();
 
     return analysisFinished.then((_) {
       return server.send(COMPLETION_GET_SUGGESTIONS, {
diff --git a/pkg/analysis_server/test/integration/integration_tests.dart b/pkg/analysis_server/test/integration/integration_tests.dart
index 47b1856..8139956 100644
--- a/pkg/analysis_server/test/integration/integration_tests.dart
+++ b/pkg/analysis_server/test/integration/integration_tests.dart
@@ -13,6 +13,8 @@
 import 'package:path/path.dart';
 import 'package:unittest/unittest.dart';
 
+import 'protocol_matchers.dart';
+
 /**
  * Base class for analysis server integration tests.
  */
@@ -53,17 +55,16 @@
   var serverConnectedParams;
 
   /**
-   * Write a source file with the given contents.  [relativePath]
-   * is relative to [sourceDirectory]; on Windows any forward slashes it
-   * contains are converted to backslashes.
+   * Write a source file with the given absolute [pathname] and [contents].
    *
    * If the file didn't previously exist, it is created.  If it did, it is
    * overwritten.
+   *
+   * Parent directories are created as necessary.
    */
-  void writeFile(String relativePath, String contents) {
-    String absolutePath = normalizePath(relativePath);
-    new Directory(dirname(absolutePath)).createSync(recursive: true);
-    new File(absolutePath).writeAsStringSync(contents);
+  void writeFile(String pathname, String contents) {
+    new Directory(dirname(pathname)).createSync(recursive: true);
+    new File(pathname).writeAsStringSync(contents);
   }
 
   /**
@@ -71,16 +72,17 @@
    * relative to [sourceDirectory].  On Windows any forward slashes in
    * [relativePath] are converted to backslashes.
    */
-  String normalizePath(String relativePath) {
+  String sourcePath(String relativePath) {
     return join(sourceDirectory.path, relativePath.replaceAll('/', separator));
   }
 
   /**
-   * Send the server an 'analysis.setAnalysisRoots' command.
+   * Send the server an 'analysis.setAnalysisRoots' command directing it to
+   * analyze [sourceDirectory].
    */
-  Future setAnalysisRoots(List<String> relativeRoots) {
+  Future standardAnalysisRoot() {
     return server.send(ANALYSIS_SET_ANALYSIS_ROOTS, {
-      'included': relativeRoots.map(normalizePath).toList(),
+      'included': [sourceDirectory.path],
       'excluded': []
     });
   }
@@ -150,7 +152,9 @@
     });
     return server.start().then((params) {
       serverConnectedParams = params;
-      server.exitCode.then((_) { skipShutdown = true; });
+      server.exitCode.then((_) {
+        skipShutdown = true;
+      });
       return serverConnected.future;
     });
   }
@@ -181,137 +185,124 @@
   }
 }
 
-// Matchers for data types defined in the analysis server API
-// ==========================================================
-// TODO(paulberry): add more matchers.
-
-// Matchers common to all domains
-// ------------------------------
-
-const Matcher isResponse = const MatchesJsonObject('response', const {
+final Matcher isResponse = new MatchesJsonObject('response', {
   'id': isString
-}, optionalFields: const {
+}, optionalFields: {
   'result': anything,
   'error': isError
 });
 
-const Matcher isError = const MatchesJsonObject('Error', const {
-  // TODO(paulberry): once we decide what the set of permitted error codes are,
-  // add validation for 'code'.
-  'code': anything,
-  'message': isString
-}, optionalFields: const {
-  // TODO(paulberry): API spec says that 'data' is required, but sometimes we
-  // don't see it (example: error "Expected parameter subscriptions to be a
-  // string list map" in response to a malformed "analysis.setSubscriptions"
-  // command).
-  'data': anything
-});
-
 const Matcher isNotification = const MatchesJsonObject('notification', const {
   'event': isString
 }, optionalFields: const {
   'params': isMap
 });
 
-// Matchers for specific responses and notifications
-// -------------------------------------------------
-
-// server.getVersion
-const Matcher isServerGetVersionResult = const MatchesJsonObject(
-    'server.getVersion result', const {
-  'version': isString
-});
-
-// server.status
-const Matcher isServerStatusParams = const MatchesJsonObject(
-    'server.status params', null, optionalFields: const {
-  'analysis': isAnalysisStatus
-});
-
-// analysis.getErrors
-final Matcher isAnalysisGetErrorsResult = new MatchesJsonObject(
-    'analysis.getErrors result', {
-  'errors': isListOf(isAnalysisError)
-});
-
-// analysis.getHover
-final Matcher isAnalysisGetHoverResult = new MatchesJsonObject(
-    'analysis.getHover result', {
-  'hovers': isListOf(isHoverInformation)
-});
-
-// Matchers for data types used in responses and notifications
-// -----------------------------------------------------------
-
 const Matcher isString = const isInstanceOf<String>('String');
 
 const Matcher isInt = const isInstanceOf<int>('int');
 
 const Matcher isBool = const isInstanceOf<bool>('bool');
 
-// AnalysisError
-final Matcher isAnalysisError = new MatchesJsonObject('AnalysisError', {
-  'severity': isErrorSeverity,
-  'type': isErrorType,
-  'location': isLocation,
-  'message': isString,
-}, optionalFields: {
-  'correction': isString
-});
-
-// AnalysisStatus
-const Matcher isAnalysisStatus = const MatchesJsonObject('AnalysisStatus', const
-    {
-  'analyzing': isBool
-}, optionalFields: const {
-  'analysisTarget': isString
-});
-
-// ErrorSeverity
-final Matcher isErrorSeverity = isIn(['INFO', 'WARNING', 'ERROR']);
-
-// ErrorType
-final Matcher isErrorType = isIn(['COMPILE_TIME_ERROR', 'HINT',
-    'STATIC_TYPE_WARNING', 'STATIC_WARNING', 'SYNTACTIC_ERROR', 'TODO']);
-
-// HoverInformation
-const Matcher isHoverInformation = const MatchesJsonObject('HoverInformation',
-    const {
-  'offset': isInt,
-  'length': isInt
-}, optionalFields: const {
-  'containingLibraryPath': isString,
-  'containingLibraryName': isString,
-  'dartdoc': isString,
-  'elementDescription': isString,
-  'elementKind': isString,
-  'parameter': isString,
-  'propagatedType': isString,
-  'staticType': isString
-});
-
-// Location
-const Matcher isLocation = const MatchesJsonObject('Location', const {
-  'file': isString,
-  'offset': isInt,
-  'length': isInt,
-  'startLine': isInt,
-  'startColumn': isInt
-});
-
+const Matcher isObject = isMap;
 
 /**
  * Type of closures used by MatchesJsonObject to record field mismatches.
  */
-typedef Description MismatchDescriber(Description mismatchDescription, bool
-    verbose);
+typedef Description MismatchDescriber(Description mismatchDescription);
+
+/**
+ * Base class for matchers that operate by recursing through the contents of
+ * an object.
+ */
+abstract class _RecursiveMatcher extends Matcher {
+  const _RecursiveMatcher();
+
+  @override
+  bool matches(item, Map matchState) {
+    List<MismatchDescriber> mismatches = <MismatchDescriber>[];
+    populateMismatches(item, mismatches);
+    if (mismatches.isEmpty) {
+      return true;
+    } else {
+      addStateInfo(matchState, {
+        'mismatches': mismatches
+      });
+      return false;
+    }
+  }
+
+  @override
+  Description describeMismatch(item, Description mismatchDescription, Map
+      matchState, bool verbose) {
+    List<MismatchDescriber> mismatches = matchState['mismatches'];
+    if (mismatches != null) {
+      for (int i = 0; i < mismatches.length; i++) {
+        MismatchDescriber mismatch = mismatches[i];
+        if (i > 0) {
+          if (mismatches.length == 2) {
+            mismatchDescription = mismatchDescription.add(' and ');
+          } else if (i == mismatches.length - 1) {
+            mismatchDescription = mismatchDescription.add(', and ');
+          } else {
+            mismatchDescription = mismatchDescription.add(', ');
+          }
+        }
+        mismatchDescription = mismatch(mismatchDescription);
+      }
+      return mismatchDescription;
+    } else {
+      return super.describeMismatch(item, mismatchDescription, matchState,
+          verbose);
+    }
+  }
+
+  /**
+   * Populate [mismatches] with descriptions of all the ways in which [item]
+   * does not match.
+   */
+  void populateMismatches(item, List<MismatchDescriber> mismatches);
+
+  /**
+   * Create a [MismatchDescriber] describing a mismatch with a simple string.
+   */
+  MismatchDescriber simpleDescription(String description) => (Description
+      mismatchDescription) {
+    mismatchDescription.add(description);
+  };
+
+  /**
+   * Check the type of a substructure whose value is [item], using [matcher].
+   * If it doesn't match, record a closure in [mismatches] which can describe
+   * the mismatch.  [describeSubstructure] is used to describe which
+   * substructure did not match.
+   */
+  checkSubstructure(item, Matcher matcher, List<MismatchDescriber>
+      mismatches, Description describeSubstructure(Description)) {
+    Map subState = {};
+    if (!matcher.matches(item, subState)) {
+      mismatches.add((Description mismatchDescription) {
+        mismatchDescription = mismatchDescription.add('contains malformed ');
+        mismatchDescription = describeSubstructure(mismatchDescription);
+        mismatchDescription = mismatchDescription.add(' (should be '
+            ).addDescriptionOf(matcher);
+        String subDescription = matcher.describeMismatch(item,
+            new StringDescription(), subState, false).toString();
+        if (subDescription.isNotEmpty) {
+          mismatchDescription = mismatchDescription.add('; ').add(subDescription
+              );
+        }
+        return mismatchDescription.add(')');
+      });
+    }
+  }
+}
 
 /**
  * Matcher that matches a JSON object, with a given set of required and
  * optional fields, and their associated types (expressed as [Matcher]s).
  */
-class MatchesJsonObject extends Matcher {
+class MatchesJsonObject extends _RecursiveMatcher {
   /**
    * Short description of the expected type.
    */
@@ -333,15 +324,15 @@
       MatchesJsonObject(this.description, this.requiredFields, {this.optionalFields});
 
   @override
-  bool matches(item, Map matchState) {
+  void populateMismatches(item, List<MismatchDescriber> mismatches) {
     if (item is! Map) {
-      return false;
+      mismatches.add(simpleDescription('is not a map'));
+      return;
     }
-    List<MismatchDescriber> mismatches = <MismatchDescriber>[];
     if (requiredFields != null) {
       requiredFields.forEach((String key, Matcher valueMatcher) {
         if (!item.containsKey(key)) {
-          mismatches.add((Description mismatchDescription, bool verbose) =>
+          mismatches.add((Description mismatchDescription) =>
               mismatchDescription.add('is missing field ').addDescriptionOf(key).add(' ('
               ).addDescriptionOf(valueMatcher).add(')'));
         } else {
@@ -355,49 +346,16 @@
       } else if (optionalFields != null && optionalFields.containsKey(key)) {
         _checkField(key, value, optionalFields[key], mismatches);
       } else {
-        mismatches.add((Description mismatchDescription, bool verbose) =>
+        mismatches.add((Description mismatchDescription) =>
             mismatchDescription.add('has unexpected field ').addDescriptionOf(key));
       }
     });
-    if (mismatches.isEmpty) {
-      return true;
-    } else {
-      addStateInfo(matchState, {
-        'mismatches': mismatches
-      });
-      return false;
-    }
   }
 
   @override
   Description describe(Description description) => description.add(
       this.description);
 
-  @override
-  Description describeMismatch(item, Description mismatchDescription, Map
-      matchState, bool verbose) {
-    List<MismatchDescriber> mismatches = matchState['mismatches'];
-    if (mismatches != null) {
-      for (int i = 0; i < mismatches.length; i++) {
-        MismatchDescriber mismatch = mismatches[i];
-        if (i > 0) {
-          if (mismatches.length == 2) {
-            mismatchDescription = mismatchDescription.add(' and ');
-          } else if (i == mismatches.length - 1) {
-            mismatchDescription = mismatchDescription.add(', and ');
-          } else {
-            mismatchDescription = mismatchDescription.add(', ');
-          }
-        }
-        mismatchDescription = mismatch(mismatchDescription, verbose);
-      }
-      return mismatchDescription;
-    } else {
-      return super.describeMismatch(item, mismatchDescription, matchState,
-          verbose);
-    }
-  }
-
   /**
    * Check the type of a field called [key], having value [value], using
    * [valueMatcher].  If it doesn't match, record a closure in [mismatches]
@@ -405,21 +363,8 @@
    */
   void _checkField(String key, value, Matcher
       valueMatcher, List<MismatchDescriber> mismatches) {
-    Map subState = {};
-    if (!valueMatcher.matches(value, subState)) {
-      mismatches.add((Description mismatchDescription, bool verbose) {
-        mismatchDescription = mismatchDescription.add(
-            'contains malformed field ').addDescriptionOf(key).add(' (should be '
-            ).addDescriptionOf(valueMatcher);
-        String subDescription = valueMatcher.describeMismatch(value,
-            new StringDescription(), subState, false).toString();
-        if (subDescription.isNotEmpty) {
-          mismatchDescription = mismatchDescription.add('; ').add(subDescription
-              );
-        }
-        return mismatchDescription.add(')');
-      });
-    }
+    checkSubstructure(value, valueMatcher, mismatches, (Description description)
+        => description.add('field ').addDescriptionOf(key));
   }
 }
 
@@ -470,6 +415,45 @@
 Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher);
 
 /**
+ * Matcher that matches a map of objects, where each key/value pair in the
+ * map satisies the given key and value matchers.
+ */
+class _MapOf extends _RecursiveMatcher {
+  /**
+   * Matcher which every key in the map must satisfy.
+   */
+  final Matcher keyMatcher;
+
+  /**
+   * Matcher which every value in the map must satisfy.
+   */
+  final Matcher valueMatcher;
+
+  _MapOf(this.keyMatcher, this.valueMatcher);
+
+  @override
+  void populateMismatches(item, List<MismatchDescriber> mismatches) {
+    if (item is! Map) {
+      mismatches.add(simpleDescription('is not a map'));
+      return;
+    }
+    item.forEach((key, value) {
+      checkSubstructure(key, keyMatcher, mismatches, (Description description)
+          => description.add('key ').addDescriptionOf(key));
+      checkSubstructure(value, valueMatcher, mismatches, (Description
+          description) => description.add('field ').addDescriptionOf(key));
+    });
+  }
+
+  @override
+  Description describe(Description description) => description.add('Map from '
+      ).addDescriptionOf(keyMatcher).add(' to ').addDescriptionOf(valueMatcher);
+}
+
+Matcher isMapOf(Matcher keyMatcher, Matcher valueMatcher) => new _MapOf(
+    keyMatcher, valueMatcher);
+
+/**
  * Instances of the class [Server] manage a connection to a server process, and
  * facilitate communication to and from the server.
  */
@@ -568,6 +552,7 @@
     if (Platform.packageRoot.isNotEmpty) {
       arguments.add('--package-root=${Platform.packageRoot}');
     }
+    arguments.add('--checked');
     arguments.add(serverPath);
     return Process.start(dartBinary, arguments).then((Process process) {
       _process = process;
diff --git a/pkg/analysis_server/test/integration/protocol_matchers.dart b/pkg/analysis_server/test/integration/protocol_matchers.dart
new file mode 100644
index 0000000..393aae3
--- /dev/null
+++ b/pkg/analysis_server/test/integration/protocol_matchers.dart
@@ -0,0 +1,1850 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// This file has been automatically generated.  Please do not edit it manually.
+// To regenerate the file, use the script
+// "pkg/analysis_server/spec/generate_files".
+
+/**
+ * Matchers for data types defined in the analysis server API
+ */
+library test.integration.protocol.matchers;
+
+import 'package:unittest/unittest.dart';
+
+import 'integration_tests.dart';
+
+
+/**
+ * server.getVersion params
+ */
+final Matcher isServerGetVersionParams = isNull;
+
+/**
+ * server.getVersion result
+ *
+ * {
+ *   "version": String
+ * }
+ */
+final Matcher isServerGetVersionResult = new MatchesJsonObject(
+  "server.getVersion result", {
+    "version": isString
+  });
+
+/**
+ * server.shutdown params
+ */
+final Matcher isServerShutdownParams = isNull;
+
+/**
+ * server.shutdown result
+ */
+final Matcher isServerShutdownResult = isNull;
+
+/**
+ * server.setSubscriptions params
+ *
+ * {
+ *   "subscriptions": List<ServerService>
+ * }
+ */
+final Matcher isServerSetSubscriptionsParams = new MatchesJsonObject(
+  "server.setSubscriptions params", {
+    "subscriptions": isListOf(isServerService)
+  });
+
+/**
+ * server.setSubscriptions result
+ */
+final Matcher isServerSetSubscriptionsResult = isNull;
+
+/**
+ * server.connected params
+ */
+final Matcher isServerConnectedParams = isNull;
+
+/**
+ * server.error params
+ *
+ * {
+ *   "fatal": bool
+ *   "message": String
+ *   "stackTrace": String
+ * }
+ */
+final Matcher isServerErrorParams = new MatchesJsonObject(
+  "server.error params", {
+    "fatal": isBool,
+    "message": isString,
+    "stackTrace": isString
+  });
+
+/**
+ * server.status params
+ *
+ * {
+ *   "analysis": optional AnalysisStatus
+ * }
+ */
+final Matcher isServerStatusParams = new MatchesJsonObject(
+  "server.status params", null, optionalFields: {
+    "analysis": isAnalysisStatus
+  });
+
+/**
+ * analysis.getErrors params
+ *
+ * {
+ *   "file": FilePath
+ * }
+ */
+final Matcher isAnalysisGetErrorsParams = new MatchesJsonObject(
+  "analysis.getErrors params", {
+    "file": isFilePath
+  });
+
+/**
+ * analysis.getErrors result
+ *
+ * {
+ *   "errors": List<AnalysisError>
+ * }
+ */
+final Matcher isAnalysisGetErrorsResult = new MatchesJsonObject(
+  "analysis.getErrors result", {
+    "errors": isListOf(isAnalysisError)
+  });
+
+/**
+ * analysis.getHover params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ * }
+ */
+final Matcher isAnalysisGetHoverParams = new MatchesJsonObject(
+  "analysis.getHover params", {
+    "file": isFilePath,
+    "offset": isInt
+  });
+
+/**
+ * analysis.getHover result
+ *
+ * {
+ *   "hovers": List<HoverInformation>
+ * }
+ */
+final Matcher isAnalysisGetHoverResult = new MatchesJsonObject(
+  "analysis.getHover result", {
+    "hovers": isListOf(isHoverInformation)
+  });
+
+/**
+ * analysis.reanalyze params
+ */
+final Matcher isAnalysisReanalyzeParams = isNull;
+
+/**
+ * analysis.reanalyze result
+ */
+final Matcher isAnalysisReanalyzeResult = isNull;
+
+/**
+ * analysis.setAnalysisRoots params
+ *
+ * {
+ *   "included": List<FilePath>
+ *   "excluded": List<FilePath>
+ * }
+ */
+final Matcher isAnalysisSetAnalysisRootsParams = new MatchesJsonObject(
+  "analysis.setAnalysisRoots params", {
+    "included": isListOf(isFilePath),
+    "excluded": isListOf(isFilePath)
+  });
+
+/**
+ * analysis.setAnalysisRoots result
+ */
+final Matcher isAnalysisSetAnalysisRootsResult = isNull;
+
+/**
+ * analysis.setPriorityFiles params
+ *
+ * {
+ *   "files": List<FilePath>
+ * }
+ */
+final Matcher isAnalysisSetPriorityFilesParams = new MatchesJsonObject(
+  "analysis.setPriorityFiles params", {
+    "files": isListOf(isFilePath)
+  });
+
+/**
+ * analysis.setPriorityFiles result
+ */
+final Matcher isAnalysisSetPriorityFilesResult = isNull;
+
+/**
+ * analysis.setSubscriptions params
+ *
+ * {
+ *   "subscriptions": Map<AnalysisService, List<FilePath>>
+ * }
+ */
+final Matcher isAnalysisSetSubscriptionsParams = new MatchesJsonObject(
+  "analysis.setSubscriptions params", {
+    "subscriptions": isMapOf(isAnalysisService, isListOf(isFilePath))
+  });
+
+/**
+ * analysis.setSubscriptions result
+ */
+final Matcher isAnalysisSetSubscriptionsResult = isNull;
+
+/**
+ * analysis.updateContent params
+ *
+ * {
+ *   "files": Map<FilePath, ContentChange>
+ * }
+ */
+final Matcher isAnalysisUpdateContentParams = new MatchesJsonObject(
+  "analysis.updateContent params", {
+    "files": isMapOf(isFilePath, isContentChange)
+  });
+
+/**
+ * analysis.updateContent result
+ */
+final Matcher isAnalysisUpdateContentResult = isNull;
+
+/**
+ * analysis.updateOptions params
+ *
+ * {
+ *   "options": AnalysisOptions
+ * }
+ */
+final Matcher isAnalysisUpdateOptionsParams = new MatchesJsonObject(
+  "analysis.updateOptions params", {
+    "options": isAnalysisOptions
+  });
+
+/**
+ * analysis.updateOptions result
+ */
+final Matcher isAnalysisUpdateOptionsResult = isNull;
+
+/**
+ * analysis.errors params
+ *
+ * {
+ *   "file": FilePath
+ *   "errors": List<AnalysisError>
+ * }
+ */
+final Matcher isAnalysisErrorsParams = new MatchesJsonObject(
+  "analysis.errors params", {
+    "file": isFilePath,
+    "errors": isListOf(isAnalysisError)
+  });
+
+/**
+ * analysis.flushResults params
+ *
+ * {
+ *   "files": List<FilePath>
+ * }
+ */
+final Matcher isAnalysisFlushResultsParams = new MatchesJsonObject(
+  "analysis.flushResults params", {
+    "files": isListOf(isFilePath)
+  });
+
+/**
+ * analysis.folding params
+ *
+ * {
+ *   "file": FilePath
+ *   "regions": List<FoldingRegion>
+ * }
+ */
+final Matcher isAnalysisFoldingParams = new MatchesJsonObject(
+  "analysis.folding params", {
+    "file": isFilePath,
+    "regions": isListOf(isFoldingRegion)
+  });
+
+/**
+ * analysis.highlights params
+ *
+ * {
+ *   "file": FilePath
+ *   "regions": List<HighlightRegion>
+ * }
+ */
+final Matcher isAnalysisHighlightsParams = new MatchesJsonObject(
+  "analysis.highlights params", {
+    "file": isFilePath,
+    "regions": isListOf(isHighlightRegion)
+  });
+
+/**
+ * analysis.navigation params
+ *
+ * {
+ *   "file": FilePath
+ *   "regions": List<NavigationRegion>
+ * }
+ */
+final Matcher isAnalysisNavigationParams = new MatchesJsonObject(
+  "analysis.navigation params", {
+    "file": isFilePath,
+    "regions": isListOf(isNavigationRegion)
+  });
+
+/**
+ * analysis.occurrences params
+ *
+ * {
+ *   "file": FilePath
+ *   "occurrences": List<Occurrences>
+ * }
+ */
+final Matcher isAnalysisOccurrencesParams = new MatchesJsonObject(
+  "analysis.occurrences params", {
+    "file": isFilePath,
+    "occurrences": isListOf(isOccurrences)
+  });
+
+/**
+ * analysis.outline params
+ *
+ * {
+ *   "file": FilePath
+ *   "outline": Outline
+ * }
+ */
+final Matcher isAnalysisOutlineParams = new MatchesJsonObject(
+  "analysis.outline params", {
+    "file": isFilePath,
+    "outline": isOutline
+  });
+
+/**
+ * analysis.overrides params
+ *
+ * {
+ *   "file": FilePath
+ *   "overrides": List<Override>
+ * }
+ */
+final Matcher isAnalysisOverridesParams = new MatchesJsonObject(
+  "analysis.overrides params", {
+    "file": isFilePath,
+    "overrides": isListOf(isOverride)
+  });
+
+/**
+ * completion.getSuggestions params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ * }
+ */
+final Matcher isCompletionGetSuggestionsParams = new MatchesJsonObject(
+  "completion.getSuggestions params", {
+    "file": isFilePath,
+    "offset": isInt
+  });
+
+/**
+ * completion.getSuggestions result
+ *
+ * {
+ *   "id": CompletionId
+ * }
+ */
+final Matcher isCompletionGetSuggestionsResult = new MatchesJsonObject(
+  "completion.getSuggestions result", {
+    "id": isCompletionId
+  });
+
+/**
+ * completion.results params
+ *
+ * {
+ *   "id": CompletionId
+ *   "replacementOffset": int
+ *   "replacementLength": int
+ *   "results": List<CompletionSuggestion>
+ *   "last": bool
+ * }
+ */
+final Matcher isCompletionResultsParams = new MatchesJsonObject(
+  "completion.results params", {
+    "id": isCompletionId,
+    "replacementOffset": isInt,
+    "replacementLength": isInt,
+    "results": isListOf(isCompletionSuggestion),
+    "last": isBool
+  });
+
+/**
+ * search.findElementReferences params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ *   "includePotential": bool
+ * }
+ */
+final Matcher isSearchFindElementReferencesParams = new MatchesJsonObject(
+  "search.findElementReferences params", {
+    "file": isFilePath,
+    "offset": isInt,
+    "includePotential": isBool
+  });
+
+/**
+ * search.findElementReferences result
+ *
+ * {
+ *   "id": SearchId
+ *   "element": Element
+ * }
+ */
+final Matcher isSearchFindElementReferencesResult = new MatchesJsonObject(
+  "search.findElementReferences result", {
+    "id": isSearchId,
+    "element": isElement
+  });
+
+/**
+ * search.findMemberDeclarations params
+ *
+ * {
+ *   "name": String
+ * }
+ */
+final Matcher isSearchFindMemberDeclarationsParams = new MatchesJsonObject(
+  "search.findMemberDeclarations params", {
+    "name": isString
+  });
+
+/**
+ * search.findMemberDeclarations result
+ *
+ * {
+ *   "id": SearchId
+ * }
+ */
+final Matcher isSearchFindMemberDeclarationsResult = new MatchesJsonObject(
+  "search.findMemberDeclarations result", {
+    "id": isSearchId
+  });
+
+/**
+ * search.findMemberReferences params
+ *
+ * {
+ *   "name": String
+ * }
+ */
+final Matcher isSearchFindMemberReferencesParams = new MatchesJsonObject(
+  "search.findMemberReferences params", {
+    "name": isString
+  });
+
+/**
+ * search.findMemberReferences result
+ *
+ * {
+ *   "id": SearchId
+ * }
+ */
+final Matcher isSearchFindMemberReferencesResult = new MatchesJsonObject(
+  "search.findMemberReferences result", {
+    "id": isSearchId
+  });
+
+/**
+ * search.findTopLevelDeclarations params
+ *
+ * {
+ *   "pattern": String
+ * }
+ */
+final Matcher isSearchFindTopLevelDeclarationsParams = new MatchesJsonObject(
+  "search.findTopLevelDeclarations params", {
+    "pattern": isString
+  });
+
+/**
+ * search.findTopLevelDeclarations result
+ *
+ * {
+ *   "id": SearchId
+ * }
+ */
+final Matcher isSearchFindTopLevelDeclarationsResult = new MatchesJsonObject(
+  "search.findTopLevelDeclarations result", {
+    "id": isSearchId
+  });
+
+/**
+ * search.getTypeHierarchy params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ * }
+ */
+final Matcher isSearchGetTypeHierarchyParams = new MatchesJsonObject(
+  "search.getTypeHierarchy params", {
+    "file": isFilePath,
+    "offset": isInt
+  });
+
+/**
+ * search.getTypeHierarchy result
+ *
+ * {
+ *   "hierarchyItems": List<TypeHierarchyItem>
+ * }
+ */
+final Matcher isSearchGetTypeHierarchyResult = new MatchesJsonObject(
+  "search.getTypeHierarchy result", {
+    "hierarchyItems": isListOf(isTypeHierarchyItem)
+  });
+
+/**
+ * search.results params
+ *
+ * {
+ *   "id": SearchId
+ *   "results": List<SearchResult>
+ *   "last": bool
+ * }
+ */
+final Matcher isSearchResultsParams = new MatchesJsonObject(
+  "search.results params", {
+    "id": isSearchId,
+    "results": isListOf(isSearchResult),
+    "last": isBool
+  });
+
+/**
+ * edit.getAssists params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ *   "length": int
+ * }
+ */
+final Matcher isEditGetAssistsParams = new MatchesJsonObject(
+  "edit.getAssists params", {
+    "file": isFilePath,
+    "offset": isInt,
+    "length": isInt
+  });
+
+/**
+ * edit.getAssists result
+ *
+ * {
+ *   "assists": List<SourceChange>
+ * }
+ */
+final Matcher isEditGetAssistsResult = new MatchesJsonObject(
+  "edit.getAssists result", {
+    "assists": isListOf(isSourceChange)
+  });
+
+/**
+ * edit.getAvailableRefactorings params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ *   "length": int
+ * }
+ */
+final Matcher isEditGetAvailableRefactoringsParams = new MatchesJsonObject(
+  "edit.getAvailableRefactorings params", {
+    "file": isFilePath,
+    "offset": isInt,
+    "length": isInt
+  });
+
+/**
+ * edit.getAvailableRefactorings result
+ *
+ * {
+ *   "kinds": List<RefactoringKind>
+ * }
+ */
+final Matcher isEditGetAvailableRefactoringsResult = new MatchesJsonObject(
+  "edit.getAvailableRefactorings result", {
+    "kinds": isListOf(isRefactoringKind)
+  });
+
+/**
+ * edit.getFixes params
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ * }
+ */
+final Matcher isEditGetFixesParams = new MatchesJsonObject(
+  "edit.getFixes params", {
+    "file": isFilePath,
+    "offset": isInt
+  });
+
+/**
+ * edit.getFixes result
+ *
+ * {
+ *   "fixes": List<ErrorFixes>
+ * }
+ */
+final Matcher isEditGetFixesResult = new MatchesJsonObject(
+  "edit.getFixes result", {
+    "fixes": isListOf(isErrorFixes)
+  });
+
+/**
+ * edit.getRefactoring params
+ *
+ * {
+ *   "kindId": RefactoringKind
+ *   "file": FilePath
+ *   "offset": int
+ *   "length": int
+ *   "validateOnly": bool
+ *   "options": optional object
+ * }
+ */
+final Matcher isEditGetRefactoringParams = new MatchesJsonObject(
+  "edit.getRefactoring params", {
+    "kindId": isRefactoringKind,
+    "file": isFilePath,
+    "offset": isInt,
+    "length": isInt,
+    "validateOnly": isBool
+  }, optionalFields: {
+    "options": isObject
+  });
+
+/**
+ * edit.getRefactoring result
+ *
+ * {
+ *   "status": List<RefactoringProblem>
+ *   "feedback": optional object
+ *   "change": optional SourceChange
+ * }
+ */
+final Matcher isEditGetRefactoringResult = new MatchesJsonObject(
+  "edit.getRefactoring result", {
+    "status": isListOf(isRefactoringProblem)
+  }, optionalFields: {
+    "feedback": isObject,
+    "change": isSourceChange
+  });
+
+/**
+ * debug.createContext params
+ *
+ * {
+ *   "contextRoot": FilePath
+ * }
+ */
+final Matcher isDebugCreateContextParams = new MatchesJsonObject(
+  "debug.createContext params", {
+    "contextRoot": isFilePath
+  });
+
+/**
+ * debug.createContext result
+ *
+ * {
+ *   "id": DebugContextId
+ * }
+ */
+final Matcher isDebugCreateContextResult = new MatchesJsonObject(
+  "debug.createContext result", {
+    "id": isDebugContextId
+  });
+
+/**
+ * debug.deleteContext params
+ *
+ * {
+ *   "id": DebugContextId
+ * }
+ */
+final Matcher isDebugDeleteContextParams = new MatchesJsonObject(
+  "debug.deleteContext params", {
+    "id": isDebugContextId
+  });
+
+/**
+ * debug.deleteContext result
+ */
+final Matcher isDebugDeleteContextResult = isNull;
+
+/**
+ * debug.mapUri params
+ *
+ * {
+ *   "id": DebugContextId
+ *   "file": optional FilePath
+ *   "uri": optional String
+ * }
+ */
+final Matcher isDebugMapUriParams = new MatchesJsonObject(
+  "debug.mapUri params", {
+    "id": isDebugContextId
+  }, optionalFields: {
+    "file": isFilePath,
+    "uri": isString
+  });
+
+/**
+ * debug.mapUri result
+ *
+ * {
+ *   "file": optional FilePath
+ *   "uri": optional String
+ * }
+ */
+final Matcher isDebugMapUriResult = new MatchesJsonObject(
+  "debug.mapUri result", null, optionalFields: {
+    "file": isFilePath,
+    "uri": isString
+  });
+
+/**
+ * debug.setSubscriptions params
+ *
+ * {
+ *   "subscriptions": List<DebugService>
+ * }
+ */
+final Matcher isDebugSetSubscriptionsParams = new MatchesJsonObject(
+  "debug.setSubscriptions params", {
+    "subscriptions": isListOf(isDebugService)
+  });
+
+/**
+ * debug.setSubscriptions result
+ */
+final Matcher isDebugSetSubscriptionsResult = isNull;
+
+/**
+ * debug.launchData params
+ *
+ * {
+ *   "executables": List<ExecutableFile>
+ *   "dartToHtml": Map<FilePath, List<FilePath>>
+ *   "htmlToDart": Map<FilePath, List<FilePath>>
+ * }
+ */
+final Matcher isDebugLaunchDataParams = new MatchesJsonObject(
+  "debug.launchData params", {
+    "executables": isListOf(isExecutableFile),
+    "dartToHtml": isMapOf(isFilePath, isListOf(isFilePath)),
+    "htmlToDart": isMapOf(isFilePath, isListOf(isFilePath))
+  });
+
+/**
+ * AnalysisError
+ *
+ * {
+ *   "severity": ErrorSeverity
+ *   "type": ErrorType
+ *   "location": Location
+ *   "message": String
+ *   "correction": optional String
+ * }
+ */
+final Matcher isAnalysisError = new MatchesJsonObject(
+  "AnalysisError", {
+    "severity": isErrorSeverity,
+    "type": isErrorType,
+    "location": isLocation,
+    "message": isString
+  }, optionalFields: {
+    "correction": isString
+  });
+
+/**
+ * AnalysisOptions
+ *
+ * {
+ *   "analyzeAngular": optional bool
+ *   "analyzePolymer": optional bool
+ *   "enableAsync": optional bool
+ *   "enableDeferredLoading": optional bool
+ *   "enableEnums": optional bool
+ *   "generateDart2jsHints": optional bool
+ *   "generateHints": optional bool
+ * }
+ */
+final Matcher isAnalysisOptions = new MatchesJsonObject(
+  "AnalysisOptions", null, optionalFields: {
+    "analyzeAngular": isBool,
+    "analyzePolymer": isBool,
+    "enableAsync": isBool,
+    "enableDeferredLoading": isBool,
+    "enableEnums": isBool,
+    "generateDart2jsHints": isBool,
+    "generateHints": isBool
+  });
+
+/**
+ * AnalysisService
+ *
+ * enum {
+ *   FOLDING
+ *   HIGHLIGHTS
+ *   NAVIGATION
+ *   OCCURRENCES
+ *   OUTLINE
+ *   OVERRIDES
+ * }
+ */
+final Matcher isAnalysisService = isIn([
+  "FOLDING",
+  "HIGHLIGHTS",
+  "NAVIGATION",
+  "OCCURRENCES",
+  "OUTLINE",
+  "OVERRIDES"
+]);
+
+/**
+ * AnalysisStatus
+ *
+ * {
+ *   "analyzing": bool
+ *   "analysisTarget": optional String
+ * }
+ */
+final Matcher isAnalysisStatus = new MatchesJsonObject(
+  "AnalysisStatus", {
+    "analyzing": isBool
+  }, optionalFields: {
+    "analysisTarget": isString
+  });
+
+/**
+ * CompletionId
+ *
+ * String
+ */
+final Matcher isCompletionId = isString;
+
+/**
+ * CompletionRelevance
+ *
+ * enum {
+ *   LOW
+ *   DEFAULT
+ *   HIGH
+ * }
+ */
+final Matcher isCompletionRelevance = isIn([
+  "LOW",
+  "DEFAULT",
+  "HIGH"
+]);
+
+/**
+ * CompletionSuggestion
+ *
+ * {
+ *   "kind": CompletionSuggestionKind
+ *   "relevance": CompletionRelevance
+ *   "completion": String
+ *   "selectionOffset": int
+ *   "selectionLength": int
+ *   "isDeprecated": bool
+ *   "isPotential": bool
+ *   "docSummary": optional String
+ *   "docComplete": optional String
+ *   "declaringType": optional String
+ *   "returnType": optional String
+ *   "parameterNames": optional List<String>
+ *   "parameterTypes": optional List<String>
+ *   "requiredParameterCount": optional int
+ *   "positionalParameterCount": optional int
+ *   "parameterName": optional String
+ *   "parameterType": optional String
+ * }
+ */
+final Matcher isCompletionSuggestion = new MatchesJsonObject(
+  "CompletionSuggestion", {
+    "kind": isCompletionSuggestionKind,
+    "relevance": isCompletionRelevance,
+    "completion": isString,
+    "selectionOffset": isInt,
+    "selectionLength": isInt,
+    "isDeprecated": isBool,
+    "isPotential": isBool
+  }, optionalFields: {
+    "docSummary": isString,
+    "docComplete": isString,
+    "declaringType": isString,
+    "returnType": isString,
+    "parameterNames": isListOf(isString),
+    "parameterTypes": isListOf(isString),
+    "requiredParameterCount": isInt,
+    "positionalParameterCount": isInt,
+    "parameterName": isString,
+    "parameterType": isString
+  });
+
+/**
+ * CompletionSuggestionKind
+ *
+ * enum {
+ *   ARGUMENT_LIST
+ *   CLASS
+ *   CLASS_ALIAS
+ *   CONSTRUCTOR
+ *   FIELD
+ *   FUNCTION
+ *   FUNCTION_TYPE_ALIAS
+ *   GETTER
+ *   IMPORT
+ *   LIBRARY_PREFIX
+ *   LOCAL_VARIABLE
+ *   METHOD
+ *   METHOD_NAME
+ *   NAMED_ARGUMENT
+ *   OPTIONAL_ARGUMENT
+ *   PARAMETER
+ *   SETTER
+ *   TOP_LEVEL_VARIABLE
+ *   TYPE_PARAMETER
+ * }
+ */
+final Matcher isCompletionSuggestionKind = isIn([
+  "ARGUMENT_LIST",
+  "CLASS",
+  "CLASS_ALIAS",
+  "CONSTRUCTOR",
+  "FIELD",
+  "FUNCTION",
+  "FUNCTION_TYPE_ALIAS",
+  "GETTER",
+  "IMPORT",
+  "LIBRARY_PREFIX",
+  "LOCAL_VARIABLE",
+  "METHOD",
+  "METHOD_NAME",
+  "NAMED_ARGUMENT",
+  "OPTIONAL_ARGUMENT",
+  "PARAMETER",
+  "SETTER",
+  "TOP_LEVEL_VARIABLE",
+  "TYPE_PARAMETER"
+]);
+
+/**
+ * ContentChange
+ *
+ * {
+ *   "content": String
+ *   "offset": optional int
+ *   "oldLength": optional int
+ *   "newLength": optional int
+ * }
+ */
+final Matcher isContentChange = new MatchesJsonObject(
+  "ContentChange", {
+    "content": isString
+  }, optionalFields: {
+    "offset": isInt,
+    "oldLength": isInt,
+    "newLength": isInt
+  });
+
+/**
+ * DebugContextId
+ *
+ * String
+ */
+final Matcher isDebugContextId = isString;
+
+/**
+ * DebugService
+ *
+ * enum {
+ *   LAUNCH_DATA
+ * }
+ */
+final Matcher isDebugService = isIn([
+  "LAUNCH_DATA"
+]);
+
+/**
+ * Element
+ *
+ * {
+ *   "kind": ElementKind
+ *   "name": String
+ *   "location": optional Location
+ *   "flags": int
+ *   "parameters": optional String
+ *   "returnType": optional String
+ * }
+ */
+final Matcher isElement = new MatchesJsonObject(
+  "Element", {
+    "kind": isElementKind,
+    "name": isString,
+    "flags": isInt
+  }, optionalFields: {
+    "location": isLocation,
+    "parameters": isString,
+    "returnType": isString
+  });
+
+/**
+ * ElementKind
+ *
+ * enum {
+ *   CLASS
+ *   CLASS_TYPE_ALIAS
+ *   COMPILATION_UNIT
+ *   CONSTRUCTOR
+ *   GETTER
+ *   FIELD
+ *   FUNCTION
+ *   FUNCTION_TYPE_ALIAS
+ *   LIBRARY
+ *   LOCAL_VARIABLE
+ *   METHOD
+ *   SETTER
+ *   TOP_LEVEL_VARIABLE
+ *   TYPE_PARAMETER
+ *   UNKNOWN
+ *   UNIT_TEST_GROUP
+ *   UNIT_TEST_TEST
+ * }
+ */
+final Matcher isElementKind = isIn([
+  "CLASS",
+  "CLASS_TYPE_ALIAS",
+  "COMPILATION_UNIT",
+  "CONSTRUCTOR",
+  "GETTER",
+  "FIELD",
+  "FUNCTION",
+  "FUNCTION_TYPE_ALIAS",
+  "LIBRARY",
+  "LOCAL_VARIABLE",
+  "METHOD",
+  "SETTER",
+  "TOP_LEVEL_VARIABLE",
+  "TYPE_PARAMETER",
+  "UNKNOWN",
+  "UNIT_TEST_GROUP",
+  "UNIT_TEST_TEST"
+]);
+
+/**
+ * Error
+ *
+ * {
+ *   "code": String
+ *   "message": String
+ *   "data": optional object
+ * }
+ */
+final Matcher isError = new MatchesJsonObject(
+  "Error", {
+    "code": isString,
+    "message": isString
+  }, optionalFields: {
+    "data": isObject
+  });
+
+/**
+ * ErrorFixes
+ *
+ * {
+ *   "error": AnalysisError
+ *   "fixes": List<SourceChange>
+ * }
+ */
+final Matcher isErrorFixes = new MatchesJsonObject(
+  "ErrorFixes", {
+    "error": isAnalysisError,
+    "fixes": isListOf(isSourceChange)
+  });
+
+/**
+ * ErrorSeverity
+ *
+ * enum {
+ *   INFO
+ *   WARNING
+ *   ERROR
+ * }
+ */
+final Matcher isErrorSeverity = isIn([
+  "INFO",
+  "WARNING",
+  "ERROR"
+]);
+
+/**
+ * ErrorType
+ *
+ * enum {
+ *   COMPILE_TIME_ERROR
+ *   HINT
+ *   STATIC_TYPE_WARNING
+ *   STATIC_WARNING
+ *   SYNTACTIC_ERROR
+ *   TODO
+ * }
+ */
+final Matcher isErrorType = isIn([
+  "COMPILE_TIME_ERROR",
+  "HINT",
+  "STATIC_TYPE_WARNING",
+  "STATIC_WARNING",
+  "SYNTACTIC_ERROR",
+  "TODO"
+]);
+
+/**
+ * ExecutableFile
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": ExecutableKind
+ * }
+ */
+final Matcher isExecutableFile = new MatchesJsonObject(
+  "ExecutableFile", {
+    "file": isFilePath,
+    "offset": isExecutableKind
+  });
+
+/**
+ * ExecutableKind
+ *
+ * enum {
+ *   CLIENT
+ *   EITHER
+ *   SERVER
+ * }
+ */
+final Matcher isExecutableKind = isIn([
+  "CLIENT",
+  "EITHER",
+  "SERVER"
+]);
+
+/**
+ * FilePath
+ *
+ * String
+ */
+final Matcher isFilePath = isString;
+
+/**
+ * FoldingKind
+ *
+ * enum {
+ *   COMMENT
+ *   CLASS_MEMBER
+ *   DIRECTIVES
+ *   DOCUMENTATION_COMMENT
+ *   TOP_LEVEL_DECLARATION
+ * }
+ */
+final Matcher isFoldingKind = isIn([
+  "COMMENT",
+  "CLASS_MEMBER",
+  "DIRECTIVES",
+  "DOCUMENTATION_COMMENT",
+  "TOP_LEVEL_DECLARATION"
+]);
+
+/**
+ * FoldingRegion
+ *
+ * {
+ *   "kind": FoldingKind
+ *   "offset": int
+ *   "length": int
+ * }
+ */
+final Matcher isFoldingRegion = new MatchesJsonObject(
+  "FoldingRegion", {
+    "kind": isFoldingKind,
+    "offset": isInt,
+    "length": isInt
+  });
+
+/**
+ * HighlightRegion
+ *
+ * {
+ *   "type": HighlightRegionType
+ *   "offset": int
+ *   "length": int
+ * }
+ */
+final Matcher isHighlightRegion = new MatchesJsonObject(
+  "HighlightRegion", {
+    "type": isHighlightRegionType,
+    "offset": isInt,
+    "length": isInt
+  });
+
+/**
+ * HighlightRegionType
+ *
+ * enum {
+ *   ANNOTATION
+ *   BUILT_IN
+ *   CLASS
+ *   COMMENT_BLOCK
+ *   COMMENT_DOCUMENTATION
+ *   COMMENT_END_OF_LINE
+ *   CONSTRUCTOR
+ *   DIRECTIVE
+ *   DYNAMIC_TYPE
+ *   FIELD
+ *   FIELD_STATIC
+ *   FUNCTION_DECLARATION
+ *   FUNCTION
+ *   FUNCTION_TYPE_ALIAS
+ *   GETTER_DECLARATION
+ *   KEYWORD
+ *   IDENTIFIER_DEFAULT
+ *   IMPORT_PREFIX
+ *   LITERAL_BOOLEAN
+ *   LITERAL_DOUBLE
+ *   LITERAL_INTEGER
+ *   LITERAL_LIST
+ *   LITERAL_MAP
+ *   LITERAL_STRING
+ *   LOCAL_VARIABLE_DECLARATION
+ *   LOCAL_VARIABLE
+ *   METHOD_DECLARATION
+ *   METHOD_DECLARATION_STATIC
+ *   METHOD
+ *   METHOD_STATIC
+ *   PARAMETER
+ *   SETTER_DECLARATION
+ *   TOP_LEVEL_VARIABLE
+ *   TYPE_NAME_DYNAMIC
+ *   TYPE_PARAMETER
+ * }
+ */
+final Matcher isHighlightRegionType = isIn([
+  "ANNOTATION",
+  "BUILT_IN",
+  "CLASS",
+  "COMMENT_BLOCK",
+  "COMMENT_DOCUMENTATION",
+  "COMMENT_END_OF_LINE",
+  "CONSTRUCTOR",
+  "DIRECTIVE",
+  "DYNAMIC_TYPE",
+  "FIELD",
+  "FIELD_STATIC",
+  "FUNCTION_DECLARATION",
+  "FUNCTION",
+  "FUNCTION_TYPE_ALIAS",
+  "GETTER_DECLARATION",
+  "KEYWORD",
+  "IDENTIFIER_DEFAULT",
+  "IMPORT_PREFIX",
+  "LITERAL_BOOLEAN",
+  "LITERAL_DOUBLE",
+  "LITERAL_INTEGER",
+  "LITERAL_LIST",
+  "LITERAL_MAP",
+  "LITERAL_STRING",
+  "LOCAL_VARIABLE_DECLARATION",
+  "LOCAL_VARIABLE",
+  "METHOD_DECLARATION",
+  "METHOD_DECLARATION_STATIC",
+  "METHOD",
+  "METHOD_STATIC",
+  "PARAMETER",
+  "SETTER_DECLARATION",
+  "TOP_LEVEL_VARIABLE",
+  "TYPE_NAME_DYNAMIC",
+  "TYPE_PARAMETER"
+]);
+
+/**
+ * HoverInformation
+ *
+ * {
+ *   "offset": int
+ *   "length": int
+ *   "containingLibraryPath": optional String
+ *   "containingLibraryName": optional String
+ *   "dartdoc": optional String
+ *   "elementDescription": optional String
+ *   "elementKind": optional String
+ *   "parameter": optional String
+ *   "propagatedType": optional String
+ *   "staticType": optional String
+ * }
+ */
+final Matcher isHoverInformation = new MatchesJsonObject(
+  "HoverInformation", {
+    "offset": isInt,
+    "length": isInt
+  }, optionalFields: {
+    "containingLibraryPath": isString,
+    "containingLibraryName": isString,
+    "dartdoc": isString,
+    "elementDescription": isString,
+    "elementKind": isString,
+    "parameter": isString,
+    "propagatedType": isString,
+    "staticType": isString
+  });
+
+/**
+ * LinkedEditGroup
+ *
+ * {
+ *   "positions": List<Position>
+ *   "length": int
+ *   "suggestions": List<LinkedEditSuggestion>
+ * }
+ */
+final Matcher isLinkedEditGroup = new MatchesJsonObject(
+  "LinkedEditGroup", {
+    "positions": isListOf(isPosition),
+    "length": isInt,
+    "suggestions": isListOf(isLinkedEditSuggestion)
+  });
+
+/**
+ * LinkedEditSuggestion
+ *
+ * {
+ *   "value": String
+ *   "kind": LinkedEditSuggestionKind
+ * }
+ */
+final Matcher isLinkedEditSuggestion = new MatchesJsonObject(
+  "LinkedEditSuggestion", {
+    "value": isString,
+    "kind": isLinkedEditSuggestionKind
+  });
+
+/**
+ * LinkedEditSuggestionKind
+ *
+ * enum {
+ *   METHOD
+ *   PARAMETER
+ *   TYPE
+ *   VARIABLE
+ * }
+ */
+final Matcher isLinkedEditSuggestionKind = isIn([
+  "METHOD",
+  "PARAMETER",
+  "TYPE",
+  "VARIABLE"
+]);
+
+/**
+ * Location
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ *   "length": int
+ *   "startLine": int
+ *   "startColumn": int
+ * }
+ */
+final Matcher isLocation = new MatchesJsonObject(
+  "Location", {
+    "file": isFilePath,
+    "offset": isInt,
+    "length": isInt,
+    "startLine": isInt,
+    "startColumn": isInt
+  });
+
+/**
+ * NavigationRegion
+ *
+ * {
+ *   "offset": int
+ *   "length": int
+ *   "targets": List<Element>
+ * }
+ */
+final Matcher isNavigationRegion = new MatchesJsonObject(
+  "NavigationRegion", {
+    "offset": isInt,
+    "length": isInt,
+    "targets": isListOf(isElement)
+  });
+
+/**
+ * Occurrences
+ *
+ * {
+ *   "element": Element
+ *   "offsets": List<int>
+ *   "length": int
+ * }
+ */
+final Matcher isOccurrences = new MatchesJsonObject(
+  "Occurrences", {
+    "element": isElement,
+    "offsets": isListOf(isInt),
+    "length": isInt
+  });
+
+/**
+ * Outline
+ *
+ * {
+ *   "element": Element
+ *   "offset": int
+ *   "length": int
+ *   "children": optional List<Outline>
+ * }
+ */
+final Matcher isOutline = new MatchesJsonObject(
+  "Outline", {
+    "element": isElement,
+    "offset": isInt,
+    "length": isInt
+  }, optionalFields: {
+    "children": isListOf(isOutline)
+  });
+
+/**
+ * Override
+ *
+ * {
+ *   "offset": int
+ *   "length": int
+ *   "superclassMember": optional OverriddenMember
+ *   "interfaceMembers": optional List<OverriddenMember>
+ * }
+ */
+final Matcher isOverride = new MatchesJsonObject(
+  "Override", {
+    "offset": isInt,
+    "length": isInt
+  }, optionalFields: {
+    "superclassMember": isOverriddenMember,
+    "interfaceMembers": isListOf(isOverriddenMember)
+  });
+
+/**
+ * OverriddenMember
+ *
+ * {
+ *   "element": Element
+ *   "className": String
+ * }
+ */
+final Matcher isOverriddenMember = new MatchesJsonObject(
+  "OverriddenMember", {
+    "element": isElement,
+    "className": isString
+  });
+
+/**
+ * Parameter
+ *
+ * {
+ *   "type": String
+ *   "name": String
+ * }
+ */
+final Matcher isParameter = new MatchesJsonObject(
+  "Parameter", {
+    "type": isString,
+    "name": isString
+  });
+
+/**
+ * Position
+ *
+ * {
+ *   "file": FilePath
+ *   "offset": int
+ * }
+ */
+final Matcher isPosition = new MatchesJsonObject(
+  "Position", {
+    "file": isFilePath,
+    "offset": isInt
+  });
+
+/**
+ * RefactoringKind
+ *
+ * enum {
+ *   CONVERT_GETTER_TO_METHOD
+ *   CONVERT_METHOD_TO_GETTER
+ *   EXTRACT_LOCAL_VARIABLE
+ *   EXTRACT_METHOD
+ *   INLINE_LOCAL_VARIABLE
+ *   INLINE_METHOD
+ *   RENAME
+ * }
+ */
+final Matcher isRefactoringKind = isIn([
+  "CONVERT_GETTER_TO_METHOD",
+  "CONVERT_METHOD_TO_GETTER",
+  "EXTRACT_LOCAL_VARIABLE",
+  "EXTRACT_METHOD",
+  "INLINE_LOCAL_VARIABLE",
+  "INLINE_METHOD",
+  "RENAME"
+]);
+
+/**
+ * RefactoringProblem
+ *
+ * {
+ *   "severity": RefactoringProblemSeverity
+ *   "message": String
+ *   "location": Location
+ * }
+ */
+final Matcher isRefactoringProblem = new MatchesJsonObject(
+  "RefactoringProblem", {
+    "severity": isRefactoringProblemSeverity,
+    "message": isString,
+    "location": isLocation
+  });
+
+/**
+ * RefactoringProblemSeverity
+ *
+ * enum {
+ *   INFO
+ *   WARNING
+ *   ERROR
+ *   FATAL
+ * }
+ */
+final Matcher isRefactoringProblemSeverity = isIn([
+  "INFO",
+  "WARNING",
+  "ERROR",
+  "FATAL"
+]);
+
+/**
+ * SearchId
+ *
+ * String
+ */
+final Matcher isSearchId = isString;
+
+/**
+ * SearchResult
+ *
+ * {
+ *   "location": Location
+ *   "kind": SearchResultKind
+ *   "isPotential": bool
+ *   "path": List<Element>
+ * }
+ */
+final Matcher isSearchResult = new MatchesJsonObject(
+  "SearchResult", {
+    "location": isLocation,
+    "kind": isSearchResultKind,
+    "isPotential": isBool,
+    "path": isListOf(isElement)
+  });
+
+/**
+ * SearchResultKind
+ *
+ * enum {
+ *   DECLARATION
+ *   INVOCATION
+ *   READ
+ *   READ_WRITE
+ *   REFERENCE
+ *   WRITE
+ * }
+ */
+final Matcher isSearchResultKind = isIn([
+  "DECLARATION",
+  "INVOCATION",
+  "READ",
+  "READ_WRITE",
+  "REFERENCE",
+  "WRITE"
+]);
+
+/**
+ * ServerService
+ *
+ * enum {
+ *   STATUS
+ * }
+ */
+final Matcher isServerService = isIn([
+  "STATUS"
+]);
+
+/**
+ * SourceChange
+ *
+ * {
+ *   "message": String
+ *   "edits": List<SourceFileEdit>
+ *   "linkedEditGroups": List<LinkedEditGroup>
+ *   "selection": optional Position
+ * }
+ */
+final Matcher isSourceChange = new MatchesJsonObject(
+  "SourceChange", {
+    "message": isString,
+    "edits": isListOf(isSourceFileEdit),
+    "linkedEditGroups": isListOf(isLinkedEditGroup)
+  }, optionalFields: {
+    "selection": isPosition
+  });
+
+/**
+ * SourceEdit
+ *
+ * {
+ *   "offset": int
+ *   "length": int
+ *   "replacement": String
+ * }
+ */
+final Matcher isSourceEdit = new MatchesJsonObject(
+  "SourceEdit", {
+    "offset": isInt,
+    "length": isInt,
+    "replacement": isString
+  });
+
+/**
+ * SourceFileEdit
+ *
+ * {
+ *   "file": FilePath
+ *   "edits": List<SourceEdit>
+ * }
+ */
+final Matcher isSourceFileEdit = new MatchesJsonObject(
+  "SourceFileEdit", {
+    "file": isFilePath,
+    "edits": isListOf(isSourceEdit)
+  });
+
+/**
+ * TypeHierarchyItem
+ *
+ * {
+ *   "classElement": Element
+ *   "displayName": optional String
+ *   "memberElement": optional Element
+ *   "superclass": optional int
+ *   "interfaces": List<int>
+ *   "mixins": List<int>
+ *   "subclasses": List<int>
+ * }
+ */
+final Matcher isTypeHierarchyItem = new MatchesJsonObject(
+  "TypeHierarchyItem", {
+    "classElement": isElement,
+    "interfaces": isListOf(isInt),
+    "mixins": isListOf(isInt),
+    "subclasses": isListOf(isInt)
+  }, optionalFields: {
+    "displayName": isString,
+    "memberElement": isElement,
+    "superclass": isInt
+  });
+
+/**
+ * convertGetterToMethod feedback
+ */
+final Matcher isConvertGetterToMethodFeedback = isNull;
+
+/**
+ * convertGetterToMethod options
+ */
+final Matcher isConvertGetterToMethodOptions = isNull;
+
+/**
+ * convertMethodToGetter feedback
+ */
+final Matcher isConvertMethodToGetterFeedback = isNull;
+
+/**
+ * convertMethodToGetter options
+ */
+final Matcher isConvertMethodToGetterOptions = isNull;
+
+/**
+ * extractLocalVariable feedback
+ *
+ * {
+ *   "names": List<String>
+ *   "offsets": List<int>
+ *   "lengths": List<int>
+ * }
+ */
+final Matcher isExtractLocalVariableFeedback = new MatchesJsonObject(
+  "extractLocalVariable feedback", {
+    "names": isListOf(isString),
+    "offsets": isListOf(isInt),
+    "lengths": isListOf(isInt)
+  });
+
+/**
+ * extractLocalVariable options
+ *
+ * {
+ *   "name": String
+ *   "extractAll": bool
+ * }
+ */
+final Matcher isExtractLocalVariableOptions = new MatchesJsonObject(
+  "extractLocalVariable options", {
+    "name": isString,
+    "extractAll": isBool
+  });
+
+/**
+ * extractMethod feedback
+ *
+ * {
+ *   "offset": int
+ *   "length": int
+ *   "returnType": String
+ *   "names": List<String>
+ *   "canCreateGetter": bool
+ *   "parameters": List<Parameter>
+ *   "occurrences": int
+ *   "offsets": List<int>
+ *   "lengths": List<int>
+ * }
+ */
+final Matcher isExtractMethodFeedback = new MatchesJsonObject(
+  "extractMethod feedback", {
+    "offset": isInt,
+    "length": isInt,
+    "returnType": isString,
+    "names": isListOf(isString),
+    "canCreateGetter": isBool,
+    "parameters": isListOf(isParameter),
+    "occurrences": isInt,
+    "offsets": isListOf(isInt),
+    "lengths": isListOf(isInt)
+  });
+
+/**
+ * extractMethod options
+ *
+ * {
+ *   "returnType": String
+ *   "createGetter": bool
+ *   "name": String
+ *   "parameters": List<Parameter>
+ *   "extractAll": bool
+ * }
+ */
+final Matcher isExtractMethodOptions = new MatchesJsonObject(
+  "extractMethod options", {
+    "returnType": isString,
+    "createGetter": isBool,
+    "name": isString,
+    "parameters": isListOf(isParameter),
+    "extractAll": isBool
+  });
+
+/**
+ * inlineLocalVariable feedback
+ */
+final Matcher isInlineLocalVariableFeedback = isNull;
+
+/**
+ * inlineLocalVariable options
+ */
+final Matcher isInlineLocalVariableOptions = isNull;
+
+/**
+ * inlineMethod feedback
+ */
+final Matcher isInlineMethodFeedback = isNull;
+
+/**
+ * inlineMethod options
+ *
+ * {
+ *   "deleteSource": bool
+ *   "inlineAll": bool
+ * }
+ */
+final Matcher isInlineMethodOptions = new MatchesJsonObject(
+  "inlineMethod options", {
+    "deleteSource": isBool,
+    "inlineAll": isBool
+  });
+
+/**
+ * rename feedback
+ *
+ * {
+ *   "offset": int
+ *   "length": int
+ * }
+ */
+final Matcher isRenameFeedback = new MatchesJsonObject(
+  "rename feedback", {
+    "offset": isInt,
+    "length": isInt
+  });
+
+/**
+ * rename options
+ *
+ * {
+ *   "newName": String
+ * }
+ */
+final Matcher isRenameOptions = new MatchesJsonObject(
+  "rename options", {
+    "newName": isString
+  });
+
diff --git a/pkg/analysis_server/test/integration/server_domain_int_test.dart b/pkg/analysis_server/test/integration/server_domain_int_test.dart
index c577942..99856c7 100644
--- a/pkg/analysis_server/test/integration/server_domain_int_test.dart
+++ b/pkg/analysis_server/test/integration/server_domain_int_test.dart
@@ -11,6 +11,7 @@
 import 'package:unittest/unittest.dart';
 
 import 'integration_tests.dart';
+import 'protocol_matchers.dart';
 
 @ReflectiveTestCase()
 class ServerDomainIntegrationTest extends AbstractAnalysisServerIntegrationTest
@@ -48,18 +49,19 @@
     });
     return server_setSubscriptions([]).then((response) {
       expect(response, isNull);
-      writeFile('test.dart', '''
+      String pathname = sourcePath('test.dart');
+      writeFile(pathname, '''
 main() {
   var x;
 }''');
-      setAnalysisRoots(['']);
+      standardAnalysisRoot();
       // Analysis should begin, but no server.status notification should be
       // received.
       return analysisBegun.future.then((_) {
         expect(statusReceived, isFalse);
         return server_setSubscriptions(['STATUS']).then((_) {
           // Tickle test.dart just in case analysis has already completed.
-          writeFile('test.dart', '''
+          writeFile(pathname, '''
 main() {
   var y;
 }''');
@@ -111,11 +113,11 @@
         }
       }
     });
-    writeFile('test.dart', '''
+    writeFile(sourcePath('test.dart'), '''
 main() {
   var x;
 }''');
-    setAnalysisRoots(['']);
+    standardAnalysisRoot();
     expect(analysisBegun.isCompleted, isFalse);
     expect(analysisFinished.isCompleted, isFalse);
     return analysisBegun.future.then((_) {
diff --git a/pkg/analysis_server/test/protocol_test.dart b/pkg/analysis_server/test/protocol_test.dart
index 1e4c4dc..3fcabfb 100644
--- a/pkg/analysis_server/test/protocol_test.dart
+++ b/pkg/analysis_server/test/protocol_test.dart
@@ -27,7 +27,7 @@
 
 @ReflectiveTestCase()
 class InvalidParameterResponseMatcher extends Matcher {
-  static const int ERROR_CODE = -2;
+  static const String ERROR_CODE = 'INVALID_PARAMETER';
 
   @override
   Description describe(Description description) => description.add(
@@ -396,11 +396,11 @@
 @ReflectiveTestCase()
 class RequestErrorTest {
   void test_create() {
-    RequestError error = new RequestError(42, 'msg');
-    expect(error.code, 42);
+    RequestError error = new RequestError('ERROR_CODE', 'msg');
+    expect(error.code, 'ERROR_CODE');
     expect(error.message, "msg");
     expect(error.toJson(), equals({
-      RequestError.CODE: 42,
+      RequestError.CODE: 'ERROR_CODE',
       RequestError.MESSAGE: "msg"
     }));
   }
@@ -457,11 +457,11 @@
   }
 
   void test_toJson() {
-    RequestError error = new RequestError(0, 'msg');
+    RequestError error = new RequestError('ERROR_CODE', 'msg');
     error.setData('answer', 42);
     error.setData('question', 'unknown');
     expect(error.toJson(), {
-      RequestError.CODE: 0,
+      RequestError.CODE: 'ERROR_CODE',
       RequestError.MESSAGE: 'msg',
       RequestError.DATA: {
         'answer': 42,
@@ -587,7 +587,7 @@
     expect(response.toJson(), equals({
       Response.ID: '0',
       Response.ERROR: {
-        'code': -1,
+        'code': 'NONEXISTENT_CONTEXT',
         'message': 'Context does not exist'
       }
     }));
@@ -600,7 +600,7 @@
     expect(response.toJson(), equals({
       Response.ID: '',
       Response.ERROR: {
-        'code': -4,
+        'code': 'INVALID_REQUEST',
         'message': 'Invalid request'
       }
     }));
@@ -614,7 +614,7 @@
     expect(response.toJson(), equals({
       Response.ID: '0',
       Response.ERROR: {
-        'code': -5,
+        'code': 'MISSING_PARAMETER',
         'message': 'Missing required parameter: x'
       }
     }));
@@ -628,7 +628,7 @@
     expect(response.toJson(), equals({
       Response.ID: '0',
       Response.ERROR: {
-        'code': -11,
+        'code': 'UNANALYZED_PRIORITY_FILES',
         'message': "Unanalyzed files cannot be a priority: 'file list'"
       }
     }));
@@ -642,7 +642,7 @@
     expect(response.toJson(), equals({
       Response.ID: '0',
       Response.ERROR: {
-        'code': -6,
+        'code': 'UNKNOWN_ANALYSIS_OPTION',
         'message': 'Unknown analysis option: "x"'
       }
     }));
@@ -655,7 +655,7 @@
     expect(response.toJson(), equals({
       Response.ID: '0',
       Response.ERROR: {
-        'code': -7,
+        'code': 'UNKNOWN_REQUEST',
         'message': 'Unknown request'
       }
     }));
@@ -673,7 +673,7 @@
     expect(response.id, equals(''));
     expect(response.error, isNotNull);
     RequestError error = response.error;
-    expect(error.code, equals(-4));
+    expect(error.code, equals('INVALID_REQUEST'));
     expect(error.message, equals('Invalid request'));
   }
 
diff --git a/pkg/analysis_server/test/search/type_hierarchy_test.dart b/pkg/analysis_server/test/search/type_hierarchy_test.dart
index b0a54f8..c0192e0 100644
--- a/pkg/analysis_server/test/search/type_hierarchy_test.dart
+++ b/pkg/analysis_server/test/search/type_hierarchy_test.dart
@@ -46,8 +46,8 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('main() {').then((json) {
-        expect(json, isNull);
+      return _getTypeHierarchy('main() {').then((jsons) {
+        expect(jsons, isEmpty);
       });
     });
   }
@@ -60,29 +60,30 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('B extends A').then((json) {
-        expect(json, {
-          'classElement': {
-            'kind': 'CLASS',
-            'name': 'B',
-            'location': anything,
-            'flags': 0
-          },
-          'superclass': {
+      return _getTypeHierarchy('B extends A').then((jsons) {
+        expect(jsons, [{
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'B',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
             'classElement': {
               'kind': 'CLASS',
               'name': 'A',
               'location': anything,
               'flags': 0
             },
+            'superclass': 0,
             'interfaces': [],
             'mixins': [],
-            'subclasses': []
-          },
-          'interfaces': [],
-          'mixins': [],
-          'subclasses': []
-        });
+            'subclasses': [1]
+          }]);
       });
     });
   }
@@ -95,8 +96,9 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('B extends').then((jsonB) {
-        var jsonA = jsonB[SUPERCLASS];
+      return _getTypeHierarchy('B extends').then((jsons) {
+        var jsonB = jsons[0];
+        var jsonA = jsons[jsonB[SUPERCLASS]];
         expect(jsonA[CLASS_ELEMENT][NAME], 'A');
         expect(jsonB[CLASS_ELEMENT][NAME], 'B');
         expect(jsonA[DISPLAY_NAME], 'A<int>');
@@ -113,15 +115,19 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('A {}').then((json) {
-        expect(json, {
-          'classElement': {
-            'kind': 'CLASS',
-            'name': 'A',
-            'location': anything,
-            'flags': 0
-          },
-          'superclass': {
+      return _getTypeHierarchy('A {}').then((jsons) {
+        expect(jsons, [{
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'A',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': [2]
+          }, {
             'classElement': {
               'kind': 'CLASS',
               'name': 'Object',
@@ -131,31 +137,29 @@
             'interfaces': [],
             'mixins': [],
             'subclasses': []
-          },
-          'interfaces': [],
-          'mixins': [],
-          'subclasses': [{
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'B',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': [{
-                  'classElement': {
-                    'kind': 'CLASS',
-                    'name': 'C',
-                    'location': anything,
-                    'flags': 0
-                  },
-                  'interfaces': [],
-                  'mixins': [],
-                  'subclasses': []
-                }]
-            }]
-        });
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'B',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 0,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': [3]
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'C',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 2,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }]);
       });
     });
   }
@@ -170,50 +174,51 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('B extends').then((json) {
-        expect(json, {
-          'classElement': {
-            'kind': 'CLASS',
-            'name': 'B',
-            'location': anything,
-            'flags': 0
-          },
-          'superclass': {
+      return _getTypeHierarchy('B extends').then((jsons) {
+        expect(jsons, [{
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'B',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': [3]
+          }, {
             'classElement': {
               'kind': 'CLASS',
               'name': 'A',
               'location': anything,
               'flags': 0
             },
-            'superclass': {
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'Object',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
+            'superclass': 2,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'Object',
+              'location': anything,
+              'flags': 0
             },
             'interfaces': [],
             'mixins': [],
             'subclasses': []
-          },
-          'interfaces': [],
-          'mixins': [],
-          'subclasses': [{
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'C',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
-            }]
-        });
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'C',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 0,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }]);
       });
     });
   }
@@ -228,51 +233,51 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('C extends').then((json) {
-        expect(json, {
-          'classElement': {
-            'kind': 'CLASS',
-            'name': 'C',
-            'location': anything,
-            'flags': 0
-          },
-          'superclass': {
+      return _getTypeHierarchy('C extends').then((jsons) {
+        expect(jsons, [{
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'C',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
             'classElement': {
               'kind': 'CLASS',
               'name': 'B',
               'location': anything,
               'flags': 0
             },
-            'superclass': {
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'A',
-                'location': anything,
-                'flags': 0
-              },
-              'superclass': {
-                'classElement': {
-                  'kind': 'CLASS',
-                  'name': 'Object',
-                  'location': anything,
-                  'flags': 0
-                },
-                'interfaces': [],
-                'mixins': [],
-                'subclasses': []
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
+            'superclass': 2,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'A',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 3,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'Object',
+              'location': anything,
+              'flags': 0
             },
             'interfaces': [],
             'mixins': [],
             'subclasses': []
-          },
-          'interfaces': [],
-          'mixins': [],
-          'subclasses': []
-        });
+          }]);
       });
     });
   }
@@ -287,15 +292,19 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('T implements').then((json) {
-        expect(json, {
-          'classElement': {
-            'kind': 'CLASS',
-            'name': 'T',
-            'location': anything,
-            'flags': 0
-          },
-          'superclass': {
+      return _getTypeHierarchy('T implements').then((jsons) {
+        expect(jsons, [{
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'T',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [2, 3],
+            'mixins': [],
+            'subclasses': []
+          }, {
             'classElement': {
               'kind': 'CLASS',
               'name': 'Object',
@@ -305,31 +314,29 @@
             'interfaces': [],
             'mixins': [],
             'subclasses': []
-          },
-          'interfaces': [{
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'MA',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
-            }, {
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'MB',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
-            }],
-          'mixins': [],
-          'subclasses': []
-        });
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'MA',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'MB',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }]);
       });
     });
   }
@@ -344,15 +351,19 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('T extends Object').then((json) {
-        expect(json, {
-          'classElement': {
-            'kind': 'CLASS',
-            'name': 'T',
-            'location': anything,
-            'flags': 0
-          },
-          'superclass': {
+      return _getTypeHierarchy('T extends Object').then((jsons) {
+        expect(jsons, [{
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'T',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [2, 3],
+            'subclasses': []
+          }, {
             'classElement': {
               'kind': 'CLASS',
               'name': 'Object',
@@ -362,31 +373,29 @@
             'interfaces': [],
             'mixins': [],
             'subclasses': []
-          },
-          'interfaces': [],
-          'mixins': [{
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'MA',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
-            }, {
-              'classElement': {
-                'kind': 'CLASS',
-                'name': 'MB',
-                'location': anything,
-                'flags': 0
-              },
-              'interfaces': [],
-              'mixins': [],
-              'subclasses': []
-            }],
-          'subclasses': []
-        });
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'MA',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }, {
+            'classElement': {
+              'kind': 'CLASS',
+              'name': 'MB',
+              'location': anything,
+              'flags': 0
+            },
+            'superclass': 1,
+            'interfaces': [],
+            'mixins': [],
+            'subclasses': []
+          }]);
       });
     });
   }
@@ -406,10 +415,11 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('test => null; // in B').then((jsonB) {
-        var jsonA = jsonB[SUPERCLASS];
-        var jsonC = jsonB[SUBCLASSES][0];
-        var jsonD = jsonC[SUBCLASSES][0];
+      return _getTypeHierarchy('test => null; // in B').then((jsons) {
+        Map jsonB = jsons[0];
+        Map jsonA = jsons[jsonB[SUPERCLASS]];
+        Map jsonC = jsons[jsonB[SUBCLASSES][0]];
+        Map jsonD = jsons[jsonC[SUBCLASSES][0]];
         expect(jsonA[CLASS_ELEMENT][NAME], 'A');
         expect(jsonB[CLASS_ELEMENT][NAME], 'B');
         expect(jsonC[CLASS_ELEMENT][NAME], 'C');
@@ -443,10 +453,11 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('test() {} // in B').then((jsonB) {
-        var jsonA = jsonB[SUPERCLASS];
-        var jsonC = jsonB[SUBCLASSES][0];
-        var jsonD = jsonC[SUBCLASSES][0];
+      return _getTypeHierarchy('test() {} // in B').then((jsons) {
+        var jsonB = jsons[0];
+        var jsonA = jsons[jsonB[SUPERCLASS]];
+        var jsonC = jsons[jsonB[SUBCLASSES][0]];
+        var jsonD = jsons[jsonC[SUBCLASSES][0]];
         expect(jsonA[CLASS_ELEMENT][NAME], 'A');
         expect(jsonB[CLASS_ELEMENT][NAME], 'B');
         expect(jsonC[CLASS_ELEMENT][NAME], 'C');
@@ -480,10 +491,11 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('==(x) => null; // in B').then((jsonB) {
-        var jsonA = jsonB[SUPERCLASS];
-        var jsonC = jsonB[SUBCLASSES][0];
-        var jsonD = jsonC[SUBCLASSES][0];
+      return _getTypeHierarchy('==(x) => null; // in B').then((jsons) {
+        var jsonB = jsons[0];
+        var jsonA = jsons[jsonB[SUPERCLASS]];
+        var jsonC = jsons[jsonB[SUBCLASSES][0]];
+        var jsonD = jsons[jsonC[SUBCLASSES][0]];
         expect(jsonA[CLASS_ELEMENT][NAME], 'A');
         expect(jsonB[CLASS_ELEMENT][NAME], 'B');
         expect(jsonC[CLASS_ELEMENT][NAME], 'C');
@@ -517,10 +529,11 @@
 }
 ''');
     return waitForTasksFinished().then((_) {
-      return _getTypeHierarchy('test(x) {} // in B').then((jsonB) {
-        var jsonA = jsonB[SUPERCLASS];
-        var jsonC = jsonB[SUBCLASSES][0];
-        var jsonD = jsonC[SUBCLASSES][0];
+      return _getTypeHierarchy('test(x) {} // in B').then((jsons) {
+        var jsonB = jsons[0];
+        var jsonA = jsons[jsonB[SUPERCLASS]];
+        var jsonC = jsons[jsonB[SUBCLASSES][0]];
+        var jsonD = jsons[jsonC[SUBCLASSES][0]];
         expect(jsonA[CLASS_ELEMENT][NAME], 'A');
         expect(jsonB[CLASS_ELEMENT][NAME], 'B');
         expect(jsonC[CLASS_ELEMENT][NAME], 'C');
@@ -547,10 +560,10 @@
     return request;
   }
 
-  Future<Map<String, Object>> _getTypeHierarchy(String search) {
+  Future<List<Map<String, Object>>> _getTypeHierarchy(String search) {
     Request request = _createGetTypeHierarchyRequest(search);
     return serverChannel.sendRequest(request).then((Response response) {
-      return response.getResult(HIERARCHY) as Map<String, Object>;
+      return response.getResult(HIERARCHY_ITEMS) as List<Map<String, Object>>;
     });
   }
 }
diff --git a/pkg/analysis_server/tool/spec/api.dart b/pkg/analysis_server/tool/spec/api.dart
new file mode 100644
index 0000000..489e730
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/api.dart
@@ -0,0 +1,449 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Data structures representing an API definition, and visitor base classes
+ * for visiting those data structures.
+ */
+library api;
+
+import 'dart:collection';
+
+import 'package:html5lib/dom.dart' as dom;
+
+/**
+ * Base class for visiting the API definition.
+ */
+abstract class ApiVisitor<T> {
+  T visitTypeReference(TypeReference typeReference);
+  T visitTypeObject(TypeObject typeObject);
+  T visitTypeList(TypeList typeList);
+  T visitTypeMap(TypeMap typeMap);
+  T visitTypeEnum(TypeEnum typeEnum);
+
+  /**
+   * Dispatch the given [type] to the visitor.
+   */
+  T visitTypeDecl(TypeDecl type) => type.accept(this);
+}
+
+/**
+ * API visitor that visits the entire API hierarchically by default.
+ */
+class HierarchicalApiVisitor extends ApiVisitor {
+  /**
+   * The API to visit.
+   */
+  final Api api;
+
+  HierarchicalApiVisitor(this.api);
+
+  void visitApi() {
+    api.domains.forEach(visitDomain);
+    visitTypes(api.types);
+    visitRefactorings(api.refactorings);
+  }
+
+  void visitRefactorings(Refactorings refactorings) {
+    refactorings.forEach(visitRefactoring);
+  }
+
+  void visitRefactoring(Refactoring refactoring) {
+    if (refactoring.feedback != null) {
+      visitTypeDecl(refactoring.feedback);
+    }
+    if (refactoring.options != null) {
+      visitTypeDecl(refactoring.options);
+    }
+  }
+
+  void visitTypes(Types types) {
+    types.forEach(visitTypeDefinition);
+  }
+
+  void visitDomain(Domain domain) {
+    domain.requests.forEach(visitRequest);
+    domain.notifications.forEach(visitNotification);
+  }
+
+  void visitNotification(Notification notification) {
+    if (notification.params != null) {
+      visitTypeDecl(notification.params);
+    }
+  }
+
+  void visitRequest(Request request) {
+    if (request.params != null) {
+      visitTypeDecl(request.params);
+    }
+    if (request.result != null) {
+      visitTypeDecl(request.result);
+    }
+  }
+
+  void visitTypeDefinition(TypeDefinition typeDefinition) {
+    visitTypeDecl(typeDefinition.type);
+  }
+
+  @override
+  void visitTypeEnum(TypeEnum typeEnum) {
+    typeEnum.values.forEach(visitTypeEnumValue);
+  }
+
+  void visitTypeEnumValue(TypeEnumValue typeEnumValue) {
+  }
+
+  @override
+  void visitTypeList(TypeList typeList) {
+    visitTypeDecl(typeList.itemType);
+  }
+
+  @override
+  void visitTypeMap(TypeMap typeMap) {
+    visitTypeDecl(typeMap.keyType);
+    visitTypeDecl(typeMap.valueType);
+  }
+
+  @override
+  void visitTypeObject(TypeObject typeObject) {
+    typeObject.fields.forEach(visitTypeObjectField);
+  }
+
+  void visitTypeObjectField(TypeObjectField typeObjectField) {
+    visitTypeDecl(typeObjectField.type);
+  }
+
+  @override
+  void visitTypeReference(TypeReference typeReference) {
+  }
+
+  /**
+   * If [type] is a [TypeReference] which points to another [TypeReference],
+   * follow the chain until the last [TypeReference] is reached.
+   */
+  TypeReference resolveTypeReferenceChain(TypeReference type) {
+    while (api.types.containsKey(type.typeName)) {
+      TypeDecl referredType = api.types[type.typeName].type;
+      if (referredType is TypeReference) {
+        type = referredType;
+        continue;
+      }
+      break;
+    }
+    return type;
+  }
+}
+
+/**
+ * Base class for objects in the API model.
+ */
+class ApiNode {
+  /**
+   * Html element representing this part of the API.
+   */
+  final dom.Element html;
+
+  ApiNode(this.html);
+}
+
+/**
+ * Toplevel container for the API.
+ */
+class Api extends ApiNode {
+  final String version;
+  final List<Domain> domains;
+  final Types types;
+  final Refactorings refactorings;
+
+  Api(this.version, this.domains, this.types, this.refactorings, dom.Element
+      html) : super(html);
+}
+
+/**
+ * A collection of refactoring definitions.
+ */
+class Refactorings extends ApiNode with IterableMixin<Refactoring> {
+  final List<Refactoring> refactorings;
+
+  Refactorings(this.refactorings, dom.Element html) : super(html);
+
+  @override
+  Iterator<Refactoring> get iterator => refactorings.iterator;
+}
+
+/**
+ * Description of a single refactoring.
+ */
+class Refactoring extends ApiNode {
+  /**
+   * Name of the refactoring.  This should match one of the values allowed for
+   * RefactoringKind.
+   */
+  final String kind;
+
+  /**
+   * Type of the refactoring feedback, or null if the refactoring has no
+   * feedback.
+   */
+  final TypeObject feedback;
+
+  /**
+   * Type of the refactoring options, or null if the refactoring has no options.
+   */
+  final TypeObject options;
+
+  Refactoring(this.kind, this.feedback, this.options, dom.Element html) : super(
+      html);
+}
+
+/**
+ * A collection of type definitions.
+ */
+class Types extends ApiNode with IterableMixin<TypeDefinition> {
+  final Map<String, TypeDefinition> types;
+
+  Types(this.types, dom.Element html) : super(html);
+
+  bool containsKey(String typeName) => types.containsKey(typeName);
+
+  @override
+  Iterator<TypeDefinition> get iterator => types.values.iterator;
+
+  TypeDefinition operator [](String typeName) => types[typeName];
+
+  Iterable<String> get keys => types.keys;
+}
+
+/**
+ * Definition of a single domain.
+ */
+class Domain extends ApiNode {
+  final String name;
+  final List<Request> requests;
+  final List<Notification> notifications;
+
+  Domain(this.name, this.requests, this.notifications, dom.Element html) :
+      super(html);
+}
+
+/**
+ * Description of a request method.
+ */
+class Request extends ApiNode {
+  /**
+   * Name of the domain enclosing this request.
+   */
+  final String domainName;
+
+  /**
+   * Name of the request, without the domain prefix.
+   */
+  final String method;
+
+  /**
+   * Type of the object associated with the "params" key in the request object,
+   * or null if the request has no parameters.
+   */
+  final TypeObject params;
+
+  /**
+   * Type of the object associated with the "result" key in the response object,
+   * or null if the response has no results.
+   */
+  final TypeObject result;
+
+  Request(this.domainName, this.method, this.params, this.result, dom.Element
+      html) : super(html);
+
+  /**
+   * Get the name of the request, including the domain prefix.
+   */
+  String get longMethod => '$domainName.$method';
+
+  /**
+   * Get the full type of the request object, including the common "id" and
+   * "method" fields.
+   */
+  TypeDecl get requestType {
+    List<TypeObjectField> fields = [new TypeObjectField('id', new TypeReference(
+        'String', null), null), new TypeObjectField('method', new TypeReference(
+        'String', null), null, value: '$domainName.$method')];
+    if (params != null) {
+      fields.add(new TypeObjectField('params', params, null));
+    }
+    return new TypeObject(fields, null);
+  }
+
+  /**
+   * Get the full type of the response object, including the common "id" and
+   * "error" fields.
+   */
+  TypeDecl get responseType {
+    List<TypeObjectField> fields = [new TypeObjectField('id', new TypeReference(
+        'String', null), null), new TypeObjectField('error', new TypeReference('Error',
+        null), null, optional: true)];
+    if (result != null) {
+      fields.add(new TypeObjectField('result', result, null));
+    }
+    return new TypeObject(fields, null);
+  }
+}
+
+/**
+ * Description of a request method.
+ */
+class Notification extends ApiNode {
+  /**
+   * Name of the domain enclosing this request.
+   */
+  final String domainName;
+
+  /**
+   * Name of the notification, without the domain prefix.
+   */
+  final String event;
+
+  /**
+   * Type of the object associated with the "params" key in the notification
+   * object, or null if the notification has no parameters.
+   */
+  final TypeObject params;
+
+  Notification(this.domainName, this.event, this.params, dom.Element html) :
+      super(html);
+
+  /**
+   * Get the name of the notification, including the domain prefix.
+   */
+  String get longEvent => '$domainName.$event';
+
+  /**
+   * Get the full type of the notification object, including the common "id"
+   * and "error" fields.
+   */
+  TypeDecl get notificationType {
+    List<TypeObjectField> fields = [new TypeObjectField('event',
+        new TypeReference('String', null), null, value: '$domainName.$event')];
+    if (params != null) {
+      fields.add(new TypeObjectField('params', params, null));
+    }
+    return new TypeObject(fields, null);
+  }
+}
+
+/**
+ * Description of a named type definition.
+ */
+class TypeDefinition extends ApiNode {
+  final String name;
+  final TypeDecl type;
+
+  TypeDefinition(this.name, this.type, dom.Element html) : super(html);
+}
+
+/**
+ * Base class for all possible types.
+ */
+abstract class TypeDecl extends ApiNode {
+  TypeDecl(dom.Element html) : super(html);
+
+  accept(ApiVisitor visitor);
+}
+
+/**
+ * A reference to a type which is either defined elsewhere in the API or which
+ * is built-in ([String], [bool], or [int]).
+ */
+class TypeReference extends TypeDecl {
+  final String typeName;
+
+  TypeReference(this.typeName, dom.Element html) : super(html) {
+    if (typeName.isEmpty) {
+      throw new Exception('Empty type name');
+    }
+  }
+
+  accept(ApiVisitor visitor) => visitor.visitTypeReference(this);
+}
+
+/**
+ * Type of a JSON object with specified fields, some of which may be optional.
+ */
+class TypeObject extends TypeDecl {
+  final List<TypeObjectField> fields;
+
+  TypeObject(this.fields, dom.Element html) : super(html);
+
+  accept(ApiVisitor visitor) => visitor.visitTypeObject(this);
+}
+
+/**
+ * Description of a single field in a [TypeObject].
+ */
+class TypeObjectField extends ApiNode {
+  final String name;
+  final TypeDecl type;
+  final bool optional;
+
+  /**
+   * Value which the field is required to contain, or null if it may vary.
+   */
+  final Object value;
+
+  TypeObjectField(this.name, this.type, dom.Element html, {this.optional:
+      false, this.value}) : super(html);
+}
+
+/**
+ * Type of a JSON list.
+ */
+class TypeList extends TypeDecl {
+  final TypeDecl itemType;
+
+  TypeList(this.itemType, dom.Element html) : super(html);
+
+  accept(ApiVisitor visitor) => visitor.visitTypeList(this);
+}
+
+/**
+ * Type of a JSON map.
+ */
+class TypeMap extends TypeDecl {
+  /**
+   * Type of map keys.  Note that since JSON map keys must always be strings,
+   * this must either be a [TypeReference] for [String], or a [TypeReference]
+   * to a type which is defined in the API as an enum or a synonym for [String].
+   */
+  final TypeReference keyType;
+
+  /**
+   * Type of map values.
+   */
+  final TypeDecl valueType;
+
+  TypeMap(this.keyType, this.valueType, dom.Element html) : super(html);
+
+  accept(ApiVisitor visitor) => visitor.visitTypeMap(this);
+}
+
+/**
+ * Type of an enum.  We represent enums in JSON as strings, so this type
+ * declaration simply lists the allowed values.
+ */
+class TypeEnum extends TypeDecl {
+  final List<TypeEnumValue> values;
+
+  TypeEnum(this.values, dom.Element html) : super(html);
+
+  accept(ApiVisitor visitor) => visitor.visitTypeEnum(this);
+}
+
+/**
+ * Description of a single allowed value for an enum.
+ */
+class TypeEnumValue extends ApiNode {
+  final String value;
+
+  TypeEnumValue(this.value, dom.Element html) : super(html);
+}
diff --git a/pkg/analysis_server/tool/spec/codegen_matchers.dart b/pkg/analysis_server/tool/spec/codegen_matchers.dart
new file mode 100644
index 0000000..3f6d9ed
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/codegen_matchers.dart
@@ -0,0 +1,195 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Code generation for the file "matchers.dart".
+ */
+library codegen.matchers;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'api.dart';
+import 'codegen_tools.dart';
+import 'from_html.dart';
+import 'to_html.dart';
+
+class CodegenMatchersVisitor extends HierarchicalApiVisitor with CodeGenerator {
+  /**
+   * Visitor used to produce doc comments.
+   */
+  final ToHtmlVisitor toHtmlVisitor;
+
+  /**
+   * Short human-readable string describing the context of the matcher being
+   * created.
+   */
+  String context;
+
+  CodegenMatchersVisitor(Api api)
+      : super(api),
+        toHtmlVisitor = new ToHtmlVisitor(api);
+
+  /**
+   * Create a matcher for the part of the API called [name], optionally
+   * clarified by [nameSuffix].  The matcher should verify that its input
+   * matches the given [type].
+   */
+  void makeMatcher(String name, String nameSuffix, TypeDecl type) {
+    context = name;
+    List<String> nameParts = ['is'];
+    nameParts.addAll(name.split('.'));
+    if (nameSuffix != null) {
+      context += ' $nameSuffix';
+      nameParts.add(nameSuffix);
+    }
+    docComment(toHtmlVisitor.collectHtml(() {
+      toHtmlVisitor.p(() {
+        toHtmlVisitor.write(context);
+      });
+      if (type != null) {
+        toHtmlVisitor.showType(null, type);
+      }
+    }), false);
+    write('final Matcher ${camelJoin(nameParts)} = ');
+    if (type == null) {
+      write('isNull');
+    } else {
+      visitTypeDecl(type);
+    }
+    writeln(';');
+    writeln();
+  }
+
+  @override
+  visitApi() {
+    outputHeader();
+    writeln('/**');
+    writeln(' * Matchers for data types defined in the analysis server API');
+    writeln(' */');
+    writeln('library test.integration.protocol.matchers;');
+    writeln();
+    writeln("import 'package:unittest/unittest.dart';");
+    writeln();
+    writeln("import 'integration_tests.dart';");
+    writeln();
+    writeln();
+    super.visitApi();
+  }
+
+  @override
+  visitNotification(Notification notification) {
+    makeMatcher(notification.longEvent, 'params', notification.params);
+  }
+
+  @override
+  visitRequest(Request request) {
+    makeMatcher(request.longMethod, 'params', request.params);
+    makeMatcher(request.longMethod, 'result', request.result);
+  }
+
+  @override
+  visitRefactoring(Refactoring refactoring) {
+    String camelKind = camelJoin(refactoring.kind.toLowerCase().split('_'));
+    makeMatcher(camelKind, 'feedback', refactoring.feedback);
+    makeMatcher(camelKind, 'options', refactoring.options);
+  }
+
+  @override
+  visitTypeDefinition(TypeDefinition typeDefinition) {
+    makeMatcher(typeDefinition.name, null, typeDefinition.type);
+  }
+
+  @override
+  visitTypeEnum(TypeEnum typeEnum) {
+    writeln('isIn([');
+    indent(() {
+      bool commaNeeded = false;
+      for (TypeEnumValue value in typeEnum.values) {
+        if (commaNeeded) {
+          writeln(',');
+        }
+        write('${JSON.encode(value.value)}');
+        commaNeeded = true;
+      }
+      writeln();
+    });
+    write('])');
+  }
+
+  @override
+  visitTypeList(TypeList typeList) {
+    write('isListOf(');
+    visitTypeDecl(typeList.itemType);
+    write(')');
+  }
+
+  @override
+  visitTypeMap(TypeMap typeMap) {
+    write('isMapOf(');
+    visitTypeDecl(typeMap.keyType);
+    write(', ');
+    visitTypeDecl(typeMap.valueType);
+    write(')');
+  }
+
+  @override
+  void visitTypeObject(TypeObject typeObject) {
+    writeln('new MatchesJsonObject(');
+    indent(() {
+      write('${JSON.encode(context)}, ');
+      Iterable<TypeObjectField> requiredFields = typeObject.fields.where(
+          (TypeObjectField field) => !field.optional);
+      outputObjectFields(requiredFields);
+      List<TypeObjectField> optionalFields = typeObject.fields.where(
+          (TypeObjectField field) => field.optional).toList();
+      if (optionalFields.isNotEmpty) {
+        write(', optionalFields: ');
+        outputObjectFields(optionalFields);
+      }
+    });
+    write(')');
+  }
+
+  /**
+   * Generate a map describing the given set of fields, for use as the
+   * 'requiredFields' or 'optionalFields' argument to the [MatchesJsonObject]
+   * constructor.
+   */
+  void outputObjectFields(Iterable<TypeObjectField> fields) {
+    if (fields.isEmpty) {
+      write('null');
+      return;
+    }
+    writeln('{');
+    indent(() {
+      bool commaNeeded = false;
+      for (TypeObjectField field in fields) {
+        if (commaNeeded) {
+          writeln(',');
+        }
+        write('${JSON.encode(field.name)}: ');
+        visitTypeDecl(field.type);
+        commaNeeded = true;
+      }
+      writeln();
+    });
+    write('}');
+  }
+
+  @override
+  void visitTypeReference(TypeReference typeReference) {
+    write(camelJoin(['is', typeReference.typeName]));
+  }
+}
+
+/**
+ * Translate spec_input.html into protocol_matchers.dart.
+ */
+main() {
+  CodegenMatchersVisitor visitor = new CodegenMatchersVisitor(readApi());
+  String code = visitor.collectCode(visitor.visitApi);
+  File outputFile = new File('../../test/integration/protocol_matchers.dart');
+  outputFile.writeAsStringSync(code);
+}
diff --git a/pkg/analysis_server/tool/spec/codegen_tools.dart b/pkg/analysis_server/tool/spec/codegen_tools.dart
new file mode 100644
index 0000000..2a0dda8
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/codegen_tools.dart
@@ -0,0 +1,272 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Tools for code generation.
+ */
+library codegen.tools;
+
+import 'package:html5lib/dom.dart' as dom;
+
+import 'text_formatter.dart';
+import 'html_tools.dart';
+
+/**
+ * Join the given strings using camelCase.  If [capitalize] is true, the first
+ * part will be capitalized as well.
+ */
+String camelJoin(List<String> parts, {bool capitalize: false}) {
+  List<String> upcasedParts = <String>[];
+  for (int i = 0; i < parts.length; i++) {
+    if (i == 0 && !capitalize) {
+      upcasedParts.add(parts[i]);
+    } else {
+      upcasedParts.add(parts[i][0].toUpperCase() + parts[i].substring(1));
+    }
+  }
+  return upcasedParts.join();
+}
+
+final RegExp trailingWhitespaceRegExp = new RegExp(r' +$', multiLine: true);
+
+/**
+ * Mixin class for generating code.
+ */
+class CodeGenerator {
+  _CodeGeneratorState _state;
+
+  /**
+   * Execute [callback], collecting any code that is output using [write]
+   * or [writeln], and return the result as a string.
+   */
+  String collectCode(void callback()) {
+    _CodeGeneratorState oldState = _state;
+    try {
+      _state = new _CodeGeneratorState();
+      callback();
+      return _state.buffer.toString().replaceAll(trailingWhitespaceRegExp, '');
+    } finally {
+      _state = oldState;
+    }
+  }
+
+  /**
+   * Output text without ending the current line.
+   */
+  void write(Object obj) {
+    _state.write(obj.toString());
+  }
+
+  /**
+   * Output text, ending the current line.
+   */
+  void writeln([Object obj = '']) {
+    _state.write('$obj\n');
+  }
+
+  /**
+   * Execute [callback], indenting any code it outputs by two spaces.
+   */
+  void indent(void callback()) => indentSpecial('  ', '  ', callback);
+
+  /**
+   * Execute [callback], using [additionalIndent] to indent any code it outputs.
+   */
+  void indentBy(String additionalIndent, void callback()) => indentSpecial(
+      additionalIndent, additionalIndent, callback);
+
+  /**
+   * Execute [callback], using [additionalIndent] to indent any code it outputs.
+   * The first line of output is indented by [firstAdditionalIndent] instead of
+   * [additionalIndent].
+   */
+  void indentSpecial(String firstAdditionalIndent, String additionalIndent, void
+      callback()) {
+    String oldNextIndent = _state.nextIndent;
+    String oldIndent = _state.indent;
+    try {
+      _state.nextIndent += firstAdditionalIndent;
+      _state.indent += additionalIndent;
+      callback();
+    } finally {
+      _state.nextIndent = oldNextIndent;
+      _state.indent = oldIndent;
+    }
+  }
+
+  /**
+   * Measure the width of the current indentation level.
+   */
+  int get indentWidth => _state.nextIndent.length;
+
+  /**
+   * Generate a doc comment based on the HTML in [docs].
+   *
+   * If [javadocStyle] is true, then the output is compatable with Javadoc,
+   * which understands certain HTML constructs.
+   */
+  void docComment(List<dom.Node> docs, bool javadocStyle) {
+    writeln('/**');
+    indentBy(' * ', () {
+      write(nodesToText(docs, 79 - _state.indent.length, javadocStyle));
+    });
+    writeln(' */');
+  }
+
+  void outputHeader() {
+    String header =
+        '''
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// This file has been automatically generated.  Please do not edit it manually.
+// To regenerate the file, use the script
+// "pkg/analysis_server/spec/generate_files".
+''';
+    writeln(header.trim());
+    writeln();
+  }
+}
+
+/**
+ * State used by [CodeGenerator].
+ */
+class _CodeGeneratorState {
+  StringBuffer buffer = new StringBuffer();
+  String nextIndent = '';
+  String indent = '';
+  bool indentNeeded = true;
+
+  void write(String text) {
+    List<String> lines = text.split('\n');
+    for (int i = 0; i < lines.length; i++) {
+      if (i == lines.length - 1 && lines[i].isEmpty) {
+        break;
+      }
+      if (indentNeeded) {
+        buffer.write(nextIndent);
+        nextIndent = indent;
+      }
+      indentNeeded = false;
+      buffer.write(lines[i]);
+      if (i != lines.length - 1) {
+        buffer.writeln();
+        indentNeeded = true;
+      }
+    }
+  }
+}
+
+/**
+ * Mixin class for generating HTML representations of code that are suitable
+ * for enclosing inside a <pre> element.
+ */
+abstract class HtmlCodeGenerator {
+  _HtmlCodeGeneratorState _state;
+
+  /**
+   * Execute [callback], collecting any code that is output using [write],
+   * [writeln], [add], or [addAll], and return the result as a list of DOM
+   * nodes.
+   */
+  List<dom.Node> collectHtml(void callback()) {
+    _HtmlCodeGeneratorState oldState = _state;
+    try {
+      _state = new _HtmlCodeGeneratorState();
+      if (callback != null) {
+        callback();
+      }
+      return _state.buffer;
+    } finally {
+      _state = oldState;
+    }
+  }
+
+  /**
+   * Add the given [node] to the HTML output.
+   */
+  void add(dom.Node node) {
+    _state.add(node);
+  }
+
+  /**
+   * Add the given [nodes] to the HTML output.
+   */
+  void addAll(Iterable<dom.Node> nodes) {
+    for (dom.Node node in nodes) {
+      _state.add(node);
+    }
+  }
+
+  /**
+   * Output text without ending the current line.
+   */
+  void write(Object obj) {
+    _state.write(obj.toString());
+  }
+
+  /**
+   * Output text, ending the current line.
+   */
+  void writeln([Object obj = '']) {
+    _state.write('$obj\n');
+  }
+
+  /**
+   * Execute [callback], indenting any code it outputs by two spaces.
+   */
+  void indent(void callback()) {
+    String oldIndent = _state.indent;
+    try {
+      _state.indent += '  ';
+      callback();
+    } finally {
+      _state.indent = oldIndent;
+    }
+  }
+
+  /**
+   * Execute [callback], wrapping its output in an element with the given
+   * [name] and [attributes].
+   */
+  void element(String name, Map<String, String> attributes, [void callback()]) {
+    add(makeElement(name, attributes, collectHtml(callback)));
+  }
+}
+
+/**
+ * State used by [HtmlCodeGenerator].
+ */
+class _HtmlCodeGeneratorState {
+  List<dom.Node> buffer = <dom.Node>[];
+  String indent = '';
+  bool indentNeeded = true;
+
+  void add(dom.Node node) {
+    if (node is dom.Text) {
+      write(node.text);
+    } else {
+      buffer.add(node);
+    }
+  }
+
+  void write(String text) {
+    if (text.isEmpty) {
+      return;
+    }
+    if (indentNeeded) {
+      buffer.add(new dom.Text(indent));
+    }
+    List<String> lines = text.split('\n');
+    if (lines.last.isEmpty) {
+      lines.removeLast();
+      buffer.add(new dom.Text(lines.join('\n$indent') + '\n'));
+      indentNeeded = true;
+    } else {
+      buffer.add(new dom.Text(lines.join('\n$indent')));
+      indentNeeded = false;
+    }
+  }
+}
diff --git a/pkg/analysis_server/tool/spec/from_html.dart b/pkg/analysis_server/tool/spec/from_html.dart
new file mode 100644
index 0000000..868f17e
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/from_html.dart
@@ -0,0 +1,485 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Code for reading an HTML API description.
+ */
+library from.html;
+
+import 'dart:io';
+
+import 'package:html5lib/dom.dart' as dom;
+import 'package:html5lib/parser.dart' as parser;
+
+import 'api.dart';
+import 'html_tools.dart';
+
+/**
+ * Check that the given [element] has the given [expectedName].
+ */
+void checkName(dom.Element element, String expectedName) {
+  if (element.localName != expectedName) {
+    throw new Exception('Expected $expectedName, found ${element.localName}');
+  }
+}
+
+/**
+ * Check that the given [element] has all of the attributes in
+ * [requiredAttributes], possibly some of the attributes in
+ * [optionalAttributes], and no others.
+ */
+void checkAttributes(dom.Element element, List<String>
+    requiredAttributes, {List<String> optionalAttributes: const []}) {
+  Set<String> attributesFound = new Set<String>();
+  element.attributes.forEach((String name, String value) {
+    if (!requiredAttributes.contains(name) && !optionalAttributes.contains(name
+        )) {
+      throw new Exception('Unexpected attribute in ${element.localName}: $name'
+          );
+    }
+    attributesFound.add(name);
+  });
+  for (String expectedAttribute in requiredAttributes) {
+    if (!attributesFound.contains(expectedAttribute)) {
+      throw new Exception(
+          '${element.localName} must contain attribute ${expectedAttribute}');
+    }
+  }
+}
+
+const List<String> specialElements = const ['domain', 'feedback',
+    'object', 'refactorings', 'refactoring', 'type', 'types', 'request',
+    'notification', 'params', 'result', 'field', 'list', 'map', 'enum', 'key',
+    'value', 'options', 'ref', 'code', 'version'];
+
+typedef void ElementProcessor(dom.Element element);
+typedef void TextProcessor(dom.Text text);
+
+void recurse(dom.Element parent, Map<String, ElementProcessor>
+    elementProcessors) {
+  for (String key in elementProcessors.keys) {
+    if (!specialElements.contains(key)) {
+      throw new Exception('$key is not a special element');
+    }
+  }
+  for (dom.Node node in parent.nodes) {
+    if (node is dom.Element) {
+      if (elementProcessors.containsKey(node.localName)) {
+        elementProcessors[node.localName](node);
+      } else if (specialElements.contains(node.localName)) {
+        throw new Exception('Unexpected use of <${node.localName}');
+      } else {
+        recurse(node, elementProcessors);
+      }
+    }
+  }
+}
+
+dom.Element getAncestor(dom.Element html, String name) {
+  dom.Element ancestor = html.parent;
+  while (ancestor != null) {
+    if (ancestor.localName == name) {
+      return ancestor;
+    }
+    ancestor = ancestor.parent;
+  }
+  throw new Exception('<${html.localName}> must be nested within <$name>');
+}
+
+/**
+ * Create an [Api] object from an HTML representation such as:
+ *
+ * <html>
+ *   ...
+ *   <body>
+ *     ... <version>1.0</version> ...
+ *     <domain name="...">...</domain> <!-- zero or more -->
+ *     <types>...</types>
+ *     <refactorings>...</refactorings>
+ *   </body>
+ * </html>
+ *
+ * Child elements of <api> can occur in any order.
+ */
+Api apiFromHtml(dom.Element html) {
+  Api api;
+  List<String> versions = <String>[];
+  List<Domain> domains = <Domain>[];
+  Types types = null;
+  Refactorings refactorings = null;
+  recurse(html, {
+    'domain': (dom.Element element) {
+      domains.add(domainFromHtml(element));
+    },
+    'refactorings': (dom.Element element) {
+      refactorings = refactoringsFromHtml(element);
+    },
+    'types': (dom.Element element) {
+      types = typesFromHtml(element);
+    },
+    'version': (dom.Element element) {
+      versions.add(innerText(element));
+    }
+  });
+  if (versions.length != 1) {
+    throw new Exception('The API must contain exactly one <version> element');
+  }
+  api = new Api(versions[0], domains, types, refactorings, html);
+  return api;
+}
+
+/**
+ * Create a [Refactorings] object from an HTML representation such as:
+ *
+ * <refactorings>
+ *   <refactoring kind="...">...</refactoring> <!-- zero or more -->
+ * </refactorings>
+ */
+Refactorings refactoringsFromHtml(dom.Element html) {
+  checkName(html, 'refactorings');
+  checkAttributes(html, []);
+  List<Refactoring> refactorings = <Refactoring>[];
+  recurse(html, {
+    'refactoring': (dom.Element child) {
+      refactorings.add(refactoringFromHtml(child));
+    }
+  });
+  return new Refactorings(refactorings, html);
+}
+
+/**
+ * Create a [Refactoring] object from an HTML representation such as:
+ *
+ * <refactoring kind="refactoringKind">
+ *   <feedback>...</feedback> <!-- optional -->
+ *   <options>...</options> <!-- optional -->
+ * </refactoring>
+ *
+ * <feedback> and <options> have the same form as <object>, as described in
+ * [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+Refactoring refactoringFromHtml(dom.Element html) {
+  checkName(html, 'refactoring');
+  checkAttributes(html, ['kind']);
+  String kind = html.attributes['kind'];
+  TypeDecl feedback;
+  TypeDecl options;
+  recurse(html, {
+    'feedback': (dom.Element child) {
+      feedback = typeObjectFromHtml(child);
+    },
+    'options': (dom.Element child) {
+      options = typeObjectFromHtml(child);
+    }
+  });
+  return new Refactoring(kind, feedback, options, html);
+}
+
+/**
+ * Create a [Types] object from an HTML representation such as:
+ *
+ * <types>
+ *   <type name="...">...</type> <!-- zero or more -->
+ * </types>
+ */
+Types typesFromHtml(dom.Element html) {
+  checkName(html, 'types');
+  checkAttributes(html, []);
+  Map<String, TypeDefinition> types = <String, TypeDefinition> {};
+  recurse(html, {
+    'type': (dom.Element child) {
+      TypeDefinition typeDefinition = typeDefinitionFromHtml(child);
+      types[typeDefinition.name] = typeDefinition;
+    }
+  });
+  return new Types(types, html);
+}
+
+/**
+ * Create a [TypeDefinition] object from an HTML representation such as:
+ *
+ * <type name="typeName">
+ *   TYPE
+ * </type>
+ *
+ * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+TypeDefinition typeDefinitionFromHtml(dom.Element html) {
+  checkName(html, 'type');
+  checkAttributes(html, ['name']);
+  String name = html.attributes['name'];
+  TypeDecl type = processContentsAsType(html);
+  return new TypeDefinition(name, type, html);
+}
+
+/**
+ * Create a [Domain] object from an HTML representation such as:
+ *
+ * <domain name="domainName">
+ *   <request method="...">...</request> <!-- zero or more -->
+ *   <notification event="...">...</notification> <!-- zero or more -->
+ * </domain>
+ *
+ * Child elements can occur in any order.
+ */
+Domain domainFromHtml(dom.Element html) {
+  checkName(html, 'domain');
+  checkAttributes(html, ['name']);
+  String name = html.attributes['name'];
+  List<Request> requests = <Request>[];
+  List<Notification> notifications = <Notification>[];
+  recurse(html, {
+    'request': (dom.Element child) {
+      requests.add(requestFromHtml(child));
+    },
+    'notification': (dom.Element child) {
+      notifications.add(notificationFromHtml(child));
+    }
+  });
+  return new Domain(name, requests, notifications, html);
+}
+
+/**
+ * Create a [Request] object from an HTML representation such as:
+ *
+ * <request method="methodName">
+ *   <params>...</params> <!-- optional -->
+ *   <result>...</result> <!-- optional -->
+ * </request>
+ *
+ * Note that the method name should not include the domain name.
+ *
+ * <params> and <result> have the same form as <object>, as described in
+ * [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+Request requestFromHtml(dom.Element html) {
+  String domainName = getAncestor(html, 'domain').attributes['name'];
+  checkName(html, 'request');
+  checkAttributes(html, ['method']);
+  String method = html.attributes['method'];
+  TypeDecl params;
+  TypeDecl result;
+  recurse(html, {
+    'params': (dom.Element child) {
+      params = typeObjectFromHtml(child);
+    },
+    'result': (dom.Element child) {
+      result = typeObjectFromHtml(child);
+    }
+  });
+  return new Request(domainName, method, params, result, html);
+}
+
+/**
+ * Create a [Notification] object from an HTML representation such as:
+ *
+ * <notification event="methodName">
+ *   <params>...</params> <!-- optional -->
+ * </notification>
+ *
+ * Note that the event name should not include the domain name.
+ *
+ * <params> has the same form as <object>, as described in [typeDeclFromHtml].
+ *
+ * Child elements can occur in any order.
+ */
+Notification notificationFromHtml(dom.Element html) {
+  String domainName = getAncestor(html, 'domain').attributes['name'];
+  checkName(html, 'notification');
+  checkAttributes(html, ['event']);
+  String event = html.attributes['event'];
+  TypeDecl params;
+  recurse(html, {
+    'params': (dom.Element child) {
+      params = typeObjectFromHtml(child);
+    }
+  });
+  return new Notification(domainName, event, params, html);
+}
+
+/**
+ * Create a [TypeDecl] from an HTML description.  The following forms are
+ * supported.
+ *
+ * To refer to a type declared elsewhere (or a built-in type):
+ *
+ *   <ref>typeName</ref>
+ *
+ * For a list: <list>ItemType</list>
+ *
+ * For a map: <map><key>KeyType</key><value>ValueType</value></map>
+ *
+ * For a JSON object:
+ *
+ *   <object>
+ *     <field name="...">...</field> <!-- zero or more -->
+ *   </object>
+ *
+ * For an enum:
+ *
+ *   <enum>
+ *     <value>...</value> <!-- zero or more -->
+ *   </enum>
+ */
+TypeDecl processContentsAsType(dom.Element html) {
+  List<TypeDecl> types = <TypeDecl>[];
+  recurse(html, {
+    'object': (dom.Element child) {
+      types.add(typeObjectFromHtml(child));
+    },
+    'list': (dom.Element child) {
+      checkAttributes(child, []);
+      types.add(new TypeList(processContentsAsType(child), child));
+    },
+    'map': (dom.Element child) {
+      checkAttributes(child, []);
+      TypeDecl keyType;
+      TypeDecl valueType;
+      recurse(child, {
+        'key': (dom.Element child) {
+          if (keyType != null) {
+            throw new Exception('Key type already specified');
+          }
+          keyType = processContentsAsType(child);
+        },
+        'value': (dom.Element child) {
+          if (valueType != null) {
+            throw new Exception('Value type already specified');
+          }
+          valueType = processContentsAsType(child);
+        }
+      });
+      if (keyType == null) {
+        throw new Exception('Key type not specified');
+      }
+      if (valueType == null) {
+        throw new Exception('Value type not specified');
+      }
+      types.add(new TypeMap(keyType, valueType, child));
+    },
+    'enum': (dom.Element child) {
+      types.add(typeEnumFromHtml(child));
+    },
+    'ref': (dom.Element child) {
+      checkAttributes(child, []);
+      types.add(new TypeReference(innerText(child), child));
+    }
+  });
+  if (types.length != 1) {
+    throw new Exception('Exactly one type must be specified');
+  }
+  return types[0];
+}
+
+/**
+ * Create a [TypeEnum] from an HTML description.
+ */
+TypeEnum typeEnumFromHtml(dom.Element html) {
+  checkName(html, 'enum');
+  checkAttributes(html, []);
+  List<TypeEnumValue> values = <TypeEnumValue>[];
+  recurse(html, {
+    'value': (dom.Element child) {
+      values.add(typeEnumValueFromHtml(child));
+    }
+  });
+  return new TypeEnum(values, html);
+}
+
+/**
+ * Create a [TypeEnumValue] from an HTML description such as:
+ *
+ * <enum>
+ *   <code>VALUE</code>
+ * </enum>
+ *
+ * Where VALUE is the text of the enumerated value.
+ *
+ * Child elements can occur in any order.
+ */
+TypeEnumValue typeEnumValueFromHtml(dom.Element html) {
+  checkName(html, 'value');
+  checkAttributes(html, []);
+  List<String> values = <String>[];
+  recurse(html, {
+    'code': (dom.Element child) {
+      String text = innerText(child).trim();
+      values.add(text);
+    }
+  });
+  if (values.length != 1) {
+    throw new Exception('Exactly one value must be specified');
+  }
+  return new TypeEnumValue(values[0], html);
+}
+
+/**
+ * Create a [TypeObject] from an HTML description.
+ */
+TypeObject typeObjectFromHtml(dom.Element html) {
+  checkAttributes(html, []);
+  List<TypeObjectField> fields = <TypeObjectField>[];
+  recurse(html, {
+    'field': (dom.Element child) {
+      fields.add(typeObjectFieldFromHtml(child));
+    }
+  });
+  return new TypeObject(fields, html);
+}
+
+/**
+ * Create a [TypeObjectField] from an HTML description such as:
+ *
+ * <field name="fieldName">
+ *   TYPE
+ * </field>
+ *
+ * Where TYPE is any HTML that can be parsed by [typeDeclFromHtml].
+ *
+ * In addition, the attribute optional="true" may be used to specify that the
+ * field is optional, and the attribute value="..." may be used to specify that
+ * the field is required to have a certain value.
+ *
+ * Child elements can occur in any order.
+ */
+TypeObjectField typeObjectFieldFromHtml(dom.Element html) {
+  checkName(html, 'field');
+  checkAttributes(html, ['name'], optionalAttributes: ['optional', 'value']);
+  String name = html.attributes['name'];
+  bool optional = false;
+  String optionalString = html.attributes['optional'];
+  if (optionalString != null) {
+    switch (optionalString) {
+      case 'true':
+        optional = true;
+        break;
+      case 'false':
+        optional = false;
+        break;
+      default:
+        throw new Exception(
+            'field contains invalid "optional" attribute: "$optionalString"');
+    }
+  }
+  String value = html.attributes['value'];
+  TypeDecl type = processContentsAsType(html);
+  return new TypeObjectField(name, type, html, optional: optional, value: value
+      );
+}
+
+/**
+ * Read the API description from the file 'spec_input.html'.
+ */
+Api readApi() {
+  File htmlFile = new File('spec_input.html');
+  String htmlContents = htmlFile.readAsStringSync();
+  dom.Document document = parser.parse(htmlContents);
+  return apiFromHtml(document.firstChild);
+}
diff --git a/pkg/analysis_server/tool/spec/generate_files b/pkg/analysis_server/tool/spec/generate_files
new file mode 100755
index 0000000..3bc714f
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/generate_files
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+#
+# This script generates the following files, based on the contents of
+# spec_input.html:
+#
+# - ../../doc/api.html: The human-readable API spec.
+#
+# - ../../test/integration/protocol_matchers.dart: matchers to be used by
+#   integration tests.
+
+set -e
+
+function follow_links() {
+  file="$1"
+  while [ -h "$file" ]; do
+    # On Mac OS, readlink -f doesn't work.
+    file="$(readlink "$file")"
+  done
+  echo "$file"
+}
+
+# Unlike $0, $BASH_SOURCE points to the absolute path of this file.
+PROG_NAME="$(follow_links "$BASH_SOURCE")"
+
+SCRIPT_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+
+ROOT_DIR="$(cd "${SCRIPT_DIR}/../../../.." ; pwd -P)"
+
+BIN_DIR="${ROOT_DIR}/sdk/bin"
+
+if [ -z "$DART_CONFIGURATION" ];
+then
+  DART_CONFIGURATION="ReleaseIA32"
+fi
+
+if [[ `uname` == 'Darwin' ]];
+then
+  BUILD_DIR="${ROOT_DIR}/xcodebuild/$DART_CONFIGURATION"
+else
+  BUILD_DIR="${ROOT_DIR}/out/$DART_CONFIGURATION"
+fi
+
+PKG_DIR="${BUILD_DIR}/packages"
+
+DART="${BIN_DIR}/dart"
+
+declare -a VM_OPTIONS
+VM_OPTIONS+=("--checked")
+VM_OPTIONS+=("--package-root=${PKG_DIR}")
+
+cd "${SCRIPT_DIR}"
+"${DART}" "${VM_OPTIONS[@]}" "to_html.dart"
+"${DART}" "${VM_OPTIONS[@]}" "codegen_matchers.dart"
diff --git a/pkg/analysis_server/tool/spec/html_tools.dart b/pkg/analysis_server/tool/spec/html_tools.dart
new file mode 100644
index 0000000..1dc83de
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/html_tools.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Tools for HTML manipulation.
+ */
+library html.tools;
+
+import 'package:html5lib/dom.dart' as dom;
+
+/**
+ * Make a deep copy of the given HTML nodes.
+ */
+List<dom.Node> cloneHtmlNodes(List<dom.Node> nodes) => nodes.map((dom.Node node)
+    => node.clone(true)).toList();
+
+/**
+ * Create an HTML element with the given name, attributes, and child nodes.
+ */
+dom.Element makeElement(String name, Map<dynamic, String>
+    attributes, List<dom.Node> children) {
+  dom.Element result = new dom.Element.tag(name);
+  result.attributes.addAll(attributes);
+  for (dom.Node child in children) {
+    result.append(child);
+  }
+  return result;
+}
+
+/**
+ * Get the text contents of the element, ignoring all markup.
+ */
+String innerText(dom.Element parent) {
+  StringBuffer buffer = new StringBuffer();
+  void recurse(dom.Element parent) {
+    for (dom.Node child in parent.nodes) {
+      if (child is dom.Text) {
+        buffer.write(child.text);
+      } else if (child is dom.Element) {
+        recurse(child);
+      }
+    }
+  }
+  recurse(parent);
+  return buffer.toString();
+}
+
+/**
+ * Mixin class for generating HTML.
+ */
+class HtmlGenerator {
+  List<dom.Node> _html;
+
+  /**
+   * Execute [callback], collecting any code that is output using [write],
+   * [writeln], [add], or [addAll], and return the result as a list of HTML
+   * nodes.
+   */
+  List<dom.Node> collectHtml(void callback()) {
+    List<dom.Node> oldHtml = _html;
+    try {
+      _html = <dom.Node>[];
+      if (callback != null) {
+        callback();
+      }
+      return _html;
+    } finally {
+      _html = oldHtml;
+    }
+  }
+
+  /**
+   * Add the given [node] to the HTML output.
+   */
+  void add(dom.Node node) {
+    _html.add(node);
+  }
+
+  /**
+   * Add the given [nodes] to the HTML output.
+   */
+  void addAll(Iterable<dom.Node> nodes) {
+    for (dom.Node node in nodes) {
+      add(node);
+    }
+  }
+
+  /**
+   * Output text without ending the current line.
+   */
+  void write(String text) {
+    _html.add(new dom.Text(text));
+  }
+
+  /**
+   * Output text, ending the current line.
+   */
+  void writeln([Object obj = '']) {
+    write('$obj\n');
+  }
+
+  /**
+   * Execute [callback], wrapping its output in an element with the given
+   * [name] and [attributes].
+   */
+  void element(String name, Map<dynamic, String> attributes, [void callback()]) {
+    add(makeElement(name, attributes, collectHtml(callback)));
+  }
+}
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
new file mode 100644
index 0000000..6212494
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -0,0 +1,3026 @@
+<html>
+  <head>
+    <meta charset="UTF-8"/>
+    <title>Analysis Server API Specification</title>
+  </head>
+  <body>
+    <h1>Analysis Server API Specification</h1>
+    <h1 style="color:#999999">WORKING DRAFT - Version <version>0.3</version></h1>
+    <p>
+      This document contains a specification of the API provided by
+      the analysis server. The API in this document is currently under
+      development and should be expected to change. In some cases
+      those changes will be substantial.
+    </p>
+    <h2>Overview</h2>
+    <p>
+      The analysis server API is a bi-directional client-server
+      API. The API is independent of the transport mechanism used, but
+      is heavily influenced by a model in which sockets or character
+      streams are used to transport JSON-RPC encoded information.
+    </p>
+    <h3>Transport Mechanism</h3>
+    <p>
+      The characters passed to the server are expected to be encoded
+      using UTF-8.
+    </p>
+    <p>
+      When character streams are used as the transport, messages are
+      delineated by newlines. This means, in particular, that the JSON
+      encoding process must not introduce newlines within a
+      message. Note however that newlines are used in this document
+      for readability.
+    </p>
+    <p>
+      To ease interoperability with Lisp-based clients (which may not
+      be able to easily distinguish between empty lists, empty maps,
+      and null), client-to-server communication is allowed to replace
+      any instance of “<tt>{}</tt>” or “<tt>[]</tt>” with null. The
+      server will always properly represent empty lists as
+      “<tt>[]</tt>” and empty maps as “<tt>{}</tt>”.
+    </p>
+    <h3>Communication Structure</h3>
+    <p>
+      Clients can make a request of the server and the server will
+      provide a response for each request that it receives. While many
+      of the requests that can be made by a client are informational
+      in nature, we have chosen to always return a response so that
+      clients can know whether the request was received and was
+      correct.
+    </p>
+    <p>
+      There is no guarantee concerning the order in which responses
+      will be returned, but there is a guarantee that the server will
+      process requests in the order in which they are sent as long as
+      the transport mechanism also makes this guarantee. Responses can
+      be returned in an order that is different from the order in
+      which the requests were received because some requests take
+      longer to process than others.
+    </p>
+    <p>
+      Every request is required to have two fields and may have an
+      optional third field. The first required field is the ‘id’
+      field, which is only used by the server to associate a response
+      with the request that generated the response. The second
+      required field is the ‘method’ field, which is used to determine
+      what the server is being requested to do. The optional field is
+      the ‘params’ field, whose structure is dependent on the method
+      being requested. The structure of this field is described with
+      each request for which it is required.
+    </p>
+    <p>
+      Every response has up to three fields. The first field is the
+      ‘id’ field, which is always present and whose value is the
+      identifier that was passed to the request that generated the
+      response. The second field is the ‘error’ field, which is only
+      present if an error was encountered while processing the
+      request. The third field is the ‘result’ field, whose structure
+      is dependent on the method being responded to, and is described
+      with each request that will produce it.
+    </p>
+    <p>
+      The server can also communicate to the clients by sending a
+      notification. The purpose of these notifications is to provide
+      information to clients as it becomes available rather than to
+      require that clients poll for it. Unless explicitly stated, all
+      notifications are designed to return the complete information
+      available at the time the notification is sent; clients are not
+      required to update previously communicated
+      results. Consequently, the server can and should return partial
+      results before all results are available. For example, the
+      syntactic errors for a file can be returned as soon as the
+      syntactic analysis is complete, and both syntactic and semantic
+      errors can be returned together at a later time.
+    </p>
+    <p>
+      Each notification has two fields. The first field is the ‘event’
+      field, which identifies the kind of notification. The second
+      field is the ‘params’ field, whose structure is dependent on the
+      kind of notification being sent. The structure of this field is
+      described with each notification.
+    </p>
+    <h3>Eventual Consistency</h3>
+    <p>
+      TBD
+    </p>
+    <h3>Domains</h3>
+    <p>
+      For convenience, the API is divided into domains. Each domain is
+      specified in a separate section below:
+    </p>
+    <ul>
+      <li><a href="#domain_server">Server</a></li>
+      <li><a href="#domain_analysis">Analysis</a></li>
+      <li><a href="#domain_completion">Code Completion</a></li>
+      <li><a href="#domain_search">Search</a></li>
+      <li><a href="#domain_edit">Edit</a></li>
+      <li><a href="#domain_debug">Debugging</a></li>
+    </ul>
+    <p>
+      The specifications of the API’s refer to data structures beyond
+      the standard JSON primitives. These data structures are
+      documented in the section titled <a href="#types">Types</a>.
+    </p>
+    <h3>Command-line Arguments</h3>
+    <p>
+      The command-line arguments that can be passed to the server.
+    </p>
+    <h4>Options</h4>
+    <dl>
+      <dt>--no-error-notification</dt>
+      <dd></dd>
+    </dl>
+    <p>
+      Disable notifications about errors (see analysis.error). If this
+      flag is not specified then notifications will be sent for all
+      errors produced for all files in the actual analysis roots.
+    </p>
+    <domain name="server">
+      <p>
+        The server domain contains API’s related to the execution of
+        the server.
+      </p>
+      <request method="getVersion">
+        <p>Return the version number of the analysis server.</p>
+        <result>
+          <field name="version">
+            <ref>String</ref>
+            <p>The version number of the analysis server.</p>
+          </field>
+        </result>
+      </request>
+      <request method="shutdown">
+        <p>
+          Cleanly shutdown the analysis server. Requests that are
+          received after this request will not be processed. Requests
+          that were received before this request, but for which a
+          response has not yet been sent, will not be responded to. No
+          further responses or notifications will be sent after the
+          response to this request has been sent.
+        </p>
+      </request>
+      <request method="setSubscriptions">
+        <p>
+          Subscribe for services. All previous subscriptions are
+          replaced by the given set of services.
+        </p>
+        <p>
+          It is an error if any of the elements in the list are not
+          valid services. If there is an error, then the current
+          subscriptions will remain unchanged.
+        </p>
+        <params>
+          <field name="subscriptions">
+            <list><ref>ServerService</ref></list>
+            <p>A list of the services being subscribed to.</p>
+          </field>
+        </params>
+      </request>
+      <notification event="connected">
+        <p>
+          Reports that the server is running. This notification is
+          issued once after the server has started running but before
+          any requests are processed to let the client know that it
+          started correctly.
+        </p>
+        <p>
+          It is not possible to subscribe to or unsubscribe from this
+          notification.
+        </p>
+      </notification>
+      <notification event="error">
+        <p>
+          Reports that an unexpected error has occurred while
+          executing the server. This notification is not used for
+          problems with specific requests (which are returned as part
+          of the response) but is used for exceptions that occur while
+          performing other tasks, such as analysis or preparing
+          notifications.
+        </p>
+        <p>
+          It is not possible to subscribe to or unsubscribe from this
+          notification.
+        </p>
+        <params>
+          <field name="fatal">
+            <ref>bool</ref>
+            <p>
+              True if the error is a fatal error, meaning that the
+              server will shutdown automatically after sending this
+              notification.
+            </p>
+          </field>
+          <field name="message">
+            <ref>String</ref>
+            <p>
+              The error message indicating what kind of error was
+              encountered.
+            </p>
+          </field>
+          <field name="stackTrace">
+            <ref>String</ref>
+            <p>
+              The stack trace associated with the generation of the
+              error, used for debugging the server.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="status">
+        <p>
+          Reports the current status of the server. Parameters are
+          omitted if there has been no change in the status
+          represented by that parameter.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"STATUS"</tt> in
+          the list of services passed in a server.setSubscriptions
+          request.
+        </p>
+        <params>
+          <field name="analysis" optional="true">
+            <ref>AnalysisStatus</ref>
+            <p>
+              The current status of analysis, including whether
+              analysis is being performed and if so what is being
+              analyzed.
+            </p>
+          </field>
+        </params>
+      </notification>
+    </domain>
+    <domain name="analysis">
+      <p>
+        The analysis domain contains API’s related to the analysis of
+        files.
+      </p>
+      <request method="getErrors">
+        <p>
+          Return the errors associated with the given file. If the
+          errors for the given file have not yet been computed, or the
+          most recently computed errors for the given file are out of
+          date, then the response for this request will be delayed
+          until they have been computed. If some or all of the errors
+          for the file cannot be computed, then the subset of the
+          errors that can be computed will be returned and the
+          response will contain an error to indicate why the errors
+          could not be computed.
+        </p>
+        <p>
+          This request is intended to be used by clients that cannot
+          asynchronously apply updated error information. Clients that
+          <b>can</b> apply error information as it becomes available
+          should use the information provided by the 'analysis.errors'
+          notification.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file for which errors are being requested.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="errors">
+            <list><ref>AnalysisError</ref></list>
+            <p>
+              The errors associated with the file.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="getHover">
+        <p>
+          Return the hover information associate with the given
+          location. If some or all of the hover information is not
+          available at the time this request is processed the
+          information will be omitted from the response.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file in which hover information is being
+              requested.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset for which hover information is being
+              requested.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="hovers">
+            <list><ref>HoverInformation</ref></list>
+            <p>
+              The hover information associated with the
+              location. The list will be empty if no information
+              could be determined for the location. The list can
+              contain multiple items if the file is being analyzed
+              in multiple contexts in conflicting ways (such as a
+              part that is included in multiple libraries).
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="reanalyze">
+        <p>
+          Force the re-analysis of everything contained in the
+          existing analysis roots. This will cause all previously
+          computed analysis results to be discarded and recomputed,
+          and will cause all subscribed notifications to be re-sent.
+        </p>
+      </request>
+      <request method="setAnalysisRoots">
+        <p>
+          Sets the root paths used to determine which files to
+          analyze. The set of files to be analyzed are all of the
+          files in one of the root paths that are not also in one of
+          the excluded paths.
+        </p>
+        <p>
+          Note that this request determines the set of requested
+          analysis roots. The actual set of analysis roots at any
+          given time is the intersection of this set with the set of
+          files and directories actually present on the
+          filesystem. When the filesystem changes, the actual set of
+          analysis roots is automatically updated, but the set of
+          requested analysis roots is unchanged. This means that if
+          the client sets an analysis root before the root becomes
+          visible to server in the filesystem, there is no error; once
+          the server sees the root in the filesystem it will start
+          analyzing it. Similarly, server will stop analyzing files
+          that are removed from the file system but they will remain
+          in the set of requested roots.
+        </p>
+        <p>
+          If an included path represents a file, then server will look
+          in the directory containing the file for a pubspec.yaml
+          file. If none is found, then the parents of the directory
+          will be searched until such a file is found or the root of
+          the file system is reached. If such a file is found, it will
+          be used to resolve package: URI’s within the file.
+        </p>
+        <params>
+          <field name="included">
+            <list><ref>FilePath</ref></list>
+            <p>
+              A list of the files and directories that should be
+              analyzed.
+            </p>
+          </field>
+          <field name="excluded">
+            <list><ref>FilePath</ref></list>
+            <p>
+              A list of the files and directories within the
+              included directories that should not be analyzed.
+            </p>
+          </field>
+        </params>
+      </request>
+      <request method="setPriorityFiles">
+        <p>
+          Set the priority files to the files in the given list. A
+          priority file is a file that is given priority when
+          scheduling which analysis work to do first. The list
+          typically contains those files that are visible to the user
+          and those for which analysis results will have the biggest
+          impact on the user experience. The order of the files within
+          the list is significant: the first file will be given higher
+          priority than the second, the second higher priority than
+          the third, and so on.
+        </p>
+        <p>
+          Note that this request determines the set of requested
+          priority files. The actual set of priority files is the
+          intersection of the requested set of priority files with the
+          set of files currently subject to analysis. (See
+          analysis.setSubscriptions for a description of files that
+          are subject to analysis.)
+        </p>
+        <p>
+          If a requested priority file is a directory it is ignored,
+          but remains in the set of requested priority files so that
+          if it later becomes a file it can be included in the set of
+          actual priority files.
+        </p>
+        <params>
+          <field name="files">
+            <list><ref>FilePath</ref></list>
+            <p>
+              The files that are to be a priority for analysis.
+            </p>
+          </field>
+        </params>
+      </request>
+      <request method="setSubscriptions">
+        <p>
+          Subscribe for services. All previous subscriptions are
+          replaced by the current set of subscriptions. If a given
+          service is not included as a key in the map then no files
+          will be subscribed to the service, exactly as if the service
+          had been included in the map with an explicit empty list of
+          files.
+        </p>
+        <p>
+          Note that this request determines the set of requested
+          subscriptions. The actual set of subscriptions at any given
+          time is the intersection of this set with the set of files
+          currently subject to analysis. The files currently subject
+          to analysis are the set of files contained within an actual
+          analysis root but not excluded, plus all of the files
+          transitively reachable from those files via import, export
+          and part directives. (See analysis.setAnalysisRoots for an
+          explanation of how the actual analysis roots are
+          determined.) When the actual analysis roots change, the
+          actual set of subscriptions is automatically updated, but
+          the set of requested subscriptions is unchanged.
+        </p>
+        <p>
+          If a requested subscription is a directory it is ignored,
+          but remains in the set of requested subscriptions so that if
+          it later becomes a file it can be included in the set of
+          actual subscriptions.
+        </p>
+        <p>
+          It is an error if any of the keys in the map are not valid
+          services. If there is an error, then the existing
+          subscriptions will remain unchanged.
+        </p>
+        <params>
+          <field name="subscriptions">
+            <map>
+              <key><ref>AnalysisService</ref></key>
+              <value>
+                <list><ref>FilePath</ref></list>
+              </value>
+            </map>
+            <p>
+              A table mapping services to a list of the files being
+              subscribed to the service.
+            </p>
+          </field>
+        </params>
+      </request>
+      <request method="updateContent">
+        <p>
+          Update the content of one or more files. Files that were
+          previously updated but not included in this update remain
+          unchanged. This effectively represents an overlay of the
+          filesystem. The files whose content is overridden are
+          therefore seen by server as being files with the given
+          content, even if the files do not exist on the filesystem or
+          if the file path represents the path to a directory on the
+          filesystem.
+        </p>
+        <params>
+          <field name="files">
+            <map>
+              <key><ref>FilePath</ref></key>
+              <value><ref>ContentChange</ref></value>
+            </map>
+            <p>
+              A table mapping the files whose content has changed to
+              a description of the content.
+            </p>
+          </field>
+        </params>
+      </request>
+      <request method="updateOptions">
+        <p>
+          Update the options controlling analysis based on the given
+          set of options. Any options that are not included in the
+          analysis options will not be changed. If there are options
+          in the analysis options that are not valid an error will be
+          reported but the values of the valid options will still be
+          updated.
+        </p>
+        <params>
+          <field name="options">
+            <ref>AnalysisOptions</ref>
+            <p>
+              The options that are to be used to control analysis.
+            </p>
+          </field>
+        </params>
+      </request>
+      <notification event="errors">
+        <p>
+          Reports the errors associated with a given file. The set of
+          errors included in the notification is always a complete
+          list that supersedes any previously reported errors.
+        </p>
+        <p>
+          It is only possible to unsubscribe from this notification by
+          using the command-line flag --no-error-notification.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the errors.
+            </p>
+          </field>
+          <field name="errors">
+            <list><ref>AnalysisError</ref></list>
+            <p>
+              The errors contained in the file.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="flushResults">
+        <p>
+          Reports that any analysis results that were previously
+          associated with the given files should be considered to be
+          invalid because those files are no longer being analyzed,
+          either because the analysis root that contained it is no
+          longer being analyzed or because the file no longer exists.
+        </p>
+        <p>
+          If a file is included in this notification and at some later
+          time a notification with results for the file is received,
+          clients should assume that the file is once again being
+          analyzed and the information should be processed.
+        </p>
+        <p>
+          It is not possible to subscribe to or unsubscribe from this
+          notification.
+        </p>
+        <params>
+          <field name="files">
+            <list><ref>FilePath</ref></list>
+            <p>
+              The files that are no longer being analyzed.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="folding">
+        <p>
+          Reports the folding regions associated with a given
+          file. Folding regions can be nested, but will not be
+          overlapping. Nesting occurs when a foldable element, such as
+          a method, is nested inside another foldable element such as
+          a class.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"FOLDING"</tt> in
+          the list of services passed in an analysis.setSubscriptions
+          request.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the folding regions.
+            </p>
+          </field>
+          <field name="regions">
+            <list><ref>FoldingRegion</ref></list>
+            <p>
+              The folding regions contained in the file.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="highlights">
+        <p>
+          Reports the highlight regions associated with a given file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"HIGHLIGHTS"</tt>
+          in the list of services passed in an
+          analysis.setSubscriptions request.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the highlight regions.
+            </p>
+          </field>
+          <field name="regions">
+            <list><ref>HighlightRegion</ref></list>
+            <p>
+              The highlight regions contained in the file. Each
+              highlight region represents a particular syntactic or
+              semantic meaning associated with some range. Note that
+              the highlight regions that are returned can overlap
+              other highlight regions if there is more than one
+              meaning associated with a particular region.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="navigation">
+        <p>
+          Reports the navigation targets associated with a given file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"NAVIGATION"</tt>
+          in the list of services passed in an
+          analysis.setSubscriptions request.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the navigation regions.
+            </p>
+          </field>
+          <field name="regions">
+            <list><ref>NavigationRegion</ref></list>
+            <p>
+              The navigation regions contained in the file. Each
+              navigation region represents a list of targets
+              associated with some range. The lists will usually
+              contain a single target, but can contain more in the
+              case of a part that is included in multiple libraries
+              or in Dart code that is compiled against multiple
+              versions of a package. Note that the navigation
+              regions that are returned do not overlap other
+              navigation regions.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="occurrences">
+        <p>
+          Reports the occurrences of references to elements within a
+          single file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"OCCURRENCES"</tt>
+          in the list of services passed in an
+          analysis.setSubscriptions request.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file in which the references occur.
+            </p>
+          </field>
+          <field name="occurrences">
+            <list><ref>Occurrences</ref></list>
+            <p>
+              The occurrences of references to elements within the
+              file.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="outline">
+        <p>
+          Reports the outline associated with a single file.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"OUTLINE"</tt> in
+          the list of services passed in an analysis.setSubscriptions
+          request.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file with which the outline is associated.
+            </p>
+          </field>
+          <field name="outline">
+            <ref>Outline</ref>
+            <p>
+              The outline associated with the file.
+            </p>
+          </field>
+        </params>
+      </notification>
+      <notification event="overrides">
+        <p>
+          Reports the overridding members in a file. This notification
+          currently includes only members that override a member from
+          a superclass. In particular, it does not include members
+          that override members from interfaces.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value <tt>"OVERRIDES"</tt> in
+          the list of services passed in an analysis.setSubscriptions
+          request.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file with which the overrides are associated.
+            </p>
+          </field>
+          <field name="overrides">
+            <list><ref>Override</ref></list>
+            <p>
+              The overrides associated with the file.
+            </p>
+          </field>
+        </params>
+      </notification>
+    </domain>
+    <domain name="completion">
+      <p>
+        The code completion domain contains commands related to
+        getting code completion suggestions.
+      </p>
+      <request method="getSuggestions">
+        <p>
+          Request that completion suggestions for the given offset in
+          the given file be returned.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the point at which suggestions are
+              to be made.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset within the file at which suggestions are to
+              be made.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="id">
+            <ref>CompletionId</ref>
+            <p>
+              The identifier used to associate results with this
+              completion request.
+            </p>
+          </field>
+        </result>
+      </request>
+      <notification event="results">
+        <p>
+          Reports the completion suggestions that should be presented
+          to the user. The set of suggestions included in the
+          notification is always a complete list that supersedes any
+          previously reported suggestions.
+        </p>
+        <params>
+          <field name="id">
+            <ref>CompletionId</ref>
+            <p>
+              The id associated with the completion.
+            </p>
+          </field>
+          <field name="replacementOffset">
+            <ref>int</ref>
+            <p>
+              The offset of the start of the text to be
+              replaced. This will be different than the offset used
+              to request the completion suggestions if there was a
+              portion of an identifier before the original
+              offset. In particular, the replacementOffset will be
+              the offset of the beginning of said identifier.
+            </p>
+          </field>
+          <field name="replacementLength">
+            <ref>int</ref>
+            <p>
+              The length of the text to be replaced if the remainder
+              of the identifier containing the cursor is to be
+              replaced when the suggestion is applied (that is, the
+              number of characters in the existing identifier).
+            </p>
+          </field>
+          <field name="results">
+            <list><ref>CompletionSuggestion</ref></list>
+            <p>
+              The completion suggestions being reported.
+            </p>
+          </field>
+          <field name="last">
+            <ref>bool</ref>
+            <p>
+              True if this is that last set of results that will be
+              returned for the indicated completion.
+            </p>
+          </field>
+        </params>
+      </notification>
+    </domain>
+    <domain name="search">
+      <p>
+        The search domain contains commands related to searches that
+        can be performed against the code base.
+      </p>
+      <request method="findElementReferences">
+        <p>
+          Perform a search for references to the element defined or
+          referenced at the given offset in the given file.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the declaration of or reference to
+              the element used to define the search.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset within the file of the declaration of or
+              reference to the element.
+            </p>
+          </field>
+          <field name="includePotential">
+            <ref>bool</ref>
+            <p>
+              True if potential matches are to be included in the
+              results.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="id">
+            <ref>SearchId</ref>
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </field>
+          <field name="element">
+            <ref>Element</ref>
+            <p>
+              The element referenced or defined at the given offset
+              and whose references will be returned in the search
+              results.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="findMemberDeclarations">
+        <p>
+          Perform a search for declarations of members whose name is
+          equal to the given name.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        <params>
+          <field name="name">
+            <ref>String</ref>
+            <p>
+              The name of the declarations to be found.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="id">
+            <ref>SearchId</ref>
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="findMemberReferences">
+        <p>
+          Perform a search for references to members whose name is
+          equal to the given name. This search does not check to see
+          that there is a member defined with the given name, so it is
+          able to find references to undefined members as well.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        <params>
+          <field name="name">
+            <ref>String</ref>
+            <p>
+              The name of the references to be found.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="id">
+            <ref>SearchId</ref>
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="findTopLevelDeclarations">
+        <p>
+          Perform a search for declarations of top-level elements
+          (classes, typedefs, getters, setters, functions and fields)
+          whose name matches the given pattern.
+        </p>
+        <p>
+          An identifier is returned immediately, and individual
+          results will be returned via the search.results notification
+          as they become available.
+        </p>
+        <params>
+          <field name="pattern">
+            <ref>String</ref>
+            <p>
+              The regular expression used to match the names of the
+              declarations to be found.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="id">
+            <ref>SearchId</ref>
+            <p>
+              The identifier used to associate results with this
+              search request.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="getTypeHierarchy">
+        <p>
+          Return the type hierarchy of the class declared or
+          referenced at the given location.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the declaration or reference to the
+              type for which a hierarchy is being requested.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the name of the type within the file.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="hierarchyItems">
+            <list><ref>TypeHierarchyItem</ref></list>
+            <p>
+              A list of the types in the requested hierarchy. The
+              first element of the list is the item representing the
+              type for which the hierarchy was requested. The index of
+              other elements of the list is unspecified, but
+              correspond to the integers used to reference supertype
+              and subtype items within the items.
+            </p>
+          </field>
+        </result>
+      </request>
+      <notification event="results">
+        <p>
+          Reports some or all of the results of performing a requested
+          search. Unlike other notifications, this notification
+          contains search results that should be added to any
+          previously received search results associated with the same
+          search id.
+        </p>
+        <params>
+          <field name="id">
+            <ref>SearchId</ref>
+            <p>
+              The id associated with the search.
+            </p>
+          </field>
+          <field name="results">
+            <list><ref>SearchResult</ref></list>
+            <p>
+              The search results being reported.
+            </p>
+          </field>
+          <field name="last">
+            <ref>bool</ref>
+            <p>
+              True if this is that last set of results that will be
+              returned for the indicated search.
+            </p>
+          </field>
+        </params>
+      </notification>
+    </domain>
+    <domain name="edit">
+      <p>
+        The edit domain contains commands related to edits that can be
+        applied to the code.
+      </p>
+      <request method="getAssists">
+        <p>
+          Return the set of assists that are available at the given
+          location. An assist is distinguished from a refactoring
+          primarily by the fact that it affects a single file and does
+          not require user input in order to be performed.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the code for which assists are being
+              requested.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the code for which assists are being
+              requested.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the code for which assists are being
+              requested.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="assists">
+            <list><ref>SourceChange</ref></list>
+            <p>
+              The assists that are available at the given location.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="getAvailableRefactorings">
+        <p>
+          Get a list of the kinds of refactorings that are valid for
+          the given selection in the given file.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the code on which the refactoring
+              would be based.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the code on which the refactoring would be
+              based.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the code on which the refactoring would be
+              based.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="kinds">
+            <list><ref>RefactoringKind</ref></list>
+            <p>
+              The kinds of refactorings that are valid for the given
+              selection.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="getFixes">
+        <p>
+          Return the set of fixes that are available for the errors at
+          a given offset in a given file.
+        </p>
+        <params>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the errors for which fixes are being
+              requested.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset used to select the errors for which fixes
+              will be returned.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="fixes">
+            <list><ref>ErrorFixes</ref></list>
+            <p>
+              The fixes that are available for each of the analysis
+              errors. There is a one-to-one correspondence between the
+              analysis errors in the request and the lists of changes
+              in the response. In particular, it is always the case
+              that errors.length == fixes.length and that fixes[i] is
+              the list of fixes for the error in errors[i]. The list
+              of changes corresponding to an error can be empty if
+              there are no fixes available for that error.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="getRefactoring">
+        <p>
+          Get the changes required to perform a refactoring.
+        </p>
+        <params>
+          <field name="kindId">
+            <ref>RefactoringKind</ref>
+            <p>
+              The identifier of the kind of refactoring to be
+              performed.
+            </p>
+          </field>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the code involved in the
+              refactoring.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the region involved in the refactoring.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the region involved in the refactoring.
+            </p>
+          </field>
+          <field name="validateOnly">
+            <ref>bool</ref>
+            <p>
+              True if the client is only requesting that the values of
+              the options be validated and no change be generated.
+            </p>
+          </field>
+          <field name="options" optional="true">
+            <ref>object</ref>
+            <p>
+              Data used to provide values provided by the user. The
+              structure of the data is dependent on the kind of
+              refactoring being performed. The data that is expected is
+              documented in the section titled <a
+              href="#refactorings">Refactorings</a>, labeled as
+              “Options”. This field can be omitted if the refactoring
+              does not require any options or if the values of those
+              options are not known.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="status">
+            <list><ref>RefactoringProblem</ref></list>
+            <p>
+              The status of the refactoring. The array will be empty
+              if there are no known problems.
+            </p>
+          </field>
+          <field name="feedback" optional="true">
+            <ref>object</ref>
+            <p>
+              Data used to provide feedback to the user. The structure
+              of the data is dependent on the kind of refactoring
+              being created. The data that is returned is documented
+              in the section titled <a
+              href="#refactorings">Refactorings</a>, labeled as
+              “Feedback”.
+            </p>
+          </field>
+          <field name="change" optional="true">
+            <ref>SourceChange</ref>
+            <p>
+              The changes that are to be applied to affect the
+              refactoring. This field will be omitted if there are
+              problems that prevent a set of changed from being
+              computed, such as having no options specified for a
+              refactoring that requires them, or if only validation
+              was requested.
+            </p>
+          </field>
+        </result>
+      </request>
+    </domain>
+    <domain name="debug">
+      <p>
+        The debugging domain contains commands related to providing a
+        debugging experience.
+      </p>
+      <request method="createContext">
+        <p>
+          Create a debugging context for the executable file with the
+          given path. The context that is created will persist until
+          debug.deleteContext is used to delete it. Clients,
+          therefore, are responsible for managing the lifetime of
+          debugging contexts.
+        </p>
+        <params>
+          <field name="contextRoot">
+            <ref>FilePath</ref>
+            <p>
+              The path of the Dart or HTML file that will be launched.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="id">
+            <ref>DebugContextId</ref>
+            <p>
+              The identifier used to refer to the debugging context
+              that was created.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="deleteContext">
+        <p>
+          Delete the debugging context with the given identifier. The
+          context id is no longer valid after this command. The server
+          is allowed to re-use ids when they are no longer valid.
+        </p>
+        <params>
+          <field name="id">
+            <ref>DebugContextId</ref>
+            <p>
+              The identifier of the debugging context that is to be
+              deleted.
+            </p>
+          </field>
+        </params>
+      </request>
+      <request method="mapUri">
+        <p>
+          Map a URI from the debugging context to the file that it
+          corresponds to, or map a file to the URI that it corresponds
+          to in the debugging context.
+        </p>
+        <p>
+          Exactly one of the file and uri fields must be provided.
+        </p>
+        <params>
+          <field name="id">
+            <ref>DebugContextId</ref>
+            <p>
+              The identifier of the debugging context in which the URI
+              is to be mapped.
+            </p>
+          </field>
+          <field name="file" optional="true">
+            <ref>FilePath</ref>
+            <p>
+              The path of the file to be mapped into a URI.
+            </p>
+          </field>
+          <field name="uri" optional="true">
+            <ref>String</ref>
+            <p>
+              The URI to be mapped into a file path.
+            </p>
+          </field>
+        </params>
+        <result>
+          <field name="file" optional="true">
+            <ref>FilePath</ref>
+            <p>
+              The file to which the URI was mapped. This field is
+              omitted if the uri field was not given in the request.
+            </p>
+          </field>
+          <field name="uri" optional="true">
+            <ref>String</ref>
+            <p>
+              The URI to which the file path was mapped. This field is
+              omitted if the file field was not given in the request.
+            </p>
+          </field>
+        </result>
+      </request>
+      <request method="setSubscriptions">
+        <p>
+          Subscribe for services. All previous subscriptions are
+          replaced by the given set of services.
+        </p>
+        <p>
+          It is an error if any of the elements in the list are not
+          valid services. If there is an error, then the current
+          subscriptions will remain unchanged.
+        </p>
+        <params>
+          <field name="subscriptions">
+            <list><ref>DebugService</ref></list>
+            <p>
+              A list of the services being subscribed to.
+            </p>
+          </field>
+        </params>
+      </request>
+      <notification event="launchData">
+        <p>
+          Reports information needed to allow applications within the
+          given context to be launched.
+        </p>
+        <p>
+          This notification is not subscribed to by default. Clients
+          can subscribe by including the value "LAUNCH_DATA" in the
+          list of services passed in a debug.setSubscriptions request.
+        </p>
+        <params>
+          <field name="executables">
+            <list><ref>ExecutableFile</ref></list>
+            <p>
+              A list of the files that are executable in the given
+              context. This list replaces any previous list provided
+              for the given context.
+            </p>
+          </field>
+          <field name="dartToHtml">
+            <map>
+              <key><ref>FilePath</ref></key>
+              <value>
+                <list><ref>FilePath</ref></list>
+              </value>
+            </map>
+            <p>
+              A mapping from the paths of Dart files that are
+              referenced by HTML files to a list of the HTML files
+              that reference the Dart files.
+            </p>
+          </field>
+          <field name="htmlToDart">
+            <map>
+              <key><ref>FilePath</ref></key>
+              <value>
+                <list><ref>FilePath</ref></list>
+              </value>
+            </map>
+            <p>
+              A mapping from the paths of HTML files that reference
+              Dart files to a list of the Dart files they reference.
+            </p>
+          </field>
+        </params>
+      </notification>
+    </domain>
+    <types>
+      <h2><a name="types">Types</a></h2>
+      <p>
+        This section contains descriptions of the data types referenced
+        in the API’s of the various domains.
+      </p>
+      <type name="AnalysisError">
+        <p>
+          An indication of an error, warning, or hint that was produced
+          by the analysis.
+        </p>
+        <object>
+          <field name="severity">
+            <ref>ErrorSeverity</ref>
+            <p>
+              The severity of the error.
+            </p>
+          </field>
+          <field name="type">
+            <ref>ErrorType</ref>
+            <p>
+              The type of the error.
+            </p>
+          </field>
+          <field name="location">
+            <ref>Location</ref>
+            <p>
+              The location associated with the error.
+            </p>
+          </field>
+          <field name="message">
+            <ref>String</ref>
+            <p>
+              The message to be displayed for this error. The message
+              should indicate what is wrong with the code and why it is
+              wrong.
+            </p>
+          </field>
+          <field name="correction" optional="true">
+            <ref>String</ref>
+            <p>
+              The correction message to be displayed for this error. The
+              correction message should indicate how the user can fix
+              the error. The field is omitted if there is no correction
+              message associated with the error code.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="AnalysisOptions">
+        <p>
+          A set of options controlling what kind of analysis is to be
+          performed. If the value of a field is omitted the value of the
+          option will not be changed.
+        </p>
+        <p>
+          NOTE: These options need to change.
+        </p>
+        <object>
+          <field name="analyzeAngular" optional="true">
+            <ref>bool</ref>
+            <p>
+              True if the client wants Angular code to be analyzed.
+            </p>
+          </field>
+          <field name="analyzePolymer" optional="true">
+            <ref>bool</ref>
+            <p>
+              True if the client wants Polymer code to be analyzed.
+            </p>
+          </field>
+          <field name="enableAsync" optional="true">
+            <ref>bool</ref>
+            <p>
+              True if the client wants to enable support for the
+              proposed async feature.
+            </p>
+          </field>
+          <field name="enableDeferredLoading" optional="true">
+            <ref>bool</ref>
+            <p>
+              True if the client wants to enable support for the
+              proposed deferred loading feature.
+            </p>
+          </field>
+          <field name="enableEnums" optional="true">
+            <ref>bool</ref>
+            <p>
+              True if the client wants to enable support for the
+              proposed enum feature.
+            </p>
+          </field>
+          <field name="generateDart2jsHints" optional="true">
+            <ref>bool</ref>
+            <p>
+              True if hints that are specific to dart2js should be
+              generated. This option is ignored if either provideErrors
+              or generateHints is false.
+            </p>
+          </field>
+          <field name="generateHints" optional="true">
+            <ref>bool</ref>
+            <p>
+              True is hints should be generated as part of generating
+              errors and warnings. This option is ignored if
+              provideErrors is false.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="AnalysisService">
+        <p>
+          An enumeration of the services provided by the analysis
+          domain.
+        </p>
+        <enum>
+          <value><code>FOLDING</code></value>
+          <value><code>HIGHLIGHTS</code></value>
+          <value><code>NAVIGATION</code></value>
+          <value><code>OCCURRENCES</code></value>
+          <value><code>OUTLINE</code></value>
+          <value><code>OVERRIDES</code></value>
+        </enum>
+      </type>
+      <type name="AnalysisStatus">
+        <p>
+          An indication of the current state of analysis.
+        </p>
+        <object>
+          <field name="analyzing">
+            <ref>bool</ref>
+            <p>True if analysis is currently being performed.</p>
+          </field>
+          <field name="analysisTarget" optional="true">
+            <ref>String</ref>
+            <p>
+              The name of the current target of analysis. This field is
+              omitted if analyzing is false.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="CompletionId">
+        <ref>String</ref>
+        <p>
+          An identifier used to associate completion results with a
+          completion request.
+        </p>
+      </type>
+      <type name="CompletionRelevance">
+        <p>
+          An enumeration of the relevance of a completion
+          suggestion.
+        </p>
+        <enum>
+          <value><code>LOW</code></value>
+          <value><code>DEFAULT</code></value>
+          <value><code>HIGH</code></value>
+        </enum>
+      </type>
+      <type name="CompletionSuggestion">
+        <p>
+          A suggestion for how to complete partially entered text. Many
+          of the fields are optional, depending on the kind of element
+          being suggested.
+        </p>
+        <object>
+          <field name="kind">
+            <ref>CompletionSuggestionKind</ref>
+            <p>
+              The kind of element being suggested.
+            </p>
+          </field>
+          <field name="relevance">
+            <ref>CompletionRelevance</ref>
+            <p>
+              The relevance of this completion suggestion.
+            </p>
+          </field>
+          <field name="completion">
+            <ref>String</ref>
+            <p>
+              The identifier to be inserted if the suggestion is
+              selected. If the suggestion is for a method or function,
+              the client might want to additionally insert a template
+              for the parameters. The information required in order to
+              do so is contained in other fields.
+            </p>
+          </field>
+          <field name="selectionOffset">
+            <ref>int</ref>
+            <p>
+              The offset, relative to the beginning of the completion,
+              of where the selection should be placed after insertion.
+            </p>
+          </field>
+          <field name="selectionLength">
+            <ref>int</ref>
+            <p>
+              The number of characters that should be selected after
+              insertion.
+            </p>
+          </field>
+          <field name="isDeprecated">
+            <ref>bool</ref>
+            <p>
+              True if the suggested element is deprecated.
+            </p>
+          </field>
+          <field name="isPotential">
+            <ref>bool</ref>
+            <p>
+              True if the element is not known to be valid for the
+              target. This happens if the type of the target is dynamic.
+            </p>
+          </field>
+          <field name="docSummary" optional="true">
+            <ref>String</ref>
+            <p>
+              An abbreviated version of the Dartdoc associated with the
+              element being suggested, This field is omitted if there is
+              no Dartdoc associated with the element.
+            </p>
+          </field>
+          <field name="docComplete" optional="true">
+            <ref>String</ref>
+            <p>
+              The Dartdoc associated with the element being suggested,
+              This field is omitted if there is no Dartdoc associated
+              with the element.
+            </p>
+          </field>
+          <field name="declaringType" optional="true">
+            <ref>String</ref>
+            <p>
+              The class that declares the element being suggested. This
+              field is omitted if the suggested element is not a member
+              of a class.
+            </p>
+          </field>
+          <field name="returnType" optional="true">
+            <ref>String</ref>
+            <p>
+              The return type of the getter, function or method being
+              suggested. This field is omitted if the suggested element
+              is not a getter, function or method.
+            </p>
+          </field>
+          <field name="parameterNames" optional="true">
+            <list><ref>String</ref></list>
+            <p>
+              The names of the parameters of the function or method
+              being suggested. This field is omitted if the suggested
+              element is not a setter, function or method.
+            </p>
+          </field>
+          <field name="parameterTypes" optional="true">
+            <list><ref>String</ref></list>
+            <p>
+              The types of the parameters of the function or method
+              being suggested. This field is omitted if the
+              parameterNames field is omitted.
+            </p>
+          </field>
+          <field name="requiredParameterCount" optional="true">
+            <ref>int</ref>
+            <p>
+              The number of required parameters for the function or
+              method being suggested. This field is omitted if the
+              parameterNames field is omitted.
+            </p>
+          </field>
+          <field name="positionalParameterCount" optional="true">
+            <ref>int</ref>
+            <p>
+              The number of positional parameters for the function or
+              method being suggested. This field is omitted if the
+              parameterNames field is omitted.
+            </p>
+          </field>
+          <field name="parameterName" optional="true">
+            <ref>String</ref>
+            <p>
+              The name of the optional parameter being suggested. This
+              field is omitted if the suggestion is not the addition of
+              an optional argument within an argument list.
+            </p>
+          </field>
+          <field name="parameterType" optional="true">
+            <ref>String</ref>
+            <p>
+              The type of the options parameter being suggested. This
+              field is omitted if the parameterName field is omitted.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="CompletionSuggestionKind">
+        <p>
+          An enumeration of the kinds of elements that can be included
+          in a completion suggestion.
+        </p>
+        <enum>
+          <value><code>ARGUMENT_LIST</code></value>
+          <value><code>CLASS</code></value>
+          <value><code>CLASS_ALIAS</code></value>
+          <value><code>CONSTRUCTOR</code></value>
+          <value><code>FIELD</code></value>
+          <value><code>FUNCTION</code></value>
+          <value><code>FUNCTION_TYPE_ALIAS</code></value>
+          <value><code>GETTER</code></value>
+          <value><code>IMPORT</code></value>
+          <value><code>LIBRARY_PREFIX</code></value>
+          <value><code>LOCAL_VARIABLE</code></value>
+          <value><code>METHOD</code></value>
+          <value><code>METHOD_NAME</code></value>
+          <value><code>NAMED_ARGUMENT</code></value>
+          <value><code>OPTIONAL_ARGUMENT</code></value>
+          <value><code>PARAMETER</code></value>
+          <value><code>SETTER</code></value>
+          <value><code>TOP_LEVEL_VARIABLE</code></value>
+          <value><code>TYPE_PARAMETER</code></value>
+        </enum>
+      </type>
+      <type name="ContentChange">
+        <p>
+          A description of the change to the content of a file. The
+          optional fields are omitted if there is no single range of
+          characters that was modified. If any of the optional fields
+          are provided then all of the optional fields must be provided.
+        </p>
+        <object>
+          <field name="content">
+            <ref>String</ref>
+            <p>
+              The new content of the file, or null if the content of the
+              file should be read from disk.
+            </p>
+          </field>
+          <field name="offset" optional="true">
+            <ref>int</ref>
+            <p>
+              The offset of the region that was modified.
+            </p>
+          </field>
+          <field name="oldLength" optional="true">
+            <ref>int</ref>
+            <p>
+              The length of the region that was removed.
+            </p>
+          </field>
+          <field name="newLength" optional="true">
+            <ref>int</ref>
+            <p>
+              The length of the region that was added.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="DebugContextId">
+        <ref>String</ref>
+        <p>
+          The identifier for a debug context.
+        </p>
+      </type>
+      <type name="DebugService">
+        <p>
+          An enumeration of the services provided by the debug
+          domain.
+        </p>
+        <enum>
+          <value><code>LAUNCH_DATA</code></value>
+        </enum>
+      </type>
+      <type name="Element">
+        <p>
+          Information about an element (something that can be declared
+          in code).
+        </p>
+        <object>
+          <field name="kind">
+            <ref>ElementKind</ref>
+            <p>
+              The kind of the element.
+            </p>
+          </field>
+          <field name="name">
+            <ref>String</ref>
+            <p>
+              The name of the element. This is typically used as the
+              label in the outline.
+            </p>
+          </field>
+          <field name="location" optional="true">
+            <ref>Location</ref>
+            <p>
+              The location of the name in the declaration of the
+              element.
+            </p>
+          </field>
+          <field name="flags">
+            <ref>int</ref>
+            <p>
+              A bit-map containing the following flags:
+            </p>
+            <ul>
+              <li>0x01 - set if the element is explicitly or implicitly abstract</li>
+              <li>0x02 - set if the element was declared to be ‘const’</li>
+              <li>0x04 - set if the element was declared to be ‘final’</li>
+              <li>0x08 - set if the element is a static member of a class or is a top-level function or field</li>
+              <li>0x10 - set if the element is private</li>
+              <li>0x20 - set if the element is deprecated</li>
+            </ul>
+          </field>
+          <field name="parameters" optional="true">
+            <ref>String</ref>
+            <p>
+              The parameter list for the element. If the element is not
+              a method or function this field will not be defined. If
+              the element has zero parameters, this field will have a
+              value of "()".
+            </p>
+          </field>
+          <field name="returnType" optional="true">
+            <ref>String</ref>
+            <p>
+              The return type of the element. If the element is not a
+              method or function this field will not be defined. If the
+              element does not have a declared return type, this field
+              will contain an empty string.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="ElementKind">
+        <p>
+          An enumeration of the kinds of elements.
+        </p>
+        <enum>
+          <value><code>CLASS</code></value>
+          <value><code>CLASS_TYPE_ALIAS</code></value>
+          <value><code>COMPILATION_UNIT</code></value>
+          <value><code>CONSTRUCTOR</code></value>
+          <value><code>GETTER</code></value>
+          <value><code>FIELD</code></value>
+          <value><code>FUNCTION</code></value>
+          <value><code>FUNCTION_TYPE_ALIAS</code></value>
+          <value><code>LIBRARY</code></value>
+          <value><code>LOCAL_VARIABLE</code></value>
+          <value><code>METHOD</code></value>
+          <value><code>SETTER</code></value>
+          <value><code>TOP_LEVEL_VARIABLE</code></value>
+          <value><code>TYPE_PARAMETER</code></value>
+          <value><code>UNKNOWN</code></value>
+          <value><code>UNIT_TEST_GROUP</code></value>
+          <value><code>UNIT_TEST_TEST</code></value>
+        </enum>
+      </type>
+      <type name="Error">
+        <p>
+          An indication of a problem with the execution of the server,
+          typically in response to a request. The error codes that can
+          be returned are documented in the section titled Errors.
+        </p>
+        <object>
+          <field name="code">
+            <ref>String</ref>
+            <p>
+              A code that uniquely identifies the error that occurred.
+            </p>
+          </field>
+          <field name="message">
+            <ref>String</ref>
+            <p>
+              A short description of the error.
+            </p>
+          </field>
+          <field name="data" optional="true">
+            <ref>object</ref>
+            <p>
+              Additional data related to the error. This field is
+              omitted if there is no additional data available.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="ErrorFixes">
+        <p>
+          A list of fixes associated with a specific error
+        </p>
+        <object>
+          <field name="error">
+            <ref>AnalysisError</ref>
+            <p>
+              The error with which the fixes are associated.
+            </p>
+          </field>
+          <field name="fixes">
+            <list><ref>SourceChange</ref></list>
+            <p>
+              The fixes associated with the error.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="ErrorSeverity">
+        <p>
+          An enumeration of the possible severities of analysis
+          errors.
+        </p>
+        <enum>
+          <value><code>INFO</code></value>
+          <value><code>WARNING</code></value>
+          <value><code>ERROR</code></value>
+        </enum>
+      </type>
+      <type name="ErrorType">
+        <p>
+          An enumeration of the possible types of analysis errors.
+        </p>
+        <enum>
+          <value><code>COMPILE_TIME_ERROR</code></value>
+          <value><code>HINT</code></value>
+          <value><code>STATIC_TYPE_WARNING</code></value>
+          <value><code>STATIC_WARNING</code></value>
+          <value><code>SYNTACTIC_ERROR</code></value>
+          <value><code>TODO</code></value>
+        </enum>
+      </type>
+      <type name="ExecutableFile">
+        <p>
+          A description of an executable file.
+        </p>
+        <object>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The path of the executable file.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>ExecutableKind</ref>
+            <p>
+              The offset of the region to be highlighted.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="ExecutableKind">
+        <p>
+          An enumeration of the kinds of executable files.
+        </p>
+        <enum>
+          <value><code>CLIENT</code></value>
+          <value><code>EITHER</code></value>
+          <value><code>SERVER</code></value>
+        </enum>
+      </type>
+      <type name="FilePath">
+        <ref>String</ref>
+        <p>
+          The absolute path of a file.
+        </p>
+      </type>
+      <type name="FoldingKind">
+        <p>
+          An enumeration of the kinds of folding regions.
+        </p>
+        <enum>
+          <value><code>COMMENT</code></value>
+          <value><code>CLASS_MEMBER</code></value>
+          <value><code>DIRECTIVES</code></value>
+          <value><code>DOCUMENTATION_COMMENT</code></value>
+          <value><code>TOP_LEVEL_DECLARATION</code></value>
+        </enum>
+      </type>
+      <type name="FoldingRegion">
+        <p>
+          A description of a region that can be folded.
+        </p>
+        <object>
+          <field name="kind">
+            <ref>FoldingKind</ref>
+            <p>
+              The kind of the region.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the region to be folded.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the region to be folded.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="HighlightRegion">
+        <p>
+          A description of a region that could have special highlighting
+          associated with it.
+        </p>
+        <object>
+          <field name="type">
+            <ref>HighlightRegionType</ref>
+            <p>
+              The type of highlight associated with the region.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the region to be highlighted.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the region to be highlighted.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="HighlightRegionType">
+        <p>
+          An enumeration of the kinds of highlighting that can be
+          applied to files.
+        </p>
+        <enum>
+          <value><code>ANNOTATION</code></value>
+          <value><code>BUILT_IN</code></value>
+          <value><code>CLASS</code></value>
+          <value><code>COMMENT_BLOCK</code></value>
+          <value><code>COMMENT_DOCUMENTATION</code></value>
+          <value><code>COMMENT_END_OF_LINE</code></value>
+          <value><code>CONSTRUCTOR</code></value>
+          <value><code>DIRECTIVE</code></value>
+          <value><code>DYNAMIC_TYPE</code></value>
+          <value><code>FIELD</code></value>
+          <value><code>FIELD_STATIC</code></value>
+          <value><code>FUNCTION_DECLARATION</code></value>
+          <value><code>FUNCTION</code></value>
+          <value><code>FUNCTION_TYPE_ALIAS</code></value>
+          <value><code>GETTER_DECLARATION</code></value>
+          <value><code>KEYWORD</code></value>
+          <value><code>IDENTIFIER_DEFAULT</code></value>
+          <value><code>IMPORT_PREFIX</code></value>
+          <value><code>LITERAL_BOOLEAN</code></value>
+          <value><code>LITERAL_DOUBLE</code></value>
+          <value><code>LITERAL_INTEGER</code></value>
+          <value><code>LITERAL_LIST</code></value>
+          <value><code>LITERAL_MAP</code></value>
+          <value><code>LITERAL_STRING</code></value>
+          <value><code>LOCAL_VARIABLE_DECLARATION</code></value>
+          <value><code>LOCAL_VARIABLE</code></value>
+          <value><code>METHOD_DECLARATION</code></value>
+          <value><code>METHOD_DECLARATION_STATIC</code></value>
+          <value><code>METHOD</code></value>
+          <value><code>METHOD_STATIC</code></value>
+          <value><code>PARAMETER</code></value>
+          <value><code>SETTER_DECLARATION</code></value>
+          <value><code>TOP_LEVEL_VARIABLE</code></value>
+          <value><code>TYPE_NAME_DYNAMIC</code></value>
+          <value><code>TYPE_PARAMETER</code></value>
+        </enum>
+      </type>
+      <type name="HoverInformation">
+        <p>
+          The hover information associated with a specific location.
+        </p>
+        <object>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the range of characters that encompases the
+              cursor position and has the same hover information as the
+              cursor position.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the range of characters that encompases the
+              cursor position and has the same hover information as the
+              cursor position.
+            </p>
+          </field>
+          <field name="containingLibraryPath" optional="true">
+            <ref>String</ref>
+            <p>
+              The path to the defining compilation unit of the library
+              in which the referenced element is declared. This data is
+              omitted if there is no referenced element.
+            </p>
+          </field>
+          <field name="containingLibraryName" optional="true">
+            <ref>String</ref>
+            <p>
+              The name of the library in which the referenced element is
+              declared. This data is omitted if there is no referenced
+              element.
+            </p>
+          </field>
+          <field name="dartdoc" optional="true">
+            <ref>String</ref>
+            <p>
+              The dartdoc associated with the referenced element. Other
+              than the removal of the comment delimiters, including
+              leading asterisks in the case of a block comment, the
+              dartdoc is unprocessed markdown. This data is omitted if
+              there is no referenced element.
+            </p>
+          </field>
+          <field name="elementDescription" optional="true">
+            <ref>String</ref>
+            <p>
+              A human-readable description of the element being
+              referenced. This data is omitted if there is no referenced
+              element.
+            </p>
+          </field>
+          <field name="elementKind" optional="true">
+            <ref>String</ref>
+            <p>
+              A human-readable description of the kind of element being
+              referenced (such as “class” or “function type
+              alias”). This data is omitted if there is no referenced
+              element.
+            </p>
+          </field>
+          <field name="parameter" optional="true">
+            <ref>String</ref>
+            <p>
+              A human-readable description of the parameter
+              corresponding to the expression being hovered over. This
+              data is omitted if the location is not in an argument to a
+              function.
+            </p>
+          </field>
+          <field name="propagatedType" optional="true">
+            <ref>String</ref>
+            <p>
+              The name of the propagated type of the expression. This
+              data is omitted if the location does not correspond to an
+              expression or if there is no propagated type information.
+            </p>
+          </field>
+          <field name="staticType" optional="true">
+            <ref>String</ref>
+            <p>
+              The name of the static type of the expression. This data
+              is omitted if the location does not correspond to an
+              expression.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="LinkedEditGroup">
+        <p>
+          A collection of positions that should be linked (edited
+          simultaneously) for the purposes of updating code after a
+          source change. For example, if a set of edits introduced a
+          new variable name, the group would contain all of the
+          positions of the variable name so that if the client wanted
+          to let the user edit the variable name after the operation,
+          all occurrences of the name could be edited simultaneously.
+        </p>
+        <object>
+          <field name="positions">
+            <list><ref>Position</ref></list>
+            <p>
+              The positions of the regions that should be edited
+              simultaneously.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the regions that should be edited
+              simultaneously.
+            </p>
+          </field>
+          <field name="suggestions">
+            <list><ref>LinkedEditSuggestion</ref></list>
+            <p>
+              Pre-computed suggestions for what every region might
+              want to be changed to.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="LinkedEditSuggestion">
+        <p>
+          A suggestion of a value that could be used to replace all of
+          the linked edit regions in a LinkedEditGroup.
+        </p>
+        <object>
+          <field name="value">
+            <ref>String</ref>
+            <p>
+              The value that could be used to replace all of the linked
+              edit regions.
+            </p>
+          </field>
+          <field name="kind">
+            <ref>LinkedEditSuggestionKind</ref>
+            <p>
+              The kind of value being proposed.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="LinkedEditSuggestionKind">
+        <p>
+          An enumeration of the kind of values that can be suggested
+          for a linked edit.
+        </p>
+        <enum>
+          <value><code>METHOD</code></value>
+          <value><code>PARAMETER</code></value>
+          <value><code>TYPE</code></value>
+          <value><code>VARIABLE</code></value>
+        </enum>
+      </type>
+      <type name="Location">
+        <p>
+          A location (character range) within a file.
+        </p>
+        <object>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the range.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the range.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the range.
+            </p>
+          </field>
+          <field name="startLine">
+            <ref>int</ref>
+            <p>
+              The one-based index of the line containing the first
+              character of the range.
+            </p>
+          </field>
+          <field name="startColumn">
+            <ref>int</ref>
+            <p>
+              The one-based index of the column containing the first
+              character of the range.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="NavigationRegion">
+        <p>
+          A description of a region from which the user can navigate to
+          the declaration of an element.
+        </p>
+        <object>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the region from which the user can navigate.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the region from which the user can navigate.
+            </p>
+          </field>
+          <field name="targets">
+            <list><ref>Element</ref></list>
+            <p>
+              The elements to which the given region is bound. By
+              opening the declaration of the elements, clients can
+              implement one form of navigation.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="Occurrences">
+        <p>
+          A description of the references to a single element within a
+          single file.
+        </p>
+        <object>
+          <field name="element">
+            <ref>Element</ref>
+            <p>
+              The element that was referenced.
+            </p>
+          </field>
+          <field name="offsets">
+            <list><ref>int</ref></list>
+            <p>
+              The offsets of the name of the referenced element within
+              the file.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the name of the referenced element.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="Outline">
+        <p>
+          An node in the outline structure of a file.
+        </p>
+        <object>
+          <field name="element">
+            <ref>Element</ref>
+            <p>
+              A description of the element represented by this node.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the first character of the element. This is
+              different than the offset in the Element, which if the
+              offset of the name of the element. It can be used, for
+              example, to map locations in the file back to an outline.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the element.
+            </p>
+          </field>
+          <field name="children" optional="true">
+            <list><ref>Outline</ref></list>
+            <p>
+              The children of the node. The field will be omitted if the
+              node has no children.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="Override">
+        <p>
+          A description of a member that overrides an inherited member.
+        </p>
+        <object>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the name of the overriding member.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the name of the overriding member.
+            </p>
+          </field>
+          <field name="superclassMember" optional="true">
+            <ref>OverriddenMember</ref>
+            <p>
+              The member inherited from a superclass that is overridden
+              by the overriding member. The field is omitted if there is
+              no superclass member, in which case there must be at least
+              one interface member.
+            </p>
+          </field>
+          <field name="interfaceMembers" optional="true">
+            <list><ref>OverriddenMember</ref></list>
+            <p>
+              The members inherited from interfaces that are overridden
+              by the overriding member. The field is omitted if there
+              are no interface members, in which case there must be a
+              superclass member.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="OverriddenMember">
+        <p>
+          A description of a member that is being overridden.
+        </p>
+        <object>
+          <field name="element">
+            <ref>Element</ref>
+            <p>
+              The element that is being overridden.
+            </p>
+          </field>
+          <field name="className">
+            <ref>String</ref>
+            <p>
+              The name of the class in which the member is defined.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="Parameter">
+        <p>
+          A description of a parameter.
+        </p>
+        <object>
+          <field name="type">
+            <ref>String</ref>
+            <p>
+              The type that should be given to the parameter.
+            </p>
+          </field>
+          <field name="name">
+            <ref>String</ref>
+            <p>
+              The name that should be given to the parameter.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="Position">
+        <p>
+          A position within a file.
+        </p>
+        <object>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the position.
+            </p>
+          </field>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the position.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="RefactoringKind">
+        <p>
+          An enumeration of the kinds of refactorings that can be
+          created.
+        </p>
+        <enum>
+          <value><code>CONVERT_GETTER_TO_METHOD</code></value>
+          <value><code>CONVERT_METHOD_TO_GETTER</code></value>
+          <value><code>EXTRACT_LOCAL_VARIABLE</code></value>
+          <value><code>EXTRACT_METHOD</code></value>
+          <value><code>INLINE_LOCAL_VARIABLE</code></value>
+          <value><code>INLINE_METHOD</code></value>
+          <value><code>RENAME</code></value>
+        </enum>
+      </type>
+      <type name="RefactoringProblem">
+        <p>
+          A description of a problem related to a refactoring.
+        </p>
+        <object>
+          <field name="severity">
+            <ref>RefactoringProblemSeverity</ref>
+            <p>
+              The severity of the problem being represented.
+            </p>
+          </field>
+          <field name="message">
+            <ref>String</ref>
+            <p>
+              A human-readable description of the problem being
+              represented.
+            </p>
+          </field>
+          <field name="location">
+            <ref>Location</ref>
+            <p>
+              The location of the problem being represented.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="RefactoringProblemSeverity">
+        <p>
+          An enumeration of the severities of problems that can be
+          returned by the refactoring requests.
+        </p>
+        <enum>
+          <value><code>INFO</code></value>
+          <value><code>WARNING</code></value>
+          <value><code>ERROR</code></value>
+          <value><code>FATAL</code></value>
+        </enum>
+      </type>
+      <type name="SearchId">
+        <ref>String</ref>
+        <p>
+          An identifier used to associate search results with a search
+          request.
+        </p>
+      </type>
+      <type name="SearchResult">
+        <p>
+          A single result from a search request.
+        </p>
+        <object>
+          <field name="location">
+            <ref>Location</ref>
+            <p>
+              The location of the code that matched the search criteria.
+            </p>
+          </field>
+          <field name="kind">
+            <ref>SearchResultKind</ref>
+            <p>
+              The kind of element that was found or the kind of
+              reference that was found.
+            </p>
+          </field>
+          <field name="isPotential">
+            <ref>bool</ref>
+            <p>
+              True if the result is a potential match but cannot be
+              confirmed to be a match. For example, if all references to
+              a method m defined in some class were requested, and a
+              reference to a method m from an unknown class were found,
+              it would be marked as being a potential match.
+            </p>
+          </field>
+          <field name="path">
+            <list><ref>Element</ref></list>
+            <p>
+              The elements that contain the result, starting with the
+              most immediately enclosing ancestor and ending with the
+              library.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="SearchResultKind">
+        <p>
+          An enumeration of the kinds of search results returned by the
+          search domain.
+        </p>
+        <enum>
+          <value>
+            <code>DECLARATION</code>
+            <p>
+              The declaration of an element.
+            </p>
+          </value>
+          <value>
+            <code>INVOCATION</code>
+            <p>
+              The invocation of a function or method.
+            </p>
+          </value>
+          <value>
+            <code>READ</code>
+            <p>
+              A reference to a field, parameter or variable where it is being read.
+            </p>
+          </value>
+          <value>
+            <code>READ_WRITE</code>
+            <p>
+              A reference to a field, parameter or variable where it is being read and written.
+            </p>
+          </value>
+          <value>
+            <code>REFERENCE</code>
+            <p>
+              A reference to an element.
+            </p>
+          </value>
+          <value>
+            <code>WRITE</code>
+            <p>
+              A reference to a field, parameter or variable where it is being written.
+            </p>
+          </value>
+        </enum>
+      </type>
+      <type name="ServerService">
+        <p>
+          An enumeration of the services provided by the server domain.
+        </p>
+        <enum>
+          <value><code>STATUS</code></value>
+        </enum>
+      </type>
+      <type name="SourceChange">
+        <p>
+          A description of a set of edits that implement a single
+          conceptual change.
+        </p>
+        <object>
+          <field name="message">
+            <ref>String</ref>
+            <p>
+              A human-readable description of the change to be applied.
+            </p>
+          </field>
+          <field name="edits">
+            <list><ref>SourceFileEdit</ref></list>
+            <p>
+              A list of the edits used to effect the change, grouped by
+              file.
+            </p>
+          </field>
+          <field name="linkedEditGroups">
+            <list><ref>LinkedEditGroup</ref></list>
+            <p>
+              A list of the linked editing groups used to customize
+              the changes that were made.
+            </p>
+          </field>
+          <field name="selection" optional="true">
+            <ref>Position</ref>
+            <p>
+              The position that should be selected after the edits
+              have been applied.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="SourceEdit">
+        <p>
+          A description of a single change to a single file.
+        </p>
+        <object>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset of the region to be modified.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the region to be modified.
+            </p>
+          </field>
+          <field name="replacement">
+            <ref>String</ref>
+            <p>
+              The code that is to replace the specified region in the
+              original code.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="SourceFileEdit">
+        <p>
+          A description of a set of changes to a single file.
+        </p>
+        <object>
+          <field name="file">
+            <ref>FilePath</ref>
+            <p>
+              The file containing the code to be modified.
+            </p>
+          </field>
+          <field name="edits">
+            <list><ref>SourceEdit</ref></list>
+            <p>
+              A list of the edits used to effect the change.
+            </p>
+          </field>
+        </object>
+      </type>
+      <type name="TypeHierarchyItem">
+        <p>
+          A representation of a class in a type hierarchy.
+        </p>
+        <object>
+          <field name="classElement">
+            <ref>Element</ref>
+            <p>
+              The class element represented by this item.
+            </p>
+          </field>
+          <field name="displayName" optional="true">
+            <ref>String</ref>
+            <p>
+              The name to be displayed for the class. This field will be
+              omitted if the display name is the same as the name of the
+              element. The display name is different if there is
+              additional type information to be displayed, such as type
+              arguments.
+            </p>
+          </field>
+          <field name="memberElement" optional="true">
+            <ref>Element</ref>
+            <p>
+              The member in the class corresponding to the member on
+              which the hierarchy was requested. This field will be
+              omitted if the hierarchy was not requested for a member or
+              if the class does not have a corresponding member.
+            </p>
+          </field>
+          <field name="superclass" optional="true">
+            <ref>int</ref>
+            <p>
+              The index of the item representing the superclass of
+              this class. This field will be omitted if this item
+              represents the class Object.
+            </p>
+          </field>
+          <field name="interfaces">
+            <list><ref>int</ref></list>
+            <p>
+              The indexes of the items representing the interfaces
+              implemented by this class. The list will be empty if
+              there are no implemented interfaces.
+            </p>
+          </field>
+          <field name="mixins">
+            <list><ref>int</ref></list>
+            <p>
+              The indexes of the items representing the mixins
+              referenced by this class. The list will be empty if
+              there are no classes mixed in to this class.
+            </p>
+          </field>
+          <field name="subclasses">
+            <list><ref>int</ref></list>
+            <p>
+              The indexes of the items representing the subtypes of
+              this class. The list will be empty if there are no
+              subtypes or if this item represents a supertype of the
+              pivot type.
+            </p>
+          </field>
+        </object>
+      </type>
+    </types>
+    <refactorings>
+      <h2><a name="refactorings">Refactorings</a></h2>
+      <p>
+        This section contains additional information for each kind of
+        refactoring. In addition to a brief description of the
+        refactoring, there is a specification of the feedback that is
+        provided when a refactoring is created using the
+        edit.createRefactoring request (designed to improve the UX)
+        and the options that must be set using the
+        edit.setRefactoringOptions request before the refactoring can
+        be applied.
+      </p>
+      <refactoring kind="CONVERT_GETTER_TO_METHOD">
+        <p>
+          Convert a getter into a method by removing the keyword get
+          and adding an empty parameter list.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single getter.
+        </p>
+      </refactoring>
+      <refactoring kind="CONVERT_METHOD_TO_GETTER">
+        <p>
+          Convert a method into a getter by adding the keyword get and
+          removing the parameter list.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single method or if the method has
+          a non-empty parameter list.
+        </p>
+      </refactoring>
+      <refactoring kind="EXTRACT_LOCAL_VARIABLE">
+        <p>
+          Create a local variable initialized by a specified
+          expression.
+        </p>
+        <p>
+          It is an error if the range contains anything other than a
+          complete expression (no partial expressions are allowed).
+        </p>
+        <feedback>
+          <field name="names">
+            <list><ref>String</ref></list>
+            <p>
+              The proposed names for the local variable.
+            </p>
+          </field>
+          <field name="offsets">
+            <list><ref>int</ref></list>
+            <p>
+              The offsets of the expressions that would be replaced by
+              a reference to the variable.
+            </p>
+          </field>
+          <field name="lengths">
+            <list><ref>int</ref></list>
+            <p>
+              The lengths of the expressions that would be replaced by
+              a reference to the variable. The lengths correspond to
+              the offsets. In other words, for a given expression, if
+              the offset of that expression is offsets[i], then the
+              length of that expression is lengths[i].
+            </p>
+          </field>
+        </feedback>
+        <options>
+          <field name="name">
+            <ref>String</ref>
+            <p>
+              The name that the local variable should be given.
+            </p>
+          </field>
+          <field name="extractAll">
+            <ref>bool</ref>
+            <p>
+              True if all occurrences of the expression within the
+              scope in which the variable will be defined should be
+              replaced by a reference to the local variable. The
+              expression used to initiate the refactoring will always
+              be replaced.
+            </p>
+          </field>
+        </options>
+      </refactoring>
+      <refactoring kind="EXTRACT_METHOD">
+        <p>
+          Create a method whose body is the specified expression or
+          list of statements, possibly augmented with a return
+          statement.
+        </p>
+        <p>
+          It is an error if the range contains anything other than a
+          complete expression (no partial expressions are allowed) or
+          a complete sequence of statements.
+        </p>
+        <feedback>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset to the beginning of the expression or
+              statements that will be extracted.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the expression or statements that will be
+              extracted.
+            </p>
+          </field>
+          <field name="returnType">
+            <ref>String</ref>
+            <p>
+              The proposed return type for the method.
+            </p>
+          </field>
+          <field name="names">
+            <list><ref>String</ref></list>
+            <p>
+              The proposed names for the method.
+            </p>
+          </field>
+          <field name="canCreateGetter">
+            <ref>bool</ref>
+            <p>
+              True if a getter could be created rather than a method.
+            </p>
+          </field>
+          <field name="parameters">
+            <list><ref>Parameter</ref></list>
+            <p>
+              The proposed parameters for the method.
+            </p>
+          </field>
+          <field name="occurrences">
+            <ref>int</ref>
+            <p>
+              The number of times the expression or statements occurs.
+            </p>
+          </field>
+          <field name="offsets">
+            <list><ref>int</ref></list>
+            <p>
+              The offsets of the expressions or statements that would
+              be replaced by an invocation of the method.
+            </p>
+          </field>
+          <field name="lengths">
+            <list><ref>int</ref></list>
+            <p>
+              The lengths of the expressions or statements that would
+              be replaced by an invocation of the method. The lengths
+              correspond to the offsets. In other words, for a given
+              expression (or block of statements), if the offset of
+              that expression is offsets[i], then the length of that
+              expression is lengths[i].
+            </p>
+          </field>
+        </feedback>
+        <options>
+          <field name="returnType">
+            <ref>String</ref>
+            <p>
+              The return type that should be defined for the method.
+            </p>
+          </field>
+          <field name="createGetter">
+            <ref>bool</ref>
+            <p>
+              True if a getter should be created rather than a
+              method. It is an error if this field is true and the
+              list of parameters is non-empty.
+            </p>
+          </field>
+          <field name="name">
+            <ref>String</ref>
+            <p>
+              The name that the method should be given.
+            </p>
+          </field>
+          <field name="parameters">
+            <list><ref>Parameter</ref></list>
+            <p>
+              The parameters that should be defined for the method.
+            </p>
+          </field>
+          <field name="extractAll">
+            <ref>bool</ref>
+            <p>
+              True if all occurrences of the expression or statements
+              should be replaced by an invocation of the method. The
+              expression or statements used to initiate the
+              refactoring will always be replaced.
+            </p>
+          </field>
+        </options>
+      </refactoring>
+      <refactoring kind="INLINE_LOCAL_VARIABLE">
+        <p>
+          Inline the initializer expression of a local variable in
+          place of any references to that variable.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single local variable.
+        </p>
+      </refactoring>
+      <refactoring kind="INLINE_METHOD">
+        <p>
+          Inline a method in place of one or all references to that
+          method.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single method.
+        </p>
+        <options>
+          <field name="deleteSource">
+            <ref>bool</ref>
+            <p>
+              True if the method being inlined should be removed. It
+              is an error if this field is true and inlineAll is
+              false.
+            </p>
+          </field>
+          <field name="inlineAll">
+            <ref>bool</ref>
+            <p>
+              True if all invocations of the method should be inlined,
+              or false if only the invocation site used to create this
+              refactoring should be inlined.
+            </p>
+          </field>
+        </options>
+      </refactoring>
+      <refactoring kind="RENAME">
+        <p>
+          Rename a given element and all of the references to that
+          element.
+        </p>
+        <p>
+          It is an error if the range contains anything other than all
+          or part of the name of a single function (including methods,
+          getters and setters), variable (including fields, parameters
+          and local variables), class or function type.
+        </p>
+        <feedback>
+          <field name="offset">
+            <ref>int</ref>
+            <p>
+              The offset to the beginning of the name selected to be
+              renamed.
+            </p>
+          </field>
+          <field name="length">
+            <ref>int</ref>
+            <p>
+              The length of the name selected to be renamed.
+            </p>
+          </field>
+        </feedback>
+        <options>
+          <field name="newName">
+            <ref>String</ref>
+            <p>
+              The name that the element should have after the
+              refactoring.
+            </p>
+          </field>
+        </options>
+      </refactoring>
+    </refactorings>
+    <h2>Errors</h2>
+    <p>
+      This section contains a list of all of the errors that are
+      produced by the server and the data that is returned with each.
+    </p>
+    <p>
+      TBD
+    </p>
+  </body>
+</html>
diff --git a/pkg/analysis_server/tool/spec/text_formatter.dart b/pkg/analysis_server/tool/spec/text_formatter.dart
new file mode 100644
index 0000000..522b469
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/text_formatter.dart
@@ -0,0 +1,233 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Code for converting HTML into text, for use in doc comments.
+ */
+library text.formatter;
+
+import 'package:html5lib/dom.dart' as dom;
+
+import 'codegen_tools.dart';
+
+/**
+ * Convert the HTML in [desc] into text, word wrapping at width [width].
+ *
+ * If [javadocStyle] is true, then the output is compatable with Javadoc,
+ * which understands certain HTML constructs.
+ */
+String nodesToText(List<dom.Node> desc, int width, bool javadocStyle) {
+  _TextFormatter formatter = new _TextFormatter(width, javadocStyle);
+  return formatter.collectCode(() {
+    formatter.addAll(desc);
+    formatter.lineBreak(false);
+  });
+}
+
+final RegExp whitespace = new RegExp(r'\s');
+
+/**
+ * Engine that transforms HTML to text.  The input HTML is processed one
+ * character at a time, gathering characters into words and words into lines.
+ */
+class _TextFormatter extends CodeGenerator {
+  /**
+   * Word-wrapping width.
+   */
+  final int width;
+
+  /**
+   * The word currently being gathered.
+   */
+  String word = '';
+
+  /**
+   * The line currently being gathered.
+   */
+  String line = '';
+
+  /**
+   * True if a blank line should be inserted before the next word.
+   */
+  bool verticalSpaceNeeded = false;
+
+  /**
+   * True if no text has been output yet.  This suppresses blank lines.
+   */
+  bool atStart = true;
+
+  /**
+   * True if we are processing a <pre> element, thus whitespace should be
+   * preserved.
+   */
+  bool preserveSpaces = false;
+
+  /**
+   * True if the output should be Javadoc compatible.
+   */
+  final bool javadocStyle;
+
+  _TextFormatter(this.width, this.javadocStyle);
+
+  /**
+   * Escape the given character for HTML.
+   */
+  String escape(String char) {
+    if (javadocStyle) {
+      switch (char) {
+        case '<':
+          return '&lt;';
+        case '>':
+          return '&gt;';
+        case '&':
+          return '&amp;';
+      }
+    }
+    return char;
+  }
+
+  /**
+   * Process an HTML node.
+   */
+  void add(dom.Node node) {
+    if (node is dom.Text) {
+      for (String char in node.text.split('')) {
+        if (preserveSpaces) {
+          wordBreak();
+          write(escape(char));
+        } else if (whitespace.hasMatch(char)) {
+          wordBreak();
+        } else {
+          resolveVerticalSpace();
+          word += escape(char);
+        }
+      }
+    } else if (node is dom.Element) {
+      switch (node.localName) {
+        case 'br':
+          lineBreak(false);
+          break;
+        case 'dl':
+        case 'dt':
+        case 'h1':
+        case 'h2':
+        case 'h3':
+        case 'h4':
+        case 'p':
+          lineBreak(true);
+          addAll(node.nodes);
+          lineBreak(true);
+          break;
+        case 'ul':
+          lineBreak(false);
+          addAll(node.nodes);
+          lineBreak(false);
+          break;
+        case 'li':
+          lineBreak(false);
+          resolveVerticalSpace();
+          indentSpecial('- ', '  ', () {
+            addAll(node.nodes);
+            lineBreak(false);
+          });
+          break;
+        case 'dd':
+          lineBreak(true);
+          indent(() {
+            addAll(node.nodes);
+            lineBreak(true);
+          });
+          break;
+        case 'pre':
+          lineBreak(false);
+          resolveVerticalSpace();
+          if (javadocStyle) {
+            writeln('<pre>');
+          }
+          bool oldPreserveSpaces = preserveSpaces;
+          try {
+            preserveSpaces = true;
+            addAll(node.nodes);
+          } finally {
+            preserveSpaces = oldPreserveSpaces;
+          }
+          writeln();
+          if (javadocStyle) {
+            writeln('</pre>');
+          }
+          lineBreak(false);
+          break;
+        case 'a':
+        case 'b':
+        case 'body':
+        case 'html':
+        case 'i':
+        case 'span':
+        case 'tt':
+          addAll(node.nodes);
+          break;
+        case 'head':
+          break;
+        default:
+          throw new Exception('Unexpected HTML element: ${node.localName}');
+      }
+    } else {
+      throw new Exception('Unexpected HTML: $node');
+    }
+  }
+
+  /**
+   * Insert vertical space if necessary.
+   */
+  void resolveVerticalSpace() {
+    if (verticalSpaceNeeded) {
+      writeln();
+      verticalSpaceNeeded = false;
+    }
+  }
+
+  /**
+   * Terminate the current word, if a word is in progress.
+   */
+  void wordBreak() {
+    if (word.isNotEmpty) {
+      atStart = false;
+      if (line.isNotEmpty) {
+        if (indentWidth + line.length + 1 + word.length <= width)
+            {
+          line += ' $word';
+        } else {
+          writeln(line);
+          line = word;
+        }
+      } else {
+        line = word;
+      }
+      word = '';
+    }
+  }
+
+  /**
+   * Terminate the current word and/or line, if either is in progress.
+   */
+  void lineBreak(bool gap) {
+    wordBreak();
+    if (line.isNotEmpty) {
+      writeln(line);
+      line = '';
+    }
+    if (gap && !atStart) {
+      verticalSpaceNeeded = true;
+    }
+  }
+
+  /**
+   * Process a list of HTML nodes.
+   */
+  void addAll(List<dom.Node> nodes) {
+    for (dom.Node node in nodes) {
+      add(node);
+    }
+  }
+}
diff --git a/pkg/analysis_server/tool/spec/to_html.dart b/pkg/analysis_server/tool/spec/to_html.dart
new file mode 100644
index 0000000..0cb3226
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/to_html.dart
@@ -0,0 +1,528 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Code for displaying the API as HTML.  This is used both for generating a
+ * full description of the API as a web page, and for generating doc comments
+ * in generated code.
+ */
+library to.html;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:html5lib/dom.dart' as dom;
+
+import 'api.dart';
+import 'codegen_tools.dart';
+import 'from_html.dart';
+import 'html_tools.dart';
+
+/**
+ * Embedded stylesheet
+ */
+final String stylesheet =
+    '''
+h1 {
+  text-align: center;
+}
+pre {
+  margin: 0px;
+}
+div.box {
+  border: 1px solid rgb(0, 0, 0);
+  background-color: rgb(207, 226, 243);
+  padding: 0.5em;
+}
+dt {
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+'''.trim(
+    );
+
+/**
+ * Helper methods for creating HTML elements.
+ */
+abstract class HtmlMixin {
+  void element(String name, Map<dynamic, String> attributes, [void callback()]);
+
+  void anchor(String id, void callback()) {
+    element('a', {
+      'name': id
+    }, callback);
+  }
+  void link(String id, void callback()) {
+    element('a', {
+      'href': '#$id'
+    }, callback);
+  }
+  void b(void callback()) => element('b', {}, callback);
+  void box(void callback()) {
+    element('div', {
+      'class': 'box'
+    }, callback);
+  }
+  void br() => element('br', {});
+  void body(void callback()) => element('body', {}, callback);
+  void dd(void callback()) => element('dd', {}, callback);
+  void dl(void callback()) => element('dl', {}, callback);
+  void dt(String cls, void callback()) => element('dt', {
+    'class': cls
+  }, callback);
+  void gray(void callback()) => element('span', {
+    'style': 'color:#999999'
+  }, callback);
+  void h1(void callback()) => element('h1', {}, callback);
+  void h2(void callback()) => element('h2', {}, callback);
+  void h3(void callback()) => element('h3', {}, callback);
+  void h4(void callback()) => element('h4', {}, callback);
+  void head(void callback()) => element('head', {}, callback);
+  void html(void callback()) => element('html', {}, callback);
+  void i(void callback()) => element('i', {}, callback);
+  void p(void callback()) => element('p', {}, callback);
+  void pre(void callback()) => element('pre', {}, callback);
+  void title(void callback()) => element('title', {}, callback);
+  void tt(void callback()) => element('tt', {}, callback);
+}
+
+/**
+ * Visitor that generates a compact representation of a type, such as:
+ *
+ * {
+ *   "id": String
+ *   "error": optional Error
+ *   "result": {
+ *     "version": String
+ *   }
+ * }
+ */
+class TypeVisitor extends HierarchicalApiVisitor with HtmlMixin,
+    HtmlCodeGenerator {
+  /**
+   * Set of fields which should be shown in boldface, or null if no field
+   * should be shown in boldface.
+   */
+  final Set<String> fieldsToBold;
+
+  /**
+   * True if a short description should be generated.  In a short description,
+   * objects are shown as simply "object", and enums are shown as "String".
+   */
+  final bool short;
+
+  TypeVisitor(Api api, {this.fieldsToBold, this.short: false}) : super(api);
+
+  @override
+  void visitTypeEnum(TypeEnum typeEnum) {
+    if (short) {
+      write('String');
+      return;
+    }
+    writeln('enum {');
+    indent(() {
+      for (TypeEnumValue value in typeEnum.values) {
+        writeln(value.value);
+      }
+    });
+    write('}');
+  }
+
+  @override
+  void visitTypeList(TypeList typeList) {
+    write('List<');
+    visitTypeDecl(typeList.itemType);
+    write('>');
+  }
+
+  @override
+  void visitTypeMap(TypeMap typeMap) {
+    write('Map<');
+    visitTypeDecl(typeMap.keyType);
+    write(', ');
+    visitTypeDecl(typeMap.valueType);
+    write('>');
+  }
+
+  @override
+  void visitTypeObject(TypeObject typeObject) {
+    if (short) {
+      write('object');
+      return;
+    }
+    writeln('{');
+    indent(() {
+      for (TypeObjectField field in typeObject.fields) {
+        write('"');
+        if (fieldsToBold != null && fieldsToBold.contains(field.name)) {
+          b(() {
+            write(field.name);
+          });
+        } else {
+          write(field.name);
+        }
+        write('": ');
+        if (field.value != null) {
+          write(JSON.encode(field.value));
+        } else {
+          if (field.optional) {
+            gray(() {
+              write('optional');
+            });
+            write(' ');
+          }
+          visitTypeDecl(field.type);
+        }
+        writeln();
+      }
+    });
+    write('}');
+  }
+
+  @override
+  void visitTypeReference(TypeReference typeReference) {
+    String displayName = typeReference.typeName;
+    if (api.types.containsKey(typeReference.typeName)) {
+      link('type_${typeReference.typeName}', () {
+        write(displayName);
+      });
+    } else {
+      write(displayName);
+    }
+  }
+}
+
+/**
+ * Visitor that records the mapping from HTML elements to various kinds of API
+ * nodes.
+ */
+class ApiMappings extends HierarchicalApiVisitor {
+  ApiMappings(Api api) : super(api);
+
+  Map<dom.Element, Domain> domains = <dom.Element, Domain> {};
+
+  @override
+  void visitDomain(Domain domain) {
+    domains[domain.html] = domain;
+  }
+}
+
+/**
+ * Visitor that generates HTML documentation of the API.
+ */
+class ToHtmlVisitor extends HierarchicalApiVisitor with HtmlMixin, HtmlGenerator
+    {
+  /**
+   * Set of types defined in the API.
+   */
+  Set<String> definedTypes = new Set<String>();
+
+  /**
+   * Mappings from HTML elements to API nodes.
+   */
+  ApiMappings apiMappings;
+
+  ToHtmlVisitor(Api api)
+      : super(api),
+        apiMappings = new ApiMappings(api) {
+    apiMappings.visitApi();
+  }
+
+  @override
+  void visitApi() {
+    definedTypes = api.types.keys.toSet();
+
+    html(() {
+      translateHtml(api.html);
+    });
+  }
+
+  @override
+  void visitRefactorings(Refactorings refactorings) {
+    translateHtml(refactorings.html);
+    dl(() {
+      super.visitRefactorings(refactorings);
+    });
+  }
+
+  @override visitRefactoring(Refactoring refactoring) {
+    dt('refactoring', () {
+      write(refactoring.kind);
+    });
+    dd(() {
+      translateHtml(refactoring.html);
+      describePayload(refactoring.feedback, 'Feedback', force: true);
+      describePayload(refactoring.options, 'Options', force: true);
+    });
+  }
+
+  @override
+  void visitTypes(Types types) {
+    translateHtml(types.html);
+    dl(() {
+      super.visitTypes(types);
+    });
+  }
+
+  @override
+  void visitDomain(Domain domain) {
+    h2(() {
+      anchor('domain_${domain.name}', () {
+        write('Domain: ${domain.name}');
+      });
+    });
+    translateHtml(domain.html);
+    if (domain.requests.isNotEmpty) {
+      h3(() {
+        write('Requests');
+      });
+      dl(() {
+        domain.requests.forEach(visitRequest);
+      });
+    }
+    if (domain.notifications.isNotEmpty) {
+      h3(() {
+        write('Notifications');
+      });
+      dl(() {
+        domain.notifications.forEach(visitNotification);
+      });
+    }
+  }
+
+  @override
+  void visitNotification(Notification notification) {
+    dt('notification', () {
+      write(notification.longEvent);
+    });
+    dd(() {
+      box(() {
+        showType('notification', notification.notificationType,
+            notification.params);
+      });
+      translateHtml(notification.html);
+      describePayload(notification.params, 'Parameters');
+    });
+  }
+
+  /**
+   * Copy the contents of the given HTML element, translating the special
+   * elements that define the API appropriately.
+   */
+  void translateHtml(dom.Element html) {
+    for (dom.Node node in html.nodes) {
+      if (node is dom.Element) {
+        switch (node.localName) {
+          case 'api':
+            translateHtml(node);
+            break;
+          case 'domain':
+            visitDomain(apiMappings.domains[node]);
+            break;
+          case 'head':
+            head(() {
+              translateHtml(node);
+              element('style', {}, () {
+                writeln(stylesheet);
+              });
+            });
+            break;
+          case 'refactorings':
+            visitRefactorings(api.refactorings);
+            break;
+          case 'types':
+            visitTypes(api.types);
+            break;
+          case 'version':
+            translateHtml(node);
+            break;
+          default:
+            if (!specialElements.contains(node.localName)) {
+              element(node.localName, node.attributes, () {
+                translateHtml(node);
+              });
+            }
+        }
+      } else if (node is dom.Text) {
+        String text = node.text;
+        write(text);
+      }
+    }
+  }
+
+  /**
+   * Generate a description of [type] using [TypeVisitor].
+   *
+   * If [shortDesc] is non-null, the output is prefixed with this string
+   * and a colon.
+   *
+   * If [typeForBolding] is supplied, then fields in this type are shown in
+   * boldface.
+   */
+  void showType(String shortDesc, TypeDecl type, [TypeObject typeForBolding]) {
+    Set<String> fieldsToBold = new Set<String>();
+    if (typeForBolding != null) {
+      for (TypeObjectField field in typeForBolding.fields) {
+        fieldsToBold.add(field.name);
+      }
+    }
+    pre(() {
+      if (shortDesc != null) {
+        write('$shortDesc: ');
+      }
+      TypeVisitor typeVisitor = new TypeVisitor(api, fieldsToBold: fieldsToBold
+          );
+      addAll(typeVisitor.collectHtml(() {
+        typeVisitor.visitTypeDecl(type);
+      }));
+    });
+  }
+
+  /**
+   * Describe the payload of request, response, notification, refactoring
+   * feedback, or refactoring options.
+   *
+   * If [force] is true, then a section is inserted even if the payload is
+   * null.
+   */
+  void describePayload(TypeObject subType, String name, {bool force: false}) {
+    if (force || subType != null) {
+      h4(() {
+        write(name);
+      });
+      if (subType == null) {
+        p(() {
+          write('none');
+        });
+      } else {
+        visitTypeDecl(subType);
+      }
+    }
+  }
+
+  @override
+  void visitRequest(Request request) {
+    dt('request', () {
+      write(request.longMethod);
+    });
+    dd(() {
+      box(() {
+        showType('request', request.requestType, request.params);
+        br();
+        showType('response', request.responseType, request.result);
+      });
+      translateHtml(request.html);
+      describePayload(request.params, 'Parameters');
+      describePayload(request.result, 'Returns');
+    });
+  }
+
+  @override
+  void visitTypeDefinition(TypeDefinition typeDefinition) {
+    dt('typeDefinition', () {
+      anchor('type_${typeDefinition.name}', () {
+        write('${typeDefinition.name}: ');
+        TypeVisitor typeVisitor = new TypeVisitor(api, short: true);
+        addAll(typeVisitor.collectHtml(() {
+          typeVisitor.visitTypeDecl(typeDefinition.type);
+        }));
+      });
+    });
+    dd(() {
+      translateHtml(typeDefinition.html);
+      visitTypeDecl(typeDefinition.type);
+    });
+  }
+
+  @override
+  void visitTypeEnum(TypeEnum typeEnum) {
+    dl(() {
+      super.visitTypeEnum(typeEnum);
+    });
+  }
+
+  @override
+  void visitTypeEnumValue(TypeEnumValue typeEnumValue) {
+    bool isDocumented = false;
+    for (dom.Node node in typeEnumValue.html.nodes) {
+      if ((node is dom.Element && node.localName != 'code') || (node is dom.Text
+          && node.text.trim().isNotEmpty)) {
+        isDocumented = true;
+        break;
+      }
+    }
+    dt('value', () {
+      write(typeEnumValue.value);
+    });
+    if (isDocumented) {
+      dd(() {
+        translateHtml(typeEnumValue.html);
+      });
+    }
+  }
+
+  @override
+  void visitTypeList(TypeList typeList) {
+    visitTypeDecl(typeList.itemType);
+  }
+
+  @override
+  void visitTypeMap(TypeMap typeMap) {
+    visitTypeDecl(typeMap.valueType);
+  }
+
+  @override
+  void visitTypeObject(TypeObject typeObject) {
+    dl(() {
+      super.visitTypeObject(typeObject);
+    });
+  }
+
+  @override
+  void visitTypeObjectField(TypeObjectField typeObjectField) {
+    dt('field', () {
+      b(() {
+        i(() {
+          write(typeObjectField.name);
+          if (typeObjectField.value != null) {
+            write(' = ${typeObjectField.value}');
+          } else {
+            write(' ( ');
+            if (typeObjectField.optional) {
+              gray(() {
+                write('optional');
+              });
+              write(' ');
+            }
+            TypeVisitor typeVisitor = new TypeVisitor(api, short: true);
+            addAll(typeVisitor.collectHtml(() {
+              typeVisitor.visitTypeDecl(typeObjectField.type);
+            }));
+            write(' )');
+          }
+        });
+      });
+    });
+    dd(() {
+      translateHtml(typeObjectField.html);
+    });
+  }
+
+  @override
+  void visitTypeReference(TypeReference typeReference) {
+  }
+}
+
+/**
+ * Translate spec_input.html into api.html.
+ */
+main() {
+  ToHtmlVisitor visitor = new ToHtmlVisitor(readApi());
+  dom.Document document = new dom.Document();
+  for (dom.Node node in visitor.collectHtml(visitor.visitApi)) {
+    document.append(node);
+  }
+  File outputFile = new File('../../doc/api.html');
+  outputFile.writeAsStringSync(document.outerHtml);
+}
diff --git a/pkg/analysis_services/lib/completion/completion_computer.dart b/pkg/analysis_services/lib/completion/completion_computer.dart
index ad273dd..887faee 100644
--- a/pkg/analysis_services/lib/completion/completion_computer.dart
+++ b/pkg/analysis_services/lib/completion/completion_computer.dart
@@ -7,8 +7,12 @@
 import 'dart:async';
 
 import 'package:analysis_services/completion/completion_suggestion.dart';
-import 'package:analysis_services/src/completion/top_level_computer.dart';
 import 'package:analysis_services/search/search_engine.dart';
+import 'package:analysis_services/src/completion/top_level_computer.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/source.dart';
 
 /**
  * The base class for computing code completion suggestions.
@@ -16,16 +20,115 @@
 abstract class CompletionComputer {
 
   /**
-   * Create a collection of code completion computers for the given situation.
-   */
-  static Future<List<CompletionComputer>> create(SearchEngine searchEngine) {
-    List<CompletionComputer> computers = [];
-    computers.add(new TopLevelComputer(searchEngine));
-    return new Future.value(computers);
-  }
-
-  /**
    * Computes [CompletionSuggestion]s for the specified position in the source.
    */
   Future<List<CompletionSuggestion>> compute();
 }
+
+/**
+ * Manages `CompletionComputer`s for a given completion request.
+ */
+abstract class CompletionManager {
+
+  StreamController<CompletionResult> controller;
+
+  /**
+   * Compute completion results and append them to the stream.
+   * Clients should not call this method directly as it is automatically called
+   * when a client listens to the stream returned by [results].
+   */
+  void compute();
+
+  /**
+   * Generate a stream of code completion results.
+   */
+  Stream<CompletionResult> results() {
+    controller = new StreamController<CompletionResult>(onListen: () {
+      scheduleMicrotask(compute);
+    });
+    return controller.stream;
+  }
+
+  /**
+   * Create a manager for the given request.
+   */
+  static CompletionManager create(AnalysisContext context, Source source,
+      int offset, SearchEngine searchEngine) {
+    if (context != null) {
+      if (AnalysisEngine.isDartFileName(source.shortName)) {
+        return new DartCompletionManager(context, source, offset, searchEngine);
+      }
+    }
+    return new NoOpCompletionManager(source, offset);
+  }
+}
+
+/**
+ * Code completion result generated by an [CompletionManager].
+ */
+class CompletionResult {
+
+  /**
+   * The length of the text to be replaced if the remainder of the identifier
+   * containing the cursor is to be replaced when the suggestion is applied
+   * (that is, the number of characters in the existing identifier).
+   */
+  final int replacementLength;
+
+  /**
+   * The offset of the start of the text to be replaced. This will be different
+   * than the offset used to request the completion suggestions if there was a
+   * portion of an identifier before the original offset. In particular, the
+   * replacementOffset will be the offset of the beginning of said identifier.
+   */
+  final int replacementOffset;
+
+  /**
+   * The suggested completions.
+   */
+  final List<CompletionSuggestion> suggestions;
+
+  /**
+   * `true` if this is that last set of results that will be returned
+   * for the indicated completion.
+   */
+  final bool last;
+
+  CompletionResult(this.replacementOffset, this.replacementLength,
+      this.suggestions, this.last);
+}
+
+/**
+ * Manages code completion for a given Dart file completion request.
+ */
+class DartCompletionManager extends CompletionManager {
+  final AnalysisContext context;
+  final Source source;
+  final int offset;
+  final SearchEngine searchEngine;
+
+  DartCompletionManager(this.context, this.source, this.offset,
+      this.searchEngine);
+
+  @override
+  void compute() {
+    LibraryElement library = context.computeLibraryElement(source);
+    CompilationUnit unit = context.resolveCompilationUnit(source, library);
+    TopLevelComputer computer = new TopLevelComputer(searchEngine, unit);
+    computer.compute().then((List<CompletionSuggestion> suggestions) {
+      controller.add(new CompletionResult(offset, 0, suggestions, true));
+    });
+  }
+}
+
+class NoOpCompletionManager extends CompletionManager {
+  final Source source;
+  final int offset;
+
+  NoOpCompletionManager(this.source, this.offset);
+
+  @override
+  void compute() {
+    controller.add(new CompletionResult(offset, 0, [], true));
+  }
+}
diff --git a/pkg/analysis_services/lib/completion/completion_suggestion.dart b/pkg/analysis_services/lib/completion/completion_suggestion.dart
index 523319f..af468bc 100644
--- a/pkg/analysis_services/lib/completion/completion_suggestion.dart
+++ b/pkg/analysis_services/lib/completion/completion_suggestion.dart
@@ -4,11 +4,34 @@
 
 library services.completion.suggestion;
 
-import 'package:analysis_services/json.dart';
 import 'package:analysis_services/constants.dart';
+import 'package:analysis_services/json.dart';
 import 'package:analyzer/src/generated/element.dart';
 
 /**
+ * An enumeration of the relevance of a completion suggestion.
+ */
+class CompletionRelevance {
+  static const CompletionRelevance LOW = const CompletionRelevance('LOW');
+  static const CompletionRelevance DEFAULT =
+      const CompletionRelevance('DEFAULT');
+  static const CompletionRelevance HIGH = const CompletionRelevance('HIGH');
+
+  final String name;
+
+  const CompletionRelevance(this.name);
+
+  static CompletionRelevance value(String name) {
+    if (LOW.name == name) return LOW;
+    if (DEFAULT.name == name) return DEFAULT;
+    if (HIGH.name == name) return HIGH;
+    throw new ArgumentError('Unknown CompletionRelevance: $name');
+  }
+
+  toString() => 'CompletionRelevance.$name';
+}
+
+/**
  * A single completion suggestion.
  */
 class CompletionSuggestion implements HasToJson {
@@ -100,6 +123,8 @@
  * in a completion suggestion.
  */
 class CompletionSuggestionKind {
+  static const CompletionSuggestionKind ARGUMENT_LIST =
+      const CompletionSuggestionKind('ARGUMENT_LIST');
   static const CompletionSuggestionKind CLASS =
       const CompletionSuggestionKind('CLASS');
   static const CompletionSuggestionKind CLASS_ALIAS =
@@ -110,8 +135,6 @@
       const CompletionSuggestionKind('FIELD');
   static const CompletionSuggestionKind FUNCTION =
       const CompletionSuggestionKind('FUNCTION');
-  static const CompletionSuggestionKind FUNCTION_ALIAS =
-      const CompletionSuggestionKind('FUNCTION_ALIAS');
   static const CompletionSuggestionKind FUNCTION_TYPE_ALIAS =
       const CompletionSuggestionKind('FUNCTION_TYPE_ALIAS');
   static const CompletionSuggestionKind GETTER =
@@ -124,22 +147,22 @@
       const CompletionSuggestionKind('METHOD');
   static const CompletionSuggestionKind METHOD_NAME =
       const CompletionSuggestionKind('METHOD_NAME');
+  static const CompletionSuggestionKind NAMED_ARGUMENT =
+      const CompletionSuggestionKind('NAMED_ARGUMENT');
+  static const CompletionSuggestionKind OPTIONAL_ARGUMENT =
+      const CompletionSuggestionKind('OPTIONAL_ARGUMENT');
   static const CompletionSuggestionKind PARAMETER =
       const CompletionSuggestionKind('PARAMETER');
   static const CompletionSuggestionKind SETTER =
       const CompletionSuggestionKind('SETTER');
-  static const CompletionSuggestionKind VARIABLE =
-      const CompletionSuggestionKind('VARIABLE');
-  static const CompletionSuggestionKind TYPE_PARAMETER =
-      const CompletionSuggestionKind('TYPE_PARAMETER');
-  static const CompletionSuggestionKind ARGUMENT_LIST =
-      const CompletionSuggestionKind('ARGUMENT_LIST');
-  static const CompletionSuggestionKind OPTIONAL_ARGUMENT =
-      const CompletionSuggestionKind('OPTIONAL_ARGUMENT');
-  static const CompletionSuggestionKind NAMED_ARGUMENT =
-      const CompletionSuggestionKind('NAMED_ARGUMENT');
   static const CompletionSuggestionKind TOP_LEVEL_VARIABLE =
       const CompletionSuggestionKind('TOP_LEVEL_VARIABLE');
+  static const CompletionSuggestionKind TYPE_PARAMETER =
+      const CompletionSuggestionKind('TYPE_PARAMETER');
+  // TODO (danrubel) consider renaming VARIABLE --> LOCAL_VARIABLE
+  //                 to match ElementKind.LOCAL_VARIABLE
+  static const CompletionSuggestionKind VARIABLE =
+      const CompletionSuggestionKind('VARIABLE');
 
   final String name;
 
@@ -148,30 +171,6 @@
   @override
   String toString() => name;
 
-  static CompletionSuggestionKind valueOf(String name) {
-    if (CLASS.name == name) return CLASS;
-    if (CLASS_ALIAS.name == name) return CLASS_ALIAS;
-    if (CONSTRUCTOR.name == name) return CONSTRUCTOR;
-    if (FIELD.name == name) return FIELD;
-    if (FUNCTION.name == name) return FUNCTION;
-    if (FUNCTION_ALIAS.name == name) return FUNCTION_ALIAS;
-    if (FUNCTION_TYPE_ALIAS.name == name) return FUNCTION_TYPE_ALIAS;
-    if (GETTER.name == name) return GETTER;
-    if (IMPORT.name == name) return IMPORT;
-    if (LIBRARY_PREFIX.name == name) return LIBRARY_PREFIX;
-    if (METHOD.name == name) return METHOD;
-    if (METHOD_NAME.name == name) return METHOD_NAME;
-    if (PARAMETER.name == name) return PARAMETER;
-    if (SETTER.name == name) return SETTER;
-    if (VARIABLE.name == name) return VARIABLE;
-    if (TYPE_PARAMETER.name == name) return TYPE_PARAMETER;
-    if (ARGUMENT_LIST.name == name) return ARGUMENT_LIST;
-    if (OPTIONAL_ARGUMENT.name == name) return OPTIONAL_ARGUMENT;
-    if (NAMED_ARGUMENT.name == name) return NAMED_ARGUMENT;
-    if (TOP_LEVEL_VARIABLE.name == name) return TOP_LEVEL_VARIABLE;
-    throw new ArgumentError('Unknown CompletionSuggestionKind: $name');
-  }
-
   static CompletionSuggestionKind fromElementKind(ElementKind kind) {
     //    ElementKind.ANGULAR_FORMATTER,
     //    ElementKind.ANGULAR_COMPONENT,
@@ -191,12 +190,13 @@
     //    ElementKind.EXTERNAL_HTML_SCRIPT,
     if (kind == ElementKind.FIELD) return FIELD;
     if (kind == ElementKind.FUNCTION) return FUNCTION;
+    if (kind == ElementKind.FUNCTION_TYPE_ALIAS) return FUNCTION_TYPE_ALIAS;
     if (kind == ElementKind.GETTER) return GETTER;
     //    ElementKind.HTML,
     if (kind == ElementKind.IMPORT) return IMPORT;
     //    ElementKind.LABEL,
     //    ElementKind.LIBRARY,
-    //    ElementKind.LOCAL_VARIABLE,
+    if (kind == ElementKind.LOCAL_VARIABLE) return VARIABLE;
     if (kind == ElementKind.METHOD) return METHOD;
     //    ElementKind.NAME,
     if (kind == ElementKind.PARAMETER) return PARAMETER;
@@ -206,30 +206,31 @@
     //    ElementKind.PREFIX,
     if (kind == ElementKind.SETTER) return SETTER;
     if (kind == ElementKind.TOP_LEVEL_VARIABLE) return TOP_LEVEL_VARIABLE;
-    if (kind == ElementKind.FUNCTION_TYPE_ALIAS) return FUNCTION_TYPE_ALIAS;
     //    ElementKind.TYPE_PARAMETER,
     //    ElementKind.UNIVERSE
     throw new ArgumentError('Unknown CompletionSuggestionKind for: $kind');
   }
-}
 
-/**
- * An enumeration of the relevance of a completion suggestion.
- */
-class CompletionRelevance {
-  static const CompletionRelevance LOW = const CompletionRelevance('LOW');
-  static const CompletionRelevance DEFAULT =
-      const CompletionRelevance('DEFAULT');
-  static const CompletionRelevance HIGH = const CompletionRelevance('HIGH');
-
-  final String name;
-
-  const CompletionRelevance(this.name);
-
-  static CompletionRelevance value(String name) {
-    if (LOW.name == name) return LOW;
-    if (DEFAULT.name == name) return DEFAULT;
-    if (HIGH.name == name) return HIGH;
-    throw new ArgumentError('Unknown CompletionRelevance: $name');
+  static CompletionSuggestionKind valueOf(String name) {
+    if (ARGUMENT_LIST.name == name) return ARGUMENT_LIST;
+    if (CLASS.name == name) return CLASS;
+    if (CLASS_ALIAS.name == name) return CLASS_ALIAS;
+    if (CONSTRUCTOR.name == name) return CONSTRUCTOR;
+    if (FIELD.name == name) return FIELD;
+    if (FUNCTION.name == name) return FUNCTION;
+    if (FUNCTION_TYPE_ALIAS.name == name) return FUNCTION_TYPE_ALIAS;
+    if (GETTER.name == name) return GETTER;
+    if (IMPORT.name == name) return IMPORT;
+    if (LIBRARY_PREFIX.name == name) return LIBRARY_PREFIX;
+    if (METHOD.name == name) return METHOD;
+    if (METHOD_NAME.name == name) return METHOD_NAME;
+    if (NAMED_ARGUMENT.name == name) return NAMED_ARGUMENT;
+    if (OPTIONAL_ARGUMENT.name == name) return OPTIONAL_ARGUMENT;
+    if (PARAMETER.name == name) return PARAMETER;
+    if (SETTER.name == name) return SETTER;
+    if (TOP_LEVEL_VARIABLE.name == name) return TOP_LEVEL_VARIABLE;
+    if (TYPE_PARAMETER.name == name) return TYPE_PARAMETER;
+    if (VARIABLE.name == name) return VARIABLE;
+    throw new ArgumentError('Unknown CompletionSuggestionKind: $name');
   }
 }
diff --git a/pkg/analysis_services/lib/constants.dart b/pkg/analysis_services/lib/constants.dart
index def5954..bdff512 100644
--- a/pkg/analysis_services/lib/constants.dart
+++ b/pkg/analysis_services/lib/constants.dart
@@ -10,6 +10,7 @@
 const String ADDED = 'added';
 const String CHILDREN = 'children';
 const String CLASS_ELEMENT = 'classElement';
+const String CLASS_NAME = 'className';
 const String COMPLETION = 'completion';
 const String CONTAINING_LIBRARY_NAME = 'containingLibraryName';
 const String CONTAINING_LIBRARY_PATH = 'containingLibraryPath';
@@ -30,12 +31,12 @@
 const String FILES = 'files';
 const String FIXES = 'fixes';
 const String FLAGS = 'flags';
-const String HIERARCHY = 'hierarchy';
+const String HIERARCHY_ITEMS = 'hierarchyItems';
 const String HOVERS = 'hovers';
 const String ID = 'id';
 const String INCLUDE_POTENTIAL = 'includePotential';
 const String INCLUDED = 'included';
-const String INTERFACE_ELEMENTS = 'interfaceElements';
+const String INTERFACE_MEMBERS = 'interfaceMembers';
 const String INTERFACES = 'interfaces';
 const String IS_ABSTRACT = 'isAbstract';
 const String IS_DEPRECATED = 'isDeprecated';
@@ -44,7 +45,7 @@
 const String KIND = 'kind';
 const String LAST = 'last';
 const String LENGTH = 'length';
-const String LINKED_POSITION_GROUPS = 'linkedPositionGroups';
+const String LINKED_EDIT_GROUPS = 'linkedEditGroups';
 const String LOCATION = 'location';
 const String MEMBER_ELEMENT = 'memberElement';
 const String MESSAGE = 'message';
@@ -69,8 +70,11 @@
 const String RELEVANCE = 'relevance';
 const String REMOVED = 'removed';
 const String REPLACEMENT = 'relacement';
+const String REPLACEMENT_OFFSET = 'replacementOffset';
+const String REPLACEMENT_LENGTH = 'replacementLength';
 const String RETURN_TYPE = 'returnType';
 const String RESULTS = 'results';
+const String SELECTION = 'selection';
 const String SEVERITY = 'severity';
 const String SELECTION_LENGTH = 'selectionLength';
 const String SELECTION_OFFSET = 'selectionOffset';
@@ -80,8 +84,10 @@
 const String STATIC_TYPE = 'staticType';
 const String SUBCLASSES = 'subclasses';
 const String SUBSCRIPTIONS = 'subscriptions';
+const String SUGGESTIONS = 'suggestions';
 const String SUPERCLASS = 'superclass';
-const String SUPER_CLASS_ELEMENT = 'superclassElement';
+const String SUPER_CLASS_MEMBER = 'superclassMember';
 const String TARGETS = 'targets';
 const String TYPE = 'type';
+const String VALUE = 'value';
 const String VERSION = 'version';
diff --git a/pkg/analysis_services/lib/correction/change.dart b/pkg/analysis_services/lib/correction/change.dart
index d4073e9..9da653c 100644
--- a/pkg/analysis_services/lib/correction/change.dart
+++ b/pkg/analysis_services/lib/correction/change.dart
@@ -11,12 +11,6 @@
 import 'package:analysis_services/json.dart';
 
 
-_fromJsonList(List target, List<Map<String, Object>> jsonList,
-    decoder(Map<String, Object> json)) {
-  target.addAll(jsonList.map(decoder));
-}
-
-
 /**
  * A description of a single change to one or more files. 
  */
@@ -32,15 +26,14 @@
   final List<FileEdit> edits = <FileEdit>[];
 
   /**
-   * A list of the [LinkedPositionGroup]s in the change. 
+   * A list of the [LinkedEditGroup]s in the change. 
    */
-  final List<LinkedPositionGroup> linkedPositionGroups = <LinkedPositionGroup>[
-      ];
+  final List<LinkedEditGroup> linkedEditGroups = <LinkedEditGroup>[];
 
   /**
-   * An optional position to move selection to after applying this change.
+   * The position that should be selected after the edits have been applied.
    */
-  Position endPosition;
+  Position selection;
 
   Change(this.message);
 
@@ -52,36 +45,29 @@
   }
 
   /**
-   * Adds the given [LinkedPositionGroup].
+   * Adds the given [LinkedEditGroup].
    */
-  void addLinkedPositionGroup(LinkedPositionGroup linkedPositionGroup) {
-    linkedPositionGroups.add(linkedPositionGroup);
+  void addLinkedEditGroup(LinkedEditGroup linkedEditGroup) {
+    linkedEditGroups.add(linkedEditGroup);
   }
 
   @override
   Map<String, Object> toJson() {
-    return {
+    Map<String, Object> json = {
       MESSAGE: message,
       EDITS: objectToJson(edits),
-      LINKED_POSITION_GROUPS: objectToJson(linkedPositionGroups)
+      LINKED_EDIT_GROUPS: objectToJson(linkedEditGroups)
     };
+    if (selection != null) {
+      json[SELECTION] = selection.toJson();
+    }
+    return json;
   }
 
   @override
   String toString() =>
       'Change(message=$message, edits=$edits, '
-          'linkedPositionGroups=$linkedPositionGroups)';
-
-  static Change fromJson(Map<String, Object> json) {
-    String message = json[MESSAGE];
-    Change change = new Change(message);
-    _fromJsonList(change.edits, json[EDITS], FileEdit.fromJson);
-    _fromJsonList(
-        change.linkedPositionGroups,
-        json[LINKED_POSITION_GROUPS],
-        LinkedPositionGroup.fromJson);
-    return change;
-  }
+          'linkedEditGroups=$linkedEditGroups, selection=$selection)';
 }
 
 
@@ -132,13 +118,6 @@
   @override
   String toString() =>
       "Edit(offset=$offset, length=$length, replacement=:>$replacement<:)";
-
-  static Edit fromJson(Map<String, Object> json) {
-    int offset = json[OFFSET];
-    int length = json[LENGTH];
-    String replacement = json[REPLACEMENT];
-    return new Edit(offset, length, replacement);
-  }
 }
 
 
@@ -175,13 +154,6 @@
 
   @override
   String toString() => "FileEdit(file=$file, edits=$edits)";
-
-  static FileEdit fromJson(Map<String, Object> json) {
-    String file = json[FILE];
-    FileEdit fileEdit = new FileEdit(file);
-    _fromJsonList(fileEdit.edits, json[EDITS], Edit.fromJson);
-    return fileEdit;
-  }
 }
 
 
@@ -190,43 +162,84 @@
  * modified - if one gets edited, all other positions in a group are edited the
  * same way. All linked positions in a group have the same content.
  */
-class LinkedPositionGroup implements HasToJson {
+class LinkedEditGroup implements HasToJson {
   final String id;
+  int length;
   final List<Position> positions = <Position>[];
-  final List<String> proposals = <String>[];
+  final List<LinkedEditSuggestion> suggestions = <LinkedEditSuggestion>[];
 
-  LinkedPositionGroup(this.id);
+  LinkedEditGroup(this.id);
 
-  void addPosition(Position position) {
-    if (positions.isNotEmpty && position.length != positions[0].length) {
-      throw new ArgumentError(
-          'All positions should have the same length. '
-              'Was: ${positions[0].length}. New: ${position.length}');
-    }
+  void addPosition(Position position, int length) {
     positions.add(position);
+    this.length = length;
   }
 
-  void addProposal(String proposal) {
-    proposals.add(proposal);
+  void addSuggestion(LinkedEditSuggestion suggestion) {
+    suggestions.add(suggestion);
   }
 
   @override
   Map<String, Object> toJson() {
     return {
       ID: id,
-      POSITIONS: objectToJson(positions)
+      LENGTH: length,
+      POSITIONS: objectToJson(positions),
+      SUGGESTIONS: objectToJson(suggestions)
     };
   }
 
   @override
-  String toString() => 'LinkedPositionGroup(id=$id, positions=$positions)';
+  String toString() =>
+      'LinkedEditGroup(id=$id, length=$length, '
+          'positions=$positions, suggestions=$suggestions)';
+}
 
-  static LinkedPositionGroup fromJson(Map<String, Object> json) {
-    String id = json[ID];
-    LinkedPositionGroup group = new LinkedPositionGroup(id);
-    _fromJsonList(group.positions, json[POSITIONS], Position.fromJson);
-    return group;
+
+/**
+ * A suggestion of a value that could be used to replace all of the linked edit
+ * regions in a [LinkedEditGroup].
+ */
+class LinkedEditSuggestion implements HasToJson {
+  final LinkedEditSuggestionKind kind;
+  final String value;
+
+  LinkedEditSuggestion(this.kind, this.value);
+
+  bool operator ==(other) {
+    if (other is LinkedEditSuggestion) {
+      return other.kind == kind && other.value == value;
+    }
+    return false;
   }
+
+  @override
+  Map<String, Object> toJson() {
+    return {
+      KIND: kind.name,
+      VALUE: value
+    };
+  }
+
+  @override
+  String toString() => '(kind=$kind, value=$value)';
+}
+
+
+/**
+ * An enumeration of the kind of values that can be suggested for a linked edit.
+ */
+class LinkedEditSuggestionKind {
+  static const METHOD = const LinkedEditSuggestionKind('METHOD');
+  static const PARAMETER = const LinkedEditSuggestionKind('PARAMETER');
+  static const TYPE = const LinkedEditSuggestionKind('TYPE');
+  static const VARIABLE = const LinkedEditSuggestionKind('VARIABLE');
+  final String name;
+
+  const LinkedEditSuggestionKind(this.name);
+
+  @override
+  String toString() => name;
 }
 
 
@@ -236,22 +249,18 @@
 class Position implements HasToJson {
   final String file;
   final int offset;
-  final int length;
 
-  Position(this.file, this.offset, this.length);
+  Position(this.file, this.offset);
 
   int get hashCode {
     int hash = file.hashCode;
     hash = hash * 31 + offset;
-    hash = hash * 31 + length;
     return hash;
   }
 
   bool operator ==(other) {
     if (other is Position) {
-      return other.file == file &&
-          other.offset == offset &&
-          other.length == length;
+      return other.file == file && other.offset == offset;
     }
     return false;
   }
@@ -260,18 +269,10 @@
   Map<String, Object> toJson() {
     return {
       FILE: file,
-      OFFSET: offset,
-      LENGTH: length
+      OFFSET: offset
     };
   }
 
   @override
-  String toString() => 'Position(file=$file, offset=$offset, length=$length)';
-
-  static Position fromJson(Map<String, Object> json) {
-    String file = json[FILE];
-    int offset = json[OFFSET];
-    int length = json[LENGTH];
-    return new Position(file, offset, length);
-  }
+  String toString() => 'Position(file=$file, offset=$offset)';
 }
diff --git a/pkg/analysis_services/lib/src/completion/top_level_computer.dart b/pkg/analysis_services/lib/src/completion/top_level_computer.dart
index 9688a82..23bda1d 100644
--- a/pkg/analysis_services/lib/src/completion/top_level_computer.dart
+++ b/pkg/analysis_services/lib/src/completion/top_level_computer.dart
@@ -9,15 +9,18 @@
 import 'package:analysis_services/completion/completion_computer.dart';
 import 'package:analysis_services/completion/completion_suggestion.dart';
 import 'package:analysis_services/search/search_engine.dart';
+import 'package:analyzer/src/generated/ast.dart';
 import 'package:analyzer/src/generated/element.dart';
 
 /**
- * A computer for `completion.getSuggestions` request results.
+ * A computer for calculating class and top level variable
+ * `completion.getSuggestions` request results
  */
 class TopLevelComputer extends CompletionComputer {
   final SearchEngine searchEngine;
+  final CompilationUnit unit;
 
-  TopLevelComputer(this.searchEngine);
+  TopLevelComputer(this.searchEngine, this.unit);
 
   /**
    * Computes [CompletionSuggestion]s for the specified position in the source.
@@ -25,19 +28,38 @@
   Future<List<CompletionSuggestion>> compute() {
     var future = searchEngine.searchTopLevelDeclarations('');
     return future.then((List<SearchMatch> matches) {
-      return matches.map((SearchMatch match) {
-        Element element = match.element;
-        String completion = element.displayName;
-        return new CompletionSuggestion(
-            CompletionSuggestionKind.fromElementKind(element.kind),
-            CompletionRelevance.DEFAULT,
-            completion,
-            completion.length,
-            0,
-            element.isDeprecated,
-            false // isPotential
-            );
-      }).toList();
+
+      // Compute the set of visible libraries to determine relevance
+      var visibleLibraries = new Set<LibraryElement>();
+      var unitLibrary = unit.element.library;
+      visibleLibraries.add(unitLibrary);
+      visibleLibraries.addAll(unitLibrary.importedLibraries);
+
+      // Compute the set of possible classes and top level variables
+      var suggestions = new List<CompletionSuggestion>();
+      matches.forEach((SearchMatch match) {
+        if (match.kind == MatchKind.DECLARATION) {
+          Element element = match.element;
+          if (element.isPublic || element.library == unitLibrary) {
+            String completion = element.displayName;
+            var relevance = visibleLibraries.contains(element.library) ?
+                CompletionRelevance.DEFAULT :
+                CompletionRelevance.LOW;
+            suggestions.add(
+                new CompletionSuggestion(
+                    CompletionSuggestionKind.fromElementKind(element.kind),
+                    relevance,
+                    completion,
+                    completion.length,
+                    0,
+                    element.isDeprecated,
+                    false // isPotential
+            ));
+          }
+        }
+      });
+      return suggestions;
     });
   }
+
 }
diff --git a/pkg/analysis_services/lib/src/correction/assist.dart b/pkg/analysis_services/lib/src/correction/assist.dart
index c116040..b8bdd1e 100644
--- a/pkg/analysis_services/lib/src/correction/assist.dart
+++ b/pkg/analysis_services/lib/src/correction/assist.dart
@@ -7,6 +7,8 @@
 
 library services.src.correction.assist;
 
+import 'dart:collection';
+
 import 'package:analysis_services/correction/assist.dart';
 import 'package:analysis_services/correction/change.dart';
 import 'package:analysis_services/search/hierarchy.dart';
@@ -14,6 +16,7 @@
 import 'package:analysis_services/src/correction/name_suggestion.dart';
 import 'package:analysis_services/src/correction/source_buffer.dart';
 import 'package:analysis_services/src/correction/source_range.dart';
+import 'package:analysis_services/src/correction/statement_analyzer.dart';
 import 'package:analysis_services/src/correction/util.dart';
 import 'package:analyzer/src/generated/ast.dart';
 import 'package:analyzer/src/generated/element.dart';
@@ -23,6 +26,10 @@
 import 'package:path/path.dart';
 
 
+
+typedef _SimpleIdentifierVisitor(SimpleIdentifier node);
+
+
 /**
  * The computer for Dart assists.
  */
@@ -39,9 +46,9 @@
   String unitLibraryFolder;
 
   final List<Edit> edits = <Edit>[];
-  final Map<String, LinkedPositionGroup> linkedPositionGroups = <String,
-      LinkedPositionGroup>{};
-  Position endPosition = null;
+  final Map<String, LinkedEditGroup> linkedPositionGroups = <String,
+      LinkedEditGroup>{};
+  Position exitPosition = null;
   final List<Assist> assists = <Assist>[];
 
   int selectionEnd;
@@ -76,9 +83,9 @@
     _addProposal_convertToIsNot_onNot();
     _addProposal_convertToIsNotEmpty();
     _addProposal_exchangeOperands();
-    _addProposal_extractClassIntoPart();
     _addProposal_importAddShow();
     _addProposal_invertIf();
+    _addProposal_joinIfStatementInner();
     _addProposal_joinIfStatementOuter();
     _addProposal_joinVariableDeclaration_onAssignment();
     _addProposal_joinVariableDeclaration_onDeclaration();
@@ -128,15 +135,15 @@
     Change change = new Change(message);
     change.add(fileEdit);
     linkedPositionGroups.values.forEach(
-        (group) => change.addLinkedPositionGroup(group));
-    change.endPosition = endPosition;
+        (group) => change.addLinkedEditGroup(group));
+    change.selection = exitPosition;
     // add Assist
     Assist assist = new Assist(kind, change);
     assists.add(assist);
     // clear
     edits.clear();
     linkedPositionGroups.clear();
-    endPosition = null;
+    exitPosition = null;
   }
 
   /**
@@ -241,7 +248,7 @@
         if (i == 0) {
           builder.append(name);
         }
-        builder.addProposal(name);
+        builder.addSuggestion(LinkedEditSuggestionKind.VARIABLE, name);
       }
       builder.endPosition();
     }
@@ -263,7 +270,7 @@
     String prefix = utils.getNodePrefix(body.parent);
     // add change
     String indent = utils.getIndent(1);
-    String returnSource = 'return ' + _getSource(returnValue);
+    String returnSource = 'return ' + _getNodeText(returnValue);
     String newBodySource = "{$eol$prefix${indent}$returnSource;$eol$prefix}";
     _addReplaceEdit(rangeNode(body), newBodySource);
     // add proposal
@@ -295,7 +302,7 @@
       return;
     }
     // add change
-    String newBodySource = "=> ${_getSource(returnExpression)}";
+    String newBodySource = "=> ${_getNodeText(returnExpression)}";
     if (body.parent is! FunctionExpression ||
         body.parent.parent is FunctionDeclaration) {
       newBodySource += ";";
@@ -492,246 +499,197 @@
       // exchange parts of "wide" expression parts
       SourceRange leftRange = rangeStartEnd(binaryExpression, leftOperand);
       SourceRange rightRange = rangeStartEnd(rightOperand, binaryExpression);
-      _addReplaceEdit(leftRange, _getSource2(rightRange));
-      _addReplaceEdit(rightRange, _getSource2(leftRange));
+      _addReplaceEdit(leftRange, _getRangeText(rightRange));
+      _addReplaceEdit(rightRange, _getRangeText(leftRange));
     }
     // add proposal
     _addAssist(AssistKind.EXCHANGE_OPERANDS, []);
   }
 
-  void _addProposal_extractClassIntoPart() {
-    // TODO(scheglov) implement
-//    // should be on the name
-//    if (node is! SimpleIdentifier) {
-//      return;
-//    }
-//    if (node.parent is! ClassDeclaration) {
-//      return;
-//    }
-//    ClassDeclaration classDeclaration = node.parent as ClassDeclaration;
-//    SourceRange linesRange =
-//        utils.getLinesRange2(rangeNode(classDeclaration));
-//    // prepare name
-//    String className = classDeclaration.name.name;
-//    String fileName = CorrectionUtils.getRecommentedFileNameForClass(className);
-//    // prepare new file
-//    JavaFile newFile = new JavaFile.relative(_unitLibraryFolder, fileName);
-//    if (newFile.exists()) {
-//      return;
-//    }
-//    // remove class from this unit
-//    SourceChange unitChange = new SourceChange(_source.shortName, _source);
-//    unitChange.addEdit(new Edit.range(linesRange, ""));
-//    // create new unit
-//    Change createFileChange;
-//    {
-//      String newContent = "part of ${_unitLibraryElement.displayName};";
-//      newContent += utils.endOfLine;
-//      newContent += utils.endOfLine;
-//      newContent += _getSource2(linesRange);
-//      createFileChange = new CreateFileChange(fileName, newFile, newContent);
-//    }
-//    // add 'part'
-//    SourceChange libraryChange =
-//        _getInsertPartDirectiveChange(_unitLibrarySource, fileName);
-//    // add proposal
-//    Change compositeChange =
-//        new CompositeChange("", [unitChange, createFileChange, libraryChange]);
-//    _proposals.add(
-//        new ChangeCorrectionProposal(
-//            compositeChange,
-//            AssistKind.EXTRACT_CLASS,
-//            [fileName]));
-  }
-
   void _addProposal_importAddShow() {
-    // TODO(scheglov) implement
-//    // prepare ImportDirective
-//    ImportDirective importDirective =
-//        node.getAncestor((node) => node is ImportDirective);
-//    if (importDirective == null) {
-//      return;
-//    }
-//    // there should be no existing combinators
-//    if (!importDirective.combinators.isEmpty) {
-//      return;
-//    }
-//    // prepare whole import namespace
-//    ImportElement importElement = importDirective.element;
-//    Map<String, Element> namespace =
-//        getImportNamespace(importElement);
-//    // prepare names of referenced elements (from this import)
-//    Set<String> referencedNames = new Set();
-//    for (Element element in namespace.values) {
-//      List<SearchMatch> references =
-//          searchEngine.searchReferences(element, null, null);
-//      for (SearchMatch match in references) {
-//        LibraryElement library = match.element.library;
-//        if (unitLibraryElement == library) {
-//          referencedNames.add(element.displayName);
-//          break;
-//        }
-//      }
-//    }
-//    // ignore if unused
-//    if (referencedNames.isEmpty) {
-//      return;
-//    }
-//    // prepare change
-//    String sb = " show ${StringUtils.join(referencedNames, ", ")}";
-//    _addInsertEdit(importDirective.end - 1, sb.toString());
-//    // add proposal
-//    _addAssist(AssistKind.IMPORT_ADD_SHOW, []);
+    // prepare ImportDirective
+    ImportDirective importDirective =
+        node.getAncestor((node) => node is ImportDirective);
+    if (importDirective == null) {
+      _coverageMarker();
+      return;
+    }
+    // there should be no existing combinators
+    if (importDirective.combinators.isNotEmpty) {
+      _coverageMarker();
+      return;
+    }
+    // prepare whole import namespace
+    ImportElement importElement = importDirective.element;
+    Map<String, Element> namespace = getImportNamespace(importElement);
+    // prepare names of referenced elements (from this import)
+    SplayTreeSet<String> referencedNames = new SplayTreeSet<String>();
+    _SimpleIdentifierRecursiveAstVisitor visitor =
+        new _SimpleIdentifierRecursiveAstVisitor((SimpleIdentifier node) {
+      Element element = node.staticElement;
+      if (namespace[node.name] == element) {
+        referencedNames.add(element.displayName);
+      }
+    });
+    unit.accept(visitor);
+    // ignore if unused
+    if (referencedNames.isEmpty) {
+      _coverageMarker();
+      return;
+    }
+    // prepare change
+    String showCombinator = " show ${StringUtils.join(referencedNames, ", ")}";
+    _addInsertEdit(importDirective.end - 1, showCombinator);
+    // add proposal
+    _addAssist(AssistKind.IMPORT_ADD_SHOW, []);
   }
 
   void _addProposal_invertIf() {
-    // TODO(scheglov) implement
-//    if (node is! IfStatement) {
-//      return;
-//    }
-//    IfStatement ifStatement = node as IfStatement;
-//    Expression condition = ifStatement.condition;
-//    // should have both "then" and "else"
-//    Statement thenStatement = ifStatement.thenStatement;
-//    Statement elseStatement = ifStatement.elseStatement;
-//    if (thenStatement == null || elseStatement == null) {
-//      return;
-//    }
-//    // prepare source
-//    String invertedCondition = utils.invertCondition(condition);
-//    String thenSource = _getSource(thenStatement);
-//    String elseSource = _getSource(elseStatement);
-//    // do replacements
-//    _addReplaceEdit(rangeNode(condition), invertedCondition);
-//    _addReplaceEdit(rangeNode(thenStatement), elseSource);
-//    _addReplaceEdit(rangeNode(elseStatement), thenSource);
-//    // add proposal
-//    _addAssist(AssistKind.INVERT_IF_STATEMENT, []);
+    if (node is! IfStatement) {
+      return;
+    }
+    IfStatement ifStatement = node as IfStatement;
+    Expression condition = ifStatement.condition;
+    // should have both "then" and "else"
+    Statement thenStatement = ifStatement.thenStatement;
+    Statement elseStatement = ifStatement.elseStatement;
+    if (thenStatement == null || elseStatement == null) {
+      return;
+    }
+    // prepare source
+    String invertedCondition = utils.invertCondition(condition);
+    String thenSource = _getNodeText(thenStatement);
+    String elseSource = _getNodeText(elseStatement);
+    // do replacements
+    _addReplaceEdit(rangeNode(condition), invertedCondition);
+    _addReplaceEdit(rangeNode(thenStatement), elseSource);
+    _addReplaceEdit(rangeNode(elseStatement), thenSource);
+    // add proposal
+    _addAssist(AssistKind.INVERT_IF_STATEMENT, []);
   }
 
   void _addProposal_joinIfStatementInner() {
-    // TODO(scheglov) implement
-//    // climb up condition to the (supposedly) "if" statement
-//    AstNode node = this.node;
-//    while (node is Expression) {
-//      node = node.parent;
-//    }
-//    // prepare target "if" statement
-//    if (node is! IfStatement) {
-//      return;
-//    }
-//    IfStatement targetIfStatement = node as IfStatement;
-//    if (targetIfStatement.elseStatement != null) {
-//      return;
-//    }
-//    // prepare inner "if" statement
-//    Statement targetThenStatement = targetIfStatement.thenStatement;
-//    Statement innerStatement =
-//        CorrectionUtils.getSingleStatement(targetThenStatement);
-//    if (innerStatement is! IfStatement) {
-//      return;
-//    }
-//    IfStatement innerIfStatement = innerStatement as IfStatement;
-//    if (innerIfStatement.elseStatement != null) {
-//      return;
-//    }
-//    // prepare environment
-//    String prefix = utils.getNodePrefix(targetIfStatement);
-//    // merge conditions
-//    String condition;
-//    {
-//      Expression targetCondition = targetIfStatement.condition;
-//      Expression innerCondition = innerIfStatement.condition;
-//      String targetConditionSource = _getSource(targetCondition);
-//      String innerConditionSource = _getSource(innerCondition);
-//      if (_shouldWrapParenthesisBeforeAnd(targetCondition)) {
-//        targetConditionSource = "(${targetConditionSource})";
-//      }
-//      if (_shouldWrapParenthesisBeforeAnd(innerCondition)) {
-//        innerConditionSource = "(${innerConditionSource})";
-//      }
-//      condition = "${targetConditionSource} && ${innerConditionSource}";
-//    }
-//    // replace target "if" statement
-//    {
-//      Statement innerThenStatement = innerIfStatement.thenStatement;
-//      List<Statement> innerThenStatements =
-//          CorrectionUtils.getStatements(innerThenStatement);
-//      SourceRange lineRanges = utils.getLinesRange(innerThenStatements);
-//      String oldSource = utils.getText3(lineRanges);
-//      String newSource = utils.getIndentSource2(oldSource, false);
-//      // TODO(scheglov)
-////      _addReplaceEdit(
-////          rangeNode(targetIfStatement),
-////          MessageFormat.format(
-////              "if ({0}) '{'{1}{2}{3}'}'",
-////              [condition, eol, newSource, prefix]));
-//    }
-//    // done
-//    _addAssist(AssistKind.JOIN_IF_WITH_INNER, []);
+    // climb up condition to the (supposedly) "if" statement
+    AstNode node = this.node;
+    while (node is Expression) {
+      node = node.parent;
+    }
+    // prepare target "if" statement
+    if (node is! IfStatement) {
+      _coverageMarker();
+      return;
+    }
+    IfStatement targetIfStatement = node as IfStatement;
+    if (targetIfStatement.elseStatement != null) {
+      _coverageMarker();
+      return;
+    }
+    // prepare inner "if" statement
+    Statement targetThenStatement = targetIfStatement.thenStatement;
+    Statement innerStatement = getSingleStatement(targetThenStatement);
+    if (innerStatement is! IfStatement) {
+      _coverageMarker();
+      return;
+    }
+    IfStatement innerIfStatement = innerStatement as IfStatement;
+    if (innerIfStatement.elseStatement != null) {
+      _coverageMarker();
+      return;
+    }
+    // prepare environment
+    String prefix = utils.getNodePrefix(targetIfStatement);
+    // merge conditions
+    String condition;
+    {
+      Expression targetCondition = targetIfStatement.condition;
+      Expression innerCondition = innerIfStatement.condition;
+      String targetConditionSource = _getNodeText(targetCondition);
+      String innerConditionSource = _getNodeText(innerCondition);
+      if (_shouldWrapParenthesisBeforeAnd(targetCondition)) {
+        targetConditionSource = "(${targetConditionSource})";
+      }
+      if (_shouldWrapParenthesisBeforeAnd(innerCondition)) {
+        innerConditionSource = "(${innerConditionSource})";
+      }
+      condition = "${targetConditionSource} && ${innerConditionSource}";
+    }
+    // replace target "if" statement
+    {
+      Statement innerThenStatement = innerIfStatement.thenStatement;
+      List<Statement> innerThenStatements = getStatements(innerThenStatement);
+      SourceRange lineRanges =
+          utils.getLinesRangeStatements(innerThenStatements);
+      String oldSource = utils.getRangeText(lineRanges);
+      String newSource = utils.indentSourceLeftRight(oldSource, false);
+      _addReplaceEdit(
+          rangeNode(targetIfStatement),
+          "if ($condition) {${eol}${newSource}${prefix}}");
+    }
+    // done
+    _addAssist(AssistKind.JOIN_IF_WITH_INNER, []);
   }
 
   void _addProposal_joinIfStatementOuter() {
-    // TODO(scheglov) implement
-//    // climb up condition to the (supposedly) "if" statement
-//    AstNode node = this.node;
-//    while (node is Expression) {
-//      node = node.parent;
-//    }
-//    // prepare target "if" statement
-//    if (node is! IfStatement) {
-//      return;
-//    }
-//    IfStatement targetIfStatement = node as IfStatement;
-//    if (targetIfStatement.elseStatement != null) {
-//      return;
-//    }
-//    // prepare outer "if" statement
-//    AstNode parent = targetIfStatement.parent;
-//    if (parent is Block) {
-//      parent = parent.parent;
-//    }
-//    if (parent is! IfStatement) {
-//      return;
-//    }
-//    IfStatement outerIfStatement = parent as IfStatement;
-//    if (outerIfStatement.elseStatement != null) {
-//      return;
-//    }
-//    // prepare environment
-//    String prefix = utils.getNodePrefix(outerIfStatement);
-//    // merge conditions
-//    String condition;
-//    {
-//      Expression targetCondition = targetIfStatement.condition;
-//      Expression outerCondition = outerIfStatement.condition;
-//      String targetConditionSource = _getSource(targetCondition);
-//      String outerConditionSource = _getSource(outerCondition);
-//      if (_shouldWrapParenthesisBeforeAnd(targetCondition)) {
-//        targetConditionSource = "(${targetConditionSource})";
-//      }
-//      if (_shouldWrapParenthesisBeforeAnd(outerCondition)) {
-//        outerConditionSource = "(${outerConditionSource})";
-//      }
-//      condition = "${outerConditionSource} && ${targetConditionSource}";
-//    }
-//    // replace outer "if" statement
-//    {
-//      Statement targetThenStatement = targetIfStatement.thenStatement;
-//      List<Statement> targetThenStatements =
-//          CorrectionUtils.getStatements(targetThenStatement);
-//      SourceRange lineRanges = utils.getLinesRange(targetThenStatements);
-//      String oldSource = utils.getText3(lineRanges);
-//      String newSource = utils.getIndentSource2(oldSource, false);
-//      // TODO(scheglov)
-////      _addReplaceEdit(
-////          rangeNode(outerIfStatement),
-////          MessageFormat.format(
-////              "if ({0}) '{'{1}{2}{3}'}'",
-////              [condition, eol, newSource, prefix]));
-//    }
-//    // done
-//    _addAssist(AssistKind.JOIN_IF_WITH_OUTER, []);
+    // climb up condition to the (supposedly) "if" statement
+    AstNode node = this.node;
+    while (node is Expression) {
+      node = node.parent;
+    }
+    // prepare target "if" statement
+    if (node is! IfStatement) {
+      _coverageMarker();
+      return;
+    }
+    IfStatement targetIfStatement = node as IfStatement;
+    if (targetIfStatement.elseStatement != null) {
+      _coverageMarker();
+      return;
+    }
+    // prepare outer "if" statement
+    AstNode parent = targetIfStatement.parent;
+    if (parent is Block) {
+      parent = parent.parent;
+    }
+    if (parent is! IfStatement) {
+      _coverageMarker();
+      return;
+    }
+    IfStatement outerIfStatement = parent as IfStatement;
+    if (outerIfStatement.elseStatement != null) {
+      _coverageMarker();
+      return;
+    }
+    // prepare environment
+    String prefix = utils.getNodePrefix(outerIfStatement);
+    // merge conditions
+    String condition;
+    {
+      Expression targetCondition = targetIfStatement.condition;
+      Expression outerCondition = outerIfStatement.condition;
+      String targetConditionSource = _getNodeText(targetCondition);
+      String outerConditionSource = _getNodeText(outerCondition);
+      if (_shouldWrapParenthesisBeforeAnd(targetCondition)) {
+        targetConditionSource = "(${targetConditionSource})";
+      }
+      if (_shouldWrapParenthesisBeforeAnd(outerCondition)) {
+        outerConditionSource = "(${outerConditionSource})";
+      }
+      condition = "${outerConditionSource} && ${targetConditionSource}";
+    }
+    // replace outer "if" statement
+    {
+      Statement targetThenStatement = targetIfStatement.thenStatement;
+      List<Statement> targetThenStatements = getStatements(targetThenStatement);
+      SourceRange lineRanges =
+          utils.getLinesRangeStatements(targetThenStatements);
+      String oldSource = utils.getRangeText(lineRanges);
+      String newSource = utils.indentSourceLeftRight(oldSource, false);
+      _addReplaceEdit(
+          rangeNode(outerIfStatement),
+          "if ($condition) {${eol}${newSource}${prefix}}");
+    }
+    // done
+    _addAssist(AssistKind.JOIN_IF_WITH_OUTER, []);
   }
 
   void _addProposal_joinVariableDeclaration_onAssignment() {
@@ -977,9 +935,9 @@
     if (inVariable) {
       VariableDeclaration variable = conditional.parent as VariableDeclaration;
       _addRemoveEdit(rangeEndEnd(variable.name, conditional));
-      String conditionSrc = _getSource(conditional.condition);
-      String thenSrc = _getSource(conditional.thenExpression);
-      String elseSrc = _getSource(conditional.elseExpression);
+      String conditionSrc = _getNodeText(conditional.condition);
+      String thenSrc = _getNodeText(conditional.thenExpression);
+      String elseSrc = _getNodeText(conditional.elseExpression);
       String name = variable.name.name;
       String src = eol;
       src += prefix + 'if ($conditionSrc) {' + eol;
@@ -994,10 +952,10 @@
       AssignmentExpression assignment =
           conditional.parent as AssignmentExpression;
       Expression leftSide = assignment.leftHandSide;
-      String conditionSrc = _getSource(conditional.condition);
-      String thenSrc = _getSource(conditional.thenExpression);
-      String elseSrc = _getSource(conditional.elseExpression);
-      String name = _getSource(leftSide);
+      String conditionSrc = _getNodeText(conditional.condition);
+      String thenSrc = _getNodeText(conditional.thenExpression);
+      String elseSrc = _getNodeText(conditional.elseExpression);
+      String name = _getNodeText(leftSide);
       String src = '';
       src += 'if ($conditionSrc) {' + eol;
       src += prefix + indent + '$name = $thenSrc;' + eol;
@@ -1008,9 +966,9 @@
     }
     // return Conditional;
     if (inReturn) {
-      String conditionSrc = _getSource(conditional.condition);
-      String thenSrc = _getSource(conditional.thenExpression);
-      String elseSrc = _getSource(conditional.elseExpression);
+      String conditionSrc = _getNodeText(conditional.condition);
+      String thenSrc = _getNodeText(conditional.thenExpression);
+      String elseSrc = _getNodeText(conditional.elseExpression);
       String src = '';
       src += 'if ($conditionSrc) {' + eol;
       src += prefix + indent + 'return $thenSrc;' + eol;
@@ -1024,494 +982,487 @@
   }
 
   void _addProposal_replaceIfElseWithConditional() {
-    // TODO(scheglov) implement
-//    // should be "if"
-//    if (node is! IfStatement) {
-//      return;
-//    }
-//    IfStatement ifStatement = node as IfStatement;
-//    // single then/else statements
-//    Statement thenStatement =
-//        CorrectionUtils.getSingleStatement(ifStatement.thenStatement);
-//    Statement elseStatement =
-//        CorrectionUtils.getSingleStatement(ifStatement.elseStatement);
-//    if (thenStatement == null || elseStatement == null) {
-//      return;
-//    }
-//    // returns
-//    if (thenStatement is ReturnStatement || elseStatement is ReturnStatement) {
-//      ReturnStatement thenReturn = thenStatement as ReturnStatement;
-//      ReturnStatement elseReturn = elseStatement as ReturnStatement;
-//      // TODO(scheglov)
-////      _addReplaceEdit(
-////          rangeNode(ifStatement),
-////          MessageFormat.format(
-////              "return {0} ? {1} : {2};",
-////              [
-////                  _getSource(ifStatement.condition),
-////                  _getSource(thenReturn.expression),
-////                  _getSource(elseReturn.expression)]));
-//    }
-//    // assignments -> v = Conditional;
-//    if (thenStatement is ExpressionStatement &&
-//        elseStatement is ExpressionStatement) {
-//      Expression thenExpression = thenStatement.expression;
-//      Expression elseExpression = elseStatement.expression;
-//      if (thenExpression is AssignmentExpression &&
-//          elseExpression is AssignmentExpression) {
-//        AssignmentExpression thenAssignment = thenExpression;
-//        AssignmentExpression elseAssignment = elseExpression;
-//        String thenTarget = _getSource(thenAssignment.leftHandSide);
-//        String elseTarget = _getSource(elseAssignment.leftHandSide);
-//        if (thenAssignment.operator.type == TokenType.EQ &&
-//            elseAssignment.operator.type == TokenType.EQ &&
-//            StringUtils.equals(thenTarget, elseTarget)) {
-//          // TODO(scheglov)
-////          _addReplaceEdit(
-////              rangeNode(ifStatement),
-////              MessageFormat.format(
-////                  "{0} = {1} ? {2} : {3};",
-////                  [
-////                      thenTarget,
-////                      _getSource(ifStatement.condition),
-////                      _getSource(thenAssignment.rightHandSide),
-////                      _getSource(elseAssignment.rightHandSide)]));
-//        }
-//      }
-//    }
-//    // add proposal
-//    _addAssist(
-//        AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL,
-//        []);
+    // should be "if"
+    if (node is! IfStatement) {
+      _coverageMarker();
+      return;
+    }
+    IfStatement ifStatement = node as IfStatement;
+    // single then/else statements
+    Statement thenStatement = getSingleStatement(ifStatement.thenStatement);
+    Statement elseStatement = getSingleStatement(ifStatement.elseStatement);
+    if (thenStatement == null || elseStatement == null) {
+      _coverageMarker();
+      return;
+    }
+    // returns
+    if (thenStatement is ReturnStatement || elseStatement is ReturnStatement) {
+      ReturnStatement thenReturn = thenStatement as ReturnStatement;
+      ReturnStatement elseReturn = elseStatement as ReturnStatement;
+      String conditionSrc = _getNodeText(ifStatement.condition);
+      String theSrc = _getNodeText(thenReturn.expression);
+      String elseSrc = _getNodeText(elseReturn.expression);
+      _addReplaceEdit(
+          rangeNode(ifStatement),
+          'return $conditionSrc ? $theSrc : $elseSrc;');
+    }
+    // assignments -> v = Conditional;
+    if (thenStatement is ExpressionStatement &&
+        elseStatement is ExpressionStatement) {
+      Expression thenExpression = thenStatement.expression;
+      Expression elseExpression = elseStatement.expression;
+      if (thenExpression is AssignmentExpression &&
+          elseExpression is AssignmentExpression) {
+        AssignmentExpression thenAssignment = thenExpression;
+        AssignmentExpression elseAssignment = elseExpression;
+        String thenTarget = _getNodeText(thenAssignment.leftHandSide);
+        String elseTarget = _getNodeText(elseAssignment.leftHandSide);
+        if (thenAssignment.operator.type == TokenType.EQ &&
+            elseAssignment.operator.type == TokenType.EQ &&
+            StringUtils.equals(thenTarget, elseTarget)) {
+          String conditionSrc = _getNodeText(ifStatement.condition);
+          String theSrc = _getNodeText(thenAssignment.rightHandSide);
+          String elseSrc = _getNodeText(elseAssignment.rightHandSide);
+          _addReplaceEdit(
+              rangeNode(ifStatement),
+              '$thenTarget = $conditionSrc ? $theSrc : $elseSrc;');
+        }
+      }
+    }
+    // add proposal
+    _addAssist(AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL, []);
   }
 
   void _addProposal_splitAndCondition() {
-    // TODO(scheglov) implement
-//    // check that user invokes quick assist on binary expression
-//    if (node is! BinaryExpression) {
-//      return;
-//    }
-//    BinaryExpression binaryExpression = node as BinaryExpression;
-//    // prepare operator position
-//    int offset =
-//        _isOperatorSelected(binaryExpression, _selectionOffset, _selectionLength);
-//    if (offset == -1) {
-//      return;
-//    }
-//    // should be &&
-//    if (binaryExpression.operator.type != TokenType.AMPERSAND_AMPERSAND) {
-//      return;
-//    }
-//    // prepare "if"
-//    Statement statement = node.getAncestor((node) => node is Statement);
-//    if (statement is! IfStatement) {
-//      return;
-//    }
-//    IfStatement ifStatement = statement as IfStatement;
-//    // check that binary expression is part of first level && condition of "if"
-//    BinaryExpression condition = binaryExpression;
-//    while (condition.parent is BinaryExpression &&
-//        (condition.parent as BinaryExpression).operator.type ==
-//            TokenType.AMPERSAND_AMPERSAND) {
-//      condition = condition.parent as BinaryExpression;
-//    }
-//    if (!identical(ifStatement.condition, condition)) {
-//      return;
-//    }
-//    // prepare environment
-//    String prefix = utils.getNodePrefix(ifStatement);
-//    String indent = utils.getIndent(1);
-//    // prepare "rightCondition"
-//    String rightConditionSource;
-//    {
-//      SourceRange rightConditionRange =
-//          rangeStartEnd(binaryExpression.rightOperand, condition);
-//      rightConditionSource = _getSource2(rightConditionRange);
-//    }
-//    // remove "&& rightCondition"
-//    _addRemoveEdit(
-//        rangeEndEnd(binaryExpression.leftOperand, condition));
-//    // update "then" statement
-//    Statement thenStatement = ifStatement.thenStatement;
-//    Statement elseStatement = ifStatement.elseStatement;
-//    if (thenStatement is Block) {
-//      Block thenBlock = thenStatement;
-//      SourceRange thenBlockRange = rangeNode(thenBlock);
-//      // insert inner "if" with right part of "condition"
-//      {
-//        String source =
-//            "${eol}${prefix}${indent}if (${rightConditionSource}) {";
-//        int thenBlockInsideOffset = thenBlockRange.offset + 1;
-//        _addInsertEdit(thenBlockInsideOffset, source);
-//      }
-//      // insert closing "}" for inner "if"
-//      {
-//        int thenBlockEnd = thenBlockRange.end;
-//        String source = "${indent}}";
-//        // may be move "else" statements
-//        if (elseStatement != null) {
-//          List<Statement> elseStatements =
-//              CorrectionUtils.getStatements(elseStatement);
-//          SourceRange elseLinesRange = utils.getLinesRange(elseStatements);
-//          String elseIndentOld = "${prefix}${indent}";
-//          String elseIndentNew = "${elseIndentOld}${indent}";
-//          String newElseSource =
-//              utils.getIndentSource(elseLinesRange, elseIndentOld, elseIndentNew);
-//          // append "else" block
-//          source += " else {${eol}";
-//          source += newElseSource;
-//          source += "${prefix}${indent}}";
-//          // remove old "else" range
-//          _addRemoveEdit(
-//              rangeStartEnd(thenBlockEnd, elseStatement));
-//        }
-//        // insert before outer "then" block "}"
-//        source += "${eol}${prefix}";
-//        _addInsertEdit(thenBlockEnd - 1, source);
-//      }
-//    } else {
-//      // insert inner "if" with right part of "condition"
-//      {
-//        String source = "${eol}${prefix}${indent}if (${rightConditionSource})";
-//        _addInsertEdit(ifStatement.rightParenthesis.offset + 1, source);
-//      }
-//      // indent "else" statements to correspond inner "if"
-//      if (elseStatement != null) {
-//        SourceRange elseRange =
-//            rangeStartEnd(ifStatement.elseKeyword.offset, elseStatement);
-//        SourceRange elseLinesRange = utils.getLinesRange2(elseRange);
-//        String elseIndentOld = prefix;
-//        String elseIndentNew = "${elseIndentOld}${indent}";
-//        edits.add(
-//            utils.createIndentEdit(elseLinesRange, elseIndentOld, elseIndentNew));
-//      }
-//    }
-//    // indent "then" statements to correspond inner "if"
-//    {
-//      List<Statement> thenStatements =
-//          CorrectionUtils.getStatements(thenStatement);
-//      SourceRange linesRange = utils.getLinesRange(thenStatements);
-//      String thenIndentOld = "${prefix}${indent}";
-//      String thenIndentNew = "${thenIndentOld}${indent}";
-//      edits.add(
-//          utils.createIndentEdit(linesRange, thenIndentOld, thenIndentNew));
-//    }
-//    // add proposal
-//    _addAssist(AssistKind.SPLIT_AND_CONDITION, []);
+    // check that user invokes quick assist on binary expression
+    if (node is! BinaryExpression) {
+      _coverageMarker();
+      return;
+    }
+    BinaryExpression binaryExpression = node as BinaryExpression;
+    // prepare operator position
+    if (!_isOperatorSelected(
+        binaryExpression,
+        selectionOffset,
+        selectionLength)) {
+      _coverageMarker();
+      return;
+    }
+    // should be &&
+    if (binaryExpression.operator.type != TokenType.AMPERSAND_AMPERSAND) {
+      _coverageMarker();
+      return;
+    }
+    // prepare "if"
+    Statement statement = node.getAncestor((node) => node is Statement);
+    if (statement is! IfStatement) {
+      _coverageMarker();
+      return;
+    }
+    IfStatement ifStatement = statement as IfStatement;
+    // check that binary expression is part of first level && condition of "if"
+    BinaryExpression condition = binaryExpression;
+    while (condition.parent is BinaryExpression &&
+        (condition.parent as BinaryExpression).operator.type ==
+            TokenType.AMPERSAND_AMPERSAND) {
+      condition = condition.parent as BinaryExpression;
+    }
+    if (!identical(ifStatement.condition, condition)) {
+      _coverageMarker();
+      return;
+    }
+    // prepare environment
+    String prefix = utils.getNodePrefix(ifStatement);
+    String indent = utils.getIndent(1);
+    // prepare "rightCondition"
+    String rightConditionSource;
+    {
+      SourceRange rightConditionRange =
+          rangeStartEnd(binaryExpression.rightOperand, condition);
+      rightConditionSource = _getRangeText(rightConditionRange);
+    }
+    // remove "&& rightCondition"
+    _addRemoveEdit(rangeEndEnd(binaryExpression.leftOperand, condition));
+    // update "then" statement
+    Statement thenStatement = ifStatement.thenStatement;
+    Statement elseStatement = ifStatement.elseStatement;
+    if (thenStatement is Block) {
+      Block thenBlock = thenStatement;
+      SourceRange thenBlockRange = rangeNode(thenBlock);
+      // insert inner "if" with right part of "condition"
+      {
+        String source =
+            "${eol}${prefix}${indent}if (${rightConditionSource}) {";
+        int thenBlockInsideOffset = thenBlockRange.offset + 1;
+        _addInsertEdit(thenBlockInsideOffset, source);
+      }
+      // insert closing "}" for inner "if"
+      {
+        int thenBlockEnd = thenBlockRange.end;
+        String source = "${indent}}";
+        // may be move "else" statements
+        if (elseStatement != null) {
+          List<Statement> elseStatements = getStatements(elseStatement);
+          SourceRange elseLinesRange =
+              utils.getLinesRangeStatements(elseStatements);
+          String elseIndentOld = "${prefix}${indent}";
+          String elseIndentNew = "${elseIndentOld}${indent}";
+          String newElseSource =
+              utils.replaceSourceRangeIndent(elseLinesRange, elseIndentOld, elseIndentNew);
+          // append "else" block
+          source += " else {${eol}";
+          source += newElseSource;
+          source += "${prefix}${indent}}";
+          // remove old "else" range
+          _addRemoveEdit(rangeStartEnd(thenBlockEnd, elseStatement));
+        }
+        // insert before outer "then" block "}"
+        source += "${eol}${prefix}";
+        _addInsertEdit(thenBlockEnd - 1, source);
+      }
+    } else {
+      // insert inner "if" with right part of "condition"
+      {
+        String source = "${eol}${prefix}${indent}if (${rightConditionSource})";
+        _addInsertEdit(ifStatement.rightParenthesis.offset + 1, source);
+      }
+      // indent "else" statements to correspond inner "if"
+      if (elseStatement != null) {
+        SourceRange elseRange =
+            rangeStartEnd(ifStatement.elseKeyword.offset, elseStatement);
+        SourceRange elseLinesRange = utils.getLinesRange(elseRange);
+        String elseIndentOld = prefix;
+        String elseIndentNew = "${elseIndentOld}${indent}";
+        edits.add(
+            utils.createIndentEdit(elseLinesRange, elseIndentOld, elseIndentNew));
+      }
+    }
+    // indent "then" statements to correspond inner "if"
+    {
+      List<Statement> thenStatements = getStatements(thenStatement);
+      SourceRange linesRange = utils.getLinesRangeStatements(thenStatements);
+      String thenIndentOld = "${prefix}${indent}";
+      String thenIndentNew = "${thenIndentOld}${indent}";
+      edits.add(
+          utils.createIndentEdit(linesRange, thenIndentOld, thenIndentNew));
+    }
+    // add proposal
+    _addAssist(AssistKind.SPLIT_AND_CONDITION, []);
   }
 
   void _addProposal_splitVariableDeclaration() {
-    // TODO(scheglov) implement
-//    // prepare DartVariableStatement, should be part of Block
-//    VariableDeclarationStatement statement =
-//        node.getAncestor((node) => node is VariableDeclarationStatement);
-//    if (statement != null && statement.parent is Block) {
-//    } else {
-//      return;
-//    }
-//    // check that statement declares single variable
-//    List<VariableDeclaration> variables = statement.variables.variables;
-//    if (variables.length != 1) {
-//      return;
-//    }
-//    VariableDeclaration variable = variables[0];
-//    // remove initializer value
-//    _addRemoveEdit(
-//        rangeEndStart(variable.name, statement.semicolon));
-//    // TODO(scheglov)
-////    // add assignment statement
-////    String indent = _utils.getNodePrefix(statement);
-////    String assignSource =
-////        MessageFormat.format(
-////            "{0} = {1};",
-////            [variable.name.name, _getSource(variable.initializer)]);
-////    SourceRange assignRange = rangeEndLength(statement, 0);
-////    _addReplaceEdit(assignRange, "${eol}${indent}${assignSource}");
-////    // add proposal
-////    _addUnitCorrectionProposal(
-////        AssistKind.SPLIT_VARIABLE_DECLARATION,
-////        []);
+    // prepare DartVariableStatement, should be part of Block
+    VariableDeclarationStatement statement =
+        node.getAncestor((node) => node is VariableDeclarationStatement);
+    if (statement != null && statement.parent is Block) {
+    } else {
+      _coverageMarker();
+      return;
+    }
+    // check that statement declares single variable
+    List<VariableDeclaration> variables = statement.variables.variables;
+    if (variables.length != 1) {
+      _coverageMarker();
+      return;
+    }
+    VariableDeclaration variable = variables[0];
+    // prepare initializer
+    Expression initializer = variable.initializer;
+    if (initializer == null) {
+      _coverageMarker();
+      return;
+    }
+    // remove initializer value
+    _addRemoveEdit(rangeEndStart(variable.name, statement.semicolon));
+    // add assignment statement
+    String indent = utils.getNodePrefix(statement);
+    String name = variable.name.name;
+    String initSrc = _getNodeText(initializer);
+    SourceRange assignRange = rangeEndLength(statement, 0);
+    _addReplaceEdit(assignRange, eol + indent + name + ' = ' + initSrc + ';');
+    // add proposal
+    _addAssist(AssistKind.SPLIT_VARIABLE_DECLARATION, []);
   }
 
   void _addProposal_surroundWith() {
-    // TODO(scheglov) implement
-//    // prepare selected statements
-//    List<Statement> selectedStatements;
-//    {
-//      SourceRange selection =
-//          rangeStartLength(_selectionOffset, _selectionLength);
-//      StatementAnalyzer selectionAnalyzer =
-//          new StatementAnalyzer.con1(_unit, selection);
-//      _unit.accept(selectionAnalyzer);
-//      List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes;
-//      // convert nodes to statements
-//      selectedStatements = [];
-//      for (AstNode selectedNode in selectedNodes) {
-//        if (selectedNode is Statement) {
-//          selectedStatements.add(selectedNode);
-//        }
-//      }
-//      // we want only statements
-//      if (selectedStatements.isEmpty ||
-//          selectedStatements.length != selectedNodes.length) {
-//        return;
-//      }
-//    }
-//    // prepare statement information
-//    Statement firstStatement = selectedStatements[0];
-//    Statement lastStatement = selectedStatements[selectedStatements.length - 1];
-//    SourceRange statementsRange = utils.getLinesRange(selectedStatements);
-//    // prepare environment
-//    String indentOld = utils.getNodePrefix(firstStatement);
-//    String indentNew = "${indentOld}${utils.getIndent(1)}";
-//    // "block"
-//    {
-//      _addInsertEdit(statementsRange.offset, "${indentOld}{${eol}");
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
-//      _proposalEndRange = rangeEndLength(lastStatement, 0);
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_BLOCK, []);
-//    }
-//    // "if"
-//    {
-//      {
-//        int offset = statementsRange.offset;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        sb.append(indentOld);
-//        sb.append("if (");
-//        {
-//          sb.startPosition("CONDITION");
-//          sb.append("condition");
-//          sb.endPosition();
-//        }
-//        sb.append(") {");
-//        sb.append(eol);
-//        _insertBuilder(sb);
-//      }
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
-//      _proposalEndRange = rangeEndLength(lastStatement, 0);
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_IF, []);
-//    }
-//    // "while"
-//    {
-//      {
-//        int offset = statementsRange.offset;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        sb.append(indentOld);
-//        sb.append("while (");
-//        {
-//          sb.startPosition("CONDITION");
-//          sb.append("condition");
-//          sb.endPosition();
-//        }
-//        sb.append(") {");
-//        sb.append(eol);
-//        _insertBuilder(sb);
-//      }
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
-//      _proposalEndRange = rangeEndLength(lastStatement, 0);
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_WHILE, []);
-//    }
-//    // "for-in"
-//    {
-//      {
-//        int offset = statementsRange.offset;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        sb.append(indentOld);
-//        sb.append("for (var ");
-//        {
-//          sb.startPosition("NAME");
-//          sb.append("item");
-//          sb.endPosition();
-//        }
-//        sb.append(" in ");
-//        {
-//          sb.startPosition("ITERABLE");
-//          sb.append("iterable");
-//          sb.endPosition();
-//        }
-//        sb.append(") {");
-//        sb.append(eol);
-//        _insertBuilder(sb);
-//      }
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
-//      _proposalEndRange = rangeEndLength(lastStatement, 0);
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_FOR_IN, []);
-//    }
-//    // "for"
-//    {
-//      {
-//        int offset = statementsRange.offset;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        sb.append(indentOld);
-//        sb.append("for (var ");
-//        {
-//          sb.startPosition("VAR");
-//          sb.append("v");
-//          sb.endPosition();
-//        }
-//        sb.append(" = ");
-//        {
-//          sb.startPosition("INIT");
-//          sb.append("init");
-//          sb.endPosition();
-//        }
-//        sb.append("; ");
-//        {
-//          sb.startPosition("CONDITION");
-//          sb.append("condition");
-//          sb.endPosition();
-//        }
-//        sb.append("; ");
-//        {
-//          sb.startPosition("INCREMENT");
-//          sb.append("increment");
-//          sb.endPosition();
-//        }
-//        sb.append(") {");
-//        sb.append(eol);
-//        _insertBuilder(sb);
-//      }
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
-//      _proposalEndRange = rangeEndLength(lastStatement, 0);
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_FOR, []);
-//    }
-//    // "do-while"
-//    {
-//      _addInsertEdit(statementsRange.offset, "${indentOld}do {${eol}");
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      {
-//        int offset = statementsRange.end;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        sb.append(indentOld);
-//        sb.append("} while (");
-//        {
-//          sb.startPosition("CONDITION");
-//          sb.append("condition");
-//          sb.endPosition();
-//        }
-//        sb.append(");");
-//        sb.append(eol);
-//        _insertBuilder(sb);
-//      }
-//      _proposalEndRange = rangeEndLength(lastStatement, 0);
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_DO_WHILE, []);
-//    }
-//    // "try-catch"
-//    {
-//      _addInsertEdit(statementsRange.offset, "${indentOld}try {${eol}");
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      {
-//        int offset = statementsRange.end;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        sb.append(indentOld);
-//        sb.append("} on ");
-//        {
-//          sb.startPosition("EXCEPTION_TYPE");
-//          sb.append("Exception");
-//          sb.endPosition();
-//        }
-//        sb.append(" catch (");
-//        {
-//          sb.startPosition("EXCEPTION_VAR");
-//          sb.append("e");
-//          sb.endPosition();
-//        }
-//        sb.append(") {");
-//        sb.append(eol);
-//        //
-//        sb.append(indentNew);
-//        {
-//          sb.startPosition("CATCH");
-//          sb.append("// TODO");
-//          sb.endPosition();
-//          sb.setEndPosition();
-//        }
-//        sb.append(eol);
-//        //
-//        sb.append(indentOld);
-//        sb.append("}");
-//        sb.append(eol);
-//        //
-//        _insertBuilder(sb);
-//      }
-//      // add proposal
-//      _addAssist(AssistKind.SURROUND_WITH_TRY_CATCH, []);
-//    }
-//    // "try-finally"
-//    {
-//      _addInsertEdit(statementsRange.offset, "${indentOld}try {${eol}");
-//      {
-//        Edit edit =
-//            utils.createIndentEdit(statementsRange, indentOld, indentNew);
-//        edits.add(edit);
-//      }
-//      {
-//        int offset = statementsRange.end;
-//        SourceBuilder sb = new SourceBuilder.con1(offset);
-//        //
-//        sb.append(indentOld);
-//        sb.append("} finally {");
-//        sb.append(eol);
-//        //
-//        sb.append(indentNew);
-//        {
-//          sb.startPosition("FINALLY");
-//          sb.append("// TODO");
-//          sb.endPosition();
-//        }
-//        sb.setEndPosition();
-//        sb.append(eol);
-//        //
-//        sb.append(indentOld);
-//        sb.append("}");
-//        sb.append(eol);
-//        //
-//        _insertBuilder(sb);
-//      }
-//      // add proposal
-//      _addAssist(
-//          AssistKind.SURROUND_WITH_TRY_FINALLY,
-//          []);
-//    }
+    // prepare selected statements
+    List<Statement> selectedStatements;
+    {
+      SourceRange selection =
+          rangeStartLength(selectionOffset, selectionLength);
+      StatementAnalyzer selectionAnalyzer =
+          new StatementAnalyzer(unit, selection);
+      unit.accept(selectionAnalyzer);
+      List<AstNode> selectedNodes = selectionAnalyzer.selectedNodes;
+      // convert nodes to statements
+      selectedStatements = [];
+      for (AstNode selectedNode in selectedNodes) {
+        if (selectedNode is Statement) {
+          selectedStatements.add(selectedNode);
+        }
+      }
+      // we want only statements
+      if (selectedStatements.isEmpty ||
+          selectedStatements.length != selectedNodes.length) {
+        return;
+      }
+    }
+    // prepare statement information
+    Statement firstStatement = selectedStatements[0];
+    Statement lastStatement = selectedStatements[selectedStatements.length - 1];
+    SourceRange statementsRange =
+        utils.getLinesRangeStatements(selectedStatements);
+    // prepare environment
+    String indentOld = utils.getNodePrefix(firstStatement);
+    String indentNew = "${indentOld}${utils.getIndent(1)}";
+    // "block"
+    {
+      _addInsertEdit(statementsRange.offset, "${indentOld}{${eol}");
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
+      exitPosition = _newPosition(lastStatement.end);
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_BLOCK, []);
+    }
+    // "if"
+    {
+      {
+        int offset = statementsRange.offset;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        sb.append(indentOld);
+        sb.append("if (");
+        {
+          sb.startPosition("CONDITION");
+          sb.append("condition");
+          sb.endPosition();
+        }
+        sb.append(") {");
+        sb.append(eol);
+        _insertBuilder(sb);
+      }
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
+      exitPosition = _newPosition(lastStatement.end);
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_IF, []);
+    }
+    // "while"
+    {
+      {
+        int offset = statementsRange.offset;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        sb.append(indentOld);
+        sb.append("while (");
+        {
+          sb.startPosition("CONDITION");
+          sb.append("condition");
+          sb.endPosition();
+        }
+        sb.append(") {");
+        sb.append(eol);
+        _insertBuilder(sb);
+      }
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
+      exitPosition = _newPosition(lastStatement.end);
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_WHILE, []);
+    }
+    // "for-in"
+    {
+      {
+        int offset = statementsRange.offset;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        sb.append(indentOld);
+        sb.append("for (var ");
+        {
+          sb.startPosition("NAME");
+          sb.append("item");
+          sb.endPosition();
+        }
+        sb.append(" in ");
+        {
+          sb.startPosition("ITERABLE");
+          sb.append("iterable");
+          sb.endPosition();
+        }
+        sb.append(") {");
+        sb.append(eol);
+        _insertBuilder(sb);
+      }
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
+      exitPosition = _newPosition(lastStatement.end);
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_FOR_IN, []);
+    }
+    // "for"
+    {
+      {
+        int offset = statementsRange.offset;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        sb.append(indentOld);
+        sb.append("for (var ");
+        {
+          sb.startPosition("VAR");
+          sb.append("v");
+          sb.endPosition();
+        }
+        sb.append(" = ");
+        {
+          sb.startPosition("INIT");
+          sb.append("init");
+          sb.endPosition();
+        }
+        sb.append("; ");
+        {
+          sb.startPosition("CONDITION");
+          sb.append("condition");
+          sb.endPosition();
+        }
+        sb.append("; ");
+        {
+          sb.startPosition("INCREMENT");
+          sb.append("increment");
+          sb.endPosition();
+        }
+        sb.append(") {");
+        sb.append(eol);
+        _insertBuilder(sb);
+      }
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      _addInsertEdit(statementsRange.end, "${indentOld}}${eol}");
+      exitPosition = _newPosition(lastStatement.end);
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_FOR, []);
+    }
+    // "do-while"
+    {
+      _addInsertEdit(statementsRange.offset, "${indentOld}do {${eol}");
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      {
+        int offset = statementsRange.end;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        sb.append(indentOld);
+        sb.append("} while (");
+        {
+          sb.startPosition("CONDITION");
+          sb.append("condition");
+          sb.endPosition();
+        }
+        sb.append(");");
+        sb.append(eol);
+        _insertBuilder(sb);
+      }
+      exitPosition = _newPosition(lastStatement.end);
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_DO_WHILE, []);
+    }
+    // "try-catch"
+    {
+      _addInsertEdit(statementsRange.offset, "${indentOld}try {${eol}");
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      {
+        int offset = statementsRange.end;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        sb.append(indentOld);
+        sb.append("} on ");
+        {
+          sb.startPosition("EXCEPTION_TYPE");
+          sb.append("Exception");
+          sb.endPosition();
+        }
+        sb.append(" catch (");
+        {
+          sb.startPosition("EXCEPTION_VAR");
+          sb.append("e");
+          sb.endPosition();
+        }
+        sb.append(") {");
+        sb.append(eol);
+        //
+        sb.append(indentNew);
+        {
+          sb.startPosition("CATCH");
+          sb.append("// TODO");
+          sb.endPosition();
+          sb.setExitOffset();
+        }
+        sb.append(eol);
+        //
+        sb.append(indentOld);
+        sb.append("}");
+        sb.append(eol);
+        //
+        _insertBuilder(sb);
+        exitPosition = _newPosition(sb.exitOffset);
+      }
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_TRY_CATCH, []);
+    }
+    // "try-finally"
+    {
+      _addInsertEdit(statementsRange.offset, "${indentOld}try {${eol}");
+      {
+        Edit edit =
+            utils.createIndentEdit(statementsRange, indentOld, indentNew);
+        edits.add(edit);
+      }
+      {
+        int offset = statementsRange.end;
+        SourceBuilder sb = new SourceBuilder(file, offset);
+        //
+        sb.append(indentOld);
+        sb.append("} finally {");
+        sb.append(eol);
+        //
+        sb.append(indentNew);
+        {
+          sb.startPosition("FINALLY");
+          sb.append("// TODO");
+          sb.endPosition();
+        }
+        sb.setExitOffset();
+        sb.append(eol);
+        //
+        sb.append(indentOld);
+        sb.append("}");
+        sb.append(eol);
+        //
+        _insertBuilder(sb);
+        exitPosition = _newPosition(sb.exitOffset);
+      }
+      // add proposal
+      _addAssist(AssistKind.SURROUND_WITH_TRY_FINALLY, []);
+    }
   }
 
   /**
@@ -1530,31 +1481,29 @@
   }
 
   /**
-   * Returns an existing or just added [LinkedPositionGroup] with [groupId].
+   * Returns an existing or just added [LinkedEditGroup] with [groupId].
    */
-  LinkedPositionGroup _getLinkedPosition(String groupId) {
-    LinkedPositionGroup group = linkedPositionGroups[groupId];
+  LinkedEditGroup _getLinkedPosition(String groupId) {
+    LinkedEditGroup group = linkedPositionGroups[groupId];
     if (group == null) {
-      group = new LinkedPositionGroup(groupId);
+      group = new LinkedEditGroup(groupId);
       linkedPositionGroups[groupId] = group;
     }
     return group;
   }
 
   /**
-   * Returns the text of the given range in the unit.
+   * Returns the text of the given node in the unit.
    */
-  String _getSource(AstNode node) {
-    // TODO(scheglov) rename
-    return utils.getText(node);
+  String _getNodeText(AstNode node) {
+    return utils.getNodeText(node);
   }
 
   /**
    * Returns the text of the given range in the unit.
    */
-  String _getSource2(SourceRange range) {
-    // TODO(scheglov) rename
-    return utils.getText3(range);
+  String _getRangeText(SourceRange range) {
+    return utils.getRangeText(range);
   }
 
   /**
@@ -1564,17 +1513,21 @@
     String text = builder.toString();
     _addInsertEdit(builder.offset, text);
     // add linked positions
-    builder.linkedPositionGroups.forEach((LinkedPositionGroup group) {
-      LinkedPositionGroup fixGroup = _getLinkedPosition(group.id);
+    builder.linkedPositionGroups.forEach((LinkedEditGroup group) {
+      LinkedEditGroup fixGroup = _getLinkedPosition(group.id);
       group.positions.forEach((Position position) {
-        fixGroup.addPosition(position);
+        fixGroup.addPosition(position, group.length);
       });
-      group.proposals.forEach((String proposal) {
-        fixGroup.addProposal(proposal);
+      group.suggestions.forEach((LinkedEditSuggestion suggestion) {
+        fixGroup.addSuggestion(suggestion);
       });
     });
   }
 
+  Position _newPosition(int offset) {
+    return new Position(file, offset);
+  }
+
   /**
    * This method does nothing, but we invoke it in places where Dart VM
    * coverage agent fails to provide coverage information - such as almost
@@ -1611,4 +1564,29 @@
     _coverageMarker();
     return false;
   }
+
+  /**
+   * Checks if the given [Expression] should be wrapped with parenthesis when we
+   * want to use it as operand of a logical `and` expression.
+   */
+  static bool _shouldWrapParenthesisBeforeAnd(Expression expr) {
+    if (expr is BinaryExpression) {
+      BinaryExpression binary = expr;
+      int precedence = binary.operator.type.precedence;
+      return precedence < TokenClass.LOGICAL_AND_OPERATOR.precedence;
+    }
+    return false;
+  }
+}
+
+
+class _SimpleIdentifierRecursiveAstVisitor extends RecursiveAstVisitor {
+  final _SimpleIdentifierVisitor visitor;
+
+  _SimpleIdentifierRecursiveAstVisitor(this.visitor);
+
+  @override
+  visitSimpleIdentifier(SimpleIdentifier node) {
+    visitor(node);
+  }
 }
diff --git a/pkg/analysis_services/lib/src/correction/fix.dart b/pkg/analysis_services/lib/src/correction/fix.dart
index 4b242ff..069f609 100644
--- a/pkg/analysis_services/lib/src/correction/fix.dart
+++ b/pkg/analysis_services/lib/src/correction/fix.dart
@@ -50,14 +50,15 @@
   final CompilationUnit unit;
   final AnalysisError error;
   CompilationUnitElement unitElement;
+  Source unitSource;
   LibraryElement unitLibraryElement;
   String unitLibraryFile;
   String unitLibraryFolder;
 
   final List<Edit> edits = <Edit>[];
-  final Map<String, LinkedPositionGroup> linkedPositionGroups = <String,
-      LinkedPositionGroup>{};
-  Position endPosition = null;
+  final Map<String, LinkedEditGroup> linkedPositionGroups = <String,
+      LinkedEditGroup>{};
+  Position exitPosition = null;
   final List<Fix> fixes = <Fix>[];
 
   CorrectionUtils utils;
@@ -67,10 +68,10 @@
   AstNode node;
   AstNode coveredNode;
 
-
   FixProcessor(this.searchEngine, this.source, this.file, this.unit, this.error)
       {
     unitElement = unit.element;
+    unitSource = unitElement.source;
     unitLibraryElement = unitElement.library;
     unitLibraryFile = unitLibraryElement.source.fullName;
     unitLibraryFolder = dirname(unitLibraryFile);
@@ -113,10 +114,6 @@
         CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT) {
       _addFix_createConstructorSuperExplicit();
     }
-//    if (identical(errorCode, CompileTimeErrorCode.URI_DOES_NOT_EXIST)) {
-//      _addFix_createPart();
-//      _addFix_addPackageDependency();
-//    }
     if (errorCode == HintCode.DIVISION_OPTIMIZATION) {
       _addFix_useEffectiveIntegerDivision();
     }
@@ -215,41 +212,17 @@
     Change change = new Change(message);
     change.add(fileEdit);
     linkedPositionGroups.values.forEach(
-        (group) => change.addLinkedPositionGroup(group));
-    change.endPosition = endPosition;
+        (group) => change.addLinkedEditGroup(group));
+    change.selection = exitPosition;
     // add Fix
     Fix fix = new Fix(kind, change);
     fixes.add(fix);
     // clear
     edits.clear();
     linkedPositionGroups.clear();
-    endPosition = null;
+    exitPosition = null;
   }
 
-
-  void _addFix_addPackageDependency() {
-    // TODO(scheglov) implement
-//    if (node is SimpleStringLiteral && node.parent is NamespaceDirective) {
-//      SimpleStringLiteral uriLiteral = node as SimpleStringLiteral;
-//      String uriString = uriLiteral.value;
-//      // we need package: import
-//      if (!uriString.startsWith("package:")) {
-//        return;
-//      }
-//      // prepare package name
-//      String packageName = StringUtils.removeStart(uriString, "package:");
-//      packageName = StringUtils.substringBefore(packageName, "/");
-//      // add proposal
-//      _proposals.add(
-//          new AddDependencyCorrectionProposal(
-//              _unitFile,
-//              packageName,
-//              FixKind.ADD_PACKAGE_DEPENDENCY,
-//              [packageName]));
-//    }
-  }
-
-
   void _addFix_boolInsteadOfBoolean() {
     SourceRange range = rf.rangeError(error);
     _addReplaceEdit(range, "bool");
@@ -623,7 +596,7 @@
       isFirst = false;
     }
     // add proposal
-    endPosition = new Position(file, insertOffset, 0);
+    exitPosition = new Position(file, insertOffset);
     _insertBuilder(sb);
     _addFix(FixKind.CREATE_MISSING_OVERRIDES, [missingOverrides.length]);
   }
@@ -701,44 +674,11 @@
     }
     // done
     _insertBuilder(sb);
-    endPosition = new Position(file, insertOffset, 0);
+    exitPosition = new Position(file, insertOffset);
     // add proposal
     _addFix(FixKind.CREATE_NO_SUCH_METHOD, []);
   }
 
-
-  void _addFix_createPart() {
-    // TODO(scheglov) implement
-//    if (node is SimpleStringLiteral && node.parent is PartDirective) {
-//      SimpleStringLiteral uriLiteral = node as SimpleStringLiteral;
-//      String uriString = uriLiteral.value;
-//      // prepare referenced File
-//      JavaFile newFile;
-//      {
-//        Uri uri = parseUriWithException(uriString);
-//        if (uri.isAbsolute) {
-//          return;
-//        }
-//        newFile = new JavaFile.relative(_unitLibraryFolder, uriString);
-//      }
-//      if (!newFile.exists()) {
-//        // prepare new source
-//        String source;
-//        {
-//          String libraryName = _unitLibraryElement.displayName;
-//          source = "part of ${libraryName};${eol}${eol}";
-//        }
-//        // add proposal
-//        _proposals.add(
-//            new CreateFileCorrectionProposal(
-//                newFile,
-//                source,
-//                FixKind.CREATE_PART,
-//                [uriString]));
-//      }
-//    }
-  }
-
   void _addFix_importLibrary(FixKind kind, String importPath) {
     CompilationUnitElement libraryUnitElement =
         unitLibraryElement.definingCompilationUnit;
@@ -838,8 +778,9 @@
       List<SdkLibrary> sdkLibraries = sdk.sdkLibraries;
       for (SdkLibrary sdkLibrary in sdkLibraries) {
         SourceFactory sdkSourceFactory = context.sourceFactory;
-        String libraryUri = sdkLibrary.shortName;
-        Source librarySource = sdkSourceFactory.resolveUri(null, libraryUri);
+        String libraryUri = 'dart:' + sdkLibrary.shortName;
+        Source librarySource =
+            sdkSourceFactory.resolveUri(unitSource, libraryUri);
         // prepare LibraryElement
         LibraryElement libraryElement =
             context.getLibraryElement(librarySource);
@@ -1236,7 +1177,7 @@
         excluded.add(favorite);
         sb.startPosition("ARG${i}");
         sb.append(favorite);
-        sb.addProposals(suggestions);
+        sb.addSuggestions(LinkedEditSuggestionKind.PARAMETER, suggestions);
         sb.endPosition();
       }
     }
@@ -1344,9 +1285,9 @@
    * Adds a single linked position to [groupId].
    */
   void _addLinkedPosition(String groupId, SourceRange range) {
-    Position position = new Position(file, range.offset, range.length);
-    LinkedPositionGroup group = _getLinkedPosition(groupId);
-    group.addPosition(position);
+    Position position = new Position(file, range.offset);
+    LinkedEditGroup group = _getLinkedPosition(groupId);
+    group.addPosition(position, range.length);
   }
 
   /**
@@ -1628,12 +1569,12 @@
   }
 
   /**
-   * Returns an existing or just added [LinkedPositionGroup] with [groupId].
+   * Returns an existing or just added [LinkedEditGroup] with [groupId].
    */
-  LinkedPositionGroup _getLinkedPosition(String groupId) {
-    LinkedPositionGroup group = linkedPositionGroups[groupId];
+  LinkedEditGroup _getLinkedPosition(String groupId) {
+    LinkedEditGroup group = linkedPositionGroups[groupId];
     if (group == null) {
-      group = new LinkedPositionGroup(groupId);
+      group = new LinkedEditGroup(groupId);
       linkedPositionGroups[groupId] = group;
     }
     return group;
@@ -1778,13 +1719,13 @@
     String text = builder.toString();
     _addInsertEdit(builder.offset, text);
     // add linked positions
-    builder.linkedPositionGroups.forEach((LinkedPositionGroup group) {
-      LinkedPositionGroup fixGroup = _getLinkedPosition(group.id);
+    builder.linkedPositionGroups.forEach((LinkedEditGroup group) {
+      LinkedEditGroup fixGroup = _getLinkedPosition(group.id);
       group.positions.forEach((Position position) {
-        fixGroup.addPosition(position);
+        fixGroup.addPosition(position, group.length);
       });
-      group.proposals.forEach((String proposal) {
-        fixGroup.addProposal(proposal);
+      group.suggestions.forEach((LinkedEditSuggestion suggestion) {
+        fixGroup.addSuggestion(suggestion);
       });
     });
   }
@@ -1874,7 +1815,7 @@
         type.element is ClassElement) {
       alreadyAdded.add(type);
       ClassElement element = type.element as ClassElement;
-      sb.addProposal(element.name);
+      sb.addSuggestion(LinkedEditSuggestionKind.TYPE, element.name);
       _addSuperTypeProposals(sb, alreadyAdded, element.supertype);
       for (InterfaceType interfaceType in element.interfaces) {
         _addSuperTypeProposals(sb, alreadyAdded, interfaceType);
diff --git a/pkg/analysis_services/lib/src/correction/selection_analyzer.dart b/pkg/analysis_services/lib/src/correction/selection_analyzer.dart
new file mode 100644
index 0000000..d6802c4
--- /dev/null
+++ b/pkg/analysis_services/lib/src/correction/selection_analyzer.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This code was auto-generated, is not intended to be edited, and is subject to
+// significant change. Please see the README file for more information.
+
+library services.src.correction.selection_analyzer;
+
+import 'package:analysis_services/src/correction/source_range.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/source.dart';
+
+
+/**
+ * A visitor for visiting [AstNode]s covered by a selection [SourceRange].
+ */
+class SelectionAnalyzer extends GeneralizingAstVisitor<Object> {
+  final SourceRange selection;
+
+  AstNode _coveringNode;
+  List<AstNode> _selectedNodes;
+
+  SelectionAnalyzer(this.selection);
+
+  /**
+   * Return the [AstNode] with the shortest length which completely covers the
+   * specified selection.
+   */
+  AstNode get coveringNode => _coveringNode;
+
+  /**
+   * Returns the first selected [AstNode], may be `null`.
+   */
+  AstNode get firstSelectedNode {
+    if (_selectedNodes == null || _selectedNodes.isEmpty) {
+      return null;
+    }
+    return _selectedNodes[0];
+  }
+
+  /**
+   * Returns `true` if there are [AstNode]s fully covered by the
+   * selection [SourceRange].
+   */
+  bool get hasSelectedNodes =>
+      _selectedNodes != null && !_selectedNodes.isEmpty;
+
+  /**
+   * Returns `true` if there was no selected nodes yet.
+   */
+  bool get isFirstNode => _selectedNodes == null;
+
+  /**
+   * Returns the last selected [AstNode], may be `null`.
+   */
+  AstNode get lastSelectedNode {
+    if (_selectedNodes == null || _selectedNodes.isEmpty) {
+      return null;
+    }
+    return _selectedNodes[_selectedNodes.length - 1];
+  }
+
+  /**
+   * Returns the [SourceRange] which covers selected [AstNode]s, may be `null`
+   * if there are no [AstNode]s under the selection.
+   */
+  SourceRange get selectedNodeRange {
+    if (_selectedNodes == null || _selectedNodes.isEmpty) {
+      return null;
+    }
+    AstNode firstNode = _selectedNodes[0];
+    AstNode lastNode = _selectedNodes[_selectedNodes.length - 1];
+    return rangeStartEnd(firstNode, lastNode);
+  }
+
+  /**
+   * Return the [AstNode]s fully covered by the selection [SourceRange].
+   */
+  List<AstNode> get selectedNodes {
+    if (_selectedNodes == null || _selectedNodes.isEmpty) {
+      return [];
+    }
+    return _selectedNodes;
+  }
+
+  /**
+   * Adds first selected [AstNode].
+   */
+  void handleFirstSelectedNode(AstNode node) {
+    _selectedNodes = [];
+    _selectedNodes.add(node);
+  }
+
+  /**
+   * Adds second or more selected [AstNode].
+   */
+  void handleNextSelectedNode(AstNode node) {
+    if (identical(firstSelectedNode.parent, node.parent)) {
+      _selectedNodes.add(node);
+    }
+  }
+
+  /**
+   * Notifies that selection ends in given [AstNode].
+   */
+  void handleSelectionEndsIn(AstNode node) {
+  }
+
+  /**
+   * Notifies that selection starts in given [AstNode].
+   */
+  void handleSelectionStartsIn(AstNode node) {
+  }
+
+  /**
+   * Resets selected nodes.
+   */
+  void reset() {
+    _selectedNodes = null;
+  }
+
+  @override
+  Object visitNode(AstNode node) {
+    SourceRange nodeRange = rangeNode(node);
+    if (selection.covers(nodeRange)) {
+      if (isFirstNode) {
+        handleFirstSelectedNode(node);
+      } else {
+        handleNextSelectedNode(node);
+      }
+      return null;
+    } else if (selection.coveredBy(nodeRange)) {
+      _coveringNode = node;
+      node.visitChildren(this);
+      return null;
+    } else if (selection.startsIn(nodeRange)) {
+      handleSelectionStartsIn(node);
+      node.visitChildren(this);
+      return null;
+    } else if (selection.endsIn(nodeRange)) {
+      handleSelectionEndsIn(node);
+      node.visitChildren(this);
+      return null;
+    }
+    // no intersection
+    return null;
+  }
+}
diff --git a/pkg/analysis_services/lib/src/correction/source_buffer.dart b/pkg/analysis_services/lib/src/correction/source_buffer.dart
index 48a030d..18a9a90 100644
--- a/pkg/analysis_services/lib/src/correction/source_buffer.dart
+++ b/pkg/analysis_services/lib/src/correction/source_buffer.dart
@@ -19,23 +19,35 @@
   final int offset;
   final StringBuffer _buffer = new StringBuffer();
 
-  final List<LinkedPositionGroup> linkedPositionGroups = <LinkedPositionGroup>[
+  final List<LinkedEditGroup> linkedPositionGroups = <LinkedEditGroup>[
       ];
-  LinkedPositionGroup _currentLinkedPositionGroup;
+  LinkedEditGroup _currentLinkedPositionGroup;
   int _currentPositionStart;
+  int _exitOffset;
 
   SourceBuilder(this.file, this.offset);
 
   SourceBuilder.buffer() : file = null, offset = 0;
 
-  int get length => _buffer.length;
-
-  void addProposal(String proposal) {
-    _currentLinkedPositionGroup.addProposal(proposal);
+  /**
+   * Returns the exit offset, maybe `null` if not set.
+   */
+  int get exitOffset {
+    if (_exitOffset == null) {
+      return null;
+    }
+    return offset + _exitOffset;
   }
 
-  void addProposals(List<String> proposals) {
-    proposals.forEach((proposal) => addProposal(proposal));
+  int get length => _buffer.length;
+
+  void addSuggestion(LinkedEditSuggestionKind kind, String value) {
+    var suggestion = new LinkedEditSuggestion(kind, value);
+    _currentLinkedPositionGroup.addSuggestion(suggestion);
+  }
+
+  void addSuggestions(LinkedEditSuggestionKind kind, List<String> values) {
+    values.forEach((value) => addSuggestion(kind, value));
   }
 
   /**
@@ -56,18 +68,25 @@
   }
 
   /**
+   * Marks the current offset as an "exit" one.
+   */
+  void setExitOffset() {
+    _exitOffset = _buffer.length;
+  }
+
+  /**
    * Marks start of a new linked position for the group with the given ID.
    */
   void startPosition(String groupId) {
     assert(_currentLinkedPositionGroup == null);
-    for (LinkedPositionGroup position in linkedPositionGroups) {
+    for (LinkedEditGroup position in linkedPositionGroups) {
       if (position.id == groupId) {
         _currentLinkedPositionGroup = position;
         break;
       }
     }
     if (_currentLinkedPositionGroup == null) {
-      _currentLinkedPositionGroup = new LinkedPositionGroup(groupId);
+      _currentLinkedPositionGroup = new LinkedEditGroup(groupId);
       linkedPositionGroups.add(_currentLinkedPositionGroup);
     }
     _currentPositionStart = _buffer.length;
@@ -82,7 +101,8 @@
   void _addPosition() {
     int start = offset + _currentPositionStart;
     int end = offset + _buffer.length;
-    Position position = new Position(file, start, end - start);
-    _currentLinkedPositionGroup.addPosition(position);
+    int length = end - start;
+    Position position = new Position(file, start);
+    _currentLinkedPositionGroup.addPosition(position, length);
   }
 }
diff --git a/pkg/analysis_services/lib/src/correction/statement_analyzer.dart b/pkg/analysis_services/lib/src/correction/statement_analyzer.dart
new file mode 100644
index 0000000..7617991
--- /dev/null
+++ b/pkg/analysis_services/lib/src/correction/statement_analyzer.dart
@@ -0,0 +1,252 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This code was auto-generated, is not intended to be edited, and is subject to
+// significant change. Please see the README file for more information.
+
+library services.src.correction.statement_analyzer;
+
+import 'package:analysis_services/src/correction/selection_analyzer.dart';
+import 'package:analysis_services/src/correction/source_range.dart';
+import 'package:analysis_services/src/correction/util.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/scanner.dart';
+import 'package:analyzer/src/generated/source.dart';
+
+
+/**
+ * Returns [Token]s of the given Dart source, not `null`, may be empty if no
+ * tokens or some exception happens.
+ */
+List<Token> _getTokens(String text) {
+  try {
+    List<Token> tokens = <Token>[];
+    Scanner scanner = new Scanner(null, new CharSequenceReader(text), null);
+    Token token = scanner.tokenize();
+    while (token.type != TokenType.EOF) {
+      tokens.add(token);
+      token = token.next;
+    }
+    return tokens;
+  } catch (e) {
+    return new List<Token>(0);
+  }
+}
+
+/**
+ * TODO(scheglov) port the real class
+ */
+class RefactoringStatus {
+  bool get hasFatalError => false;
+
+  void addFatalError(String message, RefactoringStatusContext context) {
+  }
+}
+
+/**
+ * TODO(scheglov) port the real class
+ */
+class RefactoringStatusContext {
+  RefactoringStatusContext.forUnit(CompilationUnit unit, SourceRange range);
+}
+
+
+/**
+ * Analyzer to check if a selection covers a valid set of statements of AST.
+ */
+class StatementAnalyzer extends SelectionAnalyzer {
+  final CompilationUnit unit;
+
+  RefactoringStatus _status = new RefactoringStatus();
+
+  StatementAnalyzer(this.unit, SourceRange selection) : super(selection);
+
+  /**
+   * Returns the [RefactoringStatus] result of selection checking.
+   */
+  RefactoringStatus get status => _status;
+
+  /**
+   * Records fatal error with given message.
+   */
+  void invalidSelection(String message) {
+    invalidSelection2(message, null);
+  }
+
+  /**
+   * Records fatal error with given message and [RefactoringStatusContext].
+   */
+  void invalidSelection2(String message, RefactoringStatusContext context) {
+    _status.addFatalError(message, context);
+    reset();
+  }
+
+  @override
+  Object visitCompilationUnit(CompilationUnit node) {
+    super.visitCompilationUnit(node);
+    if (!hasSelectedNodes) {
+      return null;
+    }
+    // check that selection does not begin/end in comment
+    {
+      int selectionStart = selection.offset;
+      int selectionEnd = selection.end;
+      List<SourceRange> commentRanges = getCommentRanges(unit);
+      for (SourceRange commentRange in commentRanges) {
+        if (commentRange.contains(selectionStart)) {
+          invalidSelection("Selection begins inside a comment.");
+        }
+        if (commentRange.containsExclusive(selectionEnd)) {
+          invalidSelection("Selection ends inside a comment.");
+        }
+      }
+    }
+    // more checks
+    if (!_status.hasFatalError) {
+      _checkSelectedNodes(node);
+    }
+    return null;
+  }
+
+  @override
+  Object visitDoStatement(DoStatement node) {
+    super.visitDoStatement(node);
+    List<AstNode> selectedNodes = this.selectedNodes;
+    if (_contains(selectedNodes, node.body)) {
+      invalidSelection(
+          "Operation not applicable to a 'do' statement's body and expression.");
+    }
+    return null;
+  }
+
+  @override
+  Object visitForStatement(ForStatement node) {
+    super.visitForStatement(node);
+    List<AstNode> selectedNodes = this.selectedNodes;
+    bool containsInit =
+        _contains(selectedNodes, node.initialization) ||
+        _contains(selectedNodes, node.variables);
+    bool containsCondition = _contains(selectedNodes, node.condition);
+    bool containsUpdaters = _containsAny(selectedNodes, node.updaters);
+    bool containsBody = _contains(selectedNodes, node.body);
+    if (containsInit && containsCondition) {
+      invalidSelection(
+          "Operation not applicable to a 'for' statement's initializer and condition.");
+    } else if (containsCondition && containsUpdaters) {
+      invalidSelection(
+          "Operation not applicable to a 'for' statement's condition and updaters.");
+    } else if (containsUpdaters && containsBody) {
+      invalidSelection(
+          "Operation not applicable to a 'for' statement's updaters and body.");
+    }
+    return null;
+  }
+
+  @override
+  Object visitSwitchStatement(SwitchStatement node) {
+    super.visitSwitchStatement(node);
+    List<AstNode> selectedNodes = this.selectedNodes;
+    List<SwitchMember> switchMembers = node.members;
+    for (AstNode selectedNode in selectedNodes) {
+      if (switchMembers.contains(selectedNode)) {
+        invalidSelection(
+            "Selection must either cover whole switch statement or parts of a single case block.");
+        break;
+      }
+    }
+    return null;
+  }
+
+  @override
+  Object visitTryStatement(TryStatement node) {
+    super.visitTryStatement(node);
+    AstNode firstSelectedNode = this.firstSelectedNode;
+    if (firstSelectedNode != null) {
+      if (identical(firstSelectedNode, node.body) ||
+          identical(firstSelectedNode, node.finallyBlock)) {
+        invalidSelection(
+            "Selection must either cover whole try statement or parts of try, catch, or finally block.");
+      } else {
+        List<CatchClause> catchClauses = node.catchClauses;
+        for (CatchClause catchClause in catchClauses) {
+          if (identical(firstSelectedNode, catchClause) ||
+              identical(firstSelectedNode, catchClause.body) ||
+              identical(firstSelectedNode, catchClause.exceptionParameter)) {
+            invalidSelection(
+                "Selection must either cover whole try statement or parts of try, catch, or finally block.");
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+  @override
+  Object visitWhileStatement(WhileStatement node) {
+    super.visitWhileStatement(node);
+    List<AstNode> selectedNodes = this.selectedNodes;
+    if (_contains(selectedNodes, node.condition) &&
+        _contains(selectedNodes, node.body)) {
+      invalidSelection(
+          "Operation not applicable to a while statement's expression and body.");
+    }
+    return null;
+  }
+
+  /**
+   * Checks final selected [AstNode]s after processing [CompilationUnit].
+   */
+  void _checkSelectedNodes(CompilationUnit unit) {
+    List<AstNode> nodes = selectedNodes;
+    // some tokens before first selected node
+    {
+      AstNode firstNode = nodes[0];
+      SourceRange rangeBeforeFirstNode = rangeStartStart(selection, firstNode);
+      if (_hasTokens(rangeBeforeFirstNode)) {
+        invalidSelection2(
+            "The beginning of the selection contains characters that do not belong to a statement.",
+            new RefactoringStatusContext.forUnit(unit, rangeBeforeFirstNode));
+      }
+    }
+    // some tokens after last selected node
+    {
+      AstNode lastNode = nodes.last;
+      SourceRange rangeAfterLastNode = rangeEndEnd(lastNode, selection);
+      if (_hasTokens(rangeAfterLastNode)) {
+        invalidSelection2(
+            "The end of the selection contains characters that do not belong to a statement.",
+            new RefactoringStatusContext.forUnit(unit, rangeAfterLastNode));
+      }
+    }
+  }
+
+  /**
+   * Returns `true` if there are [Token]s in the given [SourceRange].
+   */
+  bool _hasTokens(SourceRange range) {
+    CompilationUnitElement unitElement = unit.element;
+    String fullText = unitElement.context.getContents(unitElement.source).data;
+    String rangeText = fullText.substring(range.offset, range.end);
+    return _getTokens(rangeText).isNotEmpty;
+  }
+
+  /**
+   * Returns `true` if [nodes] contains [node].
+   */
+  static bool _contains(List<AstNode> nodes, AstNode node) =>
+      nodes.contains(node);
+
+  /**
+   * Returns `true` if [nodes] contains one of the [otherNodes].
+   */
+  static bool _containsAny(List<AstNode> nodes, List<AstNode> otherNodes) {
+    for (AstNode otherNode in otherNodes) {
+      if (nodes.contains(otherNode)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/pkg/analysis_services/lib/src/correction/util.dart b/pkg/analysis_services/lib/src/correction/util.dart
index 51b3b52..0b4e74f 100644
--- a/pkg/analysis_services/lib/src/correction/util.dart
+++ b/pkg/analysis_services/lib/src/correction/util.dart
@@ -7,15 +7,37 @@
 
 library services.src.correction.util;
 
+import 'package:analysis_services/correction/change.dart';
 import 'package:analysis_services/src/correction/source_range.dart';
 import 'package:analysis_services/src/correction/strings.dart';
 import 'package:analyzer/src/generated/ast.dart';
 import 'package:analyzer/src/generated/element.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/generated/scanner.dart';
 import 'package:analyzer/src/generated/source.dart';
 
 
+/**
+ * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComments].
+ *
+ * Returns [SourceRange]s of all comments in [unit].
+ */
+List<SourceRange> getCommentRanges(CompilationUnit unit) {
+  List<SourceRange> ranges = <SourceRange>[];
+  Token token = unit.beginToken;
+  while (token != null && token.type != TokenType.EOF) {
+    Token commentToken = token.precedingComments;
+    while (commentToken != null) {
+      ranges.add(rangeToken(commentToken));
+      commentToken = commentToken.next;
+    }
+    token = token.next;
+  }
+  return ranges;
+}
+
+
 String getDefaultValueCode(DartType type) {
   if (type != null) {
     String typeName = type.displayName;
@@ -76,7 +98,6 @@
   return namespace.definedNames;
 }
 
-
 /**
  * Returns an [Element] exported from the given [LibraryElement].
  */
@@ -87,6 +108,7 @@
   return getExportNamespaceForLibrary(library)[name];
 }
 
+
 /**
  * Returns [getExpressionPrecedence] for the parent of [node],
  * or `0` if the parent node is [ParenthesizedExpression].
@@ -101,7 +123,6 @@
   return getExpressionPrecedence(parent);
 }
 
-
 /**
  * Returns the precedence of [node] it is an [Expression], negative otherwise.
  */
@@ -143,12 +164,41 @@
 }
 
 /**
+ * Returns the given [Statement] if not a [Block], or the first child
+ * [Statement] if a [Block], or `null` if more than one child.
+ */
+Statement getSingleStatement(Statement statement) {
+  if (statement is Block) {
+    List<Statement> blockStatements = statement.statements;
+    if (blockStatements.length != 1) {
+      return null;
+    }
+    return blockStatements[0];
+  }
+  return statement;
+}
+
+
+/**
  * Returns the [String] content of the given [Source].
  */
 String getSourceContent(AnalysisContext context, Source source) {
   return context.getContents(source).data;
 }
 
+
+/**
+ * Returns the given [Statement] if not a [Block], or all the children
+ * [Statement]s if a [Block].
+ */
+List<Statement> getStatements(Statement statement) {
+  if (statement is Block) {
+    return statement.statements;
+  }
+  return [statement];
+}
+
+
 class CorrectionUtils {
   final CompilationUnit unit;
 
@@ -177,6 +227,16 @@
   }
 
   /**
+   * Returns an [Edit] that changes indentation of the source of the given
+   * [SourceRange] from [oldIndent] to [newIndent], keeping indentation of lines
+   * relative to each other.
+   */
+  Edit createIndentEdit(SourceRange range, String oldIndent, String newIndent) {
+    String newSource = replaceSourceRangeIndent(range, oldIndent, newIndent);
+    return new Edit(range.offset, range.length, newSource);
+  }
+
+  /**
    * Returns the actual type source of the given [Expression], may be `null`
    * if can not be resolved, should be treated as the `dynamic` type.
    */
@@ -263,7 +323,7 @@
     String source = _buffer;
     // skip hash-bang
     if (offset < source.length - 2) {
-      String linePrefix = getText2(offset, 2);
+      String linePrefix = getText(offset, 2);
       if (linePrefix == "#!") {
         insertEmptyLineBefore = true;
         offset = getLineNext(offset);
@@ -286,7 +346,7 @@
     }
     // skip line comments
     while (offset < source.length - 2) {
-      String linePrefix = getText2(offset, 2);
+      String linePrefix = getText(offset, 2);
       if (linePrefix == "//") {
         insertEmptyLineBefore = true;
         offset = getLineNext(offset);
@@ -400,7 +460,7 @@
       }
       lineNonWhitespace++;
     }
-    return getText2(lineStart, lineNonWhitespace - lineStart);
+    return getText(lineStart, lineNonWhitespace - lineStart);
   }
 
   /**
@@ -433,6 +493,14 @@
   }
 
   /**
+   * Returns a [SourceRange] that covers all the given [Statement]s.
+   */
+  SourceRange getLinesRangeStatements(List<Statement> statements) {
+    SourceRange range = rangeNodes(statements);
+    return getLinesRange(range);
+  }
+
+  /**
    * Returns the line prefix consisting of spaces and tabs on the left from the given
    *         [AstNode].
    */
@@ -447,6 +515,13 @@
   }
 
   /**
+   * Returns the text of the given [AstNode] in the unit.
+   */
+  String getNodeText(AstNode node) {
+    return getText(node.offset, node.length);
+  }
+
+  /**
    * @return the source for the parameter with the given type and name.
    */
   String getParameterSource(DartType type, String name) {
@@ -494,30 +569,20 @@
   }
 
   /**
-   * Returns the text of the given [AstNode] in the unit.
+   * Returns the text of the given range in the unit.
    */
-  String getText(AstNode node) {
-    // TODO(scheglov) rename
-    return getText2(node.offset, node.length);
+  String getRangeText(SourceRange range) {
+    return getText(range.offset, range.length);
   }
 
   /**
    * Returns the text of the given range in the unit.
    */
-  String getText2(int offset, int length) {
-    // TODO(scheglov) rename
+  String getText(int offset, int length) {
     return _buffer.substring(offset, offset + length);
   }
 
   /**
-   * Returns the text of the given range in the unit.
-   */
-  String getText3(SourceRange range) {
-    // TODO(scheglov) rename
-    return getText2(range.offset, range.length);
-  }
-
-  /**
    * Returns the source to reference [type] in this [CompilationUnit].
    */
   String getTypeSource(DartType type) {
@@ -575,6 +640,99 @@
   }
 
   /**
+   * Indents given source left or right.
+   */
+  String indentSourceLeftRight(String source, bool right) {
+    StringBuffer sb = new StringBuffer();
+    String indent = getIndent(1);
+    String eol = endOfLine;
+    List<String> lines = source.split(eol);
+    for (int i = 0; i < lines.length; i++) {
+      String line = lines[i];
+      // last line, stop if empty
+      if (i == lines.length - 1 && isEmpty(line)) {
+        break;
+      }
+      // update line
+      if (right) {
+        line = "${indent}${line}";
+      } else {
+        line = removeStart(line, indent);
+      }
+      // append line
+      sb.write(line);
+      sb.write(eol);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * @return the source of the inverted condition for the given logical expression.
+   */
+  String invertCondition(Expression expression) =>
+      _invertCondition0(expression)._source;
+
+  /**
+   * Returns the source with indentation changed from [oldIndent] to
+   * [newIndent], keeping indentation of lines relative to each other.
+   */
+  String replaceSourceIndent(String source, String oldIndent, String newIndent) {
+    // prepare STRING token ranges
+    List<SourceRange> lineRanges = [];
+    {
+      var token = unit.beginToken;
+      while (token != null && token.type != TokenType.EOF) {
+        if (token.type == TokenType.STRING) {
+          lineRanges.add(rangeToken(token));
+        }
+        token = token.next;
+      }
+    }
+    // re-indent lines
+    StringBuffer sb = new StringBuffer();
+    String eol = endOfLine;
+    List<String> lines = source.split(eol);
+    int lineOffset = 0;
+    for (int i = 0; i < lines.length; i++) {
+      String line = lines[i];
+      // last line, stop if empty
+      if (i == lines.length - 1 && isEmpty(line)) {
+        break;
+      }
+      // check if "offset" is in one of the String ranges
+      bool inString = false;
+      for (SourceRange lineRange in lineRanges) {
+        if (lineOffset > lineRange.offset && lineOffset < lineRange.end) {
+          inString = true;
+        }
+        if (lineOffset > lineRange.end) {
+          break;
+        }
+      }
+      lineOffset += line.length + eol.length;
+      // update line indent
+      if (!inString) {
+        line = "${newIndent}${removeStart(line, oldIndent)}";
+      }
+      // append line
+      sb.write(line);
+      sb.write(eol);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Returns the source of the given [SourceRange] with indentation changed
+   * from [oldIndent] to [newIndent], keeping indentation of lines relative
+   * to each other.
+   */
+  String replaceSourceRangeIndent(SourceRange range, String oldIndent,
+      String newIndent) {
+    String oldSource = getRangeText(range);
+    return replaceSourceIndent(oldSource, oldIndent, newIndent);
+  }
+
+  /**
    * @return the [ImportElement] used to import given [Element] into [library].
    *         May be `null` if was not imported, i.e. declared in the same library.
    */
@@ -587,6 +745,97 @@
     }
     return null;
   }
+
+  /**
+   * @return the [InvertedCondition] for the given logical expression.
+   */
+  _InvertedCondition _invertCondition0(Expression expression) {
+    if (expression is BooleanLiteral) {
+      BooleanLiteral literal = expression;
+      if (literal.value) {
+        return _InvertedCondition._simple("false");
+      } else {
+        return _InvertedCondition._simple("true");
+      }
+    }
+    if (expression is BinaryExpression) {
+      BinaryExpression binary = expression;
+      TokenType operator = binary.operator.type;
+      Expression le = binary.leftOperand;
+      Expression re = binary.rightOperand;
+      _InvertedCondition ls = _invertCondition0(le);
+      _InvertedCondition rs = _invertCondition0(re);
+      if (operator == TokenType.LT) {
+        return _InvertedCondition._binary2(ls, " >= ", rs);
+      }
+      if (operator == TokenType.GT) {
+        return _InvertedCondition._binary2(ls, " <= ", rs);
+      }
+      if (operator == TokenType.LT_EQ) {
+        return _InvertedCondition._binary2(ls, " > ", rs);
+      }
+      if (operator == TokenType.GT_EQ) {
+        return _InvertedCondition._binary2(ls, " < ", rs);
+      }
+      if (operator == TokenType.EQ_EQ) {
+        return _InvertedCondition._binary2(ls, " != ", rs);
+      }
+      if (operator == TokenType.BANG_EQ) {
+        return _InvertedCondition._binary2(ls, " == ", rs);
+      }
+      if (operator == TokenType.AMPERSAND_AMPERSAND) {
+        return _InvertedCondition._binary(
+            TokenType.BAR_BAR.precedence,
+            ls,
+            " || ",
+            rs);
+      }
+      if (operator == TokenType.BAR_BAR) {
+        return _InvertedCondition._binary(
+            TokenType.AMPERSAND_AMPERSAND.precedence,
+            ls,
+            " && ",
+            rs);
+      }
+    }
+    if (expression is IsExpression) {
+      IsExpression isExpression = expression;
+      String expressionSource = getNodeText(isExpression.expression);
+      String typeSource = getNodeText(isExpression.type);
+      if (isExpression.notOperator == null) {
+        return _InvertedCondition._simple(
+            "${expressionSource} is! ${typeSource}");
+      } else {
+        return _InvertedCondition._simple(
+            "${expressionSource} is ${typeSource}");
+      }
+    }
+    if (expression is PrefixExpression) {
+      PrefixExpression prefixExpression = expression;
+      TokenType operator = prefixExpression.operator.type;
+      if (operator == TokenType.BANG) {
+        Expression operand = prefixExpression.operand;
+        while (operand is ParenthesizedExpression) {
+          ParenthesizedExpression pe = operand as ParenthesizedExpression;
+          operand = pe.expression;
+        }
+        return _InvertedCondition._simple(getNodeText(operand));
+      }
+    }
+    if (expression is ParenthesizedExpression) {
+      ParenthesizedExpression pe = expression;
+      Expression innerExpresion = pe.expression;
+      while (innerExpresion is ParenthesizedExpression) {
+        innerExpresion = (innerExpresion as ParenthesizedExpression).expression;
+      }
+      return _invertCondition0(innerExpresion);
+    }
+    DartType type = expression.bestType;
+    if (type.displayName == "bool") {
+      return _InvertedCondition._simple("!${getNodeText(expression)}");
+    }
+    return _InvertedCondition._simple(getNodeText(expression));
+  }
 }
 
 
@@ -598,3 +847,47 @@
   String prefix = "";
   String suffix = "";
 }
+
+
+/**
+ * A container with a source and its precedence.
+ */
+class _InvertedCondition {
+  final int _precedence;
+
+  final String _source;
+
+  _InvertedCondition(this._precedence, this._source);
+
+  static _InvertedCondition _binary(int precedence, _InvertedCondition left,
+      String operation, _InvertedCondition right) {
+    String src =
+        _parenthesizeIfRequired(left, precedence) +
+        operation +
+        _parenthesizeIfRequired(right, precedence);
+    return new _InvertedCondition(precedence, src);
+  }
+
+  static _InvertedCondition _binary2(_InvertedCondition left, String operation,
+      _InvertedCondition right) {
+    // TODO(scheglov) conside merging with "_binary()" after testing
+    return new _InvertedCondition(
+        1 << 20,
+        "${left._source}${operation}${right._source}");
+  }
+
+  /**
+   * Adds enclosing parenthesis if the precedence of the [_InvertedCondition] if less than the
+   * precedence of the expression we are going it to use in.
+   */
+  static String _parenthesizeIfRequired(_InvertedCondition expr,
+      int newOperatorPrecedence) {
+    if (expr._precedence < newOperatorPrecedence) {
+      return "(${expr._source})";
+    }
+    return expr._source;
+  }
+
+  static _InvertedCondition _simple(String source) =>
+      new _InvertedCondition(2147483647, source);
+}
diff --git a/pkg/analysis_services/lib/src/index/store/codec.dart b/pkg/analysis_services/lib/src/index/store/codec.dart
index a49b9ef..ee4e38e 100644
--- a/pkg/analysis_services/lib/src/index/store/codec.dart
+++ b/pkg/analysis_services/lib/src/index/store/codec.dart
@@ -174,7 +174,6 @@
     int length = components.length;
     String firstComponent = components[0];
     String lastComponent = components[length - 1];
-    firstComponent = firstComponent.substring(1);
     lastComponent = _substringBeforeAt(lastComponent);
     int firstId = _stringCodec.encode(firstComponent);
     int lastId = _stringCodec.encode(lastComponent);
diff --git a/pkg/analysis_services/test/completion/completion_computer_test.dart b/pkg/analysis_services/test/completion/completion_computer_test.dart
index d9b22d6..0956d60 100644
--- a/pkg/analysis_services/test/completion/completion_computer_test.dart
+++ b/pkg/analysis_services/test/completion/completion_computer_test.dart
@@ -4,27 +4,52 @@
 
 library test.services.completion.suggestion;
 
+import 'dart:async';
+
 import 'package:analysis_services/completion/completion_computer.dart';
-import 'package:analysis_services/src/completion/top_level_computer.dart';
-import 'package:analysis_testing/abstract_single_unit.dart';
 import 'package:analysis_testing/reflective_tests.dart';
+import 'package:analyzer/src/generated/source.dart';
 import 'package:unittest/unittest.dart';
 
+import 'completion_test_util.dart';
+
 main() {
   groupSep = ' | ';
-  runReflectiveTests(CompletionComputerTest);
+  runReflectiveTests(CompletionManagerTest);
+  runReflectiveTests(DartCompletionManagerTest);
 }
 
 @ReflectiveTestCase()
-class CompletionComputerTest extends AbstractSingleUnitTest {
+class CompletionManagerTest extends AbstractCompletionTest {
 
-  test_topLevel() {
-    CompletionComputer.create(null).then((computers) {
-      assertContainsType(computers, TopLevelComputer);
-      expect(computers, hasLength(1));
-    });
+  test_dart() {
+    Source source = addSource('/does/not/exist.dart', '');
+    var manager = CompletionManager.create(context, source, 0, null);
+    expect(manager.runtimeType, DartCompletionManager);
   }
 
+  test_html() {
+    Source source = addSource('/does/not/exist.html', '');
+    var manager = CompletionManager.create(context, source, 0, null);
+    expect(manager.runtimeType, NoOpCompletionManager);
+  }
+
+  test_null_context() {
+    Source source = addSource('/does/not/exist.dart', '');
+    var manager = CompletionManager.create(null, source, 0, null);
+    expect(manager.runtimeType, NoOpCompletionManager);
+  }
+
+  test_other() {
+    Source source = addSource('/does/not/exist.foo', '');
+    var manager = CompletionManager.create(context, source, 0, null);
+    expect(manager.runtimeType, NoOpCompletionManager);
+  }
+}
+
+@ReflectiveTestCase()
+class DartCompletionManagerTest extends AbstractCompletionTest {
+
   /// Assert that the list contains exactly one of the given type
   void assertContainsType(List computers, Type type) {
     int count = 0;
@@ -42,4 +67,16 @@
       fail(msg.toString());
     }
   }
+
+  test_topLevel() {
+    Source source = addSource('/does/not/exist.dart', '');
+    var manager = new DartCompletionManager(context, source, 0, searchEngine);
+    bool anyResult;
+    manager.results().forEach((_) {
+      anyResult = true;
+    });
+    return new Future.delayed(Duration.ZERO, () {
+      expect(anyResult, isTrue);
+    });
+  }
 }
diff --git a/pkg/analysis_services/test/completion/completion_test_util.dart b/pkg/analysis_services/test/completion/completion_test_util.dart
index c069050..7420951 100644
--- a/pkg/analysis_services/test/completion/completion_test_util.dart
+++ b/pkg/analysis_services/test/completion/completion_test_util.dart
@@ -12,23 +12,42 @@
 import 'package:analysis_services/index/local_memory_index.dart';
 import 'package:analysis_services/src/search/search_engine.dart';
 import 'package:analysis_testing/abstract_single_unit.dart';
+import 'package:analysis_testing/mock_sdk.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/source.dart';
 import 'package:unittest/unittest.dart';
 
 class AbstractCompletionTest extends AbstractSingleUnitTest {
   Index index;
   SearchEngineImpl searchEngine;
   CompletionComputer computer;
+  int completionOffset;
   List<CompletionSuggestion> suggestions;
 
-  void addTestUnit(String code) {
-    resolveTestUnit(code);
+  void addTestUnit(String content) {
+    expect(completionOffset, isNull, reason: 'Call addTestUnit exactly once');
+    completionOffset = content.indexOf('^');
+    expect(completionOffset, isNot(equals(-1)), reason: 'missing ^');
+    int nextOffset = content.indexOf('^', completionOffset + 1);
+    expect(nextOffset, equals(-1), reason: 'too many ^');
+    content = content.substring(0, completionOffset) +
+        content.substring(completionOffset + 1);
+    resolveTestUnit(content);
     index.indexUnit(context, testUnit);
   }
 
+  void addUnit(String file, String code) {
+    Source source = addSource(file, code);
+    CompilationUnit unit = resolveLibraryUnit(source);
+    assertNoErrorsInSource(source);
+    index.indexUnit(context, unit);
+  }
+
   void assertHasResult(CompletionSuggestionKind kind, String completion,
-      [CompletionRelevance relevance = CompletionRelevance.DEFAULT,
-      bool isDeprecated = false, bool isPotential = false]) {
-    var cs = suggestions.firstWhere((cs) => cs.completion == completion, orElse: () {
+      [CompletionRelevance relevance = CompletionRelevance.DEFAULT, bool isDeprecated
+      = false, bool isPotential = false]) {
+    var cs =
+        suggestions.firstWhere((cs) => cs.completion == completion, orElse: () {
       var completions = suggestions.map((s) => s.completion).toList();
       fail('expected "$completion" but found\n $completions');
     });
@@ -58,5 +77,6 @@
     index = createLocalMemoryIndex();
     searchEngine = new SearchEngineImpl(index);
     verifyNoTestUnitErrors = false;
+    addUnit(MockSdk.LIB_CORE.path, MockSdk.LIB_CORE.content);
   }
-}
\ No newline at end of file
+}
diff --git a/pkg/analysis_services/test/completion/top_level_computer_test.dart b/pkg/analysis_services/test/completion/top_level_computer_test.dart
index b12e07e..da7e34a 100644
--- a/pkg/analysis_services/test/completion/top_level_computer_test.dart
+++ b/pkg/analysis_services/test/completion/top_level_computer_test.dart
@@ -19,17 +19,30 @@
 @ReflectiveTestCase()
 class TopLevelComputerTest extends AbstractCompletionTest {
 
-  test_class() {
-    addTestUnit('class B {boolean v;}');
+  void addTestUnit(String content) {
+    super.addTestUnit(content);
+    computer = new TopLevelComputer(searchEngine, testUnit);
+  }
+
+  test_class_1() {
+    addUnit('/testA.dart', 'var T1; class A {bool x;} var _T2; class _D { }');
+    addUnit('/testB.dart', 'class B {bool y;}');
+    addTestUnit('import "/testA.dart"; class C {bool v;^} class _E { }');
     return compute().then((_) {
-      assertHasResult(CompletionSuggestionKind.CLASS, 'B');
+      assertHasResult(CompletionSuggestionKind.CLASS, 'A');
+      assertHasResult(
+          CompletionSuggestionKind.CLASS,
+          'B',
+          CompletionRelevance.LOW);
+      assertHasResult(CompletionSuggestionKind.CLASS, 'C');
+      assertNoResult('_D');
+      assertHasResult(CompletionSuggestionKind.CLASS, '_E');
+      assertHasResult(CompletionSuggestionKind.CLASS, 'Object');
+      assertHasResult(CompletionSuggestionKind.TOP_LEVEL_VARIABLE, 'T1');
+      assertNoResult('_T2');
+      assertNoResult('x');
+      assertNoResult('y');
       assertNoResult('v');
     });
   }
-
-  @override
-  void setUp() {
-    super.setUp();
-    computer = new TopLevelComputer(searchEngine);
-  }
-}
\ No newline at end of file
+}
diff --git a/pkg/analysis_services/test/correction/assist_test.dart b/pkg/analysis_services/test/correction/assist_test.dart
index 9456225..b5088b9 100644
--- a/pkg/analysis_services/test/correction/assist_test.dart
+++ b/pkg/analysis_services/test/correction/assist_test.dart
@@ -14,6 +14,7 @@
 import 'package:analysis_services/src/search/search_engine.dart';
 import 'package:analysis_testing/abstract_single_unit.dart';
 import 'package:analysis_testing/reflective_tests.dart';
+import 'package:collection/collection.dart';
 import 'package:unittest/unittest.dart';
 
 
@@ -34,7 +35,7 @@
   Assist assist;
   Change change;
   String resultCode;
-  LinkedPositionGroup linkedPositionGroup;
+  LinkedEditGroup linkedPositionGroup;
 
   /**
    * Asserts that there is an [Assist] of the given [kind] at [offset] which
@@ -60,11 +61,12 @@
     assertHasAssist(kind, expected);
   }
 
-  void assertHasPositionGroup(String id, List<Position> expectedPositions) {
-    List<LinkedPositionGroup> linkedPositionGroups =
-        change.linkedPositionGroups;
-    for (LinkedPositionGroup group in linkedPositionGroups) {
+  void assertHasPositionGroup(String id, int expectedLength,
+      List<Position> expectedPositions) {
+    List<LinkedEditGroup> linkedPositionGroups = change.linkedEditGroups;
+    for (LinkedEditGroup group in linkedPositionGroups) {
       if (group.id == id) {
+        expect(group.length, expectedLength);
         expect(group.positions, unorderedEquals(expectedPositions));
         linkedPositionGroup = group;
         return;
@@ -96,8 +98,7 @@
 
   Position expectedPosition(String search) {
     int offset = resultCode.indexOf(search);
-    int length = getLeadingIdentifierLength(search);
-    return new Position(testFile, offset, length);
+    return new Position(testFile, offset);
   }
 
   List<Position> expectedPositions(List<String> patterns) {
@@ -108,6 +109,13 @@
     return positions;
   }
 
+  List<LinkedEditSuggestion> expectedSuggestions(LinkedEditSuggestionKind kind,
+      List<String> values) {
+    return values.map((value) {
+      return new LinkedEditSuggestion(kind, value);
+    }).toList();
+  }
+
   void setUp() {
     super.setUp();
     index = createLocalMemoryIndex();
@@ -305,10 +313,13 @@
 }
 List<int> readBytes() => <int>[];
 ''');
-    assertHasPositionGroup('NAME', expectedPositions(['readBytes = ']));
+    assertHasPositionGroup('NAME', 9, expectedPositions(['readBytes = ']));
     expect(
-        linkedPositionGroup.proposals,
-        unorderedEquals(['list', 'bytes2', 'readBytes']));
+        linkedPositionGroup.suggestions,
+        unorderedEquals(
+            expectedSuggestions(
+                LinkedEditSuggestionKind.VARIABLE,
+                ['list', 'bytes2', 'readBytes'])));
   }
 
   void test_assignToLocalVariable_alreadyAssignment() {
@@ -901,6 +912,513 @@
     assertNoAssistAt('1 + 2 + 3', AssistKind.EXCHANGE_OPERANDS);
   }
 
+  void test_importAddShow_BAD_hasShow() {
+    _indexTestUnit('''
+import 'dart:math' show PI;
+main() {
+  PI;
+}
+''');
+    assertNoAssistAt('import ', AssistKind.IMPORT_ADD_SHOW);
+  }
+
+  void test_importAddShow_BAD_unused() {
+    _indexTestUnit('''
+import 'dart:math';
+''');
+    assertNoAssistAt('import ', AssistKind.IMPORT_ADD_SHOW);
+  }
+
+  void test_importAddShow_OK_onDirective() {
+    _indexTestUnit('''
+import 'dart:math';
+main() {
+  PI;
+  E;
+  max(1, 2);
+}
+''');
+    assertHasAssistAt('import ', AssistKind.IMPORT_ADD_SHOW, '''
+import 'dart:math' show E, PI, max;
+main() {
+  PI;
+  E;
+  max(1, 2);
+}
+''');
+  }
+
+  void test_importAddShow_OK_onUri() {
+    _indexTestUnit('''
+import 'dart:math';
+main() {
+  PI;
+  E;
+  max(1, 2);
+}
+''');
+    assertHasAssistAt('art:math', AssistKind.IMPORT_ADD_SHOW, '''
+import 'dart:math' show E, PI, max;
+main() {
+  PI;
+  E;
+  max(1, 2);
+}
+''');
+  }
+
+  void test_invertIfStatement_blocks() {
+    _indexTestUnit('''
+main() {
+  if (true) {
+    0;
+  } else {
+    1;
+  }
+}
+''');
+    assertHasAssistAt('if (', AssistKind.INVERT_IF_STATEMENT, '''
+main() {
+  if (false) {
+    1;
+  } else {
+    0;
+  }
+}
+''');
+  }
+
+  void test_invertIfStatement_statements() {
+    _indexTestUnit('''
+main() {
+  if (true)
+    0;
+  else
+    1;
+}
+''');
+    assertHasAssistAt('if (', AssistKind.INVERT_IF_STATEMENT, '''
+main() {
+  if (false)
+    1;
+  else
+    0;
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_conditionAndOr() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2 || 3 == 3) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (1 == 1 && (2 == 2 || 3 == 3)) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_conditionInvocation() {
+    _indexTestUnit('''
+main() {
+  if (isCheck()) {
+    if (2 == 2) {
+      print(0);
+    }
+  }
+}
+bool isCheck() => false;
+''');
+    assertHasAssistAt('if (isCheck', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (isCheck() && 2 == 2) {
+    print(0);
+  }
+}
+bool isCheck() => false;
+''');
+  }
+
+  void test_joinIfStatementInner_OK_conditionOrAnd() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1 || 2 == 2) {
+    if (3 == 3) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if ((1 == 1 || 2 == 2) && 3 == 3) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_onCondition() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_simpleConditions_block_block() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_simpleConditions_block_single() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2)
+      print(0);
+  }
+}
+''');
+    assertHasAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_simpleConditions_single_blockMulti() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(1);
+      print(2);
+      print(3);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(1);
+    print(2);
+    print(3);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_OK_simpleConditions_single_blockOne() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1)
+    if (2 == 2) {
+      print(0);
+    }
+}
+''');
+    assertHasAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementInner_wrong_innerNotIf() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    print(0);
+  }
+}
+''');
+    assertNoAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER);
+  }
+
+  void test_joinIfStatementInner_wrong_innerWithElse() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    } else {
+      print(1);
+    }
+  }
+}
+''');
+    assertNoAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER);
+  }
+
+  void test_joinIfStatementInner_wrong_targetNotIf() {
+    _indexTestUnit('''
+main() {
+  print(0);
+}
+''');
+    assertNoAssistAt('print', AssistKind.JOIN_IF_WITH_INNER);
+  }
+
+  void test_joinIfStatementInner_wrong_targetWithElse() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    }
+  } else {
+    print(1);
+  }
+}
+''');
+    assertNoAssistAt('if (1 ==', AssistKind.JOIN_IF_WITH_INNER);
+  }
+
+  void test_joinIfStatementOuter_OK_conditionAndOr() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2 || 3 == 3) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (2 ==', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && (2 == 2 || 3 == 3)) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_conditionInvocation() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (isCheck()) {
+      print(0);
+    }
+  }
+}
+bool isCheck() => false;
+''');
+    assertHasAssistAt('if (isCheck', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && isCheck()) {
+    print(0);
+  }
+}
+bool isCheck() => false;
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_conditionOrAnd() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1 || 2 == 2) {
+    if (3 == 3) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (3 == 3', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if ((1 == 1 || 2 == 2) && 3 == 3) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_onCondition() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_simpleConditions_block_block() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_simpleConditions_block_single() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2)
+      print(0);
+  }
+}
+''');
+    assertHasAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_simpleConditions_single_blockMulti() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(1);
+      print(2);
+      print(3);
+    }
+  }
+}
+''');
+    assertHasAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(1);
+    print(2);
+    print(3);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_OK_simpleConditions_single_blockOne() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1)
+    if (2 == 2) {
+      print(0);
+    }
+}
+''');
+    assertHasAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER, '''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+}
+''');
+  }
+
+  void test_joinIfStatementOuter_wrong_outerNotIf() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    print(0);
+  }
+}
+''');
+    assertNoAssistAt('if (1 == 1', AssistKind.JOIN_IF_WITH_OUTER);
+  }
+
+  void test_joinIfStatementOuter_wrong_outerWithElse() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    }
+  } else {
+    print(1);
+  }
+}
+''');
+    assertNoAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER);
+  }
+
+  void test_joinIfStatementOuter_wrong_targetNotIf() {
+    _indexTestUnit('''
+main() {
+  print(0);
+}
+''');
+    assertNoAssistAt('print', AssistKind.JOIN_IF_WITH_OUTER);
+  }
+
+  void test_joinIfStatementOuter_wrong_targetWithElse() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1) {
+    if (2 == 2) {
+      print(0);
+    } else {
+      print(1);
+    }
+  }
+}
+''');
+    assertNoAssistAt('if (2 == 2', AssistKind.JOIN_IF_WITH_OUTER);
+  }
+
   void test_joinVariableDeclaration_onAssignment_OK() {
     _indexTestUnit('''
 main() {
@@ -1207,9 +1725,476 @@
 ''');
   }
 
+  void test_replaceConditionalWithIfElse_wrong_noEnclosingStatement() {
+    _indexTestUnit('''
+var v = true ? 111 : 222;
+''');
+    assertNoAssistAt('? 111', AssistKind.REPLACE_CONDITIONAL_WITH_IF_ELSE);
+  }
+
+  void test_replaceIfElseWithConditional_OK_assignment() {
+    _indexTestUnit('''
+main() {
+  int vvv;
+  if (true) {
+    vvv = 111;
+  } else {
+    vvv = 222;
+  }
+}
+''');
+    assertHasAssistAt(
+        'if (true)',
+        AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL,
+        '''
+main() {
+  int vvv;
+  vvv = true ? 111 : 222;
+}
+''');
+  }
+
+  void test_replaceIfElseWithConditional_OK_return() {
+    _indexTestUnit('''
+main() {
+  if (true) {
+    return 111;
+  } else {
+    return 222;
+  }
+}
+''');
+    assertHasAssistAt(
+        'if (true)',
+        AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL,
+        '''
+main() {
+  return true ? 111 : 222;
+}
+''');
+  }
+
+  void test_replaceIfElseWithConditional_wrong_notIfStatement() {
+    _indexTestUnit('''
+main() {
+  print(0);
+}
+''');
+    assertNoAssistAt('print', AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL);
+  }
+
+  void test_replaceIfElseWithConditional_wrong_notSingleStatememt() {
+    _indexTestUnit('''
+main() {
+  int vvv;
+  if (true) {
+    print(0);
+    vvv = 111;
+  } else {
+    print(0);
+    vvv = 222;
+  }
+}
+''');
+    assertNoAssistAt('if (true)', AssistKind.REPLACE_IF_ELSE_WITH_CONDITIONAL);
+  }
+
+  void test_splitAndCondition_OK_innerAndExpression() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1 && 2 == 2 && 3 == 3) {
+    print(0);
+  }
+}
+''');
+    assertHasAssistAt('&& 2 == 2', AssistKind.SPLIT_AND_CONDITION, '''
+main() {
+  if (1 == 1) {
+    if (2 == 2 && 3 == 3) {
+      print(0);
+    }
+  }
+}
+''');
+  }
+
+  void test_splitAndCondition_OK_thenBlock() {
+    _indexTestUnit('''
+main() {
+  if (true && false) {
+    print(0);
+    if (3 == 3) {
+      print(1);
+    }
+  }
+}
+''');
+    assertHasAssistAt('&& false', AssistKind.SPLIT_AND_CONDITION, '''
+main() {
+  if (true) {
+    if (false) {
+      print(0);
+      if (3 == 3) {
+        print(1);
+      }
+    }
+  }
+}
+''');
+  }
+
+  void test_splitAndCondition_OK_thenBlock_elseBlock() {
+    _indexTestUnit('''
+main() {
+  if (true && false) {
+    print(0);
+  } else {
+    print(1);
+    if (2 == 2) {
+      print(2);
+    }
+  }
+}
+''');
+    assertHasAssistAt('&& false', AssistKind.SPLIT_AND_CONDITION, '''
+main() {
+  if (true) {
+    if (false) {
+      print(0);
+    } else {
+      print(1);
+      if (2 == 2) {
+        print(2);
+      }
+    }
+  }
+}
+''');
+  }
+
+  void test_splitAndCondition_OK_thenStatement() {
+    _indexTestUnit('''
+main() {
+  if (true && false)
+    print(0);
+}
+''');
+    assertHasAssistAt('&& false', AssistKind.SPLIT_AND_CONDITION, '''
+main() {
+  if (true)
+    if (false)
+      print(0);
+}
+''');
+  }
+
+  void test_splitAndCondition_OK_thenStatement_elseStatement() {
+    _indexTestUnit('''
+main() {
+  if (true && false)
+    print(0);
+  else
+    print(1);
+}
+''');
+    assertHasAssistAt('&& false', AssistKind.SPLIT_AND_CONDITION, '''
+main() {
+  if (true)
+    if (false)
+      print(0);
+    else
+      print(1);
+}
+''');
+  }
+
+  void test_splitAndCondition_wrong() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1 && 2 == 2) {
+    print(0);
+  }
+  print(3 == 3 && 4 == 4);
+}
+''');
+    // not binary expression
+    assertNoAssistAt('main() {', AssistKind.SPLIT_AND_CONDITION);
+    // selection is not empty and includes more than just operator
+    {
+      length = 5;
+      assertNoAssistAt('&& 2 == 2', AssistKind.SPLIT_AND_CONDITION);
+    }
+  }
+
+  void test_splitAndCondition_wrong_notAnd() {
+    _indexTestUnit('''
+main() {
+  if (1 == 1 || 2 == 2) {
+    print(0);
+  }
+}
+''');
+    assertNoAssistAt('|| 2', AssistKind.SPLIT_AND_CONDITION);
+  }
+
+  void test_splitAndCondition_wrong_notPartOfIf() {
+    _indexTestUnit('''
+main() {
+  print(1 == 1 && 2 == 2);
+}
+''');
+    assertNoAssistAt('&& 2', AssistKind.SPLIT_AND_CONDITION);
+  }
+
+  void test_splitAndCondition_wrong_notTopLevelAnd() {
+    _indexTestUnit('''
+main() {
+  if (true || (1 == 1 && 2 == 2)) {
+    print(0);
+  }
+  if (true && (3 == 3 && 4 == 4)) {
+    print(0);
+  }
+}
+''');
+    assertNoAssistAt('&& 2', AssistKind.SPLIT_AND_CONDITION);
+    assertNoAssistAt('&& 4', AssistKind.SPLIT_AND_CONDITION);
+  }
+
+  void test_splitVariableDeclaration_OK_onName() {
+    _indexTestUnit('''
+main() {
+  var v = 1;
+}
+''');
+    assertHasAssistAt('v =', AssistKind.SPLIT_VARIABLE_DECLARATION, '''
+main() {
+  var v;
+  v = 1;
+}
+''');
+  }
+
+  void test_splitVariableDeclaration_OK_onType() {
+    _indexTestUnit('''
+main() {
+  int v = 1;
+}
+''');
+    assertHasAssistAt('int ', AssistKind.SPLIT_VARIABLE_DECLARATION, '''
+main() {
+  int v;
+  v = 1;
+}
+''');
+  }
+
+  void test_splitVariableDeclaration_OK_onVar() {
+    _indexTestUnit('''
+main() {
+  var v = 1;
+}
+''');
+    assertHasAssistAt('var ', AssistKind.SPLIT_VARIABLE_DECLARATION, '''
+main() {
+  var v;
+  v = 1;
+}
+''');
+  }
+
+  void test_splitVariableDeclaration_wrong_notOneVariable() {
+    _indexTestUnit('''
+main() {
+  var v = 1, v2;
+}
+''');
+    assertNoAssistAt('v = 1', AssistKind.SPLIT_VARIABLE_DECLARATION);
+  }
+
+  void test_surroundWith_block() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_BLOCK, '''
+main() {
+// start
+  {
+    print(0);
+    print(1);
+  }
+// end
+}
+''');
+  }
+
+  void test_surroundWith_doWhile() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_DO_WHILE, '''
+main() {
+// start
+  do {
+    print(0);
+    print(1);
+  } while (condition);
+// end
+}
+''');
+  }
+
+  void test_surroundWith_for() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_FOR, '''
+main() {
+// start
+  for (var v = init; condition; increment) {
+    print(0);
+    print(1);
+  }
+// end
+}
+''');
+  }
+
+  void test_surroundWith_forIn() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_FOR_IN, '''
+main() {
+// start
+  for (var item in iterable) {
+    print(0);
+    print(1);
+  }
+// end
+}
+''');
+  }
+
+  void test_surroundWith_if() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_IF, '''
+main() {
+// start
+  if (condition) {
+    print(0);
+    print(1);
+  }
+// end
+}
+''');
+  }
+
+  void test_surroundWith_tryCatch() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_TRY_CATCH, '''
+main() {
+// start
+  try {
+    print(0);
+    print(1);
+  } on Exception catch (e) {
+    // TODO
+  }
+// end
+}
+''');
+  }
+
+  void test_surroundWith_tryFinally() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_TRY_FINALLY, '''
+main() {
+// start
+  try {
+    print(0);
+    print(1);
+  } finally {
+    // TODO
+  }
+// end
+}
+''');
+  }
+
+  void test_surroundWith_while() {
+    _indexTestUnit('''
+main() {
+// start
+  print(0);
+  print(1);
+// end
+}
+''');
+    _setStartEndSelection();
+    assertHasAssist(AssistKind.SURROUND_WITH_WHILE, '''
+main() {
+// start
+  while (condition) {
+    print(0);
+    print(1);
+  }
+// end
+}
+''');
+  }
+
   String _applyEdits(String code, List<Edit> edits) {
-    edits.sort((a, b) => b.offset - a.offset);
-    edits.forEach((Edit edit) {
+    mergeSort(edits, compare: (a, b) => a.offset - b.offset);
+    edits.reversed.forEach((Edit edit) {
       code = code.substring(0, edit.offset) +
           edit.replacement +
           code.substring(edit.end);
@@ -1233,8 +2218,8 @@
 
   void _assertHasLinkedPositions(String groupId, List<String> expectedStrings) {
     List<Position> expectedPositions = _findResultPositions(expectedStrings);
-    List<LinkedPositionGroup> groups = change.linkedPositionGroups;
-    for (LinkedPositionGroup group in groups) {
+    List<LinkedEditGroup> groups = change.linkedEditGroups;
+    for (LinkedEditGroup group in groups) {
       if (group.id == groupId) {
         List<Position> actualPositions = group.positions;
         expect(actualPositions, unorderedEquals(expectedPositions));
@@ -1245,10 +2230,10 @@
   }
 
   void _assertHasLinkedProposals(String groupId, List<String> expected) {
-    List<LinkedPositionGroup> groups = change.linkedPositionGroups;
-    for (LinkedPositionGroup group in groups) {
+    List<LinkedEditGroup> groups = change.linkedEditGroups;
+    for (LinkedEditGroup group in groups) {
       if (group.id == groupId) {
-        expect(group.proposals, expected);
+        expect(group.suggestions, expected);
         return;
       }
     }
@@ -1259,8 +2244,7 @@
     List<Position> positions = <Position>[];
     for (String search in searchStrings) {
       int offset = resultCode.indexOf(search);
-      int length = getLeadingIdentifierLength(search);
-      positions.add(new Position(testFile, offset, length));
+      positions.add(new Position(testFile, offset));
     }
     return positions;
   }
@@ -1269,4 +2253,9 @@
     resolveTestUnit(code);
     index.indexUnit(context, testUnit);
   }
+
+  void _setStartEndSelection() {
+    offset = findOffset('// start\n') + '// start\n'.length;
+    length = findOffset('// end') - offset;
+  }
 }
diff --git a/pkg/analysis_services/test/correction/change_test.dart b/pkg/analysis_services/test/correction/change_test.dart
index 895454b..9982edb 100644
--- a/pkg/analysis_services/test/correction/change_test.dart
+++ b/pkg/analysis_services/test/correction/change_test.dart
@@ -18,129 +18,14 @@
   runReflectiveTests(ChangeTest);
   runReflectiveTests(EditTest);
   runReflectiveTests(FileEditTest);
-  runReflectiveTests(LinkedPositionGroupTest);
+  runReflectiveTests(LinkedEditGroupTest);
+  runReflectiveTests(LinkedEditSuggestionTest);
   runReflectiveTests(PositionTest);
 }
 
 
 @ReflectiveTestCase()
 class ChangeTest {
-  void test_fromJson() {
-    var json = {
-      MESSAGE: 'msg',
-      EDITS: [{
-          FILE: '/a.dart',
-          EDITS: [{
-              OFFSET: 1,
-              LENGTH: 2,
-              REPLACEMENT: 'aaa'
-            }, {
-              OFFSET: 10,
-              LENGTH: 20,
-              REPLACEMENT: 'bbb'
-            }]
-        }, {
-          FILE: '/b.dart',
-          EDITS: [{
-              OFFSET: 21,
-              LENGTH: 22,
-              REPLACEMENT: 'xxx'
-            }, {
-              OFFSET: 210,
-              LENGTH: 220,
-              REPLACEMENT: 'yyy'
-            }]
-        }],
-      LINKED_POSITION_GROUPS: [{
-          ID: 'id-a',
-          POSITIONS: [{
-              FILE: '/ga.dart',
-              OFFSET: 1,
-              LENGTH: 2
-            }, {
-              FILE: '/ga.dart',
-              OFFSET: 10,
-              LENGTH: 2
-            }]
-        }, {
-          ID: 'id-b',
-          POSITIONS: [{
-              FILE: '/gb.dart',
-              OFFSET: 10,
-              LENGTH: 5
-            }, {
-              FILE: '/gb.dart',
-              OFFSET: 100,
-              LENGTH: 5
-            }]
-        }]
-    };
-    Change change = Change.fromJson(json);
-    expect(change.message, 'msg');
-    // edits
-    expect(change.edits, hasLength(2));
-    {
-      FileEdit fileEdit = change.edits[0];
-      expect(fileEdit.file, '/a.dart');
-      expect(fileEdit.edits, hasLength(2));
-      expect(fileEdit.edits[0], new Edit(1, 2, 'aaa'));
-      expect(fileEdit.edits[1], new Edit(10, 20, 'bbb'));
-    }
-    {
-      FileEdit fileEdit = change.edits[1];
-      expect(fileEdit.file, '/b.dart');
-      expect(fileEdit.edits, hasLength(2));
-      expect(fileEdit.edits[0], new Edit(21, 22, 'xxx'));
-      expect(fileEdit.edits[1], new Edit(210, 220, 'yyy'));
-    }
-    // linked position groups
-    expect(change.linkedPositionGroups, hasLength(2));
-    {
-      LinkedPositionGroup group = change.linkedPositionGroups[0];
-      expect(group.id, 'id-a');
-      expect(group.positions, hasLength(2));
-      expect(group.positions[0], new Position('/ga.dart', 1, 2));
-      expect(group.positions[1], new Position('/ga.dart', 10, 2));
-    }
-    {
-      LinkedPositionGroup group = change.linkedPositionGroups[1];
-      expect(group.id, 'id-b');
-      expect(group.positions, hasLength(2));
-      expect(group.positions[0], new Position('/gb.dart', 10, 5));
-      expect(group.positions[1], new Position('/gb.dart', 100, 5));
-    }
-  }
-
-  void test_new() {
-    Change change = new Change('msg');
-    change.add(new FileEdit('/a.dart')
-        ..add(new Edit(1, 2, 'aaa'))
-        ..add(new Edit(10, 20, 'bbb')));
-    change.add(new FileEdit('/b.dart')
-        ..add(new Edit(21, 22, 'xxx'))
-        ..add(new Edit(210, 220, 'yyy')));
-    change.addLinkedPositionGroup(new LinkedPositionGroup('id-a')
-        ..addPosition(new Position('/ga.dart', 1, 2))
-        ..addPosition(new Position('/ga.dart', 10, 2)));
-    change.addLinkedPositionGroup(new LinkedPositionGroup('id-b')
-        ..addPosition(new Position('/gb.dart', 10, 5))
-        ..addPosition(new Position('/gb.dart', 100, 5)));
-    expect(
-        change.toString(),
-        'Change(message=msg, edits=[FileEdit(file=/a.dart, edits=['
-            'Edit(offset=1, length=2, replacement=:>aaa<:), '
-            'Edit(offset=10, length=20, replacement=:>bbb<:)]), '
-            'FileEdit(file=/b.dart, edits=['
-            'Edit(offset=21, length=22, replacement=:>xxx<:), '
-            'Edit(offset=210, length=220, replacement=:>yyy<:)])], '
-            'linkedPositionGroups=[' 'LinkedPositionGroup(id=id-a, positions=['
-            'Position(file=/ga.dart, offset=1, length=2), '
-            'Position(file=/ga.dart, offset=10, length=2)]), '
-            'LinkedPositionGroup(id=id-b, positions=['
-            'Position(file=/gb.dart, offset=10, length=5), '
-            'Position(file=/gb.dart, offset=100, length=5)])])');
-  }
-
   void test_toJson() {
     Change change = new Change('msg');
     change.add(new FileEdit('/a.dart')
@@ -149,62 +34,77 @@
     change.add(new FileEdit('/b.dart')
         ..add(new Edit(21, 22, 'xxx'))
         ..add(new Edit(210, 220, 'yyy')));
-    change.addLinkedPositionGroup(new LinkedPositionGroup('id-a')
-        ..addPosition(new Position('/ga.dart', 1, 2))
-        ..addPosition(new Position('/ga.dart', 10, 2)));
-    change.addLinkedPositionGroup(new LinkedPositionGroup('id-b')
-        ..addPosition(new Position('/gb.dart', 10, 5))
-        ..addPosition(new Position('/gb.dart', 100, 5)));
+    {
+      var group = new LinkedEditGroup('id-a');
+      change.addLinkedEditGroup(group
+          ..addPosition(new Position('/ga.dart', 1), 2)
+          ..addPosition(new Position('/ga.dart', 10), 2));
+      group.addSuggestion(
+          new LinkedEditSuggestion(LinkedEditSuggestionKind.TYPE, 'AA'));
+      group.addSuggestion(
+          new LinkedEditSuggestion(LinkedEditSuggestionKind.TYPE, 'BB'));
+    }
+    change.addLinkedEditGroup(new LinkedEditGroup('id-b')
+        ..addPosition(new Position('/gb.dart', 10), 5)
+        ..addPosition(new Position('/gb.dart', 100), 5));
     var expectedJson = {
-      MESSAGE: 'msg',
-      EDITS: [{
-          FILE: '/a.dart',
-          EDITS: [{
-              OFFSET: 1,
-              LENGTH: 2,
-              REPLACEMENT: 'aaa'
+      'message': 'msg',
+      'edits': [{
+          'file': '/a.dart',
+          'edits': [{
+              'offset': 1,
+              'length': 2,
+              'relacement': 'aaa'
             }, {
-              OFFSET: 10,
-              LENGTH: 20,
-              REPLACEMENT: 'bbb'
+              'offset': 10,
+              'length': 20,
+              'relacement': 'bbb'
             }]
         }, {
-          FILE: '/b.dart',
-          EDITS: [{
-              OFFSET: 21,
-              LENGTH: 22,
-              REPLACEMENT: 'xxx'
+          'file': '/b.dart',
+          'edits': [{
+              'offset': 21,
+              'length': 22,
+              'relacement': 'xxx'
             }, {
-              OFFSET: 210,
-              LENGTH: 220,
-              REPLACEMENT: 'yyy'
+              'offset': 210,
+              'length': 220,
+              'relacement': 'yyy'
             }]
         }],
-      LINKED_POSITION_GROUPS: [{
-          ID: 'id-a',
-          POSITIONS: [{
-              FILE: '/ga.dart',
-              OFFSET: 1,
-              LENGTH: 2
+      'linkedEditGroups': [{
+          'id': 'id-a',
+          'length': 2,
+          'positions': [{
+              'file': '/ga.dart',
+              'offset': 1
             }, {
-              FILE: '/ga.dart',
-              OFFSET: 10,
-              LENGTH: 2
+              'file': '/ga.dart',
+              'offset': 10
+            }],
+          'suggestions': [{
+              'kind': 'TYPE',
+              'value': 'AA'
+            }, {
+              'kind': 'TYPE',
+              'value': 'BB'
             }]
         }, {
-          ID: 'id-b',
-          POSITIONS: [{
-              FILE: '/gb.dart',
-              OFFSET: 10,
-              LENGTH: 5
+          'id': 'id-b',
+          'length': 5,
+          'positions': [{
+              'file': '/gb.dart',
+              'offset': 10
             }, {
-              FILE: '/gb.dart',
-              OFFSET: 100,
-              LENGTH: 5
-            }]
+              'file': '/gb.dart',
+              'offset': 100
+            }],
+          'suggestions': []
         }]
     };
     expect(change.toJson(), expectedJson);
+    // some toString()
+    change.toString();
   }
 }
 
@@ -216,18 +116,6 @@
     expect(edit.end, 3);
   }
 
-  void test_fromJson() {
-    var json = {
-      OFFSET: 1,
-      LENGTH: 2,
-      REPLACEMENT: 'foo'
-    };
-    Edit edit = Edit.fromJson(json);
-    expect(edit.offset, 1);
-    expect(edit.length, 2);
-    expect(edit.replacement, 'foo');
-  }
-
   void test_new() {
     Edit edit = new Edit(1, 2, 'foo');
     expect(edit.offset, 1);
@@ -261,26 +149,6 @@
 
 @ReflectiveTestCase()
 class FileEditTest {
-  void test_fromJson() {
-    var json = {
-      FILE: '/test.dart',
-      EDITS: [{
-          OFFSET: 1,
-          LENGTH: 2,
-          REPLACEMENT: 'aaa'
-        }, {
-          OFFSET: 10,
-          LENGTH: 20,
-          REPLACEMENT: 'bbb'
-        },]
-    };
-    var fileEdit = FileEdit.fromJson(json);
-    expect(fileEdit.file, '/test.dart');
-    expect(fileEdit.edits, hasLength(2));
-    expect(fileEdit.edits[0], new Edit(1, 2, 'aaa'));
-    expect(fileEdit.edits[1], new Edit(10, 20, 'bbb'));
-  }
-
   void test_new() {
     FileEdit fileEdit = new FileEdit('/test.dart');
     fileEdit.add(new Edit(1, 2, 'aaa'));
@@ -296,7 +164,7 @@
     FileEdit fileEdit = new FileEdit('/test.dart');
     fileEdit.add(new Edit(1, 2, 'aaa'));
     fileEdit.add(new Edit(10, 20, 'bbb'));
-    expect(fileEdit.toJson(), {
+    var expectedJson = {
       FILE: '/test.dart',
       EDITS: [{
           OFFSET: 1,
@@ -307,69 +175,67 @@
           LENGTH: 20,
           REPLACEMENT: 'bbb'
         },]
-    });
+    };
+    expect(fileEdit.toJson(), expectedJson);
   }
 }
 
 
 @ReflectiveTestCase()
-class LinkedPositionGroupTest {
-  void test_addWrongLength() {
-    LinkedPositionGroup group = new LinkedPositionGroup('my-id');
-    group.addPosition(new Position('/a.dart', 1, 2));
-    expect(() {
-      group.addPosition(new Position('/b.dart', 10, 20));
-    }, throws);
+class LinkedEditSuggestionTest {
+  void test_eqEq() {
+    var a = new LinkedEditSuggestion(LinkedEditSuggestionKind.METHOD, 'a');
+    var a2 = new LinkedEditSuggestion(LinkedEditSuggestionKind.METHOD, 'a');
+    var b = new LinkedEditSuggestion(LinkedEditSuggestionKind.TYPE, 'a');
+    var c = new LinkedEditSuggestion(LinkedEditSuggestionKind.METHOD, 'c');
+    expect(a == a, isTrue);
+    expect(a == a2, isTrue);
+    expect(a == this, isFalse);
+    expect(a == b, isFalse);
+    expect(a == c, isFalse);
   }
+}
 
-  void test_fromJson() {
-    var json = {
-      ID: 'my-id',
-      POSITIONS: [{
-          FILE: '/a.dart',
-          OFFSET: 1,
-          LENGTH: 2
-        }, {
-          FILE: '/b.dart',
-          OFFSET: 10,
-          LENGTH: 2
-        }]
-    };
-    LinkedPositionGroup group = LinkedPositionGroup.fromJson(json);
-    expect(group.id, 'my-id');
-    expect(group.positions, hasLength(2));
-    expect(group.positions[0], new Position('/a.dart', 1, 2));
-    expect(group.positions[1], new Position('/b.dart', 10, 2));
-  }
 
+@ReflectiveTestCase()
+class LinkedEditGroupTest {
   void test_new() {
-    LinkedPositionGroup group = new LinkedPositionGroup('my-id');
-    group.addPosition(new Position('/a.dart', 1, 2));
-    group.addPosition(new Position('/b.dart', 10, 2));
+    LinkedEditGroup group = new LinkedEditGroup('my-id');
+    group.addPosition(new Position('/a.dart', 1), 2);
+    group.addPosition(new Position('/b.dart', 10), 2);
     expect(
         group.toString(),
-        'LinkedPositionGroup(id=my-id, positions=['
-            'Position(file=/a.dart, offset=1, length=2), '
-            'Position(file=/b.dart, offset=10, length=2)])');
+        'LinkedEditGroup(id=my-id, length=2, positions=['
+            'Position(file=/a.dart, offset=1), '
+            'Position(file=/b.dart, offset=10)], suggestions=[])');
   }
 
   void test_toJson() {
-    LinkedPositionGroup group = new LinkedPositionGroup('my-id');
-    group.addPosition(new Position('/a.dart', 1, 2));
-    group.addPosition(new Position('/b.dart', 10, 2));
-    var expectedJson = {
-      ID: 'my-id',
-      POSITIONS: [{
-          FILE: '/a.dart',
-          OFFSET: 1,
-          LENGTH: 2
+    LinkedEditGroup group = new LinkedEditGroup('my-id');
+    group.addPosition(new Position('/a.dart', 1), 2);
+    group.addPosition(new Position('/b.dart', 10), 2);
+    group.addSuggestion(
+        new LinkedEditSuggestion(LinkedEditSuggestionKind.TYPE, 'AA'));
+    group.addSuggestion(
+        new LinkedEditSuggestion(LinkedEditSuggestionKind.TYPE, 'BB'));
+    expect(group.toJson(), {
+      'id': 'my-id',
+      'length': 2,
+      'positions': [{
+          'file': '/a.dart',
+          'offset': 1
         }, {
-          FILE: '/b.dart',
-          OFFSET: 10,
-          LENGTH: 2
+          'file': '/b.dart',
+          'offset': 10
+        }],
+      'suggestions': [{
+          'kind': 'TYPE',
+          'value': 'AA'
+        }, {
+          'kind': 'TYPE',
+          'value': 'BB'
         }]
-    };
-    expect(group.toJson(), expectedJson);
+    });
   }
 }
 
@@ -377,48 +243,32 @@
 @ReflectiveTestCase()
 class PositionTest {
   void test_eqEq() {
-    Position a = new Position('/a.dart', 1, 2);
-    Position a2 = new Position('/a.dart', 1, 2);
-    Position b = new Position('/b.dart', 1, 2);
+    Position a = new Position('/a.dart', 1);
+    Position a2 = new Position('/a.dart', 1);
+    Position b = new Position('/b.dart', 1);
     expect(a == a, isTrue);
     expect(a == a2, isTrue);
     expect(a == b, isFalse);
     expect(a == this, isFalse);
   }
 
-  void test_fromJson() {
-    var json = {
-      FILE: '/test.dart',
-      OFFSET: 1,
-      LENGTH: 2
-    };
-    Position position = Position.fromJson(json);
-    expect(position.file, '/test.dart');
-    expect(position.offset, 1);
-    expect(position.length, 2);
-  }
-
   void test_hashCode() {
-    Position position = new Position('/test.dart', 1, 2);
+    Position position = new Position('/test.dart', 1);
     position.hashCode;
   }
 
   void test_new() {
-    Position position = new Position('/test.dart', 1, 2);
+    Position position = new Position('/test.dart', 1);
     expect(position.file, '/test.dart');
     expect(position.offset, 1);
-    expect(position.length, 2);
-    expect(
-        position.toString(),
-        'Position(file=/test.dart, offset=1, length=2)');
+    expect(position.toString(), 'Position(file=/test.dart, offset=1)');
   }
 
   void test_toJson() {
-    Position position = new Position('/test.dart', 1, 2);
+    Position position = new Position('/test.dart', 1);
     var expectedJson = {
       FILE: '/test.dart',
-      OFFSET: 1,
-      LENGTH: 2
+      OFFSET: 1
     };
     expect(position.toJson(), expectedJson);
   }
diff --git a/pkg/analysis_services/test/correction/fix_test.dart b/pkg/analysis_services/test/correction/fix_test.dart
index 052051d..cb02d84 100644
--- a/pkg/analysis_services/test/correction/fix_test.dart
+++ b/pkg/analysis_services/test/correction/fix_test.dart
@@ -50,9 +50,8 @@
   }
 
   void assertHasPositionGroup(String id, List<Position> expectedPositions) {
-    List<LinkedPositionGroup> linkedPositionGroups =
-        change.linkedPositionGroups;
-    for (LinkedPositionGroup group in linkedPositionGroups) {
+    List<LinkedEditGroup> linkedPositionGroups = change.linkedEditGroups;
+    for (LinkedEditGroup group in linkedPositionGroups) {
       if (group.id == id) {
         expect(group.positions, unorderedEquals(expectedPositions));
         return;
@@ -92,7 +91,7 @@
   Position expectedPosition(String search) {
     int offset = resultCode.indexOf(search);
     int length = getLeadingIdentifierLength(search);
-    return new Position(testFile, offset, length);
+    return new Position(testFile, offset);
   }
 
   List<Position> expectedPositions(List<String> patterns) {
@@ -563,7 +562,7 @@
     assertHasFix(FixKind.CREATE_MISSING_OVERRIDES, expectedCode);
     // end position should be on "m1", not on "m2", "m3", etc
     {
-      Position endPosition = change.endPosition;
+      Position endPosition = change.selection;
       expect(endPosition, isNotNull);
       expect(endPosition.file, testFile);
       int endOffset = endPosition.offset;
@@ -1608,11 +1607,28 @@
     _assertHasLinkedPositions('ARG1', ['d,']);
     _assertHasLinkedPositions('ARG2', ['s)']);
     // linked proposals
-    _assertHasLinkedProposals('TYPE0', ['int', 'num', 'Object', 'Comparable']);
-    _assertHasLinkedProposals(
+    _assertHasLinkedSuggestions(
+        'TYPE0',
+        expectedSuggestions(
+            LinkedEditSuggestionKind.TYPE,
+            ['int', 'num', 'Object', 'Comparable']));
+    _assertHasLinkedSuggestions(
         'TYPE1',
-        ['double', 'num', 'Object', 'Comparable']);
-    _assertHasLinkedProposals('TYPE2', ['String', 'Object', 'Comparable']);
+        expectedSuggestions(
+            LinkedEditSuggestionKind.TYPE,
+            ['double', 'num', 'Object', 'Comparable']));
+    _assertHasLinkedSuggestions(
+        'TYPE2',
+        expectedSuggestions(
+            LinkedEditSuggestionKind.TYPE,
+            ['String', 'Object', 'Comparable']));
+  }
+
+  List<LinkedEditSuggestion> expectedSuggestions(LinkedEditSuggestionKind kind,
+      List<String> values) {
+    return values.map((value) {
+      return new LinkedEditSuggestion(kind, value);
+    }).toList();
   }
 
   void test_undefinedMethod_createUnqualified_returnType() {
@@ -1811,8 +1827,8 @@
 
   void _assertHasLinkedPositions(String groupId, List<String> expectedStrings) {
     List<Position> expectedPositions = _findResultPositions(expectedStrings);
-    List<LinkedPositionGroup> groups = change.linkedPositionGroups;
-    for (LinkedPositionGroup group in groups) {
+    List<LinkedEditGroup> groups = change.linkedEditGroups;
+    for (LinkedEditGroup group in groups) {
       if (group.id == groupId) {
         List<Position> actualPositions = group.positions;
         expect(actualPositions, unorderedEquals(expectedPositions));
@@ -1822,11 +1838,12 @@
     fail('No group with ID=$groupId foind in\n${groups.join('\n')}');
   }
 
-  void _assertHasLinkedProposals(String groupId, List<String> expected) {
-    List<LinkedPositionGroup> groups = change.linkedPositionGroups;
-    for (LinkedPositionGroup group in groups) {
+  void _assertHasLinkedSuggestions(String groupId,
+      List<LinkedEditSuggestion> expected) {
+    List<LinkedEditGroup> groups = change.linkedEditGroups;
+    for (LinkedEditGroup group in groups) {
       if (group.id == groupId) {
-        expect(group.proposals, expected);
+        expect(group.suggestions, expected);
         return;
       }
     }
@@ -1864,7 +1881,7 @@
     for (String search in searchStrings) {
       int offset = resultCode.indexOf(search);
       int length = getLeadingIdentifierLength(search);
-      positions.add(new Position(testFile, offset, length));
+      positions.add(new Position(testFile, offset));
     }
     return positions;
   }
diff --git a/pkg/analysis_services/test/index/store/codec_test.dart b/pkg/analysis_services/test/index/store/codec_test.dart
index f62b9457..e0b6a03 100644
--- a/pkg/analysis_services/test/index/store/codec_test.dart
+++ b/pkg/analysis_services/test/index/store/codec_test.dart
@@ -147,7 +147,7 @@
     }
     // check strings, "foo" as a single string, no "foo@17" or "bar@35"
     expect(stringCodec.nameToIndex, hasLength(4));
-    expect(stringCodec.nameToIndex, containsPair('f/test.dart', 0));
+    expect(stringCodec.nameToIndex, containsPair('file:///test.dart', 0));
     expect(stringCodec.nameToIndex, containsPair('main', 1));
     expect(stringCodec.nameToIndex, containsPair('foo', 2));
     expect(stringCodec.nameToIndex, containsPair('bar', 3));
@@ -176,7 +176,7 @@
     }
     // check strings, "foo" as a single string, no "foo@21" or "foo@47"
     expect(stringCodec.nameToIndex, hasLength(3));
-    expect(stringCodec.nameToIndex, containsPair('f/test.dart', 0));
+    expect(stringCodec.nameToIndex, containsPair('file:///test.dart', 0));
     expect(stringCodec.nameToIndex, containsPair('main', 1));
     expect(stringCodec.nameToIndex, containsPair('foo', 2));
   }
@@ -193,7 +193,7 @@
     expect(codec.decode(context, id), element);
     // check strings
     expect(stringCodec.nameToIndex, hasLength(3));
-    expect(stringCodec.nameToIndex, containsPair('f/test.dart', 0));
+    expect(stringCodec.nameToIndex, containsPair('file:///test.dart', 0));
     expect(stringCodec.nameToIndex, containsPair('main', 1));
     expect(stringCodec.nameToIndex, containsPair('foo', 2));
   }
diff --git a/pkg/analysis_services/test/search/hierarchy_test.dart b/pkg/analysis_services/test/search/hierarchy_test.dart
index da7d085..aae0157 100644
--- a/pkg/analysis_services/test/search/hierarchy_test.dart
+++ b/pkg/analysis_services/test/search/hierarchy_test.dart
@@ -223,14 +223,14 @@
     {
       ClassElement classA = findElement('A');
       List<Element> members = getMembers(classA);
-      expect(members.map((e) => e.name), unorderedEquals(['ma1', 'ma2']));
+      expect(members.map((e) => e.name), unorderedEquals(['ma1', 'ma2', '==']));
     }
     {
       ClassElement classB = findElement('B');
       List<Element> members = getMembers(classB);
       expect(
           members.map((e) => e.name),
-          unorderedEquals(['mb1', 'mb2', 'ma1', 'ma2']));
+          unorderedEquals(['mb1', 'mb2', 'ma1', 'ma2', '==']));
     }
   }
 
diff --git a/pkg/analysis_testing/lib/abstract_context.dart b/pkg/analysis_testing/lib/abstract_context.dart
index f3805db..340c10f 100644
--- a/pkg/analysis_testing/lib/abstract_context.dart
+++ b/pkg/analysis_testing/lib/abstract_context.dart
@@ -46,10 +46,9 @@
   UriResolver resourceResolver;
   AnalysisContext context;
 
-
   Source addSource(String path, String content) {
     File file = provider.newFile(path, content);
-    Source source = file.createSource(UriKind.FILE_URI);
+    Source source = file.createSource();
     ChangeSet changeSet = new ChangeSet();
     changeSet.addedSource(source);
     context.applyChanges(changeSet);
diff --git a/pkg/analysis_testing/lib/mock_sdk.dart b/pkg/analysis_testing/lib/mock_sdk.dart
index f1e414d..de05fc6 100644
--- a/pkg/analysis_testing/lib/mock_sdk.dart
+++ b/pkg/analysis_testing/lib/mock_sdk.dart
@@ -9,16 +9,18 @@
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:path/path.dart';
 
 
 class MockSdk implements DartSdk {
-  final resource.MemoryResourceProvider provider =
-      new resource.MemoryResourceProvider();
-
   static const _MockSdkLibrary LIB_CORE =
-      const _MockSdkLibrary('dart:core', '/lib/core/core.dart', '''
+      const _MockSdkLibrary('core', '/lib/core/core.dart', '''
 library dart.core;
-class Object {}
+
+class Object {
+  bool operator ==(other) => identical(this, other);
+}
+
 class Function {}
 class StackTrace {}
 class Symbol {}
@@ -62,11 +64,13 @@
 }
 class Map<K, V> extends Object {}
 
+external bool identical(Object a, Object b);
+
 void print(Object object) {}
 ''');
 
   static const _MockSdkLibrary LIB_ASYNC =
-      const _MockSdkLibrary('dart:async', '/lib/async/async.dart', '''
+      const _MockSdkLibrary('async', '/lib/async/async.dart', '''
 library dart.async;
 class Future {
   static Future wait(List<Future> futures) => null;
@@ -76,14 +80,16 @@
 ''');
 
   static const _MockSdkLibrary LIB_MATH =
-      const _MockSdkLibrary('dart:math', '/lib/math/math.dart', '''
+      const _MockSdkLibrary('math', '/lib/math/math.dart', '''
 library dart.math;
 const double E = 2.718281828459045;
 const double PI = 3.1415926535897932;
+num min(num a, num b) => 0;
+num max(num a, num b) => 0;
 ''');
 
   static const _MockSdkLibrary LIB_HTML =
-      const _MockSdkLibrary('dart:html', '/lib/html/dartium/html_dartium.dart', '''
+      const _MockSdkLibrary('html', '/lib/html/dartium/html_dartium.dart', '''
 library dart.html;
 class HtmlElement {}
 ''');
@@ -94,13 +100,15 @@
       LIB_MATH,
       LIB_HTML,];
 
+  final resource.MemoryResourceProvider provider =
+      new resource.MemoryResourceProvider();
+
   MockSdk() {
     LIBRARIES.forEach((_MockSdkLibrary library) {
       provider.newFile(library.path, library.content);
     });
   }
 
-  // Not used
   @override
   AnalysisContext get context => throw unimplemented;
 
@@ -115,17 +123,40 @@
   @override
   List<String> get uris => throw unimplemented;
 
-  // Not used.
   @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    resource.Resource file = provider.getResource(uri.path);
-    if (file is resource.File) {
-      return file.createSource(kind);
+  Source fromFileUri(Uri uri) {
+    String filePath = uri.path;
+    String libPath = '/lib';
+    if (!filePath.startsWith("$libPath/")) {
+      return null;
+    }
+    for (SdkLibrary library in LIBRARIES) {
+      String libraryPath = library.path;
+      if (filePath.replaceAll('\\', '/') == libraryPath) {
+        String path = library.shortName;
+        try {
+          resource.File file = provider.getResource(uri.path);
+          Uri dartUri = new Uri(scheme: 'dart', path: library.shortName);
+          return file.createSource(dartUri);
+        } catch (exception) {
+          return null;
+        }
+      }
+      if (filePath.startsWith("$libraryPath/")) {
+        String pathInLibrary = filePath.substring(libraryPath.length + 1);
+        String path = '${library.shortName}/${pathInLibrary}';
+        try {
+          resource.File file = provider.getResource(uri.path);
+          Uri dartUri = new Uri(scheme: 'dart', path: path);
+          return file.createSource(dartUri);
+        } catch (exception) {
+          return null;
+        }
+      }
     }
     return null;
   }
 
-  // Not used.
   @override
   SdkLibrary getSdkLibrary(String dartUri) {
     // getSdkLibrary() is only used to determine whether a library is internal
@@ -134,7 +165,6 @@
     return null;
   }
 
-  // Not used.
   @override
   Source mapDartUri(String dartUri) {
     const Map<String, String> uriToPath = const {
@@ -147,7 +177,8 @@
     String path = uriToPath[dartUri];
     if (path != null) {
       resource.File file = provider.getResource(path);
-      return file.createSource(UriKind.DART_URI);
+      Uri uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
+      return file.createSource(uri);
     }
 
     // If we reach here then we tried to use a dartUri that's not in the
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
new file mode 100644
index 0000000..8e8cfad
--- /dev/null
+++ b/pkg/analyzer/CHANGELOG.md
@@ -0,0 +1,10 @@
+## 0.22.0
+
+  New API:
+  
+* Source.uri added.
+
+  Breaking changes:
+
+* DartSdk.fromEncoding replaced with "fromFileUri".
+* Source.resolveRelative replaced with "resolveRelativeUri".
diff --git a/pkg/analyzer/lib/file_system/file_system.dart b/pkg/analyzer/lib/file_system/file_system.dart
index d97b595..3cb66fa 100644
--- a/pkg/analyzer/lib/file_system/file_system.dart
+++ b/pkg/analyzer/lib/file_system/file_system.dart
@@ -18,7 +18,7 @@
   /**
    * Create a new [Source] instance that serves this file.
    */
-  Source createSource(UriKind uriKind);
+  Source createSource([Uri uri]);
 }
 
 
@@ -43,6 +43,11 @@
   String canonicalizePath(String path);
 
   /**
+   * Return `true` if absolute [path] references a resource in this folder.
+   */
+  bool contains(String path);
+
+  /**
    * Return an existing child [Resource] with the given [relPath].
    * Return a not existing [File] if no such child exist.
    */
@@ -115,24 +120,13 @@
   ResourceUriResolver(this._provider);
 
   @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    if (kind == UriKind.FILE_URI) {
-      Resource resource = _provider.getResource(uri.path);
-      if (resource is File) {
-        return resource.createSource(kind);
-      }
-    }
-    return null;
-  }
-
-  @override
   Source resolveAbsolute(Uri uri) {
     if (!_isFileUri(uri)) {
       return null;
     }
     Resource resource = _provider.getResource(uri.path);
     if (resource is File) {
-      return resource.createSource(UriKind.FILE_URI);
+      return resource.createSource(uri);
     }
     return null;
   }
diff --git a/pkg/analyzer/lib/file_system/memory_file_system.dart b/pkg/analyzer/lib/file_system/memory_file_system.dart
index 4ca4e61..69e1044 100644
--- a/pkg/analyzer/lib/file_system/memory_file_system.dart
+++ b/pkg/analyzer/lib/file_system/memory_file_system.dart
@@ -160,7 +160,7 @@
   int get _timestamp => _provider._pathToTimestamp[path];
 
   @override
-  Source createSource(UriKind uriKind) {
+  Source createSource([Uri uri]) {
     throw new MemoryResourceException(path, "File '$path' could not be read");
   }
 }
@@ -184,8 +184,11 @@
   int get _timestamp => _provider._pathToTimestamp[path];
 
   @override
-  Source createSource(UriKind uriKind) {
-    return new _MemoryFileSource(this, uriKind);
+  Source createSource([Uri uri]) {
+    if (uri == null) {
+      uri = posix.toUri(path);
+    }
+    return new _MemoryFileSource(this, uri);
   }
 }
 
@@ -196,9 +199,9 @@
 class _MemoryFileSource implements Source {
   final _MemoryFile _file;
 
-  final UriKind uriKind;
+  final Uri uri;
 
-  _MemoryFileSource(this._file, this.uriKind);
+  _MemoryFileSource(this._file, this.uri);
 
   @override
   TimestampedData<String> get contents {
@@ -207,7 +210,7 @@
 
   @override
   String get encoding {
-    return '${new String.fromCharCode(uriKind.encoding)}${_file.path}';
+    return uri.toString();
   }
 
   @override
@@ -226,6 +229,19 @@
   String get shortName => _file.shortName;
 
   @override
+  UriKind get uriKind {
+    String scheme = uri.scheme;
+    if (scheme == PackageUriResolver.PACKAGE_SCHEME) {
+      return UriKind.PACKAGE_URI;
+    } else if (scheme == DartUriResolver.DART_SCHEME) {
+      return UriKind.DART_URI;
+    } else if (scheme == FileUriResolver.FILE_SCHEME) {
+      return UriKind.FILE_URI;
+    }
+    return UriKind.FILE_URI;
+  }
+
+  @override
   bool operator ==(other) {
     if (other is _MemoryFileSource) {
       return other._file == _file;
@@ -237,14 +253,12 @@
   bool exists() => _file.exists;
 
   @override
-  Source resolveRelative(Uri relativeUri) {
-    String relativePath = posix.fromUri(relativeUri);
-    String folderPath = posix.dirname(_file.path);
-    String path = posix.join(folderPath, relativePath);
-    path = posix.normalize(path);
-    _MemoryFile file = new _MemoryFile(_file._provider, path);
-    return new _MemoryFileSource(file, uriKind);
+  Uri resolveRelativeUri(Uri relativeUri) {
+    return uri.resolveUri(relativeUri);
   }
+
+  @override
+  String toString() => _file.toString();
 }
 
 
@@ -279,6 +293,11 @@
   }
 
   @override
+  bool contains(String path) {
+    return posix.isWithin(this.path, path);
+  }
+
+  @override
   Resource getChild(String relPath) {
     String childPath = canonicalizePath(relPath);
     _MemoryResource resource = _provider._pathToResource[childPath];
diff --git a/pkg/analyzer/lib/file_system/physical_file_system.dart b/pkg/analyzer/lib/file_system/physical_file_system.dart
index 010fd0f..4a4298d 100644
--- a/pkg/analyzer/lib/file_system/physical_file_system.dart
+++ b/pkg/analyzer/lib/file_system/physical_file_system.dart
@@ -47,10 +47,13 @@
   _PhysicalFile(io.File file) : super(file);
 
   @override
-  Source createSource(UriKind uriKind) {
+  Source createSource([Uri uri]) {
     io.File file = _entry as io.File;
     JavaFile javaFile = new JavaFile(file.absolute.path);
-    return new FileBasedSource.con2(javaFile, uriKind);
+    if (uri == null) {
+      uri = javaFile.toURI();
+    }
+    return new FileBasedSource.con2(uri, javaFile);
   }
 }
 
@@ -70,6 +73,11 @@
   }
 
   @override
+  bool contains(String path) {
+    return isWithin(this.path, path);
+  }
+
+  @override
   Resource getChild(String relPath) {
     String canonicalPath = canonicalizePath(relPath);
     return PhysicalResourceProvider.INSTANCE.getResource(canonicalPath);
diff --git a/pkg/analyzer/lib/source/package_map_resolver.dart b/pkg/analyzer/lib/source/package_map_resolver.dart
index f083f9f..1f2f101 100644
--- a/pkg/analyzer/lib/source/package_map_resolver.dart
+++ b/pkg/analyzer/lib/source/package_map_resolver.dart
@@ -38,17 +38,6 @@
   PackageMapUriResolver(this.resourceProvider, this.packageMap);
 
   @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    if (kind == UriKind.PACKAGE_URI) {
-      Resource resource = resourceProvider.getResource(uri.path);
-      if (resource is File) {
-        return resource.createSource(kind);
-      }
-    }
-    return null;
-  }
-
-  @override
   Source resolveAbsolute(Uri uri) {
     if (!isPackageUri(uri)) {
       return null;
@@ -73,7 +62,7 @@
         if (packageDir.exists) {
           Resource result = packageDir.getChild(relPath);
           if (result is File && result.exists) {
-            return result.createSource(UriKind.PACKAGE_URI);
+            return result.createSource(uri);
           }
         }
       }
diff --git a/pkg/analyzer/lib/src/analyzer_impl.dart b/pkg/analyzer/lib/src/analyzer_impl.dart
index d6aa8b5..a2b8e13 100644
--- a/pkg/analyzer/lib/src/analyzer_impl.dart
+++ b/pkg/analyzer/lib/src/analyzer_impl.dart
@@ -8,8 +8,6 @@
 
 import 'dart:io';
 
-import 'package:path/path.dart' as pathos;
-
 import 'generated/constant.dart';
 import 'generated/engine.dart';
 import 'generated/element.dart';
@@ -89,8 +87,8 @@
       throw new ArgumentError("sourcePath cannot be null");
     }
     JavaFile sourceFile = new JavaFile(sourcePath);
-    UriKind uriKind = getUriKind(sourceFile);
-    librarySource = new FileBasedSource.con2(sourceFile, uriKind);
+    Uri uri = getUri(sourceFile);
+    librarySource = new FileBasedSource.con2(uri, sourceFile);
 
     // prepare context
     prepareAnalysisContext(sourceFile, librarySource);
@@ -347,26 +345,21 @@
   }
 
   /**
-   * Returns the [UriKind] for the given input file. Usually {@link UriKind#FILE_URI}, but if
-   * the given file is located in the "lib" directory of the [sdk], then returns
-   * {@link UriKind#DART_URI}.
+   * Returns the [Uri] for the given input file.
+   *
+   * Usually it is a `file:` [Uri], but if [file] is located in the `lib`
+   * directory of the [sdk], then returns a `dart:` [Uri].
    */
-  static UriKind getUriKind(JavaFile file) {
+  static Uri getUri(JavaFile file) {
     // may be file in SDK
-    if (sdk is DirectoryBasedDartSdk) {
-      DirectoryBasedDartSdk directoryBasedSdk = sdk;
-      var libraryDirectory = directoryBasedSdk.libraryDirectory.getAbsolutePath();
-      var sdkLibPath = libraryDirectory + pathos.separator;
-      var filePath = file.getPath();
-      if (filePath.startsWith(sdkLibPath)) {
-        var internalPath = pathos.join(libraryDirectory, '_internal') + pathos.separator;
-        if (!filePath.startsWith(internalPath)) {
-          return UriKind.DART_URI;
-        }
+    {
+      Source source = sdk.fromFileUri(file.toURI());
+      if (source != null) {
+        return source.uri;
       }
     }
     // some generic file
-    return UriKind.FILE_URI;
+    return file.toURI();
   }
 }
 
diff --git a/pkg/analyzer/lib/src/generated/element.dart b/pkg/analyzer/lib/src/generated/element.dart
index 166fc82..63d844d 100644
--- a/pkg/analyzer/lib/src/generated/element.dart
+++ b/pkg/analyzer/lib/src/generated/element.dart
@@ -3784,17 +3784,11 @@
     if (otherComponents.length != length) {
       return false;
     }
-    for (int i = length - 1; i >= 2; i--) {
+    for (int i = 0; i < length; i++) {
       if (_components[i] != otherComponents[i]) {
         return false;
       }
     }
-    if (length > 1 && !_equalSourceComponents(_components[1], otherComponents[1])) {
-      return false;
-    }
-    if (length > 0 && !_equalSourceComponents(_components[0], otherComponents[0])) {
-      return false;
-    }
     return true;
   }
 
@@ -3819,13 +3813,7 @@
     int result = 1;
     for (int i = 0; i < _components.length; i++) {
       String component = _components[i];
-      int componentHash;
-      if (i <= 1) {
-        componentHash = _hashSourceComponent(component);
-      } else {
-        componentHash = component.hashCode;
-      }
-      result = 31 * result + componentHash;
+      result = 31 * result + component.hashCode;
     }
     return result;
   }
@@ -3880,45 +3868,6 @@
       builder.appendChar(currentChar);
     }
   }
-
-  /**
-   * Return `true` if the given components, when interpreted to be encoded sources with a
-   * leading source type indicator, are equal when the source type's are ignored.
-   *
-   * @param left the left component being compared
-   * @param right the right component being compared
-   * @return `true` if the given components are equal when the source type's are ignored
-   */
-  bool _equalSourceComponents(String left, String right) {
-    // TODO(brianwilkerson) This method can go away when sources no longer have a URI kind.
-    if (left == null) {
-      return right == null;
-    } else if (right == null) {
-      return false;
-    }
-    int leftLength = left.length;
-    int rightLength = right.length;
-    if (leftLength != rightLength) {
-      return false;
-    } else if (leftLength <= 1 || rightLength <= 1) {
-      return left == right;
-    }
-    return javaStringRegionMatches(left, 1, right, 1, leftLength - 1);
-  }
-
-  /**
-   * Return the hash code of the given encoded source component, ignoring the source type indicator.
-   *
-   * @param sourceComponent the component to compute a hash code
-   * @return the hash code of the given encoded source component
-   */
-  int _hashSourceComponent(String sourceComponent) {
-    // TODO(brianwilkerson) This method can go away when sources no longer have a URI kind.
-    if (sourceComponent.length <= 1) {
-      return sourceComponent.hashCode;
-    }
-    return sourceComponent.substring(1).hashCode;
-  }
 }
 
 /**
@@ -10986,11 +10935,6 @@
     if (this == s) {
       return true;
     }
-    // S is bottom.
-    //
-    if (s.isBottom) {
-      return true;
-    }
     // S is dynamic.
     //
     if (s.isDynamic) {
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index 3641a1e..cfb3916 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -7095,7 +7095,7 @@
           continue;
         }
         try {
-          Source templateSource = _source.resolveRelative(parseUriWithException(templateUri));
+          Source templateSource = _context.sourceFactory.forUri2(_source.resolveRelativeUri(parseUriWithException(templateUri)));
           if (!_context.exists(templateSource)) {
             templateSource = _context.sourceFactory.resolveUri(_source, "package:${templateUri}");
             if (!_context.exists(templateSource)) {
@@ -12659,7 +12659,7 @@
    * @param message an explanation of why the error occurred or what it means
    * @param exception the exception being logged
    */
-  void logError2(String message, Exception exception);
+  void logError2(String message, exception);
 
   /**
    * Log the given informational message.
@@ -12687,7 +12687,7 @@
   }
 
   @override
-  void logError2(String message, Exception exception) {
+  void logError2(String message, exception) {
   }
 
   @override
@@ -12695,7 +12695,7 @@
   }
 
   @override
-  void logInformation2(String message, Exception exception) {
+  void logInformation2(String message, exception) {
   }
 }
 
@@ -15741,4 +15741,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/pkg/analyzer/lib/src/generated/java_io.dart b/pkg/analyzer/lib/src/generated/java_io.dart
index 1e1e445..347463b 100644
--- a/pkg/analyzer/lib/src/generated/java_io.dart
+++ b/pkg/analyzer/lib/src/generated/java_io.dart
@@ -56,7 +56,7 @@
   static final int separatorChar = Platform.pathSeparator.codeUnitAt(0);
   String _path;
   JavaFile(String path) {
-    _path = pathos.absolute(path);
+    _path = path;
   }
   JavaFile.relative(JavaFile base, String child) {
     if (child.isEmpty) {
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 239522b..3f6b4a8 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -18648,6 +18648,9 @@
         _overrideManager.applyOverrides(elseOverrides);
       }
     }
+    // TODO(collinsn): union the [thenOverrides] and [elseOverrides] if both branches
+    // are not abrupt. If both branches are abrupt, then we can mark the
+    // remaining code as dead.
     return null;
   }
 
@@ -19151,7 +19154,16 @@
     // TODO(brianwilkerson) This needs to be significantly improved. Ideally we would eventually
     // turn this into a method on Statement that returns a termination indication (normal, abrupt
     // with no exception, abrupt with an exception).
-    if (statement is ReturnStatement || statement is BreakStatement || statement is ContinueStatement) {
+    //
+    // collinsn: it is unsound to assume that [break] and [continue] are "abrupt".
+    // See: https://code.google.com/p/dart/issues/detail?id=19929#c4 (tests are
+    // included in TypePropagationTest.java).
+    // In general, the difficulty is loopy control flow.
+    //
+    // In the presence of exceptions things become much more complicated, but while
+    // we only use this to propagate at [if]-statement join points, checking for [return]
+    // is probably sound.
+    if (statement is ReturnStatement) {
       return true;
     } else if (statement is ExpressionStatement) {
       return _isAbruptTerminationExpression(statement.expression);
diff --git a/pkg/analyzer/lib/src/generated/sdk.dart b/pkg/analyzer/lib/src/generated/sdk.dart
index 84ccc2d..7f3bc82 100644
--- a/pkg/analyzer/lib/src/generated/sdk.dart
+++ b/pkg/analyzer/lib/src/generated/sdk.dart
@@ -12,6 +12,7 @@
 import 'ast.dart';
 import 'engine.dart' show AnalysisContext;
 
+
 /**
  * Instances of the class `DartSdk` represent a Dart SDK installed in a specified location.
  */
@@ -37,14 +38,14 @@
   static final String DEFAULT_VERSION = "0";
 
   /**
-   * Return the source representing the file with the given URI.
+   * Return a source representing the given file: URI if the file is in this SDK, or `null` if
+   * the file is not in this SDK.
    *
-   * @param kind the kind of URI that was originally resolved in order to produce an encoding with
-   *          the given URI
-   * @param uri the URI of the file to be returned
-   * @return the source representing the specified file
+   * @param uri the file URI for which a source is to be returned
+   * @return the source representing the given URI
+   * @throws
    */
-  Source fromEncoding(UriKind kind, Uri uri);
+  Source fromFileUri(Uri uri);
 
   /**
    * Return the [AnalysisContext] used for all of the sources in this [DartSdk].
diff --git a/pkg/analyzer/lib/src/generated/sdk_io.dart b/pkg/analyzer/lib/src/generated/sdk_io.dart
index c0b353d..58146c6 100644
--- a/pkg/analyzer/lib/src/generated/sdk_io.dart
+++ b/pkg/analyzer/lib/src/generated/sdk_io.dart
@@ -230,7 +230,38 @@
   }
 
   @override
-  Source fromEncoding(UriKind kind, Uri uri) => new FileBasedSource.con2(new JavaFile.fromUri(uri), kind);
+  Source fromFileUri(Uri uri) {
+    JavaFile file = new JavaFile.fromUri(uri);
+    String filePath = file.getAbsolutePath();
+    String libPath = libraryDirectory.getAbsolutePath();
+    if (!filePath.startsWith("${libPath}${JavaFile.separator}")) {
+      return null;
+    }
+    filePath = filePath.substring(libPath.length + 1);
+    for (SdkLibrary library in _libraryMap.sdkLibraries) {
+      String libraryPath = library.path;
+      if (filePath.replaceAll('\\', '/') == libraryPath) {
+        String path = library.shortName;
+        try {
+          return new FileBasedSource.con2(parseUriWithException(path), file);
+        } on URISyntaxException catch (exception) {
+          AnalysisEngine.instance.logger.logInformation2("Failed to create URI: ${path}", exception);
+          return null;
+        }
+      }
+      libraryPath = new JavaFile(libraryPath).getParent();
+      if (filePath.startsWith("${libraryPath}${JavaFile.separator}")) {
+        String path = "${library.shortName}/${filePath.substring(libraryPath.length + 1)}";
+        try {
+          return new FileBasedSource.con2(parseUriWithException(path), file);
+        } on URISyntaxException catch (exception) {
+          AnalysisEngine.instance.logger.logInformation2("Failed to create URI: ${path}", exception);
+          return null;
+        }
+      }
+    }
+    return null;
+  }
 
   @override
   AnalysisContext get context {
@@ -419,11 +450,30 @@
 
   @override
   Source mapDartUri(String dartUri) {
-    SdkLibrary library = getSdkLibrary(dartUri);
+    String libraryName;
+    String relativePath;
+    int index = dartUri.indexOf('/');
+    if (index >= 0) {
+      libraryName = dartUri.substring(0, index);
+      relativePath = dartUri.substring(index + 1);
+    } else {
+      libraryName = dartUri;
+      relativePath = "";
+    }
+    SdkLibrary library = getSdkLibrary(libraryName);
     if (library == null) {
       return null;
     }
-    return new FileBasedSource.con2(new JavaFile.relative(libraryDirectory, library.path), UriKind.DART_URI);
+    try {
+      JavaFile file = new JavaFile.relative(libraryDirectory, library.path);
+      if (!relativePath.isEmpty) {
+        file = file.getParentFile();
+        file = new JavaFile.relative(file, relativePath);
+      }
+      return new FileBasedSource.con2(parseUriWithException(dartUri), file);
+    } on URISyntaxException catch (exception) {
+      return null;
+    }
   }
 
   /**
@@ -531,7 +581,7 @@
    * @param libraryFileContents the contents from the library file
    * @return the library map read from the given source
    */
-  LibraryMap readFromFile(JavaFile file, String libraryFileContents) => readFromSource(new FileBasedSource.con2(file, UriKind.FILE_URI), libraryFileContents);
+  LibraryMap readFromFile(JavaFile file, String libraryFileContents) => readFromSource(new FileBasedSource.con1(file), libraryFileContents);
 
   /**
    * Return the library map read from the given source.
diff --git a/pkg/analyzer/lib/src/generated/source.dart b/pkg/analyzer/lib/src/generated/source.dart
index 0e682bf..b4974ae 100644
--- a/pkg/analyzer/lib/src/generated/source.dart
+++ b/pkg/analyzer/lib/src/generated/source.dart
@@ -10,7 +10,8 @@
 import 'dart:collection';
 import 'java_core.dart';
 import 'sdk.dart' show DartSdk;
-import 'engine.dart' show AnalysisContext, TimestampedData;
+import 'engine.dart';
+import 'java_engine.dart';
 
 /**
  * Instances of class `ContentCache` hold content used to override the default content of a
@@ -99,7 +100,7 @@
   /**
    * The name of the `dart` scheme.
    */
-  static String _DART_SCHEME = "dart";
+  static String DART_SCHEME = "dart";
 
   /**
    * The prefix of a URI using the dart-ext scheme to reference a native code library.
@@ -112,7 +113,7 @@
    * @param uri the URI being tested
    * @return `true` if the given URI is a `dart:` URI
    */
-  static bool isDartUri(Uri uri) => _DART_SCHEME == uri.scheme;
+  static bool isDartUri(Uri uri) => DART_SCHEME == uri.scheme;
 
   /**
    * Initialize a newly created resolver to resolve Dart URI's against the given platform within the
@@ -122,14 +123,6 @@
    */
   DartUriResolver(this._sdk);
 
-  @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    if (kind == UriKind.DART_URI) {
-      return _sdk.fromEncoding(kind, uri);
-    }
-    return null;
-  }
-
   /**
    * Return the [DartSdk] against which URIs are to be resolved.
    *
@@ -299,13 +292,16 @@
   String get shortName => _name;
 
   @override
+  Uri get uri => null;
+
+  @override
   int get hashCode => _name.hashCode;
 
   @override
   bool get isInSystemLibrary => false;
 
   @override
-  Source resolveRelative(Uri relativeUri) {
+  Uri resolveRelativeUri(Uri relativeUri) {
     throw new UnsupportedOperationException("${_name}does not exist.");
   }
 }
@@ -413,6 +409,13 @@
   String get shortName;
 
   /**
+   * Return the URI from which this source was originally derived.
+   *
+   * @return the URI from which this source was originally derived
+   */
+  Uri get uri;
+
+  /**
    * Return the kind of URI from which this source was originally derived. If this source was
    * created from an absolute URI, then the returned kind will reflect the scheme of the absolute
    * URI. If it was created from a relative URI, then the returned kind will be the same as the kind
@@ -439,9 +442,7 @@
   bool get isInSystemLibrary;
 
   /**
-   * Resolve the relative URI against the URI associated with this source object. Return a
-   * [Source] representing the URI to which it was resolved, or `null` if it
-   * could not be resolved.
+   * Resolve the relative URI against the URI associated with this source object.
    *
    * Note: This method is not intended for public use, it is only visible out of necessity. It is
    * only intended to be invoked by a [SourceFactory]. Source factories will
@@ -449,10 +450,11 @@
    * required to, and generally do not, verify the argument. The result of invoking this method with
    * an absolute URI is intentionally left unspecified.
    *
-   * @param relativeUri the relative URI to be resolved against the containing source
-   * @return a [Source] representing the URI to which given URI was resolved
+   * @param relativeUri the relative URI to be resolved against this source
+   * @return the URI to which given URI was resolved
+   * @throws AnalysisException if the relative URI could not be resolved
    */
-  Source resolveRelative(Uri relativeUri);
+  Uri resolveRelativeUri(Uri relativeUri);
 }
 
 /**
@@ -513,7 +515,26 @@
       if (uri.isAbsolute) {
         return _internalResolveUri(null, uri);
       }
-    } on URISyntaxException catch (exception) {
+    } catch (exception) {
+      AnalysisEngine.instance.logger.logError2("Could not resolve URI: ${absoluteUri}", exception);
+    }
+    return null;
+  }
+
+  /**
+   * Return a source object representing the given absolute URI, or `null` if the URI is not
+   * an absolute URI.
+   *
+   * @param absoluteUri the absolute URI to be resolved
+   * @return a source object representing the absolute URI
+   */
+  Source forUri2(Uri absoluteUri) {
+    if (absoluteUri.isAbsolute) {
+      try {
+        return _internalResolveUri(null, absoluteUri);
+      } on AnalysisException catch (exception, stackTrace) {
+        AnalysisEngine.instance.logger.logError2("Could not resolve URI: ${absoluteUri}", new CaughtException(exception, stackTrace));
+      }
     }
     return null;
   }
@@ -527,25 +548,11 @@
    * @see Source#getEncoding()
    */
   Source fromEncoding(String encoding) {
-    if (encoding.length < 2) {
-      throw new IllegalArgumentException("Invalid encoding length");
+    Source source = forUri(encoding);
+    if (source == null) {
+      throw new IllegalArgumentException("Invalid source encoding: ${encoding}");
     }
-    UriKind kind = UriKind.fromEncoding(encoding.codeUnitAt(0));
-    if (kind == null) {
-      throw new IllegalArgumentException("Invalid source kind in encoding: ${kind}");
-    }
-    try {
-      Uri uri = parseUriWithException(encoding.substring(1));
-      for (UriResolver resolver in _resolvers) {
-        Source result = resolver.fromEncoding(kind, uri);
-        if (result != null) {
-          return result;
-        }
-      }
-      throw new IllegalArgumentException("No resolver for kind: ${kind}");
-    } catch (exception) {
-      throw new IllegalArgumentException("Invalid URI in encoding");
-    }
+    return source;
   }
 
   /**
@@ -590,7 +597,8 @@
     try {
       // Force the creation of an escaped URI to deal with spaces, etc.
       return _internalResolveUri(containingSource, parseUriWithException(containedUri));
-    } on URISyntaxException catch (exception) {
+    } catch (exception) {
+      AnalysisEngine.instance.logger.logError2("Could not resolve URI (${containedUri}) relative to source (${containingSource.fullName})", exception);
       return null;
     }
   }
@@ -624,25 +632,28 @@
   /**
    * Return a source object representing the URI that results from resolving the given (possibly
    * relative) contained URI against the URI associated with an existing source object, or
-   * `null` if either the contained URI is invalid or if it cannot be resolved against the
-   * source object's URI.
+   * `null` if the URI could not be resolved.
    *
    * @param containingSource the source containing the given URI
    * @param containedUri the (possibly relative) URI to be resolved against the containing source
    * @return the source representing the contained URI
+   * @throws AnalysisException if either the contained URI is invalid or if it cannot be resolved
+   *           against the source object's URI
    */
   Source _internalResolveUri(Source containingSource, Uri containedUri) {
-    if (containedUri.isAbsolute) {
-      for (UriResolver resolver in _resolvers) {
-        Source result = resolver.resolveAbsolute(containedUri);
-        if (result != null) {
-          return result;
-        }
+    if (!containedUri.isAbsolute) {
+      if (containingSource == null) {
+        throw new AnalysisException("Cannot resolve a relative URI without a containing source: ${containedUri}");
       }
-      return null;
-    } else {
-      return containingSource.resolveRelative(containedUri);
+      containedUri = containingSource.resolveRelativeUri(containedUri);
     }
+    for (UriResolver resolver in _resolvers) {
+      Source result = resolver.resolveAbsolute(containedUri);
+      if (result != null) {
+        return result;
+      }
+    }
+    return null;
   }
 }
 
@@ -888,20 +899,6 @@
  */
 abstract class UriResolver {
   /**
-   * If this resolver should be used for URI's of the given kind, resolve the given absolute URI.
-   * The URI does not need to have the scheme handled by this resolver if the kind matches. Return a
-   * [Source] representing the file to which it was resolved, whether or not the
-   * resulting source exists, or `null` if it could not be resolved because the URI is
-   * invalid.
-   *
-   * @param kind the kind of URI that was originally resolved in order to produce an encoding with
-   *          the given URI
-   * @param uri the URI to be resolved
-   * @return a [Source] representing the file to which given URI was resolved
-   */
-  Source fromEncoding(UriKind kind, Uri uri);
-
-  /**
    * Resolve the given absolute URI. Return a [Source] representing the file to which
    * it was resolved, whether or not the resulting source exists, or `null` if it could not be
    * resolved because the URI is invalid.
diff --git a/pkg/analyzer/lib/src/generated/source_io.dart b/pkg/analyzer/lib/src/generated/source_io.dart
index 08c4677..a5b4347 100644
--- a/pkg/analyzer/lib/src/generated/source_io.dart
+++ b/pkg/analyzer/lib/src/generated/source_io.dart
@@ -13,6 +13,7 @@
 import 'utilities_general.dart';
 import 'instrumentation.dart';
 import 'engine.dart';
+import 'java_engine.dart';
 export 'source.dart';
 
 /**
@@ -84,6 +85,11 @@
  */
 class FileBasedSource implements Source {
   /**
+   * The URI from which this source was originally derived.
+   */
+  final Uri uri;
+
+  /**
    * The file represented by this source.
    */
   final JavaFile file;
@@ -94,25 +100,19 @@
   String _encoding;
 
   /**
-   * The kind of URI from which this source was originally derived.
-   */
-  final UriKind uriKind;
-
-  /**
-   * Initialize a newly created source object. The source object is assumed to not be in a system
-   * library.
+   * Initialize a newly created source object.
    *
    * @param file the file represented by this source
    */
-  FileBasedSource.con1(JavaFile file) : this.con2(file, UriKind.FILE_URI);
+  FileBasedSource.con1(JavaFile file) : this.con2(file.toURI(), file);
 
   /**
    * Initialize a newly created source object.
    *
    * @param file the file represented by this source
-   * @param flags `true` if this source is in one of the system libraries
+   * @param uri the URI from which this source was originally derived
    */
-  FileBasedSource.con2(this.file, this.uriKind);
+  FileBasedSource.con2(this.uri, this.file);
 
   @override
   bool operator ==(Object object) => object != null && object is FileBasedSource && file == object.file;
@@ -133,7 +133,7 @@
   @override
   String get encoding {
     if (_encoding == null) {
-      _encoding = "${new String.fromCharCode(uriKind.encoding)}${file.toURI().toString()}";
+      _encoding = uri.toString();
     }
     return _encoding;
   }
@@ -148,19 +148,45 @@
   String get shortName => file.getName();
 
   @override
+  UriKind get uriKind {
+    String scheme = uri.scheme;
+    if (scheme == PackageUriResolver.PACKAGE_SCHEME) {
+      return UriKind.PACKAGE_URI;
+    } else if (scheme == DartUriResolver.DART_SCHEME) {
+      return UriKind.DART_URI;
+    } else if (scheme == FileUriResolver.FILE_SCHEME) {
+      return UriKind.FILE_URI;
+    }
+    return UriKind.FILE_URI;
+  }
+
+  @override
   int get hashCode => file.hashCode;
 
   @override
-  bool get isInSystemLibrary => uriKind == UriKind.DART_URI;
+  bool get isInSystemLibrary => uri.scheme == DartUriResolver.DART_SCHEME;
 
   @override
-  Source resolveRelative(Uri containedUri) {
+  Uri resolveRelativeUri(Uri containedUri) {
     try {
-      Uri resolvedUri = file.toURI().resolveUri(containedUri);
-      return new FileBasedSource.con2(new JavaFile.fromUri(resolvedUri), uriKind);
-    } catch (exception) {
+      Uri baseUri = uri;
+      bool isOpaque = uri.isAbsolute && !uri.path.startsWith('/');
+      if (isOpaque) {
+        String scheme = uri.scheme;
+        String part = uri.path;
+        if (scheme == DartUriResolver.DART_SCHEME && part.indexOf('/') < 0) {
+          part = "${part}/${part}.dart";
+        }
+        baseUri = parseUriWithException("${scheme}:/${part}");
+      }
+      Uri result = baseUri.resolveUri(containedUri);
+      if (isOpaque) {
+        result = parseUriWithException("${result.scheme}:${result.path.substring(1)}");
+      }
+      return result;
+    } catch (exception, stackTrace) {
+      throw new AnalysisException("Could not resolve URI (${containedUri}) relative to source (${uri})", new CaughtException(exception, stackTrace));
     }
-    return null;
   }
 
   @override
@@ -221,19 +247,11 @@
   static bool isFileUri(Uri uri) => uri.scheme == FILE_SCHEME;
 
   @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    if (kind == UriKind.FILE_URI) {
-      return new FileBasedSource.con2(new JavaFile.fromUri(uri), kind);
-    }
-    return null;
-  }
-
-  @override
   Source resolveAbsolute(Uri uri) {
     if (!isFileUri(uri)) {
       return null;
     }
-    return new FileBasedSource.con1(new JavaFile.fromUri(uri));
+    return new FileBasedSource.con2(uri, new JavaFile.fromUri(uri));
   }
 }
 
@@ -328,14 +346,6 @@
   }
 
   @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    if (kind == UriKind.PACKAGE_URI) {
-      return new FileBasedSource.con2(new JavaFile.fromUri(uri), kind);
-    }
-    return null;
-  }
-
-  @override
   Source resolveAbsolute(Uri uri) {
     if (!isPackageUri(uri)) {
       return null;
@@ -366,11 +376,13 @@
       JavaFile resolvedFile = new JavaFile.relative(packagesDirectory, path);
       if (resolvedFile.exists()) {
         JavaFile canonicalFile = getCanonicalFile(packagesDirectory, pkgName, relPath);
-        UriKind uriKind = _isSelfReference(packagesDirectory, canonicalFile) ? UriKind.FILE_URI : UriKind.PACKAGE_URI;
-        return new FileBasedSource.con2(canonicalFile, uriKind);
+        if (_isSelfReference(packagesDirectory, canonicalFile)) {
+          uri = canonicalFile.toURI();
+        }
+        return new FileBasedSource.con2(uri, canonicalFile);
       }
     }
-    return new FileBasedSource.con2(getCanonicalFile(_packagesDirectories[0], pkgName, relPath), UriKind.PACKAGE_URI);
+    return new FileBasedSource.con2(uri, getCanonicalFile(_packagesDirectories[0], pkgName, relPath));
   }
 
   @override
@@ -469,14 +481,6 @@
   RelativeFileUriResolver(this._rootDirectory, this._relativeDirectories) : super();
 
   @override
-  Source fromEncoding(UriKind kind, Uri uri) {
-    if (kind == UriKind.FILE_URI) {
-      return new FileBasedSource.con2(new JavaFile.fromUri(uri), kind);
-    }
-    return null;
-  }
-
-  @override
   Source resolveAbsolute(Uri uri) {
     String rootPath = _rootDirectory.toURI().path;
     String uriPath = uri.path;
@@ -485,7 +489,7 @@
       for (JavaFile dir in _relativeDirectories) {
         JavaFile file = new JavaFile.relative(dir, filePath);
         if (file.exists()) {
-          return new FileBasedSource.con2(file, UriKind.FILE_URI);
+          return new FileBasedSource.con2(uri, file);
         }
       }
     }
diff --git a/pkg/analyzer/lib/src/string_source.dart b/pkg/analyzer/lib/src/string_source.dart
index 24d55c1..bfeedf0 100644
--- a/pkg/analyzer/lib/src/string_source.dart
+++ b/pkg/analyzer/lib/src/string_source.dart
@@ -40,6 +40,10 @@
 
   bool get isInSystemLibrary => false;
 
-  Source resolveRelative(Uri relativeUri) => throw new UnsupportedError(
+  @override
+  Uri get uri => throw new UnsupportedError(
+      "StringSource doesn't support uri.");
+
+  Uri resolveRelativeUri(Uri relativeUri) => throw new UnsupportedError(
       "StringSource doesn't support resolveRelative.");
 }
diff --git a/pkg/analyzer/pubspec.yaml b/pkg/analyzer/pubspec.yaml
index e2bef86..f53daf2 100644
--- a/pkg/analyzer/pubspec.yaml
+++ b/pkg/analyzer/pubspec.yaml
@@ -1,5 +1,5 @@
 name: analyzer
-version: 0.22.0-dev
+version: 0.22.0
 author: Dart Team <misc@dartlang.org>
 description: Static analyzer for Dart.
 homepage: http://www.dartlang.org
diff --git a/pkg/analyzer/test/file_system/memory_file_system_test.dart b/pkg/analyzer/test/file_system/memory_file_system_test.dart
index 8956b41..a91aa4b 100644
--- a/pkg/analyzer/test/file_system/memory_file_system_test.dart
+++ b/pkg/analyzer/test/file_system/memory_file_system_test.dart
@@ -171,7 +171,7 @@
         provider.newFile(path, 'contents 1');
         Resource file = provider.getResource(path);
         expect(file, new isInstanceOf<File>());
-        Source source = (file as File).createSource(UriKind.FILE_URI);
+        Source source = (file as File).createSource();
         expect(source.contents.data, equals('contents 1'));
         provider.modifyFile(path, 'contents 2');
         expect(source.contents.data, equals('contents 2'));
@@ -303,6 +303,13 @@
         expect(folder == folder2, isFalse);
       });
 
+      test('contains', () {
+        expect(folder.contains('/foo/bar/aaa.txt'), isTrue);
+        expect(folder.contains('/foo/bar/aaa/bbb.txt'), isTrue);
+        expect(folder.contains('/baz.txt'), isFalse);
+        expect(folder.contains('/foo/bar'), isFalse);
+      });
+
       group('getChild', () {
         test('does not exist', () {
           File file = folder.getChild('file.txt');
@@ -373,21 +380,21 @@
       group('existent', () {
         setUp(() {
           File file = provider.newFile('/foo/test.dart', 'library test;');
-          source = file.createSource(UriKind.FILE_URI);
+          source = file.createSource();
         });
 
         group('==', () {
           group('true', () {
             test('self', () {
               File file = provider.newFile('/foo/test.dart', '');
-              Source source = file.createSource(UriKind.FILE_URI);
+              Source source = file.createSource();
               expect(source == source, isTrue);
             });
 
             test('same file', () {
               File file = provider.newFile('/foo/test.dart', '');
-              Source sourceA = file.createSource(UriKind.FILE_URI);
-              Source sourceB = file.createSource(UriKind.FILE_URI);
+              Source sourceA = file.createSource();
+              Source sourceB = file.createSource();
               expect(sourceA == sourceB, isTrue);
             });
           });
@@ -395,15 +402,15 @@
           group('false', () {
             test('not a memory Source', () {
               File file = provider.newFile('/foo/test.dart', '');
-              Source source = file.createSource(UriKind.FILE_URI);
+              Source source = file.createSource();
               expect(source == new Object(), isFalse);
             });
 
             test('different file', () {
               File fileA = provider.newFile('/foo/a.dart', '');
               File fileB = provider.newFile('/foo/b.dart', '');
-              Source sourceA = fileA.createSource(UriKind.FILE_URI);
-              Source sourceB = fileB.createSource(UriKind.FILE_URI);
+              Source sourceA = fileA.createSource();
+              Source sourceB = fileB.createSource();
               expect(sourceA == sourceB, isFalse);
             });
           });
@@ -415,7 +422,7 @@
         });
 
         test('encoding', () {
-          expect(source.encoding, 'f/foo/test.dart');
+          expect(source.encoding, 'file:///foo/test.dart');
         });
 
         test('exists', () {
@@ -435,15 +442,15 @@
         });
 
         test('resolveRelative', () {
-          var relative = source.resolveRelative(new Uri.file('bar/baz.dart'));
-          expect(relative.fullName, '/foo/bar/baz.dart');
+          Uri relative = source.resolveRelativeUri(new Uri.file('bar/baz.dart'));
+          expect(relative.path, '/foo/bar/baz.dart');
         });
       });
 
       group('non-existent', () {
         setUp(() {
           File file = provider.getResource('/foo/test.dart');
-          source = file.createSource(UriKind.FILE_URI);
+          source = file.createSource();
         });
 
         test('contents', () {
@@ -453,7 +460,7 @@
         });
 
         test('encoding', () {
-          expect(source.encoding, 'f/foo/test.dart');
+          expect(source.encoding, 'file:///foo/test.dart');
         });
 
         test('exists', () {
@@ -469,8 +476,8 @@
         });
 
         test('resolveRelative', () {
-          var relative = source.resolveRelative(new Uri.file('bar/baz.dart'));
-          expect(relative.fullName, '/foo/bar/baz.dart');
+          Uri relative = source.resolveRelativeUri(new Uri.file('bar/baz.dart'));
+          expect(relative.path, '/foo/bar/baz.dart');
         });
       });
     });
diff --git a/pkg/analyzer/test/file_system/physical_resource_provider_test.dart b/pkg/analyzer/test/file_system/physical_resource_provider_test.dart
index 462a648..75bfcb2 100644
--- a/pkg/analyzer/test/file_system/physical_resource_provider_test.dart
+++ b/pkg/analyzer/test/file_system/physical_resource_provider_test.dart
@@ -134,7 +134,7 @@
 
         test('createSource', () {
           new io.File(path).writeAsStringSync('contents');
-          var source = file.createSource(UriKind.FILE_URI);
+          Source source = file.createSource();
           expect(source.uriKind, UriKind.FILE_URI);
           expect(source.exists(), isTrue);
           expect(source.contents.data, 'contents');
@@ -215,6 +215,13 @@
           expect(folder == folder2, isFalse);
         });
 
+        test('contains', () {
+          expect(folder.contains(join(path, 'aaa.txt')), isTrue);
+          expect(folder.contains(join(path, 'aaa', 'bbb.txt')), isTrue);
+          expect(folder.contains(join(tempPath, 'baz.txt')), isFalse);
+          expect(folder.contains(path), isFalse);
+        });
+
         group('getChild', () {
           test('does not exist', () {
             var child = folder.getChild('no-such-resource');
diff --git a/pkg/analyzer/test/file_system/resource_uri_resolver_test.dart b/pkg/analyzer/test/file_system/resource_uri_resolver_test.dart
index 76761cb..c5eeed2 100644
--- a/pkg/analyzer/test/file_system/resource_uri_resolver_test.dart
+++ b/pkg/analyzer/test/file_system/resource_uri_resolver_test.dart
@@ -23,22 +23,6 @@
       provider.newFolder('/folder');
     });
 
-    group('fromEncoding', () {
-      test('file', () {
-        var uri = new Uri(path: '/test.dart');
-        Source source = resolver.fromEncoding(UriKind.FILE_URI, uri);
-        expect(source, isNotNull);
-        expect(source.exists(), isTrue);
-        expect(source.fullName, '/test.dart');
-      });
-
-      test('not a UriKind.FILE_URI', () {
-        var uri = new Uri(path: '/test.dart');
-        Source source = resolver.fromEncoding(UriKind.DART_URI, uri);
-        expect(source, isNull);
-      });
-    });
-
     group('resolveAbsolute', () {
       test('file', () {
         var uri = new Uri(scheme: 'file', path: '/test.dart');
diff --git a/pkg/analyzer/test/generated/element_test.dart b/pkg/analyzer/test/generated/element_test.dart
index 8820276..d0181b3 100644
--- a/pkg/analyzer/test/generated/element_test.dart
+++ b/pkg/analyzer/test/generated/element_test.dart
@@ -1257,12 +1257,6 @@
     JUnitTestCase.assertTrue(first == second);
   }
 
-  void test_equals_equalWithDifferentUriKind() {
-    ElementLocationImpl first = new ElementLocationImpl.con2("fa;fb;c");
-    ElementLocationImpl second = new ElementLocationImpl.con2("pa;pb;c");
-    JUnitTestCase.assertTrue(first == second);
-  }
-
   void test_equals_notEqual_differentLengths() {
     ElementLocationImpl first = new ElementLocationImpl.con2("a;b;c");
     ElementLocationImpl second = new ElementLocationImpl.con2("a;b;c;d");
@@ -1303,12 +1297,6 @@
     JUnitTestCase.assertTrue(first.hashCode == second.hashCode);
   }
 
-  void test_hashCode_equalWithDifferentUriKind() {
-    ElementLocationImpl first = new ElementLocationImpl.con2("fa;fb;c");
-    ElementLocationImpl second = new ElementLocationImpl.con2("pa;pb;c");
-    JUnitTestCase.assertTrue(first.hashCode == second.hashCode);
-  }
-
   static dartSuite() {
     _ut.group('ElementLocationImplTest', () {
       _ut.test('test_create_encoding', () {
@@ -1323,10 +1311,6 @@
         final __test = new ElementLocationImplTest();
         runJUnitTest(__test, __test.test_equals_equal);
       });
-      _ut.test('test_equals_equalWithDifferentUriKind', () {
-        final __test = new ElementLocationImplTest();
-        runJUnitTest(__test, __test.test_equals_equalWithDifferentUriKind);
-      });
       _ut.test('test_equals_notEqual_differentLengths', () {
         final __test = new ElementLocationImplTest();
         runJUnitTest(__test, __test.test_equals_notEqual_differentLengths);
@@ -1351,10 +1335,6 @@
         final __test = new ElementLocationImplTest();
         runJUnitTest(__test, __test.test_hashCode_equal);
       });
-      _ut.test('test_hashCode_equalWithDifferentUriKind', () {
-        final __test = new ElementLocationImplTest();
-        runJUnitTest(__test, __test.test_hashCode_equalWithDifferentUriKind);
-      });
     });
   }
 }
@@ -4255,13 +4235,6 @@
     JUnitTestCase.assertEquals(element, type.element);
   }
 
-  void test_isMoreSpecificThan_typeArguments_bottom() {
-    TypeParameterElementImpl element = new TypeParameterElementImpl.forNode(AstFactory.identifier3("E"));
-    TypeParameterTypeImpl type = new TypeParameterTypeImpl(element);
-    // E << bottom
-    JUnitTestCase.assertTrue(type.isMoreSpecificThan(BottomTypeImpl.instance));
-  }
-
   void test_isMoreSpecificThan_typeArguments_dynamic() {
     TypeParameterElementImpl element = new TypeParameterElementImpl.forNode(AstFactory.identifier3("E"));
     TypeParameterTypeImpl type = new TypeParameterTypeImpl(element);
@@ -4360,10 +4333,6 @@
         final __test = new TypeParameterTypeImplTest();
         runJUnitTest(__test, __test.test_getElement);
       });
-      _ut.test('test_isMoreSpecificThan_typeArguments_bottom', () {
-        final __test = new TypeParameterTypeImplTest();
-        runJUnitTest(__test, __test.test_isMoreSpecificThan_typeArguments_bottom);
-      });
       _ut.test('test_isMoreSpecificThan_typeArguments_dynamic', () {
         final __test = new TypeParameterTypeImplTest();
         runJUnitTest(__test, __test.test_isMoreSpecificThan_typeArguments_dynamic);
diff --git a/pkg/analyzer/test/generated/resolver_test.dart b/pkg/analyzer/test/generated/resolver_test.dart
index d5059a3..09ca548 100644
--- a/pkg/analyzer/test/generated/resolver_test.dart
+++ b/pkg/analyzer/test/generated/resolver_test.dart
@@ -26692,6 +26692,91 @@
         "}"]), null, typeProvider.dynamicType);
   }
 
+  void fail_mergePropagatedTypesAtJoinPoint_6() {
+    // https://code.google.com/p/dart/issues/detail?id=19929
+    //
+    // Labeled [break]s are unsafe for the purposes of [isAbruptTerminationStatement].
+    //
+    // This is tricky: the [break] jumps back above the [if], making
+    // it into a loop of sorts. The [if] type-propagation code assumes
+    // that [break] does not introduce a loop.
+    String code = EngineTestCase.createSource([
+        "f() {",
+        "  var x = 0;",
+        "  var c = false;",
+        "  L: ",
+        "  if (c) {",
+        "  } else {",
+        "    x = '';",
+        "    c = true;",
+        "    break L;",
+        "  }",
+        "  x; // marker",
+        "}"]);
+    DartType t = _findMarkedIdentifier(code, "; // marker").propagatedType;
+    JUnitTestCase.assertTrue(typeProvider.intType.isSubtypeOf(t));
+    JUnitTestCase.assertTrue(typeProvider.stringType.isSubtypeOf(t));
+  }
+
+  void fail_mergePropagatedTypesAtJoinPoint_7() {
+    // https://code.google.com/p/dart/issues/detail?id=19929
+    //
+    // In general [continue]s are unsafe for the purposes of [isAbruptTerminationStatement].
+    //
+    // This is like example 6, but less tricky: the code in the branch that
+    // [continue]s is in effect after the [if].
+    String code = EngineTestCase.createSource([
+        "f() {",
+        "  var x = 0;",
+        "  var c = false;",
+        "  var d = true;",
+        "  while (d) {",
+        "    if (c) {",
+        "      d = false;",
+        "    } else {",
+        "      x = '';",
+        "      c = true;",
+        "      continue;",
+        "    }",
+        "    x; // marker",
+        "  }",
+        "}"]);
+    DartType t = _findMarkedIdentifier(code, "; // marker").propagatedType;
+    JUnitTestCase.assertTrue(typeProvider.intType.isSubtypeOf(t));
+    JUnitTestCase.assertTrue(typeProvider.stringType.isSubtypeOf(t));
+  }
+
+  void fail_mergePropagatedTypesAtJoinPoint_8() {
+    // https://code.google.com/p/dart/issues/detail?id=19929
+    //
+    // In nested loops [breaks]s are unsafe for the purposes of [isAbruptTerminationStatement].
+    //
+    // This is a combination of 6 and 7: we use an unlabeled [break]
+    // like a continue for the outer loop / like a labeled [break] to
+    // jump just above the [if].
+    String code = EngineTestCase.createSource([
+        "f() {",
+        "  var x = 0;",
+        "  var c = false;",
+        "  var d = true;",
+        "  while (d) {",
+        "    while (d) {",
+        "      if (c) {",
+        "        d = false;",
+        "      } else {",
+        "        x = '';",
+        "        c = true;",
+        "        break;",
+        "      }",
+        "      x; // marker",
+        "    }",
+        "  }",
+        "}"]);
+    DartType t = _findMarkedIdentifier(code, "; // marker").propagatedType;
+    JUnitTestCase.assertTrue(typeProvider.intType.isSubtypeOf(t));
+    JUnitTestCase.assertTrue(typeProvider.stringType.isSubtypeOf(t));
+  }
+
   void fail_propagatedReturnType_functionExpression() {
     // TODO(scheglov) disabled because we don't resolve function expression
     String code = EngineTestCase.createSource(["main() {", "  var v = (() {return 42;})();", "}"]);
@@ -27625,13 +27710,7 @@
    *          "v" has expected static and propagated type.
    */
   void _assertPropagatedReturnType(String code, DartType expectedStaticType, DartType expectedPropagatedType) {
-    Source source = addSource(code);
-    LibraryElement library = resolve(source);
-    assertNoErrors(source);
-    verify([source]);
-    CompilationUnit unit = resolveCompilationUnit(source, library);
-    //
-    SimpleIdentifier identifier = EngineTestCase.findNode(unit, code, "v = ", (node) => node is SimpleIdentifier);
+    SimpleIdentifier identifier = _findMarkedIdentifier(code, "v = ");
     JUnitTestCase.assertSame(expectedStaticType, identifier.staticType);
     JUnitTestCase.assertSame(expectedPropagatedType, identifier.propagatedType);
   }
@@ -27645,12 +27724,7 @@
    * @throws Exception
    */
   void _assertTypeOfMarkedExpression(String code, DartType expectedStaticType, DartType expectedPropagatedType) {
-    Source source = addSource(code);
-    LibraryElement library = resolve(source);
-    assertNoErrors(source);
-    verify([source]);
-    CompilationUnit unit = resolveCompilationUnit(source, library);
-    SimpleIdentifier identifier = EngineTestCase.findNode(unit, code, "; // marker", (node) => node is SimpleIdentifier);
+    SimpleIdentifier identifier = _findMarkedIdentifier(code, "; // marker");
     if (expectedStaticType != null) {
       JUnitTestCase.assertSame(expectedStaticType, identifier.staticType);
     }
@@ -27659,6 +27733,33 @@
     }
   }
 
+  /**
+   * Return the `SimpleIdentifier` marked by `marker`. The source code must have no
+   * errors and be verifiable.
+   *
+   * @param code source code to analyze.
+   * @param marker marker identifying sought after expression in source code.
+   * @return expression marked by the marker.
+   * @throws Exception
+   */
+  SimpleIdentifier _findMarkedIdentifier(String code, String marker) {
+    try {
+      Source source = addSource(code);
+      LibraryElement library = resolve(source);
+      assertNoErrors(source);
+      verify([source]);
+      CompilationUnit unit = resolveCompilationUnit(source, library);
+      // Could generalize this further by making [SimpleIdentifier.class] a parameter.
+      return EngineTestCase.findNode(unit, code, marker, (node) => node is SimpleIdentifier);
+    } catch (exception) {
+      // Is there a better exception to throw here? The point is that an assertion failure
+      // here should be a failure, in both "test_*" and "fail_*" tests.
+      // However, an assertion failure is success for the purpose of "fail_*" tests, so
+      // without catching them here "fail_*" tests can succeed by failing for the wrong reason.
+      throw new JavaException("Unexexpected assertion failure: ${exception}");
+    }
+  }
+
   static dartSuite() {
     _ut.group('TypePropagationTest', () {
       _ut.test('test_CanvasElement_getContext', () {
diff --git a/pkg/analyzer/test/generated/test_support.dart b/pkg/analyzer/test/generated/test_support.dart
index 382b42f..fbf9ca3 100644
--- a/pkg/analyzer/test/generated/test_support.dart
+++ b/pkg/analyzer/test/generated/test_support.dart
@@ -861,12 +861,15 @@
   Source resolve(String uri) {
     throw new UnsupportedOperationException();
   }
-  Source resolveRelative(Uri uri) {
+  Uri resolveRelativeUri(Uri uri) {
     throw new UnsupportedOperationException();
   }
   UriKind get uriKind {
     throw new UnsupportedOperationException();
   }
+  Uri get uri {
+    throw new UnsupportedOperationException();
+  }
   TimestampedData<String> get contents {
     throw new UnsupportedOperationException();
   }
diff --git a/pkg/analyzer/test/services/test_utils.dart b/pkg/analyzer/test/services/test_utils.dart
index b67ad1f..2e53cbd 100644
--- a/pkg/analyzer/test/services/test_utils.dart
+++ b/pkg/analyzer/test/services/test_utils.dart
@@ -178,9 +178,11 @@
 
   bool get isInSystemLibrary => _unsupported();
 
+  Uri get uri => _unsupported();
+
   Source resolve(String uri) => _unsupported();
 
-  Source resolveRelative(Uri uri) => _unsupported();
+  Uri resolveRelativeUri(Uri uri) => _unsupported();
 
   TimestampedData<String> get contents => _unsupported();
 }
diff --git a/pkg/analyzer/test/source/package_map_resolver_test.dart b/pkg/analyzer/test/source/package_map_resolver_test.dart
index 4b6a9c8..a665608d 100644
--- a/pkg/analyzer/test/source/package_map_resolver_test.dart
+++ b/pkg/analyzer/test/source/package_map_resolver_test.dart
@@ -16,12 +16,6 @@
 main() {
   groupSep = ' | ';
   group('PackageMapUriResolverTest', () {
-    test('fromEncoding_nonPackage', () {
-      new _PackageMapUriResolverTest().test_fromEncoding_nonPackage();
-    });
-    test('fromEncoding_package', () {
-      new _PackageMapUriResolverTest().test_fromEncoding_package();
-    });
     test('isPackageUri', () {
       new _PackageMapUriResolverTest().test_isPackageUri();
     });
@@ -64,21 +58,6 @@
   static HashMap EMPTY_MAP = new HashMap<String, List<Folder>>();
   MemoryResourceProvider provider = new MemoryResourceProvider();
 
-  void test_fromEncoding_nonPackage() {
-    UriResolver resolver = new PackageMapUriResolver(provider, EMPTY_MAP);
-    Uri uri = Uri.parse('file:/does/not/exist.dart');
-    Source result = resolver.fromEncoding(UriKind.DART_URI, uri);
-    expect(result, isNull);
-  }
-
-  void test_fromEncoding_package() {
-    UriResolver resolver = new PackageMapUriResolver(provider, EMPTY_MAP);
-    Uri uri = Uri.parse('package:/does/not/exist.dart');
-    Source result = resolver.fromEncoding(UriKind.PACKAGE_URI, uri);
-    expect(result, isNotNull);
-    expect(result.fullName, '/does/not/exist.dart');
-  }
-
   void test_isPackageUri() {
     Uri uri = Uri.parse('package:test/test.dart');
     expect(uri.scheme, 'package');
diff --git a/pkg/code_transformers/lib/src/dart_sdk.dart b/pkg/code_transformers/lib/src/dart_sdk.dart
index 3438218..5dfcab8 100644
--- a/pkg/code_transformers/lib/src/dart_sdk.dart
+++ b/pkg/code_transformers/lib/src/dart_sdk.dart
@@ -108,10 +108,20 @@
     return new DartSourceProxy(source, uri);
   }
 
+  // Note: to support both analyzer versions <0.22.0 and analyzer >=0.22.0, we
+  // implement both `resolveRelative` and `resolveRelativeUri`. Only one of them
+  // is available at a time in the analyzer package, so we use the `as dynamic`
+  // in these methods to hide warnings for the code that is missing. These APIs
+  // are invoked from the analyzer itself, so we don't expect them to cause
+  // failures.
   Source resolveRelative(Uri relativeUri) {
     // Assume that the type can be accessed via this URI, since these
     // should only be parts for dart core files.
-    return wrap(_proxy.resolveRelative(relativeUri), uri);
+    return wrap((_proxy as dynamic).resolveRelative(relativeUri), uri);
+  }
+
+  Uri resolveRelativeUri(Uri relativeUri) {
+    return (_proxy as dynamic).resolveRelativeUri(relativeUri);
   }
 
   bool exists() => _proxy.exists();
@@ -181,6 +191,11 @@
     }
     return src;
   }
+
+  @override
+  Source fromFileUri(Uri uri) {
+    throw new UnsupportedError('MockDartSdk.fromFileUri');
+  }
 }
 
 class _MockSdkSource implements UriAnnotatedSource {
@@ -211,4 +226,7 @@
 
   Source resolveRelative(Uri relativeUri) =>
       throw new UnsupportedError('not expecting relative urls in dart: mocks');
+
+  Uri resolveRelativeUri(Uri relativeUri) =>
+      throw new UnsupportedError('not expecting relative urls in dart: mocks');
 }
diff --git a/pkg/code_transformers/lib/src/resolver_impl.dart b/pkg/code_transformers/lib/src/resolver_impl.dart
index 52f8033..1d25705 100644
--- a/pkg/code_transformers/lib/src/resolver_impl.dart
+++ b/pkg/code_transformers/lib/src/resolver_impl.dart
@@ -342,6 +342,8 @@
   /// Contents of the file.
   String get rawContents => _contents;
 
+  Uri get uri => Uri.parse('asset:${assetId.package}/${assetId.path}');
+
   /// Logger for the current transform.
   ///
   /// Only valid while the resolver is updating assets.
@@ -389,6 +391,18 @@
     return source;
   }
 
+  Uri resolveRelativeUri(Uri relativeUri) {
+    var id = _resolve(assetId, relativeUri.toString(), _logger, null);
+    if (id == null) return uri.resolveUri(relativeUri);
+
+    // The entire AST should have been parsed and loaded at this point.
+    var source = _resolver.sources[id];
+    if (source == null) {
+      _logger.error('Could not load asset $id');
+    }
+    return source.uri;
+  }
+
   /// For logging errors.
   SourceSpan _getSpan(AstNode node, [String contents]) =>
       _getSourceFile(contents).span(node.offset, node.end);
@@ -422,10 +436,17 @@
   _AssetUriResolver(this._resolver);
 
   Source resolveAbsolute(Uri uri) {
-    var assetId = _resolve(null, uri.toString(), logger, null);
-    if (assetId == null) {
-      logger.error('Unable to resolve asset ID for "$uri"');
-      return null;
+    assert(uri.scheme != 'dart');
+    var assetId;
+    if (uri.scheme == 'asset') {
+      var parts = path.split(uri.path);
+      assetId = new AssetId(parts[0], path.joinAll(parts.skip(1)));
+    } else {
+      assetId = _resolve(null, uri.toString(), logger, null);
+      if (assetId == null) {
+        logger.error('Unable to resolve asset ID for "$uri"');
+        return null;
+      }
     }
     var source = _resolver.sources[assetId];
     // Analyzer expects that sources which are referenced but do not exist yet
diff --git a/pkg/code_transformers/pubspec.yaml b/pkg/code_transformers/pubspec.yaml
index 5f07de4..e82014a 100644
--- a/pkg/code_transformers/pubspec.yaml
+++ b/pkg/code_transformers/pubspec.yaml
@@ -1,10 +1,10 @@
 name: code_transformers
-version: 0.2.0+3
+version: 0.2.1-dev
 author: "Dart Team <misc@dartlang.org>"
 description: Collection of utilities related to creating barback transformers.
 homepage: http://www.dartlang.org
 dependencies:
-  analyzer: ">=0.15.6 <0.22.0"
+  analyzer: ">=0.15.6 <0.23.0"
   barback: ">=0.14.2 <0.16.0"
   path: ">=0.9.0 <2.0.0"
   source_maps: ">=0.9.4 <0.11.0"
diff --git a/pkg/csslib/lib/parser.dart b/pkg/csslib/lib/parser.dart
index 059b75c..8fbf06e 100644
--- a/pkg/csslib/lib/parser.dart
+++ b/pkg/csslib/lib/parser.dart
@@ -1295,7 +1295,7 @@
    *       : '.' IDENT
    */
   simpleSelector() {
-    // TODO(terry): Nathan makes a good point parsing of namespace and element
+    // TODO(terry): Natalie makes a good point parsing of namespace and element
     //              are essentially the same (asterisk or identifier) other
     //              than the error message for element.  Should consolidate the
     //              code.
diff --git a/pkg/path/CHANGELOG.md b/pkg/path/CHANGELOG.md
index 5331635..59f3893 100644
--- a/pkg/path/CHANGELOG.md
+++ b/pkg/path/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 1.3.0
+
+* Expose a top-level `context` field that provides access to a `Context` object
+  for the current system.
+
+## 1.2.3
+
+* Don't cache path Context based on cwd, as cwd involves a system-call to
+  compute.
+
 ## 1.2.2
 
 * Remove the documentation link from the pubspec so this is linked to
diff --git a/pkg/path/lib/path.dart b/pkg/path/lib/path.dart
index 599a351..be6e95a 100644
--- a/pkg/path/lib/path.dart
+++ b/pkg/path/lib/path.dart
@@ -49,7 +49,7 @@
 import 'src/context.dart';
 import 'src/style.dart';
 
-export 'src/context.dart';
+export 'src/context.dart' hide createInternal;
 export 'src/path_exception.dart';
 export 'src/style.dart';
 
@@ -62,27 +62,17 @@
 /// A default context for manipulating URLs.
 final url = new Context(style: Style.url);
 
-/// The result of [Uri.base] last time the current working directory was
-/// calculated.
+/// The system path context.
 ///
-/// This is used to invalidate [_cachedContext] when the working directory has
-/// changed since the last time a function was called.
-Uri _lastBaseUri;
-
-/// An internal context for the current OS so we can provide a straight
-/// functional interface and not require users to create one.
-Context get _context {
-  if (_cachedContext != null && Uri.base == _lastBaseUri) return _cachedContext;
-  _lastBaseUri = Uri.base;
-  _cachedContext = new Context();
-  return _cachedContext;
-}
-Context _cachedContext;
+/// This differs from a context created with [new Context] in that its
+/// [Context.current] is always the current working directory, rather than being
+/// set once when the context is created.
+final Context context = createInternal();
 
 /// Returns the [Style] of the current context.
 ///
 /// This is the style that all top-level path functions will use.
-Style get style => _context.style;
+Style get style => context.style;
 
 /// Gets the path to the current working directory.
 ///
@@ -102,7 +92,7 @@
 
 /// Gets the path separator for the current platform. This is `\` on Windows
 /// and `/` on other platforms (including the browser).
-String get separator => _context.separator;
+String get separator => context.separator;
 
 /// Creates a new path by appending the given path parts to [current].
 /// Equivalent to [join()] with [current] as the first argument. Example:
@@ -110,7 +100,7 @@
 ///     path.absolute('path', 'to/foo'); // -> '/your/current/dir/path/to/foo'
 String absolute(String part1, [String part2, String part3, String part4,
             String part5, String part6, String part7]) =>
-  _context.absolute(part1, part2, part3, part4, part5, part6, part7);
+  context.absolute(part1, part2, part3, part4, part5, part6, part7);
 
 /// Gets the part of [path] after the last separator.
 ///
@@ -120,7 +110,7 @@
 /// Trailing separators are ignored.
 ///
 ///     path.basename('path/to/'); // -> 'to'
-String basename(String path) => _context.basename(path);
+String basename(String path) => context.basename(path);
 
 /// Gets the part of [path] after the last separator, and without any trailing
 /// file extension.
@@ -131,7 +121,7 @@
 ///
 ///     path.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
 String basenameWithoutExtension(String path) =>
-    _context.basenameWithoutExtension(path);
+    context.basenameWithoutExtension(path);
 
 /// Gets the part of [path] before the last separator.
 ///
@@ -152,7 +142,7 @@
 ///
 ///     path.dirname('foo');  // -> '.'
 ///     path.dirname('');  // -> '.'
-String dirname(String path) => _context.dirname(path);
+String dirname(String path) => context.dirname(path);
 
 /// Gets the file extension of [path]: the portion of [basename] from the last
 /// `.` to the end (including the `.` itself).
@@ -167,7 +157,7 @@
 ///
 ///     path.extension('~/.bashrc');    // -> ''
 ///     path.extension('~/.notes.txt'); // -> '.txt'
-String extension(String path) => _context.extension(path);
+String extension(String path) => context.extension(path);
 
 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
 /// Returns the root of [path], if it's absolute, or the empty string if it's
@@ -185,7 +175,7 @@
 ///     path.rootPrefix('path/to/foo'); // -> ''
 ///     path.rootPrefix('http://dartlang.org/path/to/foo');
 ///       // -> 'http://dartlang.org'
-String rootPrefix(String path) => _context.rootPrefix(path);
+String rootPrefix(String path) => context.rootPrefix(path);
 
 /// Returns `true` if [path] is an absolute path and `false` if it is a
 /// relative path.
@@ -199,13 +189,13 @@
 /// relative to the root of the current URL. Since root-relative paths are still
 /// absolute in every other sense, [isAbsolute] will return true for them. They
 /// can be detected using [isRootRelative].
-bool isAbsolute(String path) => _context.isAbsolute(path);
+bool isAbsolute(String path) => context.isAbsolute(path);
 
 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
 /// `:/` or `:\`.
-bool isRelative(String path) => _context.isRelative(path);
+bool isRelative(String path) => context.isRelative(path);
 
 /// Returns `true` if [path] is a root-relative path and `false` if it's not.
 ///
@@ -215,7 +205,7 @@
 /// can be detected using [isRootRelative].
 ///
 /// No POSIX and Windows paths are root-relative.
-bool isRootRelative(String path) => _context.isRootRelative(path);
+bool isRootRelative(String path) => context.isRootRelative(path);
 
 /// Joins the given path parts into a single path using the current platform's
 /// [separator]. Example:
@@ -232,7 +222,7 @@
 ///     path.join('path', '/to', 'foo'); // -> '/to/foo'
 String join(String part1, [String part2, String part3, String part4,
             String part5, String part6, String part7, String part8]) =>
-  _context.join(part1, part2, part3, part4, part5, part6, part7, part8);
+  context.join(part1, part2, part3, part4, part5, part6, part7, part8);
 
 /// Joins the given path parts into a single path using the current platform's
 /// [separator]. Example:
@@ -249,7 +239,7 @@
 ///     path.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
 ///
 /// For a fixed number of parts, [join] is usually terser.
-String joinAll(Iterable<String> parts) => _context.joinAll(parts);
+String joinAll(Iterable<String> parts) => context.joinAll(parts);
 
 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
 /// Splits [path] into its components using the current platform's [separator].
@@ -272,13 +262,13 @@
 ///     // Browser
 ///     path.split('http://dartlang.org/path/to/foo');
 ///       // -> ['http://dartlang.org', 'path', 'to', 'foo']
-List<String> split(String path) => _context.split(path);
+List<String> split(String path) => context.split(path);
 
 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
 /// removing redundant path separators whenever possible.
 ///
 ///     path.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
-String normalize(String path) => _context.normalize(path);
+String normalize(String path) => context.normalize(path);
 
 /// Attempts to convert [path] to an equivalent relative path from the current
 /// directory.
@@ -308,19 +298,19 @@
 ///     path.relative('http://dartlang.org', from: 'http://pub.dartlang.org');
 ///       // -> 'http://dartlang.org'
 String relative(String path, {String from}) =>
-    _context.relative(path, from: from);
+    context.relative(path, from: from);
 
 /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise.
 ///
 ///     path.isWithin('/root/path', '/root/path/a'); // -> true
 ///     path.isWithin('/root/path', '/root/other'); // -> false
 ///     path.isWithin('/root/path', '/root/path') // -> false
-bool isWithin(String parent, String child) => _context.isWithin(parent, child);
+bool isWithin(String parent, String child) => context.isWithin(parent, child);
 
 /// Removes a trailing extension from the last part of [path].
 ///
 ///     withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
-String withoutExtension(String path) => _context.withoutExtension(path);
+String withoutExtension(String path) => context.withoutExtension(path);
 
 /// Returns the path represented by [uri], which may be a [String] or a [Uri].
 ///
@@ -342,7 +332,7 @@
 /// If [uri] is relative, a relative path will be returned.
 ///
 ///     path.fromUri('path/to/foo'); // -> 'path/to/foo'
-String fromUri(uri) => _context.fromUri(uri);
+String fromUri(uri) => context.fromUri(uri);
 
 /// Returns the URI that represents [path].
 ///
@@ -365,7 +355,7 @@
 ///
 ///     path.toUri('path/to/foo')
 ///       // -> Uri.parse('path/to/foo')
-Uri toUri(String path) => _context.toUri(path);
+Uri toUri(String path) => context.toUri(path);
 
 /// Returns a terse, human-readable representation of [uri].
 ///
@@ -388,4 +378,4 @@
 ///     path.prettyUri('http://dartlang.org/root/path/a/b.dart');
 ///         // -> r'a/b.dart'
 ///     path.prettyUri('file:///root/path'); // -> 'file:///root/path'
-String prettyUri(uri) => _context.prettyUri(uri);
+String prettyUri(uri) => context.prettyUri(uri);
diff --git a/pkg/path/lib/src/context.dart b/pkg/path/lib/src/context.dart
index 823e0c2..7a88909 100644
--- a/pkg/path/lib/src/context.dart
+++ b/pkg/path/lib/src/context.dart
@@ -10,6 +10,8 @@
 import 'path_exception.dart';
 import '../path.dart' as p;
 
+Context createInternal() => new Context._internal();
+
 /// An instantiable class for manipulating paths. Unlike the top-level
 /// functions, this lets you explicitly select what platform the paths will use.
 class Context {
@@ -41,13 +43,20 @@
     return new Context._(style, current);
   }
 
-  Context._(this.style, this.current);
+  /// Create a [Context] to be used internally within path.
+  Context._internal() : style = Style.platform, _current = null;
+
+  Context._(this.style, this._current);
 
   /// The style of path that this context works with.
   final InternalStyle style;
 
-  /// The current directory that relative paths will be relative to.
-  final String current;
+  /// The current directory given when Context was created. If null, current
+  /// directory is evaluated from 'p.current'.
+  final String _current;
+
+  /// The current directory that relative paths are relative to.
+  String get current => _current != null ? _current : p.current;
 
   /// Gets the path separator for the context's [style]. On Mac and Linux,
   /// this is `/`. On Windows, it's `\`.
diff --git a/pkg/path/pubspec.yaml b/pkg/path/pubspec.yaml
index e9948de..2491882 100644
--- a/pkg/path/pubspec.yaml
+++ b/pkg/path/pubspec.yaml
@@ -1,5 +1,5 @@
 name: path
-version: 1.2.2
+version: 1.3.0
 author: Dart Team <misc@dartlang.org>
 description: >
  A string-based path manipulation library. All of the path operations you know
diff --git a/pkg/path/test/io_test.dart b/pkg/path/test/io_test.dart
index 3e52bb9..5663920 100644
--- a/pkg/path/test/io_test.dart
+++ b/pkg/path/test/io_test.dart
@@ -41,10 +41,14 @@
     var dir = io.Directory.current.path;
     try {
       expect(path.absolute('foo/bar'), equals(path.join(dir, 'foo/bar')));
+      expect(path.absolute('foo/bar'),
+          equals(path.context.join(dir, 'foo/bar')));
 
       io.Directory.current = path.dirname(dir);
       expect(path.normalize(path.absolute('foo/bar')),
           equals(path.normalize(path.join(dir, '../foo/bar'))));
+      expect(path.normalize(path.absolute('foo/bar')),
+          equals(path.normalize(path.context.join(dir, '../foo/bar'))));
     } finally {
       io.Directory.current = dir;
     }
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 617cee3..16e6b22 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -20,14 +20,13 @@
 scheduled_test/test/scheduled_stream/stream_matcher_test: Pass, Slow
 polymer/test/build/script_compactor_test: Pass, Slow
 
-[ $compiler == none && ($runtime == drt || $runtime == dartium) ]
-third_party/angular_tests/browser_test/core_dom/shadow_root_options: Fail # Issue 18931 (Disabled for Chrome 35 roll)
-polymer/example/component/news/test/news_index_test: Pass, RuntimeError # Issue 18931
+[ $compiler == none && ($runtime == drt || $runtime == dartium || $runtime == ContentShellOnAndroid) ]
+third_party/angular_tests/browser_test/*: Skip # github perf_api.dart issue 5
+third_party/angular_tests/browser_test/core_dom/shadow_root_options: Fail # Issue 19329
+polymer/example/component/news/test/news_index_test: RuntimeError # Issue 18931
 intl/test/date_time_format_http_request_test: Pass, Timeout # Issue 19544
-polymer/e2e_test/experimental_boot/test/import_test: Skip # Issue 20307 (Timing out)
-polymer/e2e_test/experimental_boot/test/double_init_test: Skip # Issue 20307 (Timing out)
 
-[ $compiler == none && ($runtime == dartium) ]
+[ $compiler == none && ($runtime == dartium || $runtime == ContentShellOnAndroid) ]
 polymer/test/attr_deserialize_test: Pass, RuntimeError # Issue 18931
 polymer/test/attr_mustache_test: Pass, RuntimeError # Issue 18931
 polymer/test/bind_test: Pass, RuntimeError # Issue 18931
@@ -60,6 +59,9 @@
 [ $compiler == none && $runtime == dartium && $system == windows ]
 polymer/test/property_observe_test: Pass, Timeout # Issue 19326
 
+[ $compiler == none && $runtime == ContentShellOnAndroid ]
+collection/test/unmodifiable_collection_test: Skip # Times out, Issue 20348
+
 [ $runtime == vm && $mode == debug]
 analysis_server/test/analysis_server_test: Skip  # Times out
 analysis_server/test/domain_context_test: Skip  # Times out
@@ -83,6 +85,7 @@
 polymer/test/build/all_phases_test: Skip # Slow
 polymer/test/build/script_compactor_test: Skip # Slow
 third_party/html5lib/test/tokenizer_test: Pass, Slow
+analysis_server/*: Skip # Timeout in some tests.
 
 [ $compiler == dart2js ]
 collection/test/equality_test/01: Fail # Issue 1533
@@ -111,19 +114,18 @@
 json_rpc_2/test/server/server_test: Fail # Issue 19750
 shelf/test/log_middleware_test: Fail # Issue 19750
 
-[ $compiler == dart2js && $runtime == drt ]
-third_party/angular_tests/browser_test/core_dom/compiler: Fail # Issue 19329
-third_party/angular_tests/browser_test/core_dom/shadow_root_options: Fail # Issue 19329
-third_party/angular_tests/browser_test/core/templateurl: Fail # Issue 19329
-web_components/test/interop_test: Pass, Fail # Issue 19329
-web_components/test/interop2_test: Fail # Issue 19329
-
 [ $compiler == dart2js && $checked ]
 crypto/test/base64_test: Slow, Pass
 
 [ $checked && $runtime == drt ]
 polymer/test/event_handlers_test: Pass, RuntimeError # Issue 19190
 
+[ $compiler == dart2js && $runtime == drt ]
+third_party/angular_tests/browser_test/core_dom/shadow_root_options: Fail # Issue 19329
+
+[ $compiler == dart2js && $csp && $runtime == drt ]
+web_components/test/interop_test: Fail # Issue 19329
+
 [ $compiler == dart2js && $checked && $runtime == ie9 ]
 crypto/test/base64_test: Timeout # Issue 12486
 collection/test/priority_queue_test: Pass, Slow # Issue 16426
@@ -168,6 +170,7 @@
 polymer/test/bind_test: Skip # uses dart:html
 polymer/test/bind_mdv_test: Skip # uses dart:html
 polymer/test/bind_properties_test: Skip # uses dart:html
+polymer/test/build/log_injector_test: Skip # uses dart:html
 polymer/test/computed_properties_test: Skip # uses dart:html
 polymer/test/custom_event_test: Skip # uses dart:html
 polymer/test/entered_view_test: Skip # uses dart:html
@@ -316,7 +319,14 @@
 intl/test/message_extraction/really_fail_extraction_test: Fail, OK # Users dart:io
 observe/test/transformer_test: Fail, OK # Uses dart:io.
 path/test/io_test: Fail, OK # Uses dart:io.
-polymer/test/build/*: Fail, OK # Uses dart:io.
+polymer/test/build/all_phases_test: Fail, OK # Uses dart:io.
+polymer/test/build/polyfill_injector_test: Fail, OK # Uses dart:io.
+polymer/test/build/static_clean_test: Fail, OK # Uses dart:io.
+polymer/test/build/build_log_combiner_test: Fail, OK # Uses dart:io.
+polymer/test/build/script_compactor_test: Fail, OK # Uses dart:io.
+polymer/test/build/utils_test: Fail, OK # Uses dart:io.
+polymer/test/build/import_inliner_test: Fail, OK # Uses dart:io.
+polymer/test/build/linter_test: Fail, OK # Uses dart:io.
 shelf/test/shelf_io_test: Fail, OK # Uses dart:io
 shelf_web_socket/test/*: Fail, OK # Uses dart:io.
 smoke/test/codegen/end_to_end_test: Skip # Uses dart:io.
@@ -348,12 +358,6 @@
 # not minified.
 matcher/test/*_minified_test: Skip # DO NOT COPY THIS UNLESS YOU WORK ON DART2JS
 
-[ $arch == mips ]
-*: Skip  # Issue 13650
-
-[ $arch == arm ]
-*: Skip  # Issue 13624
-
 [ $arch == simarm && $checked ]
 watcher/test/directory_watcher/linux_test: Skip # Issue 16118
 
@@ -364,7 +368,7 @@
 
 # Skip serialization test that explicitly has no library declaration in the
 # test on Dartium, which requires all tests to have a library.
-[ $compiler == none && ( $runtime == dartium || $runtime == drt ) ]
+[ $compiler == none && ( $runtime == dartium || $runtime == drt || $runtime == ContentShellOnAndroid) ]
 serialization/test/no_library_test: Skip # Expected Failure
 serialization/test/serialization_test: Fail # 13921
 unittest/test/async_exception_test: RuntimeError # 13921
@@ -416,10 +420,6 @@
 [ $browser || $runtime == vm ]
 unittest/test/missing_tick_test: Fail, OK # Expected to fail, due to timeout.
 
-
-[ $compiler == none && ($runtime == dartium || $runtime == drt) ]
-source_maps/test/parser_test: Pass, Timeout # Issue 13719: Please triage this failure.
-
 [ $compiler == dartanalyzer || $compiler == dart2analyzer ]
 third_party/angular_tests/vm_test: StaticWarning # Uses removed APIs. See issue 18733.
 
diff --git a/pkg/pkgbuild.status b/pkg/pkgbuild.status
index 12a9fd9..7d46334 100644
--- a/pkg/pkgbuild.status
+++ b/pkg/pkgbuild.status
@@ -29,3 +29,7 @@
 
 [ $system == windows ]
 samples/third_party/todomvc_performance: Fail # Issue 18086
+
+[ $use_public_packages && $builder_tag == russian && $system == windows]
+pkg/code_transformers: Fail # Issue 20386
+pkg/polymer_expressions: Fail # Issue 20386
diff --git a/pkg/polymer/CHANGELOG.md b/pkg/polymer/CHANGELOG.md
index b3fc2a5..486e380 100644
--- a/pkg/polymer/CHANGELOG.md
+++ b/pkg/polymer/CHANGELOG.md
@@ -4,6 +4,36 @@
 package. We will also note important changes to the polyfill packages (observe,
 web_components, and template_binding) if they impact polymer.
 
+#### Pub version 0.12.1-dev
+  * Added the ability to override the stylesheet inlining behavior. There is now
+    an option exposed in the pubspec.yaml called `inline_stylesheets`. There are
+    two possible values, a boolean or a map. If only a boolean is supplied then
+    that will set the global default behavior. If a map is supplied, then the
+    keys should be file paths, and the value is a boolean. You can use the
+    special key 'default' to set the default value.
+
+    For example, the following would change the default to not inline any
+    styles, except for the foo.css file in your web folder and the bar.css file
+    under the foo packages lib directory:
+
+        inline_stylesheets:
+            default: false
+            web/foo.css: true
+            packages/foo/bar.css: true
+
+  * Added `inject_build_logs_in_output` option to pubspec for polymer
+    transformers. When set to `true`, this will inject a small element into your
+    entry point pages that will display all log messages from the polymer
+    transformers during the build step. This element is only injected when not 
+    running in release mode (ie: `pub serve` but not `pub build`).
+
+#### Pub version 0.12.0+7
+  * Widen the constraint on `unittest`.
+
+#### Pub version 0.12.0+6
+  * Widen the constraint on analyzer.
+  * Support for `_src` and similar attributes in polymer transformers.
+
 #### Pub version 0.12.0+5
   * Raise the lower bound on the source_maps constraint to exclude incompatible
     versions.
diff --git a/pkg/polymer/bin/new_element.dart b/pkg/polymer/bin/new_element.dart
new file mode 100644
index 0000000..e2869ed
--- /dev/null
+++ b/pkg/polymer/bin/new_element.dart
@@ -0,0 +1,181 @@
+/// 
+/// Script to create boilerplate for a Polymer element.
+/// Produces .dart and .html files for the element.
+/// 
+/// Run this script with pub run:
+/// 
+///     pub run polymer:new_element element-name [-o output_dir]
+/// 
+import 'dart:io';
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path show absolute, dirname, join, split;
+
+void main(List<String> args) {
+  var parser = new ArgParser(allowTrailingOptions: true);
+  
+  parser.addOption('output-dir', abbr: 'o', help: 'Output directory');
+  
+  var options, element;
+  try {
+    options = parser.parse(args);
+    if (options.rest == null || options.rest.length > 1) {
+      throw new FormatException("No element specified");
+    }
+    element = options.rest[0];
+    _validateElementName(element);
+  } catch(e) {
+    print('${e}.\n');
+    print('Usage:');
+    print('  pub run polymer:new_element [-o output_dir] element-name');
+    exitCode = 1;
+    return;
+  } 
+  
+  var outputDir, startDir;
+  
+  var outputPath = options['output-dir'];
+  
+  if (outputPath == null) {
+    if ((new File('pubspec.yaml')).existsSync()) {
+      print('When creating elements in root directory of package, '
+          '-o <dir> must be specified');
+        exitCode = 1;
+        return;
+    }
+    outputDir = (new Directory('.')).resolveSymbolicLinksSync();
+  } else {
+    var outputDirLocation = new Directory(outputPath);
+    if (!outputDirLocation.existsSync()) {
+      outputDirLocation.createSync(recursive: true);
+    }
+    outputDir = (new Directory(outputPath)).resolveSymbolicLinksSync();
+  }
+
+  var pubspecDir = _findDirWithFile(outputDir, 'pubspec.yaml');
+  
+  if (pubspecDir == null) {
+    print('Could not find pubspec.yaml when walking up from $outputDir');
+    exitCode = 1;
+    return;
+  }
+
+  var length = path.split(pubspecDir).length;
+  var distanceToPackageRoot =
+      path.split(outputDir).length - length;
+
+  // See dartbug.com/20076 for the algorithm used here.
+  if (distanceToPackageRoot > 0) {
+    if (path.split(outputDir)[length] == 'lib') {
+      distanceToPackageRoot++;
+    } else {
+      distanceToPackageRoot--;
+    }
+  }
+    
+  try {
+    _createBoilerPlate(element, outputDir, distanceToPackageRoot);
+  } on Exception catch(e) {
+    print('Error creating files in $outputDir');
+    print('Exception: $e');
+    exitCode = 1;
+    return;
+  }
+ 
+  return;
+}
+
+String _findDirWithFile(String dir, String filename) {
+  while (!new File(path.join(dir, filename)).existsSync()) {
+    var parentDir = path.dirname(dir);
+    // If we reached root and failed to find it, bail.
+    if (parentDir == dir) return null;
+    dir = parentDir;
+  }
+  return dir;
+}
+
+void _validateElementName(String element) {
+  if (!element.contains('-') || element.toLowerCase() != element) {
+    throw new FormatException('element-name must be all lower case '
+        'and contain at least 1 hyphen');
+  }
+}
+
+String _toCamelCase(String s) {
+  return s[0].toUpperCase() + s.substring(1);
+}
+
+void _createBoilerPlate(String element, String directory,
+                        int distanceToPackageRoot) {
+  var segments = element.split('-');
+  var capitalizedName = segments.map((e) => _toCamelCase(e)).join('');
+  var underscoreName = element.replaceAll('-', '_'); 
+  var pathToPackages = '../' * distanceToPackageRoot;
+
+  String html = '''  
+<!-- import polymer-element's definition -->
+<link rel="import" href="${pathToPackages}packages/polymer/polymer.html">
+
+<polymer-element name="$element">
+  <template>
+    <style>
+      <!-- template styling here -->
+    </style>
+    <!-- template content here -->
+  </template>
+  <script type="application/dart" src="${underscoreName}.dart"></script>
+</polymer-element>
+''';
+
+  String htmlFile = path.join(directory, underscoreName + '.html');
+  new File(htmlFile).writeAsStringSync(html);
+
+  String dart = '''
+import 'package:polymer/polymer.dart';
+
+/**
+ * A Polymer $element element.
+ */
+@CustomTag('$element')
+class $capitalizedName extends PolymerElement {
+
+  /// Constructor used to create instance of ${capitalizedName}.
+  ${capitalizedName}.created() : super.created() {
+  }
+
+  /*
+   * Optional lifecycle methods - uncomment if needed.
+   *
+
+  /// Called when an instance of $element is inserted into the DOM.
+  attached() {
+    super.attached();
+  }
+
+  /// Called when an instance of $element is removed from the DOM.
+  detached() {
+    super.detached();
+  }
+
+  /// Called when an attribute (such as  a class) of an instance of
+  /// $element is added, changed, or removed.
+  attributeChanged(String name, String oldValue, String newValue) {
+  }
+
+  /// Called when $element has been fully prepared (Shadow DOM created,
+  /// property observers set up, event listeners attached).
+  ready() {
+  }
+   
+  */
+  
+}
+''';
+
+  String dartFile = path.join(directory, underscoreName + '.dart');
+  new File(dartFile).writeAsStringSync(dart);
+  
+  print('Successfully created:');
+  print('  ' + path.absolute(path.join(directory, underscoreName + '.dart')));
+  print('  ' + path.absolute(path.join(directory, underscoreName + '.html')));
+}
diff --git a/pkg/polymer/lib/src/build/build_log_combiner.dart b/pkg/polymer/lib/src/build/build_log_combiner.dart
new file mode 100644
index 0000000..cfd85ca
--- /dev/null
+++ b/pkg/polymer/lib/src/build/build_log_combiner.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Logic to combine all the ._buildLog.* logs into one ._buildLog file.
+library polymer.src.build.log_combiner;
+
+import 'dart:async';
+
+import 'package:barback/barback.dart';
+import 'package:html5lib/parser.dart' show parseFragment;
+
+import 'common.dart';
+import 'wrapped_logger.dart';
+
+/// Logic to combine all the ._buildLog.* logs into one ._buildLog file.
+class BuildLogCombiner extends Transformer with PolymerTransformer {
+  final TransformOptions options;
+
+  BuildLogCombiner(this.options);
+
+  /// Run only on entry point html files and only if
+  /// options.injectBuildLogsInOutput is true.
+  bool isPrimary(idOrAsset) {
+    if (!options.injectBuildLogsInOutput) return false;
+    var id = idOrAsset is AssetId ? idOrAsset : idOrAsset.id;
+    return options.isHtmlEntryPoint(id);
+  }
+
+  Future apply(Transform transform) {
+    // Combine all ._buildLogs* files into one ._buildLogs file.
+    return WrappedLogger.combineLogFiles(transform);
+  }
+}
diff --git a/pkg/polymer/lib/src/build/common.dart b/pkg/polymer/lib/src/build/common.dart
index ac74e89..fd62d57 100644
--- a/pkg/polymer/lib/src/build/common.dart
+++ b/pkg/polymer/lib/src/build/common.dart
@@ -52,6 +52,13 @@
   /// considered an entry point.
   final List<String> entryPoints;
 
+  /// Map of stylesheet paths that should or should not be inlined. The paths
+  /// are relative to the package root and are represented using posix style,
+  /// which matches the representation used in asset ids in barback.
+  ///
+  /// There is an additional special key 'default' for the global default.
+  final Map<String, bool> inlineStylesheets;
+
   /// True to enable Content Security Policy.
   /// This means the HTML page will include *.dart.precompiled.js
   ///
@@ -70,15 +77,21 @@
   /// minified versions of the polyfills rather than the debug versions.
   final bool releaseMode;
 
+  /// This will make a physical element appear on the page showing build logs.
+  /// It will only appear when ![releaseMode] even if this is true.
+  final bool injectBuildLogsInOutput;
+
   /// True to run liner on all html files before starting other phases.
   // TODO(jmesserly): instead of this flag, we should only run linter on
   // reachable (entry point+imported) html if deploying. See dartbug.com/17199.
   final bool lint;
 
-  TransformOptions({entryPoints, this.contentSecurityPolicy: false,
-      this.directlyIncludeJS: true, this.releaseMode: true, this.lint: true})
+  TransformOptions({entryPoints, this.inlineStylesheets,
+      this.contentSecurityPolicy: false, this.directlyIncludeJS: true,
+      this.releaseMode: true, this.lint: true,
+      this.injectBuildLogsInOutput: false})
       : entryPoints = entryPoints == null ? null
-          : entryPoints.map(_systemToAssetPath).toList();
+          : entryPoints.map(systemToAssetPath).toList();
 
   /// Whether an asset with [id] is an entry point HTML file.
   bool isHtmlEntryPoint(AssetId id) {
@@ -91,6 +104,22 @@
 
     return entryPoints.contains(id.path);
   }
+
+  // Whether a stylesheet with [id] should be inlined, the default is true.
+  bool shouldInlineStylesheet(AssetId id) {
+    // Note: [id.path] is a relative path from the root of a package.
+    // Default is to inline everything
+    if (inlineStylesheets == null) return true;
+    // First check for the full asset path overrides.
+    var override = inlineStylesheets[id.toString()];
+    if (override != null) return override;
+    // Then check just the path overrides (if the package was not specified).
+    override = inlineStylesheets[id.path];
+    if (override != null) return override;
+    // Then check the global default setting.
+    var globalDefault = inlineStylesheets['default'];
+    return (globalDefault != null) ? globalDefault : true;
+  }
 }
 
 /// Mixin for polymer transformers.
@@ -176,7 +205,7 @@
 
 
 /// Convert system paths to asset paths (asset paths are posix style).
-String _systemToAssetPath(String assetPath) {
+String systemToAssetPath(String assetPath) {
   if (path.Style.platform != path.Style.windows) return assetPath;
   return path.posix.joinAll(path.split(assetPath));
 }
@@ -206,3 +235,5 @@
 final ATTRIBUTES_REGEX = new RegExp(r'\s|,');
 
 const POLYMER_EXPERIMENTAL_HTML = 'packages/polymer/polymer_experimental.html';
+
+const String LOG_EXTENSION = '._buildLogs';
diff --git a/pkg/polymer/lib/src/build/import_inliner.dart b/pkg/polymer/lib/src/build/import_inliner.dart
index 1c871ac..1456b93 100644
--- a/pkg/polymer/lib/src/build/import_inliner.dart
+++ b/pkg/polymer/lib/src/build/import_inliner.dart
@@ -20,6 +20,7 @@
 import 'package:source_span/source_span.dart';
 
 import 'common.dart';
+import 'wrapped_logger.dart';
 
 // TODO(sigmund): move to web_components package (dartbug.com/18037).
 class _HtmlInliner extends PolymerTransformer {
@@ -36,9 +37,11 @@
   /// unique-ish filenames.
   int inlineScriptCounter = 0;
 
-  _HtmlInliner(this.options, Transform transform)
-      : transform = transform,
-        logger = transform.logger,
+  _HtmlInliner(TransformOptions options, Transform transform)
+      : options = options,
+        transform = transform,
+        logger = options.releaseMode ? transform.logger :
+            new WrappedLogger(transform, convertErrorsToWarnings: true),
         docId = transform.primaryInput.id;
 
   Future apply() {
@@ -49,7 +52,8 @@
 
     return readPrimaryAsHtml(transform).then((doc) {
       document = doc;
-      changed = new _UrlNormalizer(transform, docId).visit(document) || changed;
+      changed = new _UrlNormalizer(transform, docId, logger).visit(document)
+        || changed;
       
       experimentalBootstrap = document.querySelectorAll('link').any((link) =>
           link.attributes['rel'] == 'import' &&
@@ -73,6 +77,11 @@
             'experimental_bootstrap': experimentalBootstrap,
             'script_ids': scriptIds,
           }, toEncodable: (id) => id.serialize())));
+
+      // Write out the logs collected by our [WrappedLogger].
+      if (options.injectBuildLogsInOutput && logger is WrappedLogger) {
+        return (logger as WrappedLogger).writeOutput();
+      }
     });
   }
 
@@ -94,7 +103,7 @@
 
       // Note: URL has already been normalized so use docId.
       var href = tag.attributes['href'];
-      var id = uriToAssetId(docId, href, transform.logger, tag.sourceSpan,
+      var id = uriToAssetId(docId, href, logger, tag.sourceSpan,
           errorOnAbsolute: rel != 'stylesheet');
 
       if (rel == 'import') {
@@ -107,8 +116,9 @@
 
       } else if (rel == 'stylesheet') {
         if (id == null) return null;
-        changed = true;
+        if (!options.shouldInlineStylesheet(id)) return null;
 
+        changed = true;
         return _inlineStylesheet(id, tag);
       }
     }).then((_) => changed);
@@ -145,12 +155,12 @@
   /// html imports. Then inlines it into the main document.
   Future _inlineImport(AssetId id, Element link) {
     return readAsHtml(id, transform).catchError((error) {
-      transform.logger.error(
+      logger.error(
           "Failed to inline html import: $error", asset: id,
           span: link.sourceSpan);
     }).then((doc) {
       if (doc == null) return false;
-      new _UrlNormalizer(transform, id).visit(doc);
+      new _UrlNormalizer(transform, id, logger).visit(doc);
       return _visitImports(doc).then((_) {
         // _UrlNormalizer already ensures there is a library name.
         _extractScripts(doc, injectLibraryName: false);
@@ -169,12 +179,12 @@
       // TODO(jakemac): Move this warning to the linter once we can make it run
       // always (see http://dartbug.com/17199). Then hide this error and replace
       // with a comment pointing to the linter error (so we don't double warn).
-      transform.logger.warning(
+      logger.warning(
           "Failed to inline stylesheet: $error", asset: id,
           span: link.sourceSpan);
     }).then((css) {
       if (css == null) return;
-      css = new _UrlNormalizer(transform, id).visitCss(css);
+      css = new _UrlNormalizer(transform, id, logger).visitCss(css);
       var styleElement = new Element.tag('style')..text = css;
       // Copy over the extra attributes from the link tag to the style tag.
       // This adds support for no-shim, shim-shadowdom, etc.
@@ -268,8 +278,8 @@
       '${path.extension(id.path).substring(1)}';
   if (name.startsWith('lib/')) name = name.substring(4);
   name = name.split('/').map((part) {
-    part = part.replaceAll(INVALID_LIB_CHARS_REGEX, '_');
-    if (part.startsWith(NUM_REGEX)) part = '_${part}';
+    part = part.replaceAll(_INVALID_LIB_CHARS_REGEX, '_');
+    if (part.startsWith(_NUM_REGEX)) part = '_${part}';
     return part;
   }).join(".");
   return '${id.package}.${name}_$suffix';
@@ -326,7 +336,9 @@
   /// Whether or not the normalizer has changed something in the tree.
   bool changed = false;
 
-  _UrlNormalizer(transform, this.sourceId)
+  final TransformLogger logger;
+
+  _UrlNormalizer(transform, this.sourceId, this.logger)
       : transform = transform,
         topLevelPath =
           '../' * (transform.primaryInput.id.path.split('/').length - 2);
@@ -343,7 +355,19 @@
     if (!isCustomTagName(node.localName)) {
       node.attributes.forEach((name, value) {
         if (_urlAttributes.contains(name)) {
-          if (value != '' && !value.trim().startsWith(_BINDINGS)) {
+          if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) {
+            logger.warning(
+                'When using bindings with the "$name" attribute you may '
+                'experience errors in certain browsers. Please use the '
+                '"_$name" attribute instead. For more information, see '
+                'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId);
+          } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) {
+            logger.warning(
+                'The "$name" attribute is only supported when using bindings. '
+                'Please change to the "${name.substring(1)}" attribute.',
+                span: node.sourceSpan, asset: sourceId);
+          }
+          if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) {
             node.attributes[name] = _newUrl(value, node.sourceSpan);
             changed = changed || value != node.attributes[name];
           }
@@ -366,7 +390,6 @@
 
   static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true);
   static final _QUOTE = new RegExp('["\']', multiLine: true);
-  static final _BINDINGS = new RegExp(r'({{)|(\[\[)');
 
   /// Visit the CSS text and replace any relative URLs so we can inline it.
   // Ported from:
@@ -395,12 +418,12 @@
         var uri = directive.uri.stringValue;
         var span = _getSpan(file, directive.uri);
 
-        var id = uriToAssetId(sourceId, uri, transform.logger, span,
+        var id = uriToAssetId(sourceId, uri, logger, span,
             errorOnAbsolute: false);
         if (id == null) continue;
 
         var primaryId = transform.primaryInput.id;
-        var newUri = assetUrlFor(id, primaryId, transform.logger);
+        var newUri = assetUrlFor(id, primaryId, logger);
         if (newUri != uri) {
           output.edit(span.start.offset, span.end.offset, "'$newUri'");
         }
@@ -423,37 +446,53 @@
   }
 
   String _newUrl(String href, SourceSpan span) {
-    // Uri.parse blows up on invalid characters (like {{). Encoding the uri
-    // allows it to be parsed, which does the correct thing in the general case.
-    // This uri not used to build the new uri, so it never needs to be decoded.
-    var uri = Uri.parse(Uri.encodeFull(href));
+    // Placeholder for everything past the start of the first binding.
+    const placeholder = '_';
+    // We only want to parse the part of the href leading up to the first
+    // binding, anything after that is not informative.
+    var hrefToParse;
+    var firstBinding = href.indexOf(_BINDING_REGEX);
+    if (firstBinding == -1) {
+      hrefToParse = href;
+    } else if (firstBinding == 0) {
+      return href;
+    } else {
+      hrefToParse = '${href.substring(0, firstBinding)}$placeholder';
+    }
+
+    var uri = Uri.parse(hrefToParse);
     if (uri.isAbsolute) return href;
     if (!uri.scheme.isEmpty) return href;
     if (!uri.host.isEmpty) return href;
     if (uri.path.isEmpty) return href;  // Implies standalone ? or # in URI.
     if (path.isAbsolute(href)) return href;
 
-    var id = uriToAssetId(sourceId, href, transform.logger, span);
+    var id = uriToAssetId(sourceId, hrefToParse, logger, span);
     if (id == null) return href;
     var primaryId = transform.primaryInput.id;
 
-    if (id.path.startsWith('lib/')) {
-      return '${topLevelPath}packages/${id.package}/${id.path.substring(4)}';
+    // Build the new path, placing back any suffixes that we stripped earlier.
+    var prefix = (firstBinding == -1) ? id.path
+        : id.path.substring(0, id.path.length - placeholder.length);
+    var suffix = (firstBinding == -1) ? '' : href.substring(firstBinding);
+    var newPath = '$prefix$suffix';
+
+    if (newPath.startsWith('lib/')) {
+      return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}';
     }
 
-    if (id.path.startsWith('asset/')) {
-      return '${topLevelPath}assets/${id.package}/${id.path.substring(6)}';
+    if (newPath.startsWith('asset/')) {
+      return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}';
     }
 
     if (primaryId.package != id.package) {
       // Techincally we shouldn't get there
-      transform.logger.error("don't know how to include $id from $primaryId",
-          span: span);
+      logger.error("don't know how to include $id from $primaryId", span: span);
       return href;
     }
 
     var builder = path.url;
-    return builder.relative(builder.join('/', id.path),
+    return builder.relative(builder.join('/', newPath),
         from: builder.join('/', builder.dirname(primaryId.path)));
   }
 }
@@ -463,18 +502,20 @@
 ///
 /// Every one of these attributes is a URL in every context where it is used in
 /// the DOM. The comments show every DOM element where an attribute can be used.
+///
+/// The _* version of each attribute is also supported, see http://goo.gl/5av8cU
 const _urlAttributes = const [
-  'action',     // in form
-  'background', // in body
-  'cite',       // in blockquote, del, ins, q
-  'data',       // in object
-  'formaction', // in button, input
-  'href',       // in a, area, link, base, command
-  'icon',       // in command
-  'manifest',   // in html
-  'poster',     // in video
-  'src',        // in audio, embed, iframe, img, input, script, source, track,
-                //    video
+  'action', '_action',          // in form
+  'background', '_background',  // in body
+  'cite', '_cite',              // in blockquote, del, ins, q
+  'data', '_data',              // in object
+  'formaction', '_formaction',  // in button, input
+  'href', '_href',              // in a, area, link, base, command
+  'icon', '_icon',              // in command
+  'manifest', '_manifest',      // in html
+  'poster', '_poster',          // in video
+  'src', '_src',                // in audio, embed, iframe, img, input, script,
+                                //    source, track,video
 ];
 
 /// When inlining <link rel="stylesheet"> tags copy over all attributes to the
@@ -482,8 +523,9 @@
 const IGNORED_LINKED_STYLE_ATTRS =
     const ['charset', 'href', 'href-lang', 'rel', 'rev'];
 
-/// Global RegExp objects for validating generated library names.
-final INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]');
-final NUM_REGEX = new RegExp('[0-9]');
+/// Global RegExp objects.
+final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]');
+final _NUM_REGEX = new RegExp('[0-9]');
+final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))');
 
 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
diff --git a/pkg/polymer/lib/src/build/linter.dart b/pkg/polymer/lib/src/build/linter.dart
index 714e50e..3ec6637 100644
--- a/pkg/polymer/lib/src/build/linter.dart
+++ b/pkg/polymer/lib/src/build/linter.dart
@@ -7,6 +7,7 @@
 library polymer.src.build.linter;
 
 import 'dart:async';
+import 'dart:convert';
 
 import 'package:barback/barback.dart';
 import 'package:code_transformers/assets.dart';
@@ -16,6 +17,7 @@
 
 import 'common.dart';
 import 'utils.dart';
+import 'wrapped_logger.dart';
 
 /// A linter that checks for common Polymer errors and produces warnings to
 /// show on the editor or the command line. Leaves sources unchanged, but
@@ -34,11 +36,20 @@
     var id = primary.id;
     transform.addOutput(primary); // this phase is analysis only
     seen.add(id);
+    bool isEntryPoint = options.isHtmlEntryPoint(id);
+
+    var logger = options.releaseMode ? transform.logger :
+        new WrappedLogger(transform, convertErrorsToWarnings: true);
+
     return readPrimaryAsHtml(transform).then((document) {
-      return _collectElements(document, id, transform, seen).then((elements) {
-        bool isEntrypoint = options.isHtmlEntryPoint(id);
-        new _LinterVisitor(id, transform.logger, elements, isEntrypoint)
-            .run(document);
+      return _collectElements(document, id, transform, logger, seen)
+          .then((elements) {
+        new _LinterVisitor(id, logger, elements, isEntryPoint).run(document);
+
+        // Write out the logs collected by our [WrappedLogger].
+        if (options.injectBuildLogsInOutput && logger is WrappedLogger) {
+          return (logger as WrappedLogger).writeOutput();
+        }
       });
     });
   }
@@ -49,12 +60,14 @@
   /// first.
   Future<Map<String, _ElementSummary>> _collectElements(
       Document document, AssetId sourceId, Transform transform,
-      Set<AssetId> seen, [Map<String, _ElementSummary> elements]) {
+      TransformLogger logger, Set<AssetId> seen,
+      [Map<String, _ElementSummary> elements]) {
     if (elements == null) elements = <String, _ElementSummary>{};
-    return _getImportedIds(document, sourceId, transform)
+    return _getImportedIds(document, sourceId, transform, logger)
         // Note: the import order is relevant, so we visit in that order.
         .then((ids) => Future.forEach(ids,
-              (id) => _readAndCollectElements(id, transform, seen, elements)))
+              (id) => _readAndCollectElements(
+                  id, transform, logger, seen, elements)))
         .then((_) {
           if (sourceId.package == 'polymer' &&
               sourceId.path == 'lib/src/js/polymer/polymer.html' &&
@@ -62,23 +75,24 @@
             elements['polymer-element'] =
                 new _ElementSummary('polymer-element', null, null);
           }
-          return _addElements(document, transform.logger, elements);
+          return _addElements(document, logger, elements);
         })
         .then((_) => elements);
   }
 
   Future _readAndCollectElements(AssetId id, Transform transform,
-      Set<AssetId> seen, Map<String, _ElementSummary> elements) {
+      TransformLogger logger, Set<AssetId> seen,
+      Map<String, _ElementSummary> elements) {
     if (id == null || seen.contains(id)) return new Future.value(null);
     seen.add(id);
     return readAsHtml(id, transform, showWarnings: false).then(
-        (doc) => _collectElements(doc, id, transform, seen, elements));
+        (doc) => _collectElements(doc, id, transform, logger, seen, elements));
   }
 
   Future<List<AssetId>> _getImportedIds(
-      Document document, AssetId sourceId, Transform transform) {
+      Document document, AssetId sourceId, Transform transform,
+      TransformLogger logger) {
     var importIds = [];
-    var logger = transform.logger;
     for (var tag in document.querySelectorAll('link')) {
       if (tag.attributes['rel'] != 'import') continue;
       var href = tag.attributes['href'];
@@ -153,11 +167,11 @@
   bool _dartTagSeen = false;
   bool _polymerHtmlSeen = false;
   bool _polymerExperimentalHtmlSeen = false;
-  bool _isEntrypoint;
+  bool _isEntryPoint;
   Map<String, _ElementSummary> _elements;
 
   _LinterVisitor(
-      this._sourceId, this._logger, this._elements, this._isEntrypoint) {
+      this._sourceId, this._logger, this._elements, this._isEntryPoint) {
     // We normalize the map, so each element has a direct reference to any
     // element it extends from.
     for (var tag in _elements.values) {
@@ -183,7 +197,7 @@
   void run(Document doc) {
     visit(doc);
 
-    if (_isEntrypoint && !_dartTagSeen && !_polymerExperimentalHtmlSeen) {
+    if (_isEntryPoint && !_dartTagSeen && !_polymerExperimentalHtmlSeen) {
       _logger.warning(USE_INIT_DART, span: doc.body.sourceSpan);
     }
   }
@@ -281,7 +295,7 @@
 
     if (isDart) {
       if (_dartTagSeen) _logger.warning(ONLY_ONE_TAG, span: node.sourceSpan);
-      if (_isEntrypoint && _polymerExperimentalHtmlSeen) {
+      if (_isEntryPoint && _polymerExperimentalHtmlSeen) {
         _logger.warning(NO_DART_SCRIPT_AND_EXPERIMENTAL, span: node.sourceSpan);
       }
       _dartTagSeen = true;
@@ -456,4 +470,4 @@
     'the main document (for now).';
 
 const List<String> INTERNALLY_DEFINED_ELEMENTS = 
-    const ['auto-binding-dart', 'polymer-element'];
\ No newline at end of file
+    const ['auto-binding-dart', 'polymer-element'];
diff --git a/pkg/polymer/lib/src/build/log_injector.css b/pkg/polymer/lib/src/build/log_injector.css
new file mode 100644
index 0000000..f9ea7e3
--- /dev/null
+++ b/pkg/polymer/lib/src/build/log_injector.css
@@ -0,0 +1,59 @@
+/** Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+    for details. All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file. **/
+.build-logs {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+}
+.build-logs .log {
+  padding: 6px;
+  background: black;
+  color: white;
+  border: solid 1px #666;
+  margin-bottom: -1px;
+}
+.build-logs .fine {
+  color: green;
+}
+.build-logs .info {
+  color: yellow;
+}
+.build-logs .warning {
+  color: orange;
+}
+.build-logs .error {
+  color: red;
+}
+.build-logs .message {
+  font-size: 12px;
+}
+.build-logs .menu {
+  text-align: right;
+}
+.build-logs .menu div {
+  display: inline-block;
+  background: #666;
+  font-size: 16px;
+  font-weight: bold;
+  cursor: pointer;
+  border: solid 1px black;
+  padding: 6px 10px;
+}
+.build-logs .menu div.active {
+  background: black;
+}
+.build-logs .menu div .num {
+  color: white;
+}
+.build-logs .content {
+  max-height: 500px;
+  max-width: 500px;
+  font-size: 10px;
+}
+.build-logs .content > div {
+  display: none;
+}
+.build-logs .content > div.active {
+  display: block;
+}
diff --git a/pkg/polymer/lib/src/build/log_injector.dart b/pkg/polymer/lib/src/build/log_injector.dart
new file mode 100644
index 0000000..c1880ff
--- /dev/null
+++ b/pkg/polymer/lib/src/build/log_injector.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library provides a single function called injectLogs which when called
+/// will request a logs json file and build a small widget out of them which
+/// groups the logs by level.
+library polymer.build.log_injector;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+
+import 'package:path/path.dart' as path;
+
+class LogInjector {
+  Element selectedMenu;
+  Element selectedContent;
+
+  // Gets the logs from a url and inject them into the dom.
+  Future injectLogsFromUrl([String url]) {
+    if (url == null) url = '${Uri.base.path}._buildLogs';
+    return HttpRequest.getString(url).then((data) => injectLogs(data));
+  }
+
+  // Builds the html for the logs element given some logs, and injects that
+  // into the dom. Currently, we do not  use Polymer just to ensure that the
+  // page works regardless of the state of the app. Ideally, we could have
+  // multiple scripts running independently so we could ensure that this would
+  // always be running.
+  injectLogs(String data) {
+    // Group all logs by level.
+    var logsByLevel = {
+    };
+    JSON.decode(data).forEach((log) {
+      logsByLevel.putIfAbsent(log['level'], () => []);
+      logsByLevel[log['level']].add(log);
+    });
+    if (logsByLevel.isEmpty) return;
+
+    // Build the wrapper, menu, and content divs.
+
+    var menuWrapper = new DivElement()
+      ..classes.add('menu');
+    var contentWrapper = new DivElement()
+      ..classes.add('content');
+    var wrapperDiv = new DivElement()
+      ..classes.add('build-logs')
+      ..append(menuWrapper)
+      ..append(contentWrapper);
+
+    // For each log level, add a menu item, content section, and all the logs.
+    logsByLevel.forEach((level, logs) {
+      var levelClassName = level.toLowerCase();
+
+      // Add the menu item and content item.
+      var menuItem = new Element.html(
+          '<div class="$levelClassName">'
+          '$level <span class="num">(${logs.length})</span>'
+          '</div>');
+      menuWrapper.append(menuItem);
+      var contentItem = new DivElement()
+        ..classes.add(levelClassName);
+      contentWrapper.append(contentItem);
+
+      // Set up the click handlers.
+      menuItem.onClick.listen((_) {
+        if (selectedMenu == menuItem) {
+          selectedMenu = null;
+          selectedContent = null;
+        } else {
+          if (selectedMenu != null) {
+            selectedMenu.classes.remove('active');
+            selectedContent.classes.remove('active');
+          }
+
+          selectedMenu = menuItem;
+          selectedContent = contentItem;
+        }
+
+        menuItem.classes.toggle('active');
+        contentItem.classes.toggle('active');
+      });
+
+      // Add the logs to the content item.
+      for (var log in logs) {
+        var logHtml = new StringBuffer();
+        logHtml.write('<div class="log">');
+        logHtml.write(
+            '<div class="message $levelClassName">${log['message']}</div>');
+        var assetId = log['assetId'];
+        if (assetId != null) {
+          logHtml.write(
+              '<div class="asset">'
+              '  <span class="package">${assetId['package']}</span>:'
+              '  <span class="path">${assetId['path']}</span>''</div>');
+        }
+        var span = log['span'];
+        if (span != null) {
+          logHtml.write(
+              '<div class="span">'
+              '  <div class="location">${span['location']}</div>'
+              '  <code class="text">${span['text']}</code>''</div>');
+        }
+        logHtml.write('</div>');
+
+        contentItem.append(new Element.html(logHtml.toString()));
+      };
+    });
+
+    document.body.append(wrapperDiv);
+  }
+
+}
\ No newline at end of file
diff --git a/pkg/polymer/lib/src/build/script_compactor.dart b/pkg/polymer/lib/src/build/script_compactor.dart
index bc09c68..0245bcf 100644
--- a/pkg/polymer/lib/src/build/script_compactor.dart
+++ b/pkg/polymer/lib/src/build/script_compactor.dart
@@ -29,6 +29,7 @@
 
 import 'import_inliner.dart' show ImportInliner; // just for docs.
 import 'common.dart';
+import 'wrapped_logger.dart';
 
 /// Combines Dart script tags into a single script tag, and creates a new Dart
 /// file that calls the main function of each of the original script tags.
@@ -142,9 +143,11 @@
 
   _SubExpressionVisitor expressionVisitor;
 
-  _ScriptCompactor(Transform transform, this.options, this.resolvers)
+  _ScriptCompactor(Transform transform, options, this.resolvers)
       : transform = transform,
-        logger = transform.logger,
+        options = options,
+        logger = options.releaseMode ? transform.logger :
+          new WrappedLogger(transform, convertErrorsToWarnings: true),
         docId = transform.primaryInput.id,
         bootstrapId = transform.primaryInput.id.addExtension('_bootstrap.dart');
 
@@ -152,7 +155,13 @@
       _loadDocument()
       .then(_loadEntryLibraries)
       .then(_processHtml)
-      .then(_emitNewEntrypoint);
+      .then(_emitNewEntrypoint)
+      .then((_) {
+        // Write out the logs collected by our [WrappedLogger].
+        if (options.injectBuildLogsInOutput && logger is WrappedLogger) {
+          return (logger as WrappedLogger).writeOutput();
+        }
+      });
 
   /// Loads the primary input as an html document.
   Future _loadDocument() =>
@@ -427,6 +436,9 @@
       var url = assetUrlFor(id, bootstrapId, logger);
       if (url == null) continue;
       code.writeln("import '$url' as i$i;");
+      if (options.injectBuildLogsInOutput) {
+        code.writeln("import 'package:polymer/src/build/log_injector.dart';");
+      }
       prefixes[id] = 'i$i';
       i++;
     }
@@ -438,6 +450,11 @@
     code.write('  useGeneratedCode(');
     generator.writeStaticConfiguration(code);
     code.writeln(');');
+
+    if (options.injectBuildLogsInOutput) {
+      code.writeln('  new LogInjector().injectLogsFromUrl();');
+    }
+
     if (experimentalBootstrap) {
       code.write('  startPolymer([');
     } else {
@@ -459,6 +476,8 @@
     if (!experimentalBootstrap) {
       code.writeln('  i${entryLibraries.length - 1}.main();');
     }
+
+    // End of main().
     code.writeln('}');
     transform.addOutput(new Asset.fromString(bootstrapId, code.toString()));
 
@@ -466,7 +485,15 @@
     // Emit the bootstrap .dart file
     var srcUrl = path.url.basename(bootstrapId.path);
     document.body.nodes.add(parseFragment(
-          '<script type="application/dart" src="$srcUrl"></script>'));
+        '<script type="application/dart" src="$srcUrl"></script>'));
+
+    // Add the styles for the logger widget.
+    if (options.injectBuildLogsInOutput) {
+      document.head.append(parseFragment(
+          '<link rel="stylesheet" type="text/css"'
+              'href="packages/polymer/src/build/log_injector.css">'));
+    }
+
     transform.addOutput(new Asset.fromString(docId, document.outerHtml));
   }
 
diff --git a/pkg/polymer/lib/src/build/wrapped_logger.dart b/pkg/polymer/lib/src/build/wrapped_logger.dart
new file mode 100644
index 0000000..c732b75
--- /dev/null
+++ b/pkg/polymer/lib/src/build/wrapped_logger.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.src.build.wrapped_logger;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:barback/barback.dart';
+import 'package:source_span/source_span.dart';
+
+import 'common.dart' as common;
+
+/// A simple class to wrap one TransformLogger with another one that writes all
+/// logs to a file and then forwards the calls to the child.
+class WrappedLogger implements TransformLogger {
+  Transform _transform;
+  List<Map> _logs = new List<Map>();
+
+  bool convertErrorsToWarnings;
+
+  WrappedLogger(this._transform, {this.convertErrorsToWarnings: false});
+
+  void info(String message, {AssetId asset, SourceSpan span}) {
+    _transform.logger.info(message, asset: asset, span: span);
+    _addLog(asset, LogLevel.INFO, message, span);
+  }
+
+  void fine(String message, {AssetId asset, SourceSpan span}) {
+    _transform.logger.fine(message, asset: asset, span: span);
+    _addLog(asset, LogLevel.FINE, message, span);
+  }
+
+  void warning(String message, {AssetId asset, SourceSpan span}) {
+    _transform.logger.warning(message, asset: asset, span: span);
+    _addLog(asset, LogLevel.WARNING, message, span);
+  }
+
+  void error(String message, {AssetId asset, SourceSpan span}) {
+    if (convertErrorsToWarnings) {
+      _transform.logger.warning(message, asset: asset, span: span);
+    } else {
+      _transform.logger.error(message, asset: asset, span: span);
+    }
+    _addLog(asset, LogLevel.ERROR, message, span);
+  }
+
+  /// Outputs the log data to a JSON serialized file.
+  Future writeOutput() {
+    return getNextLogAssetPath().then((path) {
+      _transform.addOutput(new Asset.fromString(path, JSON.encode(_logs)));
+    });
+  }
+
+  // Each phase outputs a new log file with an incrementing # appended, this
+  // figures out the next # to use.
+  Future<String> getNextLogAssetPath([int nextNumber = 1]) {
+    var nextAssetPath = _transform.primaryInput.id.addExtension(
+        '${common.LOG_EXTENSION}.$nextNumber');
+    return _transform.hasInput(nextAssetPath).then((exists) {
+      if (!exists) return nextAssetPath;
+      return getNextLogAssetPath(++nextNumber);
+    });
+  }
+
+  // Combines all existing ._buildLogs.* files into a single ._buildLogs file.
+  static Future combineLogFiles(
+      Transform transform, [int nextNumber = 1, List<Map> logs]) {
+    if (logs == null) logs = new List<Map>();
+    var primaryInputId = transform.primaryInput.id;
+    var nextAssetPath =
+        primaryInputId.addExtension('${common.LOG_EXTENSION}.$nextNumber');
+    return transform.readInputAsString(nextAssetPath).then(
+        (data) {
+          logs.addAll(JSON.decode(data));
+          return combineLogFiles(transform, ++nextNumber, logs);
+        },
+        onError: (_) {
+          transform.addOutput(new Asset.fromString(
+              primaryInputId.addExtension(common.LOG_EXTENSION),
+              JSON.encode(logs)));
+        });
+  }
+
+  void _addLog(AssetId assetId, LogLevel level, String message,
+               SourceSpan span) {
+    var data = {
+        'level': level.name,
+        'message': message,
+    };
+    if (assetId != null) {
+      data['assetId'] = {
+          'package': assetId.package,
+          'path': assetId.path,
+      };
+    }
+    if (span != null) {
+      data['span'] = {
+          'location': span.start.toolString,
+          'text': new HtmlEscape().convert(span.text),
+      };
+    }
+    _logs.add(data);
+  }
+}
\ No newline at end of file
diff --git a/pkg/polymer/lib/transformer.dart b/pkg/polymer/lib/transformer.dart
index ebbfc12..997357a 100644
--- a/pkg/polymer/lib/transformer.dart
+++ b/pkg/polymer/lib/transformer.dart
@@ -7,11 +7,13 @@
 
 import 'package:barback/barback.dart';
 import 'package:observe/transformer.dart';
+import 'package:path/path.dart' as path;
 
 import 'src/build/build_filter.dart';
 import 'src/build/common.dart';
 import 'src/build/import_inliner.dart';
 import 'src/build/linter.dart';
+import 'src/build/build_log_combiner.dart';
 import 'src/build/polyfill_injector.dart';
 import 'src/build/script_compactor.dart';
 
@@ -40,12 +42,16 @@
   bool jsOption = args['js'];
   bool csp = args['csp'] == true; // defaults to false
   bool lint = args['lint'] != false; // defaults to true
+  bool injectBuildLogs =
+      !releaseMode && args['inject_build_logs_in_output'] != false;
   return new TransformOptions(
       entryPoints: _readEntrypoints(args['entry_points']),
+      inlineStylesheets: _readInlineStylesheets(args['inline_stylesheets']),
       directlyIncludeJS: jsOption == null ? releaseMode : jsOption,
       contentSecurityPolicy: csp,
       releaseMode: releaseMode,
-      lint: lint);
+      lint: lint,
+      injectBuildLogsInOutput: injectBuildLogs);
 }
 
 _readEntrypoints(value) {
@@ -67,6 +73,41 @@
   return entryPoints;
 }
 
+Map<String, bool> _readInlineStylesheets(settingValue) {
+  if (settingValue == null) return null;
+  var inlineStylesheets = {};
+  bool error = false;
+  if (settingValue is Map) {
+    settingValue.forEach((key, value) {
+      if (value is! bool || key is! String) {
+        error = true;
+        return;
+      }
+      if (key == 'default') {
+        inlineStylesheets[key] = value;
+        return;
+      };
+      key = systemToAssetPath(key);
+      // Special case package urls, convert to AssetId and use serialized form.
+      var packageMatch = _PACKAGE_PATH_REGEX.matchAsPrefix(key);
+      if (packageMatch != null) {
+        var package = packageMatch[1];
+        var path = 'lib/${packageMatch[2]}';
+        key = new AssetId(package, path).toString();
+      }
+      inlineStylesheets[key] = value;
+    });
+  } else if (settingValue is bool) {
+    inlineStylesheets['default'] = settingValue;
+  } else {
+    error = true;
+  }
+  if (error) {
+    print('Invalid value for "inline_stylesheets" in the polymer transformer.');
+  }
+  return inlineStylesheets;
+}
+
 /// Create deploy phases for Polymer. Note that inlining HTML Imports
 /// comes first (other than linter, if [options.linter] is enabled), which
 /// allows the rest of the HTML-processing phases to operate only on HTML that
@@ -82,6 +123,9 @@
     [new ObservableTransformer()],
     [new ScriptCompactor(options, sdkDir: sdkDir)],
     [new PolyfillInjector(options)],
-    [new BuildFilter(options)]
+    [new BuildFilter(options)],
+    [new BuildLogCombiner(options)],
   ]);
 }
+
+final RegExp _PACKAGE_PATH_REGEX = new RegExp(r'packages\/([^\/]+)\/(.*)');
diff --git a/pkg/polymer/pubspec.yaml b/pkg/polymer/pubspec.yaml
index a2b8185..e0e4d2f 100644
--- a/pkg/polymer/pubspec.yaml
+++ b/pkg/polymer/pubspec.yaml
@@ -1,5 +1,5 @@
 name: polymer
-version: 0.12.0+5
+version: 0.12.1-dev
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 description: >
   Polymer.dart is a new type of library for the web, built on top of Web
@@ -7,7 +7,7 @@
   browsers.
 homepage: https://www.dartlang.org/polymer-dart/
 dependencies:
-  analyzer: '>=0.15.6 <0.16.0'
+  analyzer: '>=0.15.6 <0.22.0'
   args: '>=0.10.0 <0.13.0'
   barback: '>=0.14.2 <0.16.0'
   browser: '>=0.10.0 <0.11.0'
@@ -24,7 +24,7 @@
   web_components: '>=0.5.0 <0.6.0'
   yaml: '>=0.9.0 <3.0.0'
 dev_dependencies:
-  unittest: '>=0.10.0 <0.11.0'
+  unittest: '>=0.10.0 <0.12.0'
 transformers:
 - polymer/src/build/mirrors_remover:
     $include: lib/polymer.dart
diff --git a/pkg/polymer/test/build/build_log_combiner_test.dart b/pkg/polymer/test/build/build_log_combiner_test.dart
new file mode 100644
index 0000000..6f98e9a
--- /dev/null
+++ b/pkg/polymer/test/build/build_log_combiner_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.test.build.build_log_combiner_test;
+
+import 'package:polymer/src/build/common.dart';
+import 'package:polymer/src/build/build_log_combiner.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+import 'common.dart';
+
+final options = new TransformOptions(injectBuildLogsInOutput: true);
+final phases = [[new BuildLogCombiner(options)]];
+
+void main() {
+  useCompactVMConfiguration();
+
+  testPhases('combines multiple logs', phases, {
+      'a|web/test.html': '<!DOCTYPE html><html></html>',
+      'a|web/test.html$LOG_EXTENSION.1': '[${_logString('Info', 'foo')}]',
+      'a|web/test.html$LOG_EXTENSION.2': '[${_logString('Warning', 'bar')}]',
+      'a|web/test.html$LOG_EXTENSION.3': '[${_logString('Error', 'baz')}]',
+  }, {
+      'a|web/test.html': '<!DOCTYPE html><html></html>',
+      'a|web/test.html$LOG_EXTENSION':
+      '[${_logString('Info', 'foo')},'
+       '${_logString('Warning', 'bar')},'
+       '${_logString('Error', 'baz')}]',
+  });
+}
+
+String _logString(String level, String message) =>
+  '{"level":"$level","message":"$message"}';
\ No newline at end of file
diff --git a/pkg/polymer/test/build/common.dart b/pkg/polymer/test/build/common.dart
index 14fc1b5..b6da27a 100644
--- a/pkg/polymer/test/build/common.dart
+++ b/pkg/polymer/test/build/common.dart
@@ -7,6 +7,7 @@
 import 'dart:async';
 
 import 'package:barback/barback.dart';
+import 'package:polymer/src/build/common.dart';
 import 'package:stack_trace/stack_trace.dart';
 import 'package:unittest/unittest.dart';
 
@@ -128,7 +129,6 @@
 testPhases(String testName, List<List<Transformer>> phases,
     Map<String, String> inputFiles, Map<String, String> expectedFiles,
     [List<String> expectedMessages, bool solo = false]) {
-
   // Include mock versions of the polymer library that can be used to test
   // resolver-based code generation.
   POLYMER_MOCKS.forEach((file, contents) { inputFiles[file] = contents; });
@@ -144,6 +144,45 @@
   testPhases(testName, phases, inputFiles, expectedFiles, expectedMessages,
       true);
 
+
+// Similar to testPhases, but tests all the cases around log behaviour in
+// different modes. Any expectedFiles with [LOG_EXTENSION] will be removed from
+// the expectation as appropriate, and any error logs will be changed to expect
+// warning logs as appropriate.
+testLogOutput(Function buildPhase, String testName,
+              Map<String, String> inputFiles, Map<String, String> expectedFiles,
+              [List<String> expectedMessages, bool solo = false]) {
+
+  final transformOptions = [
+      new TransformOptions(injectBuildLogsInOutput: false, releaseMode: false),
+      new TransformOptions(injectBuildLogsInOutput: false, releaseMode: true),
+      new TransformOptions(injectBuildLogsInOutput: true, releaseMode: false),
+      new TransformOptions(injectBuildLogsInOutput: true, releaseMode: true),
+  ];
+
+  for (var options in transformOptions) {
+    var phase = buildPhase(options);
+    var actualExpectedFiles = {};
+    expectedFiles.forEach((file, content) {
+      if (file.contains(LOG_EXTENSION)
+      && (!options.injectBuildLogsInOutput || options.releaseMode)) {
+        return;
+      }
+      actualExpectedFiles[file] = content;
+    });
+    var fullTestName = '$testName: '
+    'injectLogs=${options.injectBuildLogsInOutput} '
+    'releaseMode=${options.releaseMode}';
+    testPhases(
+        fullTestName, [[phase]], inputFiles,
+        actualExpectedFiles,
+        expectedMessages.map((m) =>
+            options.releaseMode ? m : m.replaceFirst('error:', 'warning:'))
+            .toList(),
+        solo);
+  }
+}
+
 /// Generate an expected ._data file, where all files are assumed to be in the
 /// same [package].
 String expectedData(List<String> urls, {package: 'a', experimental: false}) {
diff --git a/pkg/polymer/test/build/import_inliner_test.dart b/pkg/polymer/test/build/import_inliner_test.dart
index 3e7c54c..edbaa0e 100644
--- a/pkg/polymer/test/build/import_inliner_test.dart
+++ b/pkg/polymer/test/build/import_inliner_test.dart
@@ -4,7 +4,7 @@
 
 library polymer.test.build.import_inliner_test;
 
-import 'dart:convert' show JSON;
+import 'dart:convert';
 import 'package:polymer/src/build/common.dart';
 import 'package:polymer/src/build/import_inliner.dart';
 import 'package:unittest/compact_vm_config.dart';
@@ -654,30 +654,47 @@
           '<polymer-element>3</polymer-element></body></html>',
     });
 
-  testPhases("missing styles don't throw errors and are not inlined", phases, {
-      'a|web/test.html':
-          '<!DOCTYPE html><html><head>'
-          '<link rel="stylesheet" href="foo.css">'
-          '</head></html>',
-    }, {
-      'a|web/test.html':
-          '<!DOCTYPE html><html><head></head><body>'
-          '<link rel="stylesheet" href="foo.css">'
-          '</body></html>',
-    }, [
-      'warning: Failed to inline stylesheet: '
-          'Could not find asset a|web/foo.css. (web/test.html 0 27)',
-    ]);
+  testLogOutput(
+      (options) => new ImportInliner(options),
+      "missing styles don't throw errors and are not inlined", {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>'
+            '<link rel="stylesheet" href="foo.css">'
+            '</head></html>',
+      }, {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head></head><body>'
+            '<link rel="stylesheet" href="foo.css">'
+            '</body></html>',
+      }, [
+        'warning: Failed to inline stylesheet: '
+            'Could not find asset a|web/foo.css. (web/test.html 0 27)',
+      ]);
 
-  testPhases("missing html imports throw errors", phases, {
-      'a|web/test.html':
-          '<!DOCTYPE html><html><head>'
-          '<link rel="import" href="foo.html">'
-          '</head></html>',
-    }, {}, [
-      'error: Failed to inline html import: '
-          'Could not find asset a|web/foo.html. (web/test.html 0 27)',
-    ]);
+  testLogOutput(
+      (options) => new ImportInliner(options),
+      "missing html imports throw errors", {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>'
+            '<link rel="import" href="foo.html">'
+            '</head></html>',
+      }, {
+        'a|web/test.html._buildLogs.1':
+          '[{'
+            '"level":"Error",'
+            '"message":"Failed to inline html import: '
+              'Could not find asset a|web/foo.html.",'
+            '"assetId":{"package":"a","path":"web/foo.html"},'
+            '"span":{'
+              '"location":"web/test.html:1:28",'
+              '"text":"${new HtmlEscape().convert(
+                '<link rel="import" href="foo.html">')}"'
+              '}'
+            '}]',
+      }, [
+        'error: Failed to inline html import: '
+            'Could not find asset a|web/foo.html. (web/test.html 0 27)',
+      ]);
 }
 
 void stylesheetTests() {
@@ -864,6 +881,57 @@
        'a|web/bar.css':
            'h2 { font-size: 35px; }',
      });
+
+  testPhases(
+      'can configure default stylesheet inlining',
+      [[new ImportInliner(new TransformOptions(
+          inlineStylesheets: {'default': false}))]], {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head></head><body>'
+            '<link rel="stylesheet" href="foo.css">'
+            '</body></html>',
+        'a|web/foo.css':
+            'h1 { font-size: 70px; }',
+      }, {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head></head><body>'
+            '<link rel="stylesheet" href="foo.css">'
+            '</body></html>',
+      });
+
+  testPhases(
+      'can override default stylesheet inlining',
+      [[new ImportInliner(new TransformOptions(
+          inlineStylesheets: {
+              'default': false,
+              'web/foo.css': true,
+              'b|lib/baz.css': true,
+          }))]],
+      {
+          'a|web/test.html':
+            '<!DOCTYPE html><html><head></head><body>'
+            '<link rel="stylesheet" href="bar.css">'
+            '<link rel="stylesheet" href="foo.css">'
+            '<link rel="stylesheet" href="packages/b/baz.css">'
+            '<link rel="stylesheet" href="packages/c/buz.css">'
+            '</body></html>',
+          'a|web/foo.css':
+            'h1 { font-size: 70px; }',
+          'a|web/bar.css':
+            'h1 { font-size: 35px; }',
+          'b|lib/baz.css':
+            'h1 { font-size: 20px; }',
+          'c|lib/buz.css':
+            'h1 { font-size: 10px; }',
+      }, {
+          'a|web/test.html':
+            '<!DOCTYPE html><html><head></head><body>'
+            '<link rel="stylesheet" href="bar.css">'
+            '<style>h1 { font-size: 70px; }</style>'
+            '<style>h1 { font-size: 20px; }</style>'
+            '<link rel="stylesheet" href="packages/c/buz.css">'
+            '</body></html>',
+      });
 }
 
 void urlAttributeTests() {
@@ -898,7 +966,7 @@
       'a|web/foo/test.html':
           '<img src="{{bar}}">'
           '<img src="[[bar]]">',
-  }, {
+    }, {
       'a|web/test.html':
           '<!DOCTYPE html><html><head></head><body>'
           '<img src="{{bar}}">'
@@ -907,7 +975,7 @@
       'a|web/foo/test.html':
           '<img src="{{bar}}">'
           '<img src="[[bar]]">',
-  });
+    });
 
   testPhases('relative paths followed by bindings are normalized', phases, {
       'a|web/test.html':
@@ -924,6 +992,68 @@
           '<img src="foo/{{bar}}">'
           '</body></html>',
     });
+
+  testPhases('relative paths in _* attributes are normalized', phases, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head>'
+          '<link rel="import" href="foo/test.html">'
+          '</head></html>',
+      'a|web/foo/test.html':
+          '<img _src="./{{bar}}">'
+          '<a _href="./{{bar}}">test</a>',
+    }, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<img _src="foo/{{bar}}">'
+          '<a _href="foo/{{bar}}">test</a>'
+          '</body></html>',
+    });
+
+
+  testLogOutput(
+      (options) => new ImportInliner(options),
+      'warnings are given about _* attributes', {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head></head><body>'
+            '<img src="foo/{{bar}}">'
+            '<a _href="foo/bar">test</a>'
+            '</body></html>',
+      }, {}, [
+          'warning: When using bindings with the "src" attribute you may '
+              'experience errors in certain browsers. Please use the "_src" '
+              'attribute instead. For more information, see '
+              'http://goo.gl/5av8cU (web/test.html 0 40)',
+          'warning: The "_href" attribute is only supported when using '
+              'bindings. Please change to the "href" attribute. '
+              '(web/test.html 0 63)',
+
+      ]);
+
+  testPhases('arbitrary bindings can exist in paths', phases, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<img src="./{{(bar[2] + baz[\'foo\']) * 14 / foobar() - 0.5}}.jpg">'
+          '<img src="./[[bar[2]]].jpg">'
+          '</body></html>',
+    }, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<img src="{{(bar[2] + baz[\'foo\']) * 14 / foobar() - 0.5}}.jpg">'
+          '<img src="[[bar[2]]].jpg">'
+          '</body></html>',
+    });
+
+  testPhases('multiple bindings can exist in paths', phases, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<img src="./{{bar[0]}}/{{baz[1]}}.{{extension}}">'
+          '</body></html>',
+    }, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<img src="{{bar[0]}}/{{baz[1]}}.{{extension}}">'
+          '</body></html>',
+    });
 }
 
 void entryPointTests() {
@@ -973,4 +1103,4 @@
         '<script rel="import" href="../../packages/b/bar/bar.js"></script>'
         '</body></html>',
   });
-}
+}
\ No newline at end of file
diff --git a/pkg/polymer/test/build/linter_test.dart b/pkg/polymer/test/build/linter_test.dart
index 3bff05c..a834f9e 100644
--- a/pkg/polymer/test/build/linter_test.dart
+++ b/pkg/polymer/test/build/linter_test.dart
@@ -4,6 +4,8 @@
 
 library polymer.test.linter_test;
 
+import 'dart:convert';
+
 import 'package:polymer/src/build/common.dart';
 import 'package:polymer/src/build/linter.dart';
 import 'package:unittest/unittest.dart';
@@ -645,14 +647,49 @@
           </svg>
           '''.replaceAll('            ', ''),
     }, []);
+
+  group('output logs to file',  () {
+    final outputLogsPhases = [[new Linter(
+        new TransformOptions(injectBuildLogsInOutput: true,
+            releaseMode: false))]];
+
+    testPhases("logs are output to file", outputLogsPhases, {
+        'a|web/test.html': '<!DOCTYPE html><html>\n'
+          '<polymer-element name="x-a"></polymer-element>'
+          '<script type="application/dart" src="foo.dart">'
+          '</script>'
+          '<script src="packages/browser/dart.js"></script>'
+          '</html>',
+      }, {
+        'a|web/test.html._buildLogs.1':
+          '[{'
+            '"level":"Warning",'
+            '"message":${JSON.encode(usePolymerHtmlMessage(0))},'
+            '"span":{'
+              '"location":"web/test.html:2:1",'
+              '"text":'
+                '"${new HtmlEscape().convert('<polymer-element name="x-a">')}"'
+            '}'
+          '}]',
+    }, [
+        // Logs should still make it to barback too.
+        'warning: ${usePolymerHtmlMessage(0)} (web/test.html 1 0)',
+    ]);
+  });
 }
 
 _testLinter(String name, Map inputFiles, List outputMessages,
     [bool solo = false]) {
-  var linter = new Linter(new TransformOptions());
   var outputFiles = {};
   if (outputMessages.every((m) => m.startsWith('warning:'))) {
     inputFiles.forEach((k, v) => outputFiles[k] = v);
   }
-  testPhases(name, [[linter]], inputFiles, outputFiles, outputMessages, solo);
+  if (outputMessages.isEmpty) {
+    var linter = new Linter(new TransformOptions());
+    testPhases(name, [[linter]], inputFiles, outputFiles, outputMessages, solo);
+  } else {
+    testLogOutput(
+        (options) => new Linter(options), name, inputFiles, outputFiles,
+        outputMessages, solo);
+  }
 }
diff --git a/pkg/polymer/test/build/log_injector_test.dart b/pkg/polymer/test/build/log_injector_test.dart
new file mode 100644
index 0000000..34f78eb
--- /dev/null
+++ b/pkg/polymer/test/build/log_injector_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:html';
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+import 'package:polymer/src/build/log_injector.dart';
+
+
+main() {
+
+  useHtmlConfiguration();
+
+  setUp(() => new LogInjector().injectLogs(
+      '''[
+          {"level": "Info", "message": "foo"},
+          {"level": "Warning", "message": "bar"},
+          {"level": "Error", "message": "baz"}
+      ]'''
+  ));
+
+  test('can inject a functioning log widget', () {
+    var logsElement = document.querySelector(".build-logs");
+    expect(logsElement, isNotNull);
+
+    var menuElements = logsElement.querySelectorAll(".menu > div");
+    expect(menuElements.length, 3);
+    var contentElements = logsElement.querySelectorAll(".content > div");
+    expect(contentElements.length, 3);
+
+    var expectedClasses = ['info', 'warning', 'error'];
+
+    // Check initial setup.
+    for (var i = 0; i < menuElements.length; ++i) {
+      expect(menuElements[i].classes.contains(expectedClasses[i]), true);
+      expect(menuElements[i].classes.contains('active'), false);
+      expect(contentElements[i].classes.contains(expectedClasses[i]), true);
+      expect(contentElements[i].classes.contains('active'), false);
+      expect(contentElements[i].querySelectorAll('.log').length, 1);
+    }
+
+    // Test clicking each of the tabs.
+    for (var i = 0; i < menuElements.length; ++i) {
+      menuElements[i].click();
+      for (var j = 0; j < menuElements.length; ++j) {
+        expect(menuElements[j].classes.contains('active'), j == i);
+        expect(contentElements[j].classes.contains('active'), j == i);
+      }
+    }
+
+    // Test toggling same tab.
+    expect(menuElements[2].classes.contains('active'), true);
+    menuElements[2].click();
+    expect(menuElements[2].classes.contains('active'), false);
+    expect(contentElements[2].classes.contains('active'), false);
+  });
+}
diff --git a/pkg/polymer/test/build/script_compactor_test.dart b/pkg/polymer/test/build/script_compactor_test.dart
index ba53630..60dc585 100644
--- a/pkg/polymer/test/build/script_compactor_test.dart
+++ b/pkg/polymer/test/build/script_compactor_test.dart
@@ -4,6 +4,8 @@
 
 library polymer.test.build.script_compactor_test;
 
+import 'dart:convert';
+
 import 'package:code_transformers/tests.dart' show testingDartSdkDirectory;
 import 'package:polymer/src/build/common.dart';
 import 'package:polymer/src/build/script_compactor.dart';
@@ -20,6 +22,7 @@
   group('initializers', () => initializerTests(phases));
   group('experimental', () => initializerTestsExperimental(phases));
   group('codegen', () => codegenTests(phases));
+  group('log element injection', logElementInjectionTests);
 }
 
 initializerTests(phases) {
@@ -153,43 +156,61 @@
           '''.replaceAll('\n          ', '\n'),
     });
 
-  testPhases('invalid const expression', phases, {
-      'a|web/test.html':
-          '<!DOCTYPE html><html><head>',
-      'a|web/test.html._data': expectedData(['web/a.dart']),
-      'a|web/a.dart':
-          'library a;\n'
-          'import "package:polymer/polymer.dart";\n'
-          '@CustomTag("\${x}-foo")\n' // invalid, x is not defined
-          'class XFoo extends PolymerElement {\n'
-          '}\n'
-          'main(){}',
-    }, {
-      'a|web/test.html_bootstrap.dart':
-          '''$MAIN_HEADER
-          import 'a.dart' as i0;
-          ${DEFAULT_IMPORTS.join('\n')}
-          import 'a.dart' as smoke_0;
-          import 'package:polymer/polymer.dart' as smoke_1;
 
-          void main() {
-            useGeneratedCode(new StaticConfiguration(
-                checkedMode: false,
-                parents: {
-                  smoke_0.XFoo: smoke_1.PolymerElement,
-                },
-                declarations: {
-                  smoke_0.XFoo: {},
-                }));
-            configureForDeployment([]);
-            i0.main();
-          }
-          '''.replaceAll('\n          ', '\n'),
+  testLogOutput(
+      (options) =>
+          new ScriptCompactor(options, sdkDir: testingDartSdkDirectory),
+      'invalid const expression logs', {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>',
+        'a|web/test.html._data': expectedData(['web/a.dart']),
+        'a|web/a.dart':
+            'library a;\n'
+            'import "package:polymer/polymer.dart";\n'
+            '@CustomTag("\${x}-foo")\n' // invalid, x is not defined
+            'class XFoo extends PolymerElement {\n'
+            '}\n'
+            'main(){}',
+      }, {}, [
+        'warning: The parameter to @CustomTag seems to be invalid. '
+        '(web/a.dart 2 11)',
+      ]);
 
-    }, [
-      'warning: The parameter to @CustomTag seems to be invalid. '
-      '(web/a.dart 2 11)',
-    ]);
+  testPhases(
+      'invalid const expression', phases, {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>',
+        'a|web/test.html._data': expectedData(['web/a.dart']),
+        'a|web/a.dart':
+            'library a;\n'
+            'import "package:polymer/polymer.dart";\n'
+            '@CustomTag("\${x}-foo")\n' // invalid, x is not defined
+            'class XFoo extends PolymerElement {\n'
+            '}\n'
+            'main(){}',
+      }, {
+        'a|web/test.html_bootstrap.dart':
+            '''$MAIN_HEADER
+            import 'a.dart' as i0;
+            ${DEFAULT_IMPORTS.join('\n')}
+            import 'a.dart' as smoke_0;
+            import 'package:polymer/polymer.dart' as smoke_1;
+
+            void main() {
+              useGeneratedCode(new StaticConfiguration(
+                  checkedMode: false,
+                  parents: {
+                    smoke_0.XFoo: smoke_1.PolymerElement,
+                  },
+                  declarations: {
+                    smoke_0.XFoo: {},
+                  }));
+              configureForDeployment([]);
+              i0.main();
+            }
+            '''.replaceAll('\n            ', '\n'),
+
+      });
 
   testPhases('no polymer import (no warning, but no crash either)', phases, {
       'a|web/test.html':
@@ -465,71 +486,107 @@
           '''.replaceAll('\n          ', '\n'),
     });
 
-  testPhases('invalid const expression', phases, {
-      'a|web/test.html':
-          '<!DOCTYPE html><html><head>',
-      'a|web/test.html._data': expectedData(['web/a.dart'], experimental: true),
-      'a|web/a.dart':
-          'library a;\n'
-          'import "package:polymer/polymer.dart";\n'
-          '@CustomTag("\${x}-foo")\n' // invalid, x is not defined
-          'class XFoo extends PolymerElement {\n'
-          '}\n'
-          'main(){}',
-    }, {
-      'a|web/test.html_bootstrap.dart':
-          '''$MAIN_HEADER
-          import 'a.dart' as i0;
-          ${DEFAULT_IMPORTS.join('\n')}
-          import 'a.dart' as smoke_0;
-          import 'package:polymer/polymer.dart' as smoke_1;
+  testLogOutput(
+      (options) =>
+          new ScriptCompactor(options, sdkDir: testingDartSdkDirectory),
+      'invalid const expression logs', {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>',
+        'a|web/test.html._data':
+            expectedData(['web/a.dart'], experimental: true),
+        'a|web/a.dart':
+            'library a;\n'
+            'import "package:polymer/polymer.dart";\n'
+            '@CustomTag("\${x}-foo")\n' // invalid, x is not defined
+            'class XFoo extends PolymerElement {\n'
+            '}\n'
+            'main(){}',
+      }, {}, [
+        'warning: The parameter to @CustomTag seems to be invalid. '
+        '(web/a.dart 2 11)',
+        'warning: $NO_INITIALIZERS_ERROR',
+      ]);
 
-          void main() {
-            useGeneratedCode(new StaticConfiguration(
-                checkedMode: false,
-                parents: {
-                  smoke_0.XFoo: smoke_1.PolymerElement,
-                },
-                declarations: {
-                  smoke_0.XFoo: {},
-                }));
-            startPolymer([]);
-          }
-          '''.replaceAll('\n          ', '\n'),
+  testPhases(
+      'invalid const expression', phases, {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>',
+        'a|web/test.html._data':
+            expectedData(['web/a.dart'], experimental: true),
+        'a|web/a.dart':
+            'library a;\n'
+            'import "package:polymer/polymer.dart";\n'
+            '@CustomTag("\${x}-foo")\n' // invalid, x is not defined
+            'class XFoo extends PolymerElement {\n'
+            '}\n'
+            'main(){}',
+      }, {
+        'a|web/test.html_bootstrap.dart':
+            '''$MAIN_HEADER
+            import 'a.dart' as i0;
+            ${DEFAULT_IMPORTS.join('\n')}
+            import 'a.dart' as smoke_0;
+            import 'package:polymer/polymer.dart' as smoke_1;
 
-    }, [
-      'warning: The parameter to @CustomTag seems to be invalid. '
-      '(web/a.dart 2 11)',
-      'warning: $NO_INITIALIZERS_ERROR',
-    ]);
+            void main() {
+              useGeneratedCode(new StaticConfiguration(
+                  checkedMode: false,
+                  parents: {
+                    smoke_0.XFoo: smoke_1.PolymerElement,
+                  },
+                  declarations: {
+                    smoke_0.XFoo: {},
+                  }));
+              startPolymer([]);
+            }
+            '''.replaceAll('\n            ', '\n'),
+      });
 
-  testPhases('no polymer import (no warning, but no crash either)', phases, {
-      'a|web/test.html':
-          '<!DOCTYPE html><html><head>',
-      'a|web/test.html._data': expectedData(['web/a.dart'], experimental: true),
-      'a|web/a.dart':
-          'library a;\n'
-          'import "package:polymer/polymer.broken.import.dart";\n'
-          '@CustomTag("x-foo")\n'
-          'class XFoo extends PolymerElement {\n'
-          '}\n'
-          'main(){}',
-    }, {
-      'a|web/test.html_bootstrap.dart':
-          '''$MAIN_HEADER
-          import 'a.dart' as i0;
-          ${DEFAULT_IMPORTS.join('\n')}
+  testLogOutput(
+      (options) =>
+          new ScriptCompactor(options, sdkDir: testingDartSdkDirectory),
+      'no polymer import logs', {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>',
+        'a|web/test.html._data': expectedData(['web/a.dart'], experimental: true),
+        'a|web/a.dart':
+            'library a;\n'
+            'import "package:polymer/polymer.broken.import.dart";\n'
+            '@CustomTag("x-foo")\n'
+            'class XFoo extends PolymerElement {\n'
+            '}\n'
+            'main(){}',
+      }, {}, [
+        'warning: $NO_INITIALIZERS_ERROR',
+      ]);
 
-          void main() {
-            useGeneratedCode(new StaticConfiguration(
-                checkedMode: false));
-            startPolymer([]);
-          }
-          '''.replaceAll('\n          ', '\n'),
+  testPhases(
+      'no polymer import', phases, {
+        'a|web/test.html':
+            '<!DOCTYPE html><html><head>',
+        'a|web/test.html._data':
+            expectedData(['web/a.dart'], experimental: true),
+        'a|web/a.dart':
+            'library a;\n'
+            'import "package:polymer/polymer.broken.import.dart";\n'
+            '@CustomTag("x-foo")\n'
+            'class XFoo extends PolymerElement {\n'
+            '}\n'
+            'main(){}',
+      }, {
+        'a|web/test.html_bootstrap.dart':
+            '''$MAIN_HEADER
+            import 'a.dart' as i0;
+            ${DEFAULT_IMPORTS.join('\n')}
 
-    }, [
-      'warning: $NO_INITIALIZERS_ERROR',
-    ]);
+            void main() {
+              useGeneratedCode(new StaticConfiguration(
+                  checkedMode: false));
+              startPolymer([]);
+            }
+            '''.replaceAll('\n            ', '\n'),
+
+      });
 
   testPhases('several scripts', phases, {
       'a|web/test.html':
@@ -1093,3 +1150,44 @@
     });
 }
 
+void logElementInjectionTests() {
+  final outputLogsPhases = [[new ScriptCompactor(
+      new TransformOptions(injectBuildLogsInOutput: true, releaseMode: false),
+      sdkDir: testingDartSdkDirectory)]];
+
+  testPhases('Injects logging element and styles', outputLogsPhases, {
+      'a|web/test.html': '<!DOCTYPE html><html><head>',
+      'a|web/test.html._data': expectedData(['web/a.dart']),
+      'a|web/a.dart':
+        'library a;\n'
+        'import "package:polymer/polymer.dart";\n'
+        'main(){}',
+    }, {
+      'a|web/test.html':
+        '<!DOCTYPE html><html><head>'
+        '<link rel="stylesheet" type="text/css" '
+          'href="packages/polymer/src/build/log_injector.css">'
+        '</head><body>'
+        '<script type="application/dart" '
+          'src="test.html_bootstrap.dart"></script>'
+        '</body></html>',
+      'a|web/test.html_bootstrap.dart':
+        '''$MAIN_HEADER
+          import 'a.dart' as i0;
+          import 'package:polymer/src/build/log_injector.dart';
+          ${DEFAULT_IMPORTS.join('\n')}
+
+          void main() {
+            useGeneratedCode(new StaticConfiguration(
+                checkedMode: false));
+            new LogInjector().injectLogsFromUrl();
+            configureForDeployment([]);
+            i0.main();
+          }
+          '''.replaceAll('\n          ', '\n'),
+      'a|web/a.dart':
+        'library a;\n'
+        'import "package:polymer/polymer.dart";\n'
+        'main(){}',
+    });
+}
\ No newline at end of file
diff --git a/pkg/smoke/CHANGELOG.md b/pkg/smoke/CHANGELOG.md
index 79842ed..84ebd0d 100644
--- a/pkg/smoke/CHANGELOG.md
+++ b/pkg/smoke/CHANGELOG.md
@@ -2,6 +2,9 @@
 
 This file contains highlights of what changes on each version of this package.
 
+#### Pub version 0.2.0+3
+  * Widen the constraint on analyzer.
+
 #### Pub version 0.2.0+2
   * Widen the constraint on barback.
 
diff --git a/pkg/smoke/pubspec.yaml b/pkg/smoke/pubspec.yaml
index de1cdcb..c910971 100644
--- a/pkg/smoke/pubspec.yaml
+++ b/pkg/smoke/pubspec.yaml
@@ -1,5 +1,5 @@
 name: smoke
-version: 0.2.0+2
+version: 0.2.1-dev
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 homepage: "https://api.dartlang.org/apidocs/channels/be/#smoke"
 description: >
@@ -9,7 +9,7 @@
 dependencies:
   barback: ">=0.9.0 <0.16.0"
   logging: ">=0.9.0 <0.10.0"
-  analyzer: ">=0.13.0 <0.16.0"
+  analyzer: ">=0.13.0 <0.23.0"
 # TODO(sigmund): once we have some easier way to do global app-level
 # transformers, we might want to remove this transformer here and only apply it
 # in apps that need it.
diff --git a/pkg/smoke/test/codegen/testing_resolver_utils.dart b/pkg/smoke/test/codegen/testing_resolver_utils.dart
index 7a76614..1a0e6e8 100644
--- a/pkg/smoke/test/codegen/testing_resolver_utils.dart
+++ b/pkg/smoke/test/codegen/testing_resolver_utils.dart
@@ -34,7 +34,7 @@
   sdk.context.analysisOptions = options;
   var changes = new ChangeSet();
   var allSources = {};
-  contents.forEach((url, code) { 
+  contents.forEach((url, code) {
     var source = new _SimpleSource(url, code, allSources);
     allSources[url] = source;
     changes.addedSource(source);
@@ -62,11 +62,13 @@
 }
 
 class _SimpleSource extends Source {
+  final Uri uri;
   final String path;
   final String rawContents;
   final Map<String, Source> allSources;
 
-  _SimpleSource(this.path, this.rawContents, this.allSources);
+  _SimpleSource(this.path, this.rawContents, this.allSources)
+      : uri = Uri.parse('file:///path');
 
   operator ==(other) => other is _SimpleSource &&
       rawContents == other.rawContents;
@@ -91,6 +93,16 @@
     throw new UnimplementedError('relative URIs not supported: $uri');
   }
 
+  // Since this is just for simple tests we just restricted this mock
+  // to root-relative imports. For more sophisticated stuff, you should be
+  // using the test helpers in `package:code_transformers`.
+  Uri resolveRelativeUri(Uri uri) {
+    if (!uri.path.startsWith('/')) {
+      throw new UnimplementedError('relative URIs not supported: $uri');
+    }
+    return uri;
+  }
+
   void getContentsToReceiver(Source_ContentReceiver receiver) {
     receiver.accept(rawContents, modificationStamp);
   }
diff --git a/pkg/typed_mock/README.md b/pkg/typed_mock/README.md
new file mode 100644
index 0000000..2414318
--- /dev/null
+++ b/pkg/typed_mock/README.md
@@ -0,0 +1,7 @@
+A library for mocking classes and verifying expected interaction with mocks.
+
+It is inspired by [Mockito](https://code.google.com/p/mockito/).
+
+The existing "mock" package suffers from using method names as strings,
+which makes it impossible to use code-completion, static validation,
+search and refactoring.
diff --git a/pkg/watcher/CHANGELOG.md b/pkg/watcher/CHANGELOG.md
new file mode 100644
index 0000000..eb544f6
--- /dev/null
+++ b/pkg/watcher/CHANGELOG.md
@@ -0,0 +1,7 @@
+# 0.9.3
+
+* Improved support for Windows via `WindowsDirectoryWatcher`.
+
+* Simplified `PollingDirectoryWatcher`.
+
+* Fixed bugs in `MacOSDirectoryWatcher`
diff --git a/pkg/watcher/README.md b/pkg/watcher/README.md
index 75b0470..61cc1f9 100644
--- a/pkg/watcher/README.md
+++ b/pkg/watcher/README.md
@@ -1,2 +1,4 @@
-A file watcher. It monitors (currently by polling) for changes to contents of
-directories and notifies you when files have been added, removed, or modified.
\ No newline at end of file
+A file system watcher.
+
+It monitors changes to contents of directories and sends notifications when
+files have been added, removed, or modified.
diff --git a/pkg/watcher/example/watch.dart b/pkg/watcher/example/watch.dart
index aba127d..da3c263 100644
--- a/pkg/watcher/example/watch.dart
+++ b/pkg/watcher/example/watch.dart
@@ -5,8 +5,6 @@
 /// Watches the given directory and prints each modification to it.
 library watch;
 
-import 'dart:io';
-
 import 'package:path/path.dart' as p;
 import 'package:watcher/watcher.dart';
 
diff --git a/pkg/watcher/lib/src/async_queue.dart b/pkg/watcher/lib/src/async_queue.dart
index 8ac0cdf..b83493d 100644
--- a/pkg/watcher/lib/src/async_queue.dart
+++ b/pkg/watcher/lib/src/async_queue.dart
@@ -70,4 +70,4 @@
       _isProcessing = false;
     });
   }
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/lib/src/constructable_file_system_event.dart b/pkg/watcher/lib/src/constructable_file_system_event.dart
index 010d297..d00a1dc 100644
--- a/pkg/watcher/lib/src/constructable_file_system_event.dart
+++ b/pkg/watcher/lib/src/constructable_file_system_event.dart
@@ -40,11 +40,11 @@
   final type = FileSystemEvent.MODIFY;
 
   ConstructableFileSystemModifyEvent(String path, bool isDirectory,
-          this.contentChanged)
+      this.contentChanged)
       : super(path, isDirectory);
 
   String toString() =>
-    "FileSystemModifyEvent('$path', contentChanged=$contentChanged)";
+      "FileSystemModifyEvent('$path', contentChanged=$contentChanged)";
 }
 
 class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent
@@ -53,7 +53,7 @@
   final type = FileSystemEvent.MOVE;
 
   ConstructableFileSystemMoveEvent(String path, bool isDirectory,
-          this.destination)
+      this.destination)
       : super(path, isDirectory);
 
   String toString() => "FileSystemMoveEvent('$path', '$destination')";
diff --git a/pkg/watcher/lib/src/path_set.dart b/pkg/watcher/lib/src/path_set.dart
index 01d4208..e9f7d32 100644
--- a/pkg/watcher/lib/src/path_set.dart
+++ b/pkg/watcher/lib/src/path_set.dart
@@ -71,7 +71,7 @@
         // the next level.
         var part = parts.removeFirst();
         var entry = dir[part];
-        if (entry.isEmpty) return new Set();
+        if (entry == null || entry.isEmpty) return new Set();
 
         partialPath = p.join(partialPath, part);
         var paths = recurse(entry, partialPath);
diff --git a/pkg/watcher/lib/src/utils.dart b/pkg/watcher/lib/src/utils.dart
index 163e9f4..007c84c 100644
--- a/pkg/watcher/lib/src/utils.dart
+++ b/pkg/watcher/lib/src/utils.dart
@@ -20,7 +20,7 @@
 
 /// Returns the union of all elements in each set in [sets].
 Set unionAll(Iterable<Set> sets) =>
-  sets.fold(new Set(), (union, set) => union.union(set));
+    sets.fold(new Set(), (union, set) => union.union(set));
 
 /// Returns a buffered stream that will emit the same values as the stream
 /// returned by [future] once [future] completes.
@@ -76,7 +76,7 @@
 /// Returns a [Future] that completes after pumping the event queue [times]
 /// times. By default, this should pump the event queue enough times to allow
 /// any code to run, as long as it's not waiting on some external event.
-Future pumpEventQueue([int times=20]) {
+Future pumpEventQueue([int times = 20]) {
   if (times == 0) return new Future.value();
   // We use a delayed future to allow microtask events to finish. The
   // Future.value or Future() constructors use scheduleMicrotask themselves and
diff --git a/pkg/watcher/lib/src/watch_event.dart b/pkg/watcher/lib/src/watch_event.dart
index d998a25..be6d70c 100644
--- a/pkg/watcher/lib/src/watch_event.dart
+++ b/pkg/watcher/lib/src/watch_event.dart
@@ -32,4 +32,4 @@
   const ChangeType(this._name);
 
   String toString() => _name;
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/pubspec.yaml b/pkg/watcher/pubspec.yaml
index 51ba562..6f96a56 100644
--- a/pkg/watcher/pubspec.yaml
+++ b/pkg/watcher/pubspec.yaml
@@ -1,15 +1,15 @@
 name: watcher
-version: 0.9.3-dev
-author: "Dart Team <misc@dartlang.org>"
+version: 0.9.3
+author: Dart Team <misc@dartlang.org>
 homepage: http://www.dartlang.org
 description: >
-  A file watcher. It monitors for changes to contents of directories and
-  notifies you when files have been added, removed, or modified.
-dependencies:
-  path: ">=0.9.0 <2.0.0"
-  stack_trace: ">=0.9.1 <2.0.0"
-dev_dependencies:
-  scheduled_test: ">=0.9.3-dev <0.11.0"
-  unittest: ">=0.9.2 <0.10.0"
+  A file system watcher. It monitors changes to contents of directories and
+  sends notifications when files have been added, removed, or modified.
 environment:
-  sdk: ">=0.8.10+6 <2.0.0"
+  sdk: '>=1.0.0 <2.0.0'
+dependencies:
+  path: '>=0.9.0 <2.0.0'
+  stack_trace: '>=0.9.1 <2.0.0'
+dev_dependencies:
+  scheduled_test: '>=0.9.3 <0.12.0'
+  unittest: '>=0.9.2 <0.12.0'
diff --git a/pkg/watcher/test/directory_watcher/linux_test.dart b/pkg/watcher/test/directory_watcher/linux_test.dart
index cae38ad..c17eb53 100644
--- a/pkg/watcher/test/directory_watcher/linux_test.dart
+++ b/pkg/watcher/test/directory_watcher/linux_test.dart
@@ -9,7 +9,7 @@
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new LinuxDirectoryWatcher(dir);
diff --git a/pkg/watcher/test/directory_watcher/mac_os_test.dart b/pkg/watcher/test/directory_watcher/mac_os_test.dart
index 1e9bd7d..bbf966a 100644
--- a/pkg/watcher/test/directory_watcher/mac_os_test.dart
+++ b/pkg/watcher/test/directory_watcher/mac_os_test.dart
@@ -9,7 +9,7 @@
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
   MacOSDirectoryWatcher.logDebugInfo = true;
 
diff --git a/pkg/watcher/test/directory_watcher/polling_test.dart b/pkg/watcher/test/directory_watcher/polling_test.dart
index da29207..1ef49b5 100644
--- a/pkg/watcher/test/directory_watcher/polling_test.dart
+++ b/pkg/watcher/test/directory_watcher/polling_test.dart
@@ -8,7 +8,7 @@
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   // Use a short delay to make the tests run quickly.
diff --git a/pkg/watcher/test/directory_watcher/shared.dart b/pkg/watcher/test/directory_watcher/shared.dart
index d3575ea..8632401 100644
--- a/pkg/watcher/test/directory_watcher/shared.dart
+++ b/pkg/watcher/test/directory_watcher/shared.dart
@@ -7,7 +7,7 @@
 
 import '../utils.dart';
 
-sharedTests() {
+void sharedTests() {
   test('does not notify for files that already exist when started', () {
     // Make some pre-existing files.
     writeFile("a.txt");
diff --git a/pkg/watcher/test/directory_watcher/windows_test.dart b/pkg/watcher/test/directory_watcher/windows_test.dart
index 6bfb88b..ea5c8c5 100644
--- a/pkg/watcher/test/directory_watcher/windows_test.dart
+++ b/pkg/watcher/test/directory_watcher/windows_test.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a

 // BSD-style license that can be found in the LICENSE file.

 

-import 'package:path/path.dart' as p;

 import 'package:scheduled_test/scheduled_test.dart';

 import 'package:watcher/src/directory_watcher/windows.dart';

 import 'package:watcher/watcher.dart';

@@ -10,7 +9,7 @@
 import 'shared.dart';

 import '../utils.dart';

 

-main() {

+void main() {

   initConfig();

 

   watcherFactory = (dir) => new WindowsDirectoryWatcher(dir);

@@ -24,4 +23,3 @@
         new isInstanceOf<WindowsDirectoryWatcher>());

   });

 }

-

diff --git a/pkg/watcher/test/no_subscription/linux_test.dart b/pkg/watcher/test/no_subscription/linux_test.dart
index 7978830..f7f1b49 100644
--- a/pkg/watcher/test/no_subscription/linux_test.dart
+++ b/pkg/watcher/test/no_subscription/linux_test.dart
@@ -4,12 +4,11 @@
 
 import 'package:scheduled_test/scheduled_test.dart';
 import 'package:watcher/src/directory_watcher/linux.dart';
-import 'package:watcher/watcher.dart';
 
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new LinuxDirectoryWatcher(dir);
@@ -17,4 +16,4 @@
   setUp(createSandbox);
 
   sharedTests();
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/test/no_subscription/mac_os_test.dart b/pkg/watcher/test/no_subscription/mac_os_test.dart
index e0275c4..721d3e7 100644
--- a/pkg/watcher/test/no_subscription/mac_os_test.dart
+++ b/pkg/watcher/test/no_subscription/mac_os_test.dart
@@ -4,12 +4,11 @@
 
 import 'package:scheduled_test/scheduled_test.dart';
 import 'package:watcher/src/directory_watcher/mac_os.dart';
-import 'package:watcher/watcher.dart';
 
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new MacOSDirectoryWatcher(dir);
@@ -17,4 +16,4 @@
   setUp(createSandbox);
 
   sharedTests();
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/test/no_subscription/polling_test.dart b/pkg/watcher/test/no_subscription/polling_test.dart
index fa4f0cb..c71b5ce 100644
--- a/pkg/watcher/test/no_subscription/polling_test.dart
+++ b/pkg/watcher/test/no_subscription/polling_test.dart
@@ -8,7 +8,7 @@
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new PollingDirectoryWatcher(dir);
@@ -16,4 +16,4 @@
   setUp(createSandbox);
 
   sharedTests();
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/test/no_subscription/shared.dart b/pkg/watcher/test/no_subscription/shared.dart
index 99172a2..9ba5c98 100644
--- a/pkg/watcher/test/no_subscription/shared.dart
+++ b/pkg/watcher/test/no_subscription/shared.dart
@@ -3,14 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:io';
 
 import 'package:scheduled_test/scheduled_test.dart';
 import 'package:watcher/watcher.dart';
 
 import '../utils.dart';
 
-sharedTests() {
+void sharedTests() {
   test('does not notify for changes when there are no subscribers', () {
     // Note that this test doesn't rely as heavily on the test functions in
     // utils.dart because it needs to be very explicit about when the event
diff --git a/pkg/watcher/test/ready/linux_test.dart b/pkg/watcher/test/ready/linux_test.dart
index 7978830..f7f1b49 100644
--- a/pkg/watcher/test/ready/linux_test.dart
+++ b/pkg/watcher/test/ready/linux_test.dart
@@ -4,12 +4,11 @@
 
 import 'package:scheduled_test/scheduled_test.dart';
 import 'package:watcher/src/directory_watcher/linux.dart';
-import 'package:watcher/watcher.dart';
 
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new LinuxDirectoryWatcher(dir);
@@ -17,4 +16,4 @@
   setUp(createSandbox);
 
   sharedTests();
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/test/ready/mac_os_test.dart b/pkg/watcher/test/ready/mac_os_test.dart
index e0275c4..721d3e7 100644
--- a/pkg/watcher/test/ready/mac_os_test.dart
+++ b/pkg/watcher/test/ready/mac_os_test.dart
@@ -4,12 +4,11 @@
 
 import 'package:scheduled_test/scheduled_test.dart';
 import 'package:watcher/src/directory_watcher/mac_os.dart';
-import 'package:watcher/watcher.dart';
 
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new MacOSDirectoryWatcher(dir);
@@ -17,4 +16,4 @@
   setUp(createSandbox);
 
   sharedTests();
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/test/ready/polling_test.dart b/pkg/watcher/test/ready/polling_test.dart
index fa4f0cb..c71b5ce 100644
--- a/pkg/watcher/test/ready/polling_test.dart
+++ b/pkg/watcher/test/ready/polling_test.dart
@@ -8,7 +8,7 @@
 import 'shared.dart';
 import '../utils.dart';
 
-main() {
+void main() {
   initConfig();
 
   watcherFactory = (dir) => new PollingDirectoryWatcher(dir);
@@ -16,4 +16,4 @@
   setUp(createSandbox);
 
   sharedTests();
-}
\ No newline at end of file
+}
diff --git a/pkg/watcher/test/ready/shared.dart b/pkg/watcher/test/ready/shared.dart
index af1b58f..7be4833 100644
--- a/pkg/watcher/test/ready/shared.dart
+++ b/pkg/watcher/test/ready/shared.dart
@@ -6,7 +6,7 @@
 
 import '../utils.dart';
 
-sharedTests() {
+void sharedTests() {
   test('ready does not complete until after subscription', () {
     var watcher = createWatcher(waitForReady: false);
 
diff --git a/pkg/watcher/test/utils.dart b/pkg/watcher/test/utils.dart
index 8b660e8..6758dae 100644
--- a/pkg/watcher/test/utils.dart
+++ b/pkg/watcher/test/utils.dart
@@ -4,7 +4,6 @@
 
 library watcher.test.utils;
 
-import 'dart:async';
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -250,16 +249,16 @@
 
 /// Expects that the next event emitted will be for an add event for [path].
 void expectAddEvent(String path) =>
-  _expectOrCollect(isWatchEvent(ChangeType.ADD, path));
+    _expectOrCollect(isWatchEvent(ChangeType.ADD, path));
 
 /// Expects that the next event emitted will be for a modification event for
 /// [path].
 void expectModifyEvent(String path) =>
-  _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path));
+    _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path));
 
 /// Expects that the next event emitted will be for a removal event for [path].
 void expectRemoveEvent(String path) =>
-  _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path));
+    _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path));
 
 /// Consumes an add event for [path] if one is emitted at this point in the
 /// schedule, but doesn't throw an error if it isn't.
@@ -267,7 +266,7 @@
 /// If this is used at the end of a test, [startClosingEventStream] should be
 /// called before it.
 void allowAddEvent(String path) =>
-  _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path)));
+    _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path)));
 
 /// Consumes a modification event for [path] if one is emitted at this point in
 /// the schedule, but doesn't throw an error if it isn't.
@@ -275,7 +274,7 @@
 /// If this is used at the end of a test, [startClosingEventStream] should be
 /// called before it.
 void allowModifyEvent(String path) =>
-  _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path)));
+    _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path)));
 
 /// Consumes a removal event for [path] if one is emitted at this point in the
 /// schedule, but doesn't throw an error if it isn't.
@@ -283,7 +282,7 @@
 /// If this is used at the end of a test, [startClosingEventStream] should be
 /// called before it.
 void allowRemoveEvent(String path) =>
-  _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path)));
+    _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path)));
 
 /// Schedules writing a file in the sandbox at [path] with [contents].
 ///
diff --git a/runtime/bin/socket_patch.dart b/runtime/bin/socket_patch.dart
index 0d7546e..23e3f9e 100644
--- a/runtime/bin/socket_patch.dart
+++ b/runtime/bin/socket_patch.dart
@@ -418,7 +418,8 @@
               connectNext();
             } else {
               socket.port;  // Query the local port, for error messages.
-              // Set up timer for when we should retry the next address (if any).
+              // Set up timer for when we should retry the next address
+              // (if any).
               var duration = address.isLoopback ?
                   _RETRY_DURATION_LOOPBACK :
                   _RETRY_DURATION;
diff --git a/runtime/lib/integers_patch.dart b/runtime/lib/integers_patch.dart
index 7ce5ef5..4ac46dc 100644
--- a/runtime/lib/integers_patch.dart
+++ b/runtime/lib/integers_patch.dart
@@ -7,62 +7,50 @@
 
 patch class int {
 
-  static bool _isWhitespace(int codePoint) {
-    return
-      (codePoint == 32) || // Space.
-      ((9 <= codePoint) && (codePoint <= 13)); // CR, LF, TAB, etc.
-  }
-
   static bool is64Bit() => 1 << 32 is _Smi;
 
-  static int _tryParseSmi(String str) {
-    if (str.isEmpty) return null;
-    var ix = 0;
-    var endIx = str.length - 1;
-    // Find first and last non-whitespace.
-    while (ix <= endIx) {
-      if (!_isWhitespace(str.codeUnitAt(ix))) break;
-      ix++;
-    }
-    if (endIx < ix) {
-      return null;  // Empty.
-    }
-    while (endIx > ix) {
-      if (!_isWhitespace(str.codeUnitAt(endIx))) break;
-      endIx--;
-    }
-
-    var isNegative = false;
+  static int _tryParseSmi(String str, int first, int last) {
+    assert(first <= last);
+    var ix = first;
+    var sign = 1;
     var c = str.codeUnitAt(ix);
     // Check for leading '+' or '-'.
     if ((c == 0x2b) || (c == 0x2d)) {
       ix++;
-      isNegative = (c == 0x2d);
-      if (ix > endIx) {
+      sign = 0x2c - c;  // -1 for '-', +1 for '+'.
+      if (ix > last) {
         return null;  // Empty.
       }
     }
     int smiLimit = is64Bit() ? 18 : 9;
-    if ((endIx - ix) >= smiLimit) {
+    if ((last - ix) >= smiLimit) {
       return null;  // May not fit into a Smi.
     }
     var result = 0;
-    for (int i = ix; i <= endIx; i++) {
+    for (int i = ix; i <= last; i++) {
       var c = str.codeUnitAt(i) - 0x30;
       if ((c > 9) || (c < 0)) {
         return null;
       }
       result = result * 10 + c;
     }
-    return isNegative ? -result : result;
+    return sign * result;
+  }
+
+  static int _tryParseSmiWhitespace(String str) {
+    int first = str._firstNonWhitespace();
+    if (first < str.length) {
+      int last = str._lastNonWhitespace();
+      int res = _tryParseSmi(str, first, last);
+      if (res != null) return res;
+    }
+    return _native_parse(str);
   }
 
   static int _parse(String str) {
-    int res = _tryParseSmi(str);
-    if (res == null) {
-      res = _native_parse(str);
-    }
-    return res;
+    int res = _tryParseSmi(str, 0, str.length - 1);
+    if (res != null) return res;
+    return _tryParseSmiWhitespace(str);
   }
 
   static int _native_parse(String str) native "Integer_parse";
@@ -75,7 +63,8 @@
                                { int radix,
                                  int onError(String str) }) {
     if (radix == null) {
-      int result = _parse(source);
+      int result;
+      if (source.isNotEmpty) result = _parse(source);
       if (result == null) {
         if (onError == null) {
           throw new FormatException("", source);
diff --git a/runtime/lib/string_patch.dart b/runtime/lib/string_patch.dart
index 0550ca77..11f5493 100644
--- a/runtime/lib/string_patch.dart
+++ b/runtime/lib/string_patch.dart
@@ -247,6 +247,9 @@
     if (startIndex == endIndex) {
       return "";
     }
+    if ((startIndex == 0) && (endIndex == this.length)) {
+      return this;
+    }
     if ((startIndex + 1) == endIndex) {
       return this[startIndex];
     }
@@ -260,9 +263,9 @@
   static bool _isOneByteWhitespace(int codePoint) {
     return
       (codePoint == 32) || // Space.
-      ((9 <= codePoint) && (codePoint <= 13)) || // CR, LF, TAB, etc.
-      (codePoint == 0x85) ||  // NEL
-      (codePoint == 0xA0);  // NBSP
+      ((codePoint <= 13) ? (9 <= codePoint)  // CR, LF, TAB, etc.
+                         : ((codePoint == 0x85) ||  // NEL
+                            (codePoint == 0xA0)));  // NBSP
   }
 
   // Characters with Whitespace property (Unicode 6.2).
@@ -281,16 +284,17 @@
   //
   // BOM: 0xFEFF
   static bool _isTwoByteWhitespace(int codeUnit) {
-    if (codeUnit < 256) return _isOneByteWhitespace(codeUnit);
-    return (codeUnit == 0x1680) ||
-        (codeUnit == 0x180E) ||
-        ((0x2000 <= codeUnit) && (codeUnit <= 0x200A)) ||
-        (codeUnit == 0x2028) ||
-        (codeUnit == 0x2029) ||
-        (codeUnit == 0x202F) ||
-        (codeUnit == 0x205F) ||
-        (codeUnit == 0x3000) ||
-        (codeUnit == 0xFEFF);
+    if (codeUnit <= 0xA0) return _isOneByteWhitespace(codeUnit);
+    return (codeUnit <= 0x200A)
+            ? ((codeUnit == 0x1680) ||
+               (codeUnit == 0x180E) ||
+               (0x2000 <= codeUnit))
+            : ((codeUnit == 0x2028) ||
+               (codeUnit == 0x2029) ||
+               (codeUnit == 0x202F) ||
+               (codeUnit == 0x205F) ||
+               (codeUnit == 0x3000) ||
+               (codeUnit == 0xFEFF));
   }
 
   int _firstNonWhitespace() {
diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status
index c2a18b3..e4d3c80 100644
--- a/runtime/tests/vm/vm.status
+++ b/runtime/tests/vm/vm.status
@@ -50,8 +50,8 @@
 cc/Service_Profile: Skip
 
 [ $arch == simmips || $arch == mips ]
-cc/Coverage_MainWithClass: Skip # Dart bug 16250
-cc/Service_ClassesCoverage: Skip # Dart bug 16250
+cc/Coverage_MainWithClass: Skip # Issue 16250
+cc/Service_ClassesCoverage: Skip # Issue 16250
 
 [ $compiler == dart2js ]
 dart/redirection_type_shuffling_test: Skip # Depends on lazy enforcement of type bounds
@@ -67,18 +67,18 @@
 [ $compiler == dart2js ]
 # Methods can be missing in dart2js stack traces due to inlining.  Also when
 # minifying they can be renamed, which is issue 7953.
-dart/inline_stack_frame_test: RuntimeError
+dart/inline_stack_frame_test: RuntimeError # Issue 7953
 
 [ $compiler == dart2dart ]
 # Skip until we stabilize language tests.
 *: Skip
 
 [ $arch == mips ]
-cc/StaticNonNullSumCallCodegen: Crash, Pass # dartbug.com/17440
+cc/StaticNonNullSumCallCodegen: Crash, Pass # Issue 17440
+cc/Sdc1Ldc1: Crash # Issue 20182
 
 [ $arch == mips && $mode == debug ]
-cc/JSON_JSONStream_Options: Crash # Issue 19328
-cc/FindCodeObject: Skip # Takes more than 8 minutes. dartbug.com/17440.
+cc/FindCodeObject: Skip # Takes more than 8 minutes. Issue 17440
 
 [ $compiler == dartanalyzer || $compiler == dart2analyzer ]
 dart/optimized_stacktrace_test: StaticWarning
diff --git a/runtime/vm/flow_graph_optimizer.cc b/runtime/vm/flow_graph_optimizer.cc
index cf16ff9..e9d9241 100644
--- a/runtime/vm/flow_graph_optimizer.cc
+++ b/runtime/vm/flow_graph_optimizer.cc
@@ -11,6 +11,7 @@
 #include "vm/exceptions.h"
 #include "vm/flow_graph_builder.h"
 #include "vm/flow_graph_compiler.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/hash_map.h"
 #include "vm/il_printer.h"
 #include "vm/intermediate_language.h"
@@ -23,8 +24,6 @@
 
 namespace dart {
 
-DEFINE_FLAG(bool, array_bounds_check_elimination, true,
-    "Eliminate redundant bounds checks.");
 DEFINE_FLAG(int, getter_setter_ratio, 13,
     "Ratio of getter/setter usage used for double field unboxing heuristics");
 DEFINE_FLAG(bool, load_cse, true, "Use redundant load elimination.");
@@ -40,12 +39,9 @@
 DEFINE_FLAG(bool, trace_load_optimization, false,
     "Print live sets for load optimization pass.");
 DEFINE_FLAG(bool, trace_optimization, false, "Print optimization details.");
-DEFINE_FLAG(bool, trace_range_analysis, false, "Trace range analysis progress");
 DEFINE_FLAG(bool, truncating_left_shift, true,
     "Optimize left shift to truncate if possible");
 DEFINE_FLAG(bool, use_cha, true, "Use class hierarchy analysis.");
-DEFINE_FLAG(bool, trace_integer_ir_selection, false,
-    "Print integer IR selection optimization pass.");
 DECLARE_FLAG(bool, enable_type_checks);
 DECLARE_FLAG(bool, source_lines);
 DECLARE_FLAG(bool, trace_type_check_elimination);
@@ -4499,917 +4495,6 @@
 }
 
 
-// Replaces Mint IL instructions with Uint32 IL instructions
-// when possible. Uses output of RangeAnalysis.
-class IntegerInstructionSelector : public ValueObject {
- public:
-  explicit IntegerInstructionSelector(FlowGraph* flow_graph)
-      : flow_graph_(flow_graph),
-        isolate_(NULL) {
-    ASSERT(flow_graph_ != NULL);
-    isolate_ = flow_graph_->isolate();
-    ASSERT(isolate_ != NULL);
-    selected_uint32_defs_ =
-        new(I) BitVector(flow_graph_->current_ssa_temp_index());
-  }
-
-  void Select();
-
- private:
-  bool IsPotentialUint32Definition(Definition* def);
-  void FindPotentialUint32Definitions();
-  bool IsUint32NarrowingDefinition(Definition* def);
-  void FindUint32NarrowingDefinitions();
-  bool AllUsesAreUint32Narrowing(Value* list_head);
-  bool CanBecomeUint32(Definition* def);
-  void Propagate();
-  Definition* ConstructReplacementFor(Definition* def);
-  void ReplaceInstructions();
-
-  Isolate* isolate() const { return isolate_; }
-
-  GrowableArray<Definition*> potential_uint32_defs_;
-  BitVector* selected_uint32_defs_;
-
-  FlowGraph* flow_graph_;
-  Isolate* isolate_;
-};
-
-
-void IntegerInstructionSelector::Select() {
-  if (FLAG_trace_integer_ir_selection) {
-    OS::Print("---- starting integer ir selection -------\n");
-  }
-  FindPotentialUint32Definitions();
-  FindUint32NarrowingDefinitions();
-  Propagate();
-  ReplaceInstructions();
-  if (FLAG_trace_integer_ir_selection) {
-    OS::Print("---- after integer ir selection -------\n");
-    FlowGraphPrinter printer(*flow_graph_);
-    printer.PrintBlocks();
-  }
-}
-
-
-bool IntegerInstructionSelector::IsPotentialUint32Definition(Definition* def) {
-  // TODO(johnmccutchan): Consider Smi operations, to avoid unnecessary tagging
-  // & untagged of intermediate results.
-  // TODO(johnmccutchan): Consider phis.
-  return def->IsBoxInteger()   ||   // BoxMint.
-         def->IsUnboxInteger() ||   // UnboxMint.
-         def->IsBinaryMintOp() ||
-         def->IsShiftMintOp()  ||
-         def->IsUnaryMintOp();
-}
-
-
-void IntegerInstructionSelector::FindPotentialUint32Definitions() {
-  if (FLAG_trace_integer_ir_selection) {
-    OS::Print("++++ Finding potential Uint32 definitions:\n");
-  }
-
-  for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
-       !block_it.Done();
-       block_it.Advance()) {
-    BlockEntryInstr* block = block_it.Current();
-
-    for (ForwardInstructionIterator instr_it(block);
-         !instr_it.Done();
-         instr_it.Advance()) {
-      Instruction* current = instr_it.Current();
-      Definition* defn = current->AsDefinition();
-      if ((defn != NULL) && (defn->ssa_temp_index() != -1)) {
-        if (IsPotentialUint32Definition(defn)) {
-          if (FLAG_trace_integer_ir_selection) {
-           OS::Print("Adding %s\n", current->ToCString());
-          }
-          potential_uint32_defs_.Add(defn);
-        }
-      }
-    }
-  }
-}
-
-
-// BinaryMintOp masks and stores into unsigned typed arrays that truncate the
-// value into a Uint32 range.
-bool IntegerInstructionSelector::IsUint32NarrowingDefinition(Definition* def) {
-  if (def->IsBinaryMintOp()) {
-    BinaryMintOpInstr* op = def->AsBinaryMintOp();
-    // Must be a mask operation.
-    if (op->op_kind() != Token::kBIT_AND) {
-      return false;
-    }
-    Range* range = op->range();
-    if ((range == NULL) ||
-        !range->IsWithin(0, static_cast<int64_t>(kMaxUint32))) {
-      return false;
-    }
-    return true;
-  }
-  // TODO(johnmccutchan): Add typed array stores.
-  return false;
-}
-
-
-void IntegerInstructionSelector::FindUint32NarrowingDefinitions() {
-  ASSERT(selected_uint32_defs_ != NULL);
-  if (FLAG_trace_integer_ir_selection) {
-    OS::Print("++++ Selecting Uint32 definitions:\n");
-    OS::Print("++++ Initial set:\n");
-  }
-  for (intptr_t i = 0; i < potential_uint32_defs_.length(); i++) {
-    Definition* defn = potential_uint32_defs_[i];
-    if (IsUint32NarrowingDefinition(defn)) {
-      if (FLAG_trace_integer_ir_selection) {
-        OS::Print("Adding %s\n", defn->ToCString());
-      }
-      selected_uint32_defs_->Add(defn->ssa_temp_index());
-    }
-  }
-}
-
-
-bool IntegerInstructionSelector::AllUsesAreUint32Narrowing(Value* list_head) {
-  for (Value::Iterator it(list_head);
-       !it.Done();
-       it.Advance()) {
-    Value* use = it.Current();
-    Definition* defn = use->instruction()->AsDefinition();
-    if ((defn == NULL) ||
-        (defn->ssa_temp_index() == -1) ||
-        !selected_uint32_defs_->Contains(defn->ssa_temp_index())) {
-      return false;
-    }
-  }
-  return true;
-}
-
-
-bool IntegerInstructionSelector::CanBecomeUint32(Definition* def) {
-  ASSERT(IsPotentialUint32Definition(def));
-  if (def->IsBoxInteger()) {
-    // If a BoxInteger's input is a candidate, the box is a candidate.
-    BoxIntegerInstr* box = def->AsBoxInteger();
-    Definition* box_input = box->value()->definition();
-    return selected_uint32_defs_->Contains(box_input->ssa_temp_index());
-  }
-  // A right shift with an input outside of Uint32 range cannot be converted
-  // because we need the high bits.
-  if (def->IsShiftMintOp()) {
-    ShiftMintOpInstr* op = def->AsShiftMintOp();
-    if (op->op_kind() == Token::kSHR) {
-      Definition* shift_input = op->left()->definition();
-      ASSERT(shift_input != NULL);
-      Range* range = shift_input->range();
-      if ((range == NULL) ||
-          !range->IsWithin(0, static_cast<int64_t>(kMaxUint32))) {
-        return false;
-      }
-    }
-  }
-  if (!def->HasUses()) {
-    // No uses, skip.
-    return false;
-  }
-  return AllUsesAreUint32Narrowing(def->input_use_list()) &&
-         AllUsesAreUint32Narrowing(def->env_use_list());
-}
-
-
-void IntegerInstructionSelector::Propagate() {
-  ASSERT(selected_uint32_defs_ != NULL);
-  bool changed = true;
-  intptr_t iteration = 0;
-  while (changed) {
-    if (FLAG_trace_integer_ir_selection) {
-      OS::Print("+++ Iteration: %" Pd "\n", iteration++);
-    }
-    changed = false;
-    for (intptr_t i = 0; i < potential_uint32_defs_.length(); i++) {
-      Definition* defn = potential_uint32_defs_[i];
-      if (selected_uint32_defs_->Contains(defn->ssa_temp_index())) {
-        // Already marked as a candidate, skip.
-        continue;
-      }
-      if (defn->IsConstant()) {
-        // Skip constants.
-        continue;
-      }
-      if (CanBecomeUint32(defn)) {
-        if (FLAG_trace_integer_ir_selection) {
-          OS::Print("Adding %s\n", defn->ToCString());
-        }
-        // Found a new candidate.
-        selected_uint32_defs_->Add(defn->ssa_temp_index());
-        // Haven't reached fixed point yet.
-        changed = true;
-      }
-    }
-  }
-  if (FLAG_trace_integer_ir_selection) {
-    OS::Print("Reached fixed point\n");
-  }
-}
-
-
-Definition* IntegerInstructionSelector::ConstructReplacementFor(
-    Definition* def) {
-  // Should only see mint definitions.
-  ASSERT(IsPotentialUint32Definition(def));
-  // Should not see constant instructions.
-  ASSERT(!def->IsConstant());
-  if (def->IsBinaryMintOp()) {
-    BinaryMintOpInstr* op = def->AsBinaryMintOp();
-    Token::Kind op_kind = op->op_kind();
-    Value* left = op->left()->CopyWithType();
-    Value* right = op->right()->CopyWithType();
-    intptr_t deopt_id = op->DeoptimizationTarget();
-    return new(I) BinaryUint32OpInstr(op_kind, left, right, deopt_id);
-  } else if (def->IsBoxInteger()) {
-    BoxIntegerInstr* box = def->AsBoxInteger();
-    Value* value = box->value()->CopyWithType();
-    return new(I) BoxUint32Instr(value);
-  } else if (def->IsUnboxInteger()) {
-    UnboxIntegerInstr* unbox = def->AsUnboxInteger();
-    Value* value = unbox->value()->CopyWithType();
-    intptr_t deopt_id = unbox->deopt_id();
-    return new(I) UnboxUint32Instr(value, deopt_id);
-  } else if (def->IsUnaryMintOp()) {
-    UnaryMintOpInstr* op = def->AsUnaryMintOp();
-    Token::Kind op_kind = op->op_kind();
-    Value* value = op->value()->CopyWithType();
-    intptr_t deopt_id = op->DeoptimizationTarget();
-    return new(I) UnaryUint32OpInstr(op_kind, value, deopt_id);
-  } else if (def->IsShiftMintOp()) {
-    ShiftMintOpInstr* op = def->AsShiftMintOp();
-    Token::Kind op_kind = op->op_kind();
-    Value* left = op->left()->CopyWithType();
-    Value* right = op->right()->CopyWithType();
-    intptr_t deopt_id = op->DeoptimizationTarget();
-    return new(I) ShiftUint32OpInstr(op_kind, left, right, deopt_id);
-  }
-  UNREACHABLE();
-  return NULL;
-}
-
-
-void IntegerInstructionSelector::ReplaceInstructions() {
-  if (FLAG_trace_integer_ir_selection) {
-    OS::Print("++++ Replacing instructions:\n");
-  }
-  for (intptr_t i = 0; i < potential_uint32_defs_.length(); i++) {
-    Definition* defn = potential_uint32_defs_[i];
-    if (!selected_uint32_defs_->Contains(defn->ssa_temp_index())) {
-      // Not a candidate.
-      continue;
-    }
-    Definition* replacement = ConstructReplacementFor(defn);
-    ASSERT(replacement != NULL);
-    if (FLAG_trace_integer_ir_selection) {
-      OS::Print("Replacing %s with %s\n", defn->ToCString(),
-                                          replacement->ToCString());
-    }
-    defn->ReplaceWith(replacement, NULL);
-    ASSERT(flow_graph_->VerifyUseLists());
-  }
-}
-
-void FlowGraphOptimizer::SelectIntegerInstructions() {
-  IntegerInstructionSelector iis(flow_graph_);
-  iis.Select();
-}
-
-
-// Range analysis for integer values.
-class RangeAnalysis : public ValueObject {
- public:
-  explicit RangeAnalysis(FlowGraph* flow_graph)
-      : flow_graph_(flow_graph),
-        marked_defns_(NULL) { }
-
-  // Infer ranges for all values and remove overflow checks from binary smi
-  // operations when proven redundant.
-  void Analyze();
-
- private:
-  // Collect all values that were proven to be smi in smi_values_ array and all
-  // CheckSmi instructions in smi_check_ array.
-  void CollectValues();
-
-  // Iterate over smi values and constrain them at branch successors.
-  // Additionally constraint values after CheckSmi instructions.
-  void InsertConstraints();
-
-  // Iterate over uses of the given definition and discover branches that
-  // constrain it. Insert appropriate Constraint instructions at true
-  // and false successor and rename all dominated uses to refer to a
-  // Constraint instead of this definition.
-  void InsertConstraintsFor(Definition* defn);
-
-  // Create a constraint for defn, insert it after given instruction and
-  // rename all uses that are dominated by it.
-  ConstraintInstr* InsertConstraintFor(Definition* defn,
-                                       Range* constraint,
-                                       Instruction* after);
-
-  void ConstrainValueAfterBranch(Definition* defn, Value* use);
-  void ConstrainValueAfterCheckArrayBound(Definition* defn,
-                                          CheckArrayBoundInstr* check,
-                                          intptr_t use_index);
-
-  // Replace uses of the definition def that are dominated by instruction dom
-  // with uses of other definition.
-  void RenameDominatedUses(Definition* def,
-                           Instruction* dom,
-                           Definition* other);
-
-
-  // Walk the dominator tree and infer ranges for smi values.
-  void InferRanges();
-  void InferRangesRecursive(BlockEntryInstr* block);
-
-  enum Direction {
-    kUnknown,
-    kPositive,
-    kNegative,
-    kBoth
-  };
-
-  Range* InferInductionVariableRange(JoinEntryInstr* loop_header,
-                                     PhiInstr* var);
-
-  void ResetWorklist();
-  void MarkDefinition(Definition* defn);
-
-  static Direction ToDirection(Value* val);
-
-  static Direction Invert(Direction direction) {
-    return (direction == kPositive) ? kNegative : kPositive;
-  }
-
-  static void UpdateDirection(Direction* direction,
-                              Direction new_direction) {
-    if (*direction != new_direction) {
-      if (*direction != kUnknown) new_direction = kBoth;
-      *direction = new_direction;
-    }
-  }
-
-  // Remove artificial Constraint instructions and replace them with actual
-  // unconstrained definitions.
-  void RemoveConstraints();
-
-  Range* ConstraintRange(Token::Kind op, Definition* boundary);
-
-  Isolate* isolate() const { return flow_graph_->isolate(); }
-
-  FlowGraph* flow_graph_;
-
-  // Value that are known to be smi or mint.
-  GrowableArray<Definition*> values_;
-  // All CheckSmi instructions.
-  GrowableArray<CheckSmiInstr*> smi_checks_;
-
-  // All Constraints inserted during InsertConstraints phase. They are treated
-  // as smi values.
-  GrowableArray<ConstraintInstr*> constraints_;
-
-  // Bitvector for a quick filtering of known smi or mint values.
-  BitVector* definitions_;
-
-  // Worklist for induction variables analysis.
-  GrowableArray<Definition*> worklist_;
-  BitVector* marked_defns_;
-
-  DISALLOW_COPY_AND_ASSIGN(RangeAnalysis);
-};
-
-
-void RangeAnalysis::Analyze() {
-  CollectValues();
-  InsertConstraints();
-  InferRanges();
-  IntegerInstructionSelector iis(flow_graph_);
-  iis.Select();
-  RemoveConstraints();
-}
-
-
-void RangeAnalysis::CollectValues() {
-  const GrowableArray<Definition*>& initial =
-      *flow_graph_->graph_entry()->initial_definitions();
-  for (intptr_t i = 0; i < initial.length(); ++i) {
-    Definition* current = initial[i];
-    if (current->Type()->ToCid() == kSmiCid) {
-      values_.Add(current);
-    } else if (current->IsMintDefinition()) {
-      values_.Add(current);
-    }
-  }
-
-  for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
-       !block_it.Done();
-       block_it.Advance()) {
-    BlockEntryInstr* block = block_it.Current();
-
-
-    if (block->IsGraphEntry() || block->IsCatchBlockEntry()) {
-      const GrowableArray<Definition*>& initial = block->IsGraphEntry()
-          ? *block->AsGraphEntry()->initial_definitions()
-          : *block->AsCatchBlockEntry()->initial_definitions();
-      for (intptr_t i = 0; i < initial.length(); ++i) {
-        Definition* current = initial[i];
-        if (current->Type()->ToCid() == kSmiCid) {
-          values_.Add(current);
-        } else if (current->IsMintDefinition()) {
-          values_.Add(current);
-        }
-      }
-    }
-
-    JoinEntryInstr* join = block->AsJoinEntry();
-    if (join != NULL) {
-      for (PhiIterator phi_it(join); !phi_it.Done(); phi_it.Advance()) {
-        PhiInstr* current = phi_it.Current();
-        if (current->Type()->ToCid() == kSmiCid) {
-          values_.Add(current);
-        }
-      }
-    }
-
-    for (ForwardInstructionIterator instr_it(block);
-         !instr_it.Done();
-         instr_it.Advance()) {
-      Instruction* current = instr_it.Current();
-      Definition* defn = current->AsDefinition();
-      if (defn != NULL) {
-        if ((defn->Type()->ToCid() == kSmiCid) &&
-            (defn->ssa_temp_index() != -1)) {
-          values_.Add(defn);
-        } else if ((defn->IsMintDefinition()) &&
-                   (defn->ssa_temp_index() != -1)) {
-          values_.Add(defn);
-        }
-      } else if (current->IsCheckSmi()) {
-        smi_checks_.Add(current->AsCheckSmi());
-      }
-    }
-  }
-}
-
-
-// Returns true if use is dominated by the given instruction.
-// Note: uses that occur at instruction itself are not dominated by it.
-static bool IsDominatedUse(Instruction* dom, Value* use) {
-  BlockEntryInstr* dom_block = dom->GetBlock();
-
-  Instruction* instr = use->instruction();
-
-  PhiInstr* phi = instr->AsPhi();
-  if (phi != NULL) {
-    return dom_block->Dominates(phi->block()->PredecessorAt(use->use_index()));
-  }
-
-  BlockEntryInstr* use_block = instr->GetBlock();
-  if (use_block == dom_block) {
-    // Fast path for the case of block entry.
-    if (dom_block == dom) return true;
-
-    for (Instruction* curr = dom->next(); curr != NULL; curr = curr->next()) {
-      if (curr == instr) return true;
-    }
-
-    return false;
-  }
-
-  return dom_block->Dominates(use_block);
-}
-
-
-void RangeAnalysis::RenameDominatedUses(Definition* def,
-                                        Instruction* dom,
-                                        Definition* other) {
-  for (Value::Iterator it(def->input_use_list());
-       !it.Done();
-       it.Advance()) {
-    Value* use = it.Current();
-
-    // Skip dead phis.
-    PhiInstr* phi = use->instruction()->AsPhi();
-    ASSERT((phi == NULL) || phi->is_alive());
-    if (IsDominatedUse(dom, use)) {
-      use->BindTo(other);
-    }
-  }
-}
-
-
-// For a comparison operation return an operation for the equivalent flipped
-// comparison: a (op) b === b (op') a.
-static Token::Kind FlipComparison(Token::Kind op) {
-  switch (op) {
-    case Token::kEQ: return Token::kEQ;
-    case Token::kNE: return Token::kNE;
-    case Token::kLT: return Token::kGT;
-    case Token::kGT: return Token::kLT;
-    case Token::kLTE: return Token::kGTE;
-    case Token::kGTE: return Token::kLTE;
-    default:
-      UNREACHABLE();
-      return Token::kILLEGAL;
-  }
-}
-
-
-// Given a boundary (right operand) and a comparison operation return
-// a symbolic range constraint for the left operand of the comparison assuming
-// that it evaluated to true.
-// For example for the comparison a < b symbol a is constrained with range
-// [Smi::kMinValue, b - 1].
-Range* RangeAnalysis::ConstraintRange(Token::Kind op, Definition* boundary) {
-  switch (op) {
-    case Token::kEQ:
-      return new(I) Range(RangeBoundary::FromDefinition(boundary),
-                          RangeBoundary::FromDefinition(boundary));
-    case Token::kNE:
-      return Range::Unknown();
-    case Token::kLT:
-      return new(I) Range(RangeBoundary::MinSmi(),
-                          RangeBoundary::FromDefinition(boundary, -1));
-    case Token::kGT:
-      return new(I) Range(RangeBoundary::FromDefinition(boundary, 1),
-                          RangeBoundary::MaxSmi());
-    case Token::kLTE:
-      return new(I) Range(RangeBoundary::MinSmi(),
-                          RangeBoundary::FromDefinition(boundary));
-    case Token::kGTE:
-      return new(I) Range(RangeBoundary::FromDefinition(boundary),
-                          RangeBoundary::MaxSmi());
-    default:
-      UNREACHABLE();
-      return Range::Unknown();
-  }
-}
-
-
-ConstraintInstr* RangeAnalysis::InsertConstraintFor(Definition* defn,
-                                                    Range* constraint_range,
-                                                    Instruction* after) {
-  // No need to constrain constants.
-  if (defn->IsConstant()) return NULL;
-
-  ConstraintInstr* constraint = new(I) ConstraintInstr(
-      new(I) Value(defn), constraint_range);
-  flow_graph_->InsertAfter(after, constraint, NULL, FlowGraph::kValue);
-  RenameDominatedUses(defn, constraint, constraint);
-  constraints_.Add(constraint);
-  return constraint;
-}
-
-
-void RangeAnalysis::ConstrainValueAfterBranch(Definition* defn, Value* use) {
-  BranchInstr* branch = use->instruction()->AsBranch();
-  RelationalOpInstr* rel_op = branch->comparison()->AsRelationalOp();
-  if ((rel_op != NULL) && (rel_op->operation_cid() == kSmiCid)) {
-    // Found comparison of two smis. Constrain defn at true and false
-    // successors using the other operand as a boundary.
-    Definition* boundary;
-    Token::Kind op_kind;
-    if (use->use_index() == 0) {  // Left operand.
-      boundary = rel_op->InputAt(1)->definition();
-      op_kind = rel_op->kind();
-    } else {
-      ASSERT(use->use_index() == 1);  // Right operand.
-      boundary = rel_op->InputAt(0)->definition();
-      // InsertConstraintFor assumes that defn is left operand of a
-      // comparison if it is right operand flip the comparison.
-      op_kind = FlipComparison(rel_op->kind());
-    }
-
-    // Constrain definition at the true successor.
-    ConstraintInstr* true_constraint =
-        InsertConstraintFor(defn,
-                            ConstraintRange(op_kind, boundary),
-                            branch->true_successor());
-    // Mark true_constraint an artificial use of boundary. This ensures
-    // that constraint's range is recalculated if boundary's range changes.
-    if (true_constraint != NULL) {
-      true_constraint->AddDependency(boundary);
-      true_constraint->set_target(branch->true_successor());
-    }
-
-    // Constrain definition with a negated condition at the false successor.
-    ConstraintInstr* false_constraint =
-        InsertConstraintFor(
-            defn,
-            ConstraintRange(Token::NegateComparison(op_kind), boundary),
-            branch->false_successor());
-    // Mark false_constraint an artificial use of boundary. This ensures
-    // that constraint's range is recalculated if boundary's range changes.
-    if (false_constraint != NULL) {
-      false_constraint->AddDependency(boundary);
-      false_constraint->set_target(branch->false_successor());
-    }
-  }
-}
-
-
-void RangeAnalysis::InsertConstraintsFor(Definition* defn) {
-  for (Value* use = defn->input_use_list();
-       use != NULL;
-       use = use->next_use()) {
-    if (use->instruction()->IsBranch()) {
-      ConstrainValueAfterBranch(defn, use);
-    } else if (use->instruction()->IsCheckArrayBound()) {
-      ConstrainValueAfterCheckArrayBound(
-          defn,
-          use->instruction()->AsCheckArrayBound(),
-          use->use_index());
-    }
-  }
-}
-
-
-void RangeAnalysis::ConstrainValueAfterCheckArrayBound(
-    Definition* defn, CheckArrayBoundInstr* check, intptr_t use_index) {
-  Range* constraint_range = NULL;
-  if (use_index == CheckArrayBoundInstr::kIndexPos) {
-    Definition* length = check->length()->definition();
-    constraint_range = new(I) Range(
-        RangeBoundary::FromConstant(0),
-        RangeBoundary::FromDefinition(length, -1));
-  } else {
-    ASSERT(use_index == CheckArrayBoundInstr::kLengthPos);
-    Definition* index = check->index()->definition();
-    constraint_range = new(I) Range(
-        RangeBoundary::FromDefinition(index, 1),
-        RangeBoundary::MaxSmi());
-  }
-  InsertConstraintFor(defn, constraint_range, check);
-}
-
-
-void RangeAnalysis::InsertConstraints() {
-  for (intptr_t i = 0; i < smi_checks_.length(); i++) {
-    CheckSmiInstr* check = smi_checks_[i];
-    ConstraintInstr* constraint =
-        InsertConstraintFor(check->value()->definition(),
-                            Range::UnknownSmi(),
-                            check);
-    if (constraint == NULL) {
-      // No constraint was needed.
-      continue;
-    }
-    // Mark the constraint's value's reaching type as smi.
-    CompileType* smi_compile_type =
-        ZoneCompileType::Wrap(CompileType::FromCid(kSmiCid));
-    constraint->value()->SetReachingType(smi_compile_type);
-  }
-
-  for (intptr_t i = 0; i < values_.length(); i++) {
-    InsertConstraintsFor(values_[i]);
-  }
-
-  for (intptr_t i = 0; i < constraints_.length(); i++) {
-    InsertConstraintsFor(constraints_[i]);
-  }
-}
-
-
-void RangeAnalysis::ResetWorklist() {
-  if (marked_defns_ == NULL) {
-    marked_defns_ = new(I) BitVector(flow_graph_->current_ssa_temp_index());
-  } else {
-    marked_defns_->Clear();
-  }
-  worklist_.Clear();
-}
-
-
-void RangeAnalysis::MarkDefinition(Definition* defn) {
-  // Unwrap constrained value.
-  while (defn->IsConstraint()) {
-    defn = defn->AsConstraint()->value()->definition();
-  }
-
-  if (!marked_defns_->Contains(defn->ssa_temp_index())) {
-    worklist_.Add(defn);
-    marked_defns_->Add(defn->ssa_temp_index());
-  }
-}
-
-
-RangeAnalysis::Direction RangeAnalysis::ToDirection(Value* val) {
-  if (val->BindsToConstant()) {
-    return (Smi::Cast(val->BoundConstant()).Value() >= 0) ? kPositive
-                                                          : kNegative;
-  } else if (val->definition()->range() != NULL) {
-    Range* range = val->definition()->range();
-    if (Range::ConstantMin(range).ConstantValue() >= 0) {
-      return kPositive;
-    } else if (Range::ConstantMax(range).ConstantValue() <= 0) {
-      return kNegative;
-    }
-  }
-  return kUnknown;
-}
-
-
-Range* RangeAnalysis::InferInductionVariableRange(JoinEntryInstr* loop_header,
-                                                  PhiInstr* var) {
-  BitVector* loop_info = loop_header->loop_info();
-
-  Definition* initial_value = NULL;
-  Direction direction = kUnknown;
-
-  ResetWorklist();
-  MarkDefinition(var);
-  while (!worklist_.is_empty()) {
-    Definition* defn = worklist_.RemoveLast();
-
-    if (defn->IsPhi()) {
-      PhiInstr* phi = defn->AsPhi();
-      for (intptr_t i = 0; i < phi->InputCount(); i++) {
-        Definition* defn = phi->InputAt(i)->definition();
-
-        if (!loop_info->Contains(defn->GetBlock()->preorder_number())) {
-          // The value is coming from outside of the loop.
-          if (initial_value == NULL) {
-            initial_value = defn;
-            continue;
-          } else if (initial_value == defn) {
-            continue;
-          } else {
-            return NULL;
-          }
-        }
-
-        MarkDefinition(defn);
-      }
-    } else if (defn->IsBinarySmiOp()) {
-      BinarySmiOpInstr* binary_op = defn->AsBinarySmiOp();
-
-      switch (binary_op->op_kind()) {
-        case Token::kADD: {
-          const Direction growth_right =
-              ToDirection(binary_op->right());
-          if (growth_right != kUnknown) {
-            UpdateDirection(&direction, growth_right);
-            MarkDefinition(binary_op->left()->definition());
-            break;
-          }
-
-          const Direction growth_left =
-              ToDirection(binary_op->left());
-          if (growth_left != kUnknown) {
-            UpdateDirection(&direction, growth_left);
-            MarkDefinition(binary_op->right()->definition());
-            break;
-          }
-
-          return NULL;
-        }
-
-        case Token::kSUB: {
-          const Direction growth_right =
-              ToDirection(binary_op->right());
-          if (growth_right != kUnknown) {
-            UpdateDirection(&direction, Invert(growth_right));
-            MarkDefinition(binary_op->left()->definition());
-            break;
-          }
-          return NULL;
-        }
-
-        default:
-          return NULL;
-      }
-    } else {
-      return NULL;
-    }
-  }
-
-
-  // We transitively discovered all dependencies of the given phi
-  // and confirmed that it depends on a single value coming from outside of
-  // the loop and some linear combinations of itself.
-  // Compute the range based on initial value and the direction of the growth.
-  switch (direction) {
-    case kPositive:
-      return new(I) Range(RangeBoundary::FromDefinition(initial_value),
-                          RangeBoundary::MaxSmi());
-
-    case kNegative:
-      return new(I) Range(RangeBoundary::MinSmi(),
-                          RangeBoundary::FromDefinition(initial_value));
-
-    case kUnknown:
-    case kBoth:
-      return Range::UnknownSmi();
-  }
-
-  UNREACHABLE();
-  return NULL;
-}
-
-
-void RangeAnalysis::InferRangesRecursive(BlockEntryInstr* block) {
-  JoinEntryInstr* join = block->AsJoinEntry();
-  if (join != NULL) {
-    const bool is_loop_header = (join->loop_info() != NULL);
-    for (PhiIterator it(join); !it.Done(); it.Advance()) {
-      PhiInstr* phi = it.Current();
-      if (definitions_->Contains(phi->ssa_temp_index())) {
-        if (is_loop_header) {
-          // Try recognizing simple induction variables.
-          Range* range = InferInductionVariableRange(join, phi);
-          if (range != NULL) {
-            phi->range_ = range;
-            continue;
-          }
-        }
-
-        phi->InferRange();
-      }
-    }
-  }
-
-  for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
-    Instruction* current = it.Current();
-
-    Definition* defn = current->AsDefinition();
-    if ((defn != NULL) &&
-        (defn->ssa_temp_index() != -1) &&
-        definitions_->Contains(defn->ssa_temp_index())) {
-      defn->InferRange();
-    } else if (FLAG_array_bounds_check_elimination &&
-               current->IsCheckArrayBound()) {
-      CheckArrayBoundInstr* check = current->AsCheckArrayBound();
-      RangeBoundary array_length =
-          RangeBoundary::FromDefinition(check->length()->definition());
-      if (check->IsRedundant(array_length)) {
-        it.RemoveCurrentFromGraph();
-      }
-    }
-  }
-
-  for (intptr_t i = 0; i < block->dominated_blocks().length(); ++i) {
-    InferRangesRecursive(block->dominated_blocks()[i]);
-  }
-}
-
-
-void RangeAnalysis::InferRanges() {
-  if (FLAG_trace_range_analysis) {
-    OS::Print("---- before range analysis -------\n");
-    FlowGraphPrinter printer(*flow_graph_);
-    printer.PrintBlocks();
-  }
-  // Initialize bitvector for quick filtering of int values.
-  definitions_ =
-      new(I) BitVector(flow_graph_->current_ssa_temp_index());
-  for (intptr_t i = 0; i < values_.length(); i++) {
-    definitions_->Add(values_[i]->ssa_temp_index());
-  }
-  for (intptr_t i = 0; i < constraints_.length(); i++) {
-    definitions_->Add(constraints_[i]->ssa_temp_index());
-  }
-
-  // Infer initial values of ranges.
-  const GrowableArray<Definition*>& initial =
-      *flow_graph_->graph_entry()->initial_definitions();
-  for (intptr_t i = 0; i < initial.length(); ++i) {
-    Definition* definition = initial[i];
-    if (definitions_->Contains(definition->ssa_temp_index())) {
-      definition->InferRange();
-    }
-  }
-  InferRangesRecursive(flow_graph_->graph_entry());
-
-  if (FLAG_trace_range_analysis) {
-    OS::Print("---- after range analysis -------\n");
-    FlowGraphPrinter printer(*flow_graph_);
-    printer.PrintBlocks();
-  }
-}
-
-
-void RangeAnalysis::RemoveConstraints() {
-  for (intptr_t i = 0; i < constraints_.length(); i++) {
-    Definition* def = constraints_[i]->value()->definition();
-    // Some constraints might be constraining constraints. Unwind the chain of
-    // constraints until we reach the actual definition.
-    while (def->IsConstraint()) {
-      def = def->AsConstraint()->value()->definition();
-    }
-    constraints_[i]->ReplaceUsesWith(def);
-    constraints_[i]->RemoveFromGraph();
-  }
-}
-
-
 void FlowGraphOptimizer::InferIntRanges() {
   RangeAnalysis range_analysis(flow_graph_);
   range_analysis.Analyze();
diff --git a/runtime/vm/flow_graph_range_analysis.cc b/runtime/vm/flow_graph_range_analysis.cc
new file mode 100644
index 0000000..ff95ed04
--- /dev/null
+++ b/runtime/vm/flow_graph_range_analysis.cc
@@ -0,0 +1,1946 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "vm/flow_graph_range_analysis.h"
+
+#include "vm/bit_vector.h"
+#include "vm/il_printer.h"
+
+namespace dart {
+
+DEFINE_FLAG(bool, array_bounds_check_elimination, true,
+    "Eliminate redundant bounds checks.");
+DEFINE_FLAG(bool, trace_range_analysis, false, "Trace range analysis progress");
+DEFINE_FLAG(bool, trace_integer_ir_selection, false,
+    "Print integer IR selection optimization pass.");
+DECLARE_FLAG(bool, trace_constant_propagation);
+
+// Quick access to the locally defined isolate() method.
+#define I (isolate())
+
+void RangeAnalysis::Analyze() {
+  CollectValues();
+  InsertConstraints();
+  InferRanges();
+  IntegerInstructionSelector iis(flow_graph_);
+  iis.Select();
+  RemoveConstraints();
+}
+
+
+void RangeAnalysis::CollectValues() {
+  const GrowableArray<Definition*>& initial =
+      *flow_graph_->graph_entry()->initial_definitions();
+  for (intptr_t i = 0; i < initial.length(); ++i) {
+    Definition* current = initial[i];
+    if (current->Type()->ToCid() == kSmiCid) {
+      values_.Add(current);
+    } else if (current->IsMintDefinition()) {
+      values_.Add(current);
+    }
+  }
+
+  for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
+       !block_it.Done();
+       block_it.Advance()) {
+    BlockEntryInstr* block = block_it.Current();
+
+
+    if (block->IsGraphEntry() || block->IsCatchBlockEntry()) {
+      const GrowableArray<Definition*>& initial = block->IsGraphEntry()
+          ? *block->AsGraphEntry()->initial_definitions()
+          : *block->AsCatchBlockEntry()->initial_definitions();
+      for (intptr_t i = 0; i < initial.length(); ++i) {
+        Definition* current = initial[i];
+        if (current->Type()->ToCid() == kSmiCid) {
+          values_.Add(current);
+        } else if (current->IsMintDefinition()) {
+          values_.Add(current);
+        }
+      }
+    }
+
+    JoinEntryInstr* join = block->AsJoinEntry();
+    if (join != NULL) {
+      for (PhiIterator phi_it(join); !phi_it.Done(); phi_it.Advance()) {
+        PhiInstr* current = phi_it.Current();
+        if (current->Type()->ToCid() == kSmiCid) {
+          values_.Add(current);
+        }
+      }
+    }
+
+    for (ForwardInstructionIterator instr_it(block);
+         !instr_it.Done();
+         instr_it.Advance()) {
+      Instruction* current = instr_it.Current();
+      Definition* defn = current->AsDefinition();
+      if (defn != NULL) {
+        if ((defn->Type()->ToCid() == kSmiCid) &&
+            (defn->ssa_temp_index() != -1)) {
+          values_.Add(defn);
+        } else if ((defn->IsMintDefinition()) &&
+                   (defn->ssa_temp_index() != -1)) {
+          values_.Add(defn);
+        }
+      } else if (current->IsCheckSmi()) {
+        smi_checks_.Add(current->AsCheckSmi());
+      }
+    }
+  }
+}
+
+
+// Returns true if use is dominated by the given instruction.
+// Note: uses that occur at instruction itself are not dominated by it.
+static bool IsDominatedUse(Instruction* dom, Value* use) {
+  BlockEntryInstr* dom_block = dom->GetBlock();
+
+  Instruction* instr = use->instruction();
+
+  PhiInstr* phi = instr->AsPhi();
+  if (phi != NULL) {
+    return dom_block->Dominates(phi->block()->PredecessorAt(use->use_index()));
+  }
+
+  BlockEntryInstr* use_block = instr->GetBlock();
+  if (use_block == dom_block) {
+    // Fast path for the case of block entry.
+    if (dom_block == dom) return true;
+
+    for (Instruction* curr = dom->next(); curr != NULL; curr = curr->next()) {
+      if (curr == instr) return true;
+    }
+
+    return false;
+  }
+
+  return dom_block->Dominates(use_block);
+}
+
+
+void RangeAnalysis::RenameDominatedUses(Definition* def,
+                                        Instruction* dom,
+                                        Definition* other) {
+  for (Value::Iterator it(def->input_use_list());
+       !it.Done();
+       it.Advance()) {
+    Value* use = it.Current();
+
+    // Skip dead phis.
+    PhiInstr* phi = use->instruction()->AsPhi();
+    ASSERT((phi == NULL) || phi->is_alive());
+    if (IsDominatedUse(dom, use)) {
+      use->BindTo(other);
+    }
+  }
+}
+
+
+// For a comparison operation return an operation for the equivalent flipped
+// comparison: a (op) b === b (op') a.
+static Token::Kind FlipComparison(Token::Kind op) {
+  switch (op) {
+    case Token::kEQ: return Token::kEQ;
+    case Token::kNE: return Token::kNE;
+    case Token::kLT: return Token::kGT;
+    case Token::kGT: return Token::kLT;
+    case Token::kLTE: return Token::kGTE;
+    case Token::kGTE: return Token::kLTE;
+    default:
+      UNREACHABLE();
+      return Token::kILLEGAL;
+  }
+}
+
+
+// Given a boundary (right operand) and a comparison operation return
+// a symbolic range constraint for the left operand of the comparison assuming
+// that it evaluated to true.
+// For example for the comparison a < b symbol a is constrained with range
+// [Smi::kMinValue, b - 1].
+Range* RangeAnalysis::ConstraintRange(Token::Kind op, Definition* boundary) {
+  switch (op) {
+    case Token::kEQ:
+      return new(I) Range(RangeBoundary::FromDefinition(boundary),
+                          RangeBoundary::FromDefinition(boundary));
+    case Token::kNE:
+      return Range::Unknown();
+    case Token::kLT:
+      return new(I) Range(RangeBoundary::MinSmi(),
+                          RangeBoundary::FromDefinition(boundary, -1));
+    case Token::kGT:
+      return new(I) Range(RangeBoundary::FromDefinition(boundary, 1),
+                          RangeBoundary::MaxSmi());
+    case Token::kLTE:
+      return new(I) Range(RangeBoundary::MinSmi(),
+                          RangeBoundary::FromDefinition(boundary));
+    case Token::kGTE:
+      return new(I) Range(RangeBoundary::FromDefinition(boundary),
+                          RangeBoundary::MaxSmi());
+    default:
+      UNREACHABLE();
+      return Range::Unknown();
+  }
+}
+
+
+ConstraintInstr* RangeAnalysis::InsertConstraintFor(Definition* defn,
+                                                    Range* constraint_range,
+                                                    Instruction* after) {
+  // No need to constrain constants.
+  if (defn->IsConstant()) return NULL;
+
+  ConstraintInstr* constraint = new(I) ConstraintInstr(
+      new(I) Value(defn), constraint_range);
+  flow_graph_->InsertAfter(after, constraint, NULL, FlowGraph::kValue);
+  RenameDominatedUses(defn, constraint, constraint);
+  constraints_.Add(constraint);
+  return constraint;
+}
+
+
+void RangeAnalysis::ConstrainValueAfterBranch(Definition* defn, Value* use) {
+  BranchInstr* branch = use->instruction()->AsBranch();
+  RelationalOpInstr* rel_op = branch->comparison()->AsRelationalOp();
+  if ((rel_op != NULL) && (rel_op->operation_cid() == kSmiCid)) {
+    // Found comparison of two smis. Constrain defn at true and false
+    // successors using the other operand as a boundary.
+    Definition* boundary;
+    Token::Kind op_kind;
+    if (use->use_index() == 0) {  // Left operand.
+      boundary = rel_op->InputAt(1)->definition();
+      op_kind = rel_op->kind();
+    } else {
+      ASSERT(use->use_index() == 1);  // Right operand.
+      boundary = rel_op->InputAt(0)->definition();
+      // InsertConstraintFor assumes that defn is left operand of a
+      // comparison if it is right operand flip the comparison.
+      op_kind = FlipComparison(rel_op->kind());
+    }
+
+    // Constrain definition at the true successor.
+    ConstraintInstr* true_constraint =
+        InsertConstraintFor(defn,
+                            ConstraintRange(op_kind, boundary),
+                            branch->true_successor());
+    // Mark true_constraint an artificial use of boundary. This ensures
+    // that constraint's range is recalculated if boundary's range changes.
+    if (true_constraint != NULL) {
+      true_constraint->AddDependency(boundary);
+      true_constraint->set_target(branch->true_successor());
+    }
+
+    // Constrain definition with a negated condition at the false successor.
+    ConstraintInstr* false_constraint =
+        InsertConstraintFor(
+            defn,
+            ConstraintRange(Token::NegateComparison(op_kind), boundary),
+            branch->false_successor());
+    // Mark false_constraint an artificial use of boundary. This ensures
+    // that constraint's range is recalculated if boundary's range changes.
+    if (false_constraint != NULL) {
+      false_constraint->AddDependency(boundary);
+      false_constraint->set_target(branch->false_successor());
+    }
+  }
+}
+
+
+void RangeAnalysis::InsertConstraintsFor(Definition* defn) {
+  for (Value* use = defn->input_use_list();
+       use != NULL;
+       use = use->next_use()) {
+    if (use->instruction()->IsBranch()) {
+      ConstrainValueAfterBranch(defn, use);
+    } else if (use->instruction()->IsCheckArrayBound()) {
+      ConstrainValueAfterCheckArrayBound(
+          defn,
+          use->instruction()->AsCheckArrayBound(),
+          use->use_index());
+    }
+  }
+}
+
+
+void RangeAnalysis::ConstrainValueAfterCheckArrayBound(
+    Definition* defn, CheckArrayBoundInstr* check, intptr_t use_index) {
+  Range* constraint_range = NULL;
+  if (use_index == CheckArrayBoundInstr::kIndexPos) {
+    Definition* length = check->length()->definition();
+    constraint_range = new(I) Range(
+        RangeBoundary::FromConstant(0),
+        RangeBoundary::FromDefinition(length, -1));
+  } else {
+    ASSERT(use_index == CheckArrayBoundInstr::kLengthPos);
+    Definition* index = check->index()->definition();
+    constraint_range = new(I) Range(
+        RangeBoundary::FromDefinition(index, 1),
+        RangeBoundary::MaxSmi());
+  }
+  InsertConstraintFor(defn, constraint_range, check);
+}
+
+
+void RangeAnalysis::InsertConstraints() {
+  for (intptr_t i = 0; i < smi_checks_.length(); i++) {
+    CheckSmiInstr* check = smi_checks_[i];
+    ConstraintInstr* constraint =
+        InsertConstraintFor(check->value()->definition(),
+                            Range::UnknownSmi(),
+                            check);
+    if (constraint == NULL) {
+      // No constraint was needed.
+      continue;
+    }
+    // Mark the constraint's value's reaching type as smi.
+    CompileType* smi_compile_type =
+        ZoneCompileType::Wrap(CompileType::FromCid(kSmiCid));
+    constraint->value()->SetReachingType(smi_compile_type);
+  }
+
+  for (intptr_t i = 0; i < values_.length(); i++) {
+    InsertConstraintsFor(values_[i]);
+  }
+
+  for (intptr_t i = 0; i < constraints_.length(); i++) {
+    InsertConstraintsFor(constraints_[i]);
+  }
+}
+
+
+void RangeAnalysis::ResetWorklist() {
+  if (marked_defns_ == NULL) {
+    marked_defns_ = new(I) BitVector(flow_graph_->current_ssa_temp_index());
+  } else {
+    marked_defns_->Clear();
+  }
+  worklist_.Clear();
+}
+
+
+void RangeAnalysis::MarkDefinition(Definition* defn) {
+  // Unwrap constrained value.
+  while (defn->IsConstraint()) {
+    defn = defn->AsConstraint()->value()->definition();
+  }
+
+  if (!marked_defns_->Contains(defn->ssa_temp_index())) {
+    worklist_.Add(defn);
+    marked_defns_->Add(defn->ssa_temp_index());
+  }
+}
+
+
+RangeAnalysis::Direction RangeAnalysis::ToDirection(Value* val) {
+  if (val->BindsToConstant()) {
+    return (Smi::Cast(val->BoundConstant()).Value() >= 0) ? kPositive
+                                                          : kNegative;
+  } else if (val->definition()->range() != NULL) {
+    Range* range = val->definition()->range();
+    if (Range::ConstantMin(range).ConstantValue() >= 0) {
+      return kPositive;
+    } else if (Range::ConstantMax(range).ConstantValue() <= 0) {
+      return kNegative;
+    }
+  }
+  return kUnknown;
+}
+
+
+Range* RangeAnalysis::InferInductionVariableRange(JoinEntryInstr* loop_header,
+                                                  PhiInstr* var) {
+  BitVector* loop_info = loop_header->loop_info();
+
+  Definition* initial_value = NULL;
+  Direction direction = kUnknown;
+
+  ResetWorklist();
+  MarkDefinition(var);
+  while (!worklist_.is_empty()) {
+    Definition* defn = worklist_.RemoveLast();
+
+    if (defn->IsPhi()) {
+      PhiInstr* phi = defn->AsPhi();
+      for (intptr_t i = 0; i < phi->InputCount(); i++) {
+        Definition* defn = phi->InputAt(i)->definition();
+
+        if (!loop_info->Contains(defn->GetBlock()->preorder_number())) {
+          // The value is coming from outside of the loop.
+          if (initial_value == NULL) {
+            initial_value = defn;
+            continue;
+          } else if (initial_value == defn) {
+            continue;
+          } else {
+            return NULL;
+          }
+        }
+
+        MarkDefinition(defn);
+      }
+    } else if (defn->IsBinarySmiOp()) {
+      BinarySmiOpInstr* binary_op = defn->AsBinarySmiOp();
+
+      switch (binary_op->op_kind()) {
+        case Token::kADD: {
+          const Direction growth_right =
+              ToDirection(binary_op->right());
+          if (growth_right != kUnknown) {
+            UpdateDirection(&direction, growth_right);
+            MarkDefinition(binary_op->left()->definition());
+            break;
+          }
+
+          const Direction growth_left =
+              ToDirection(binary_op->left());
+          if (growth_left != kUnknown) {
+            UpdateDirection(&direction, growth_left);
+            MarkDefinition(binary_op->right()->definition());
+            break;
+          }
+
+          return NULL;
+        }
+
+        case Token::kSUB: {
+          const Direction growth_right =
+              ToDirection(binary_op->right());
+          if (growth_right != kUnknown) {
+            UpdateDirection(&direction, Invert(growth_right));
+            MarkDefinition(binary_op->left()->definition());
+            break;
+          }
+          return NULL;
+        }
+
+        default:
+          return NULL;
+      }
+    } else {
+      return NULL;
+    }
+  }
+
+
+  // We transitively discovered all dependencies of the given phi
+  // and confirmed that it depends on a single value coming from outside of
+  // the loop and some linear combinations of itself.
+  // Compute the range based on initial value and the direction of the growth.
+  switch (direction) {
+    case kPositive:
+      return new(I) Range(RangeBoundary::FromDefinition(initial_value),
+                          RangeBoundary::MaxSmi());
+
+    case kNegative:
+      return new(I) Range(RangeBoundary::MinSmi(),
+                          RangeBoundary::FromDefinition(initial_value));
+
+    case kUnknown:
+    case kBoth:
+      return Range::UnknownSmi();
+  }
+
+  UNREACHABLE();
+  return NULL;
+}
+
+
+void RangeAnalysis::InferRangesRecursive(BlockEntryInstr* block) {
+  JoinEntryInstr* join = block->AsJoinEntry();
+  if (join != NULL) {
+    const bool is_loop_header = (join->loop_info() != NULL);
+    for (PhiIterator it(join); !it.Done(); it.Advance()) {
+      PhiInstr* phi = it.Current();
+      if (definitions_->Contains(phi->ssa_temp_index())) {
+        if (is_loop_header) {
+          // Try recognizing simple induction variables.
+          Range* range = InferInductionVariableRange(join, phi);
+          if (range != NULL) {
+            phi->range_ = range;
+            continue;
+          }
+        }
+
+        phi->InferRange();
+      }
+    }
+  }
+
+  for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
+    Instruction* current = it.Current();
+
+    Definition* defn = current->AsDefinition();
+    if ((defn != NULL) &&
+        (defn->ssa_temp_index() != -1) &&
+        definitions_->Contains(defn->ssa_temp_index())) {
+      defn->InferRange();
+    } else if (FLAG_array_bounds_check_elimination &&
+               current->IsCheckArrayBound()) {
+      CheckArrayBoundInstr* check = current->AsCheckArrayBound();
+      RangeBoundary array_length =
+          RangeBoundary::FromDefinition(check->length()->definition());
+      if (check->IsRedundant(array_length)) {
+        it.RemoveCurrentFromGraph();
+      }
+    }
+  }
+
+  for (intptr_t i = 0; i < block->dominated_blocks().length(); ++i) {
+    InferRangesRecursive(block->dominated_blocks()[i]);
+  }
+}
+
+
+void RangeAnalysis::InferRanges() {
+  if (FLAG_trace_range_analysis) {
+    OS::Print("---- before range analysis -------\n");
+    FlowGraphPrinter printer(*flow_graph_);
+    printer.PrintBlocks();
+  }
+  // Initialize bitvector for quick filtering of int values.
+  definitions_ =
+      new(I) BitVector(flow_graph_->current_ssa_temp_index());
+  for (intptr_t i = 0; i < values_.length(); i++) {
+    definitions_->Add(values_[i]->ssa_temp_index());
+  }
+  for (intptr_t i = 0; i < constraints_.length(); i++) {
+    definitions_->Add(constraints_[i]->ssa_temp_index());
+  }
+
+  // Infer initial values of ranges.
+  const GrowableArray<Definition*>& initial =
+      *flow_graph_->graph_entry()->initial_definitions();
+  for (intptr_t i = 0; i < initial.length(); ++i) {
+    Definition* definition = initial[i];
+    if (definitions_->Contains(definition->ssa_temp_index())) {
+      definition->InferRange();
+    }
+  }
+  InferRangesRecursive(flow_graph_->graph_entry());
+
+  if (FLAG_trace_range_analysis) {
+    OS::Print("---- after range analysis -------\n");
+    FlowGraphPrinter printer(*flow_graph_);
+    printer.PrintBlocks();
+  }
+}
+
+
+void RangeAnalysis::RemoveConstraints() {
+  for (intptr_t i = 0; i < constraints_.length(); i++) {
+    Definition* def = constraints_[i]->value()->definition();
+    // Some constraints might be constraining constraints. Unwind the chain of
+    // constraints until we reach the actual definition.
+    while (def->IsConstraint()) {
+      def = def->AsConstraint()->value()->definition();
+    }
+    constraints_[i]->ReplaceUsesWith(def);
+    constraints_[i]->RemoveFromGraph();
+  }
+}
+
+
+IntegerInstructionSelector::IntegerInstructionSelector(FlowGraph* flow_graph)
+    : flow_graph_(flow_graph),
+      isolate_(NULL) {
+  ASSERT(flow_graph_ != NULL);
+  isolate_ = flow_graph_->isolate();
+  ASSERT(isolate_ != NULL);
+  selected_uint32_defs_ =
+      new(I) BitVector(flow_graph_->current_ssa_temp_index());
+}
+
+
+void IntegerInstructionSelector::Select() {
+  if (FLAG_trace_integer_ir_selection) {
+    OS::Print("---- starting integer ir selection -------\n");
+  }
+  FindPotentialUint32Definitions();
+  FindUint32NarrowingDefinitions();
+  Propagate();
+  ReplaceInstructions();
+  if (FLAG_trace_integer_ir_selection) {
+    OS::Print("---- after integer ir selection -------\n");
+    FlowGraphPrinter printer(*flow_graph_);
+    printer.PrintBlocks();
+  }
+}
+
+
+bool IntegerInstructionSelector::IsPotentialUint32Definition(Definition* def) {
+  // TODO(johnmccutchan): Consider Smi operations, to avoid unnecessary tagging
+  // & untagged of intermediate results.
+  // TODO(johnmccutchan): Consider phis.
+  return def->IsBoxInteger()   ||   // BoxMint.
+         def->IsUnboxInteger() ||   // UnboxMint.
+         def->IsBinaryMintOp() ||
+         def->IsShiftMintOp()  ||
+         def->IsUnaryMintOp();
+}
+
+
+void IntegerInstructionSelector::FindPotentialUint32Definitions() {
+  if (FLAG_trace_integer_ir_selection) {
+    OS::Print("++++ Finding potential Uint32 definitions:\n");
+  }
+
+  for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
+       !block_it.Done();
+       block_it.Advance()) {
+    BlockEntryInstr* block = block_it.Current();
+
+    for (ForwardInstructionIterator instr_it(block);
+         !instr_it.Done();
+         instr_it.Advance()) {
+      Instruction* current = instr_it.Current();
+      Definition* defn = current->AsDefinition();
+      if ((defn != NULL) && (defn->ssa_temp_index() != -1)) {
+        if (IsPotentialUint32Definition(defn)) {
+          if (FLAG_trace_integer_ir_selection) {
+           OS::Print("Adding %s\n", current->ToCString());
+          }
+          potential_uint32_defs_.Add(defn);
+        }
+      }
+    }
+  }
+}
+
+
+// BinaryMintOp masks and stores into unsigned typed arrays that truncate the
+// value into a Uint32 range.
+bool IntegerInstructionSelector::IsUint32NarrowingDefinition(Definition* def) {
+  if (def->IsBinaryMintOp()) {
+    BinaryMintOpInstr* op = def->AsBinaryMintOp();
+    // Must be a mask operation.
+    if (op->op_kind() != Token::kBIT_AND) {
+      return false;
+    }
+    Range* range = op->range();
+    if ((range == NULL) ||
+        !range->IsWithin(0, static_cast<int64_t>(kMaxUint32))) {
+      return false;
+    }
+    return true;
+  }
+  // TODO(johnmccutchan): Add typed array stores.
+  return false;
+}
+
+
+void IntegerInstructionSelector::FindUint32NarrowingDefinitions() {
+  ASSERT(selected_uint32_defs_ != NULL);
+  if (FLAG_trace_integer_ir_selection) {
+    OS::Print("++++ Selecting Uint32 definitions:\n");
+    OS::Print("++++ Initial set:\n");
+  }
+  for (intptr_t i = 0; i < potential_uint32_defs_.length(); i++) {
+    Definition* defn = potential_uint32_defs_[i];
+    if (IsUint32NarrowingDefinition(defn)) {
+      if (FLAG_trace_integer_ir_selection) {
+        OS::Print("Adding %s\n", defn->ToCString());
+      }
+      selected_uint32_defs_->Add(defn->ssa_temp_index());
+    }
+  }
+}
+
+
+bool IntegerInstructionSelector::AllUsesAreUint32Narrowing(Value* list_head) {
+  for (Value::Iterator it(list_head);
+       !it.Done();
+       it.Advance()) {
+    Value* use = it.Current();
+    Definition* defn = use->instruction()->AsDefinition();
+    if ((defn == NULL) ||
+        (defn->ssa_temp_index() == -1) ||
+        !selected_uint32_defs_->Contains(defn->ssa_temp_index())) {
+      return false;
+    }
+  }
+  return true;
+}
+
+
+bool IntegerInstructionSelector::CanBecomeUint32(Definition* def) {
+  ASSERT(IsPotentialUint32Definition(def));
+  if (def->IsBoxInteger()) {
+    // If a BoxInteger's input is a candidate, the box is a candidate.
+    BoxIntegerInstr* box = def->AsBoxInteger();
+    Definition* box_input = box->value()->definition();
+    return selected_uint32_defs_->Contains(box_input->ssa_temp_index());
+  }
+  // A right shift with an input outside of Uint32 range cannot be converted
+  // because we need the high bits.
+  if (def->IsShiftMintOp()) {
+    ShiftMintOpInstr* op = def->AsShiftMintOp();
+    if (op->op_kind() == Token::kSHR) {
+      Definition* shift_input = op->left()->definition();
+      ASSERT(shift_input != NULL);
+      Range* range = shift_input->range();
+      if ((range == NULL) ||
+          !range->IsWithin(0, static_cast<int64_t>(kMaxUint32))) {
+        return false;
+      }
+    }
+  }
+  if (!def->HasUses()) {
+    // No uses, skip.
+    return false;
+  }
+  return AllUsesAreUint32Narrowing(def->input_use_list()) &&
+         AllUsesAreUint32Narrowing(def->env_use_list());
+}
+
+
+void IntegerInstructionSelector::Propagate() {
+  ASSERT(selected_uint32_defs_ != NULL);
+  bool changed = true;
+  intptr_t iteration = 0;
+  while (changed) {
+    if (FLAG_trace_integer_ir_selection) {
+      OS::Print("+++ Iteration: %" Pd "\n", iteration++);
+    }
+    changed = false;
+    for (intptr_t i = 0; i < potential_uint32_defs_.length(); i++) {
+      Definition* defn = potential_uint32_defs_[i];
+      if (selected_uint32_defs_->Contains(defn->ssa_temp_index())) {
+        // Already marked as a candidate, skip.
+        continue;
+      }
+      if (defn->IsConstant()) {
+        // Skip constants.
+        continue;
+      }
+      if (CanBecomeUint32(defn)) {
+        if (FLAG_trace_integer_ir_selection) {
+          OS::Print("Adding %s\n", defn->ToCString());
+        }
+        // Found a new candidate.
+        selected_uint32_defs_->Add(defn->ssa_temp_index());
+        // Haven't reached fixed point yet.
+        changed = true;
+      }
+    }
+  }
+  if (FLAG_trace_integer_ir_selection) {
+    OS::Print("Reached fixed point\n");
+  }
+}
+
+
+Definition* IntegerInstructionSelector::ConstructReplacementFor(
+    Definition* def) {
+  // Should only see mint definitions.
+  ASSERT(IsPotentialUint32Definition(def));
+  // Should not see constant instructions.
+  ASSERT(!def->IsConstant());
+  if (def->IsBinaryMintOp()) {
+    BinaryMintOpInstr* op = def->AsBinaryMintOp();
+    Token::Kind op_kind = op->op_kind();
+    Value* left = op->left()->CopyWithType();
+    Value* right = op->right()->CopyWithType();
+    intptr_t deopt_id = op->DeoptimizationTarget();
+    return new(I) BinaryUint32OpInstr(op_kind, left, right, deopt_id);
+  } else if (def->IsBoxInteger()) {
+    BoxIntegerInstr* box = def->AsBoxInteger();
+    Value* value = box->value()->CopyWithType();
+    return new(I) BoxUint32Instr(value);
+  } else if (def->IsUnboxInteger()) {
+    UnboxIntegerInstr* unbox = def->AsUnboxInteger();
+    Value* value = unbox->value()->CopyWithType();
+    intptr_t deopt_id = unbox->deopt_id();
+    return new(I) UnboxUint32Instr(value, deopt_id);
+  } else if (def->IsUnaryMintOp()) {
+    UnaryMintOpInstr* op = def->AsUnaryMintOp();
+    Token::Kind op_kind = op->op_kind();
+    Value* value = op->value()->CopyWithType();
+    intptr_t deopt_id = op->DeoptimizationTarget();
+    return new(I) UnaryUint32OpInstr(op_kind, value, deopt_id);
+  } else if (def->IsShiftMintOp()) {
+    ShiftMintOpInstr* op = def->AsShiftMintOp();
+    Token::Kind op_kind = op->op_kind();
+    Value* left = op->left()->CopyWithType();
+    Value* right = op->right()->CopyWithType();
+    intptr_t deopt_id = op->DeoptimizationTarget();
+    return new(I) ShiftUint32OpInstr(op_kind, left, right, deopt_id);
+  }
+  UNREACHABLE();
+  return NULL;
+}
+
+
+void IntegerInstructionSelector::ReplaceInstructions() {
+  if (FLAG_trace_integer_ir_selection) {
+    OS::Print("++++ Replacing instructions:\n");
+  }
+  for (intptr_t i = 0; i < potential_uint32_defs_.length(); i++) {
+    Definition* defn = potential_uint32_defs_[i];
+    if (!selected_uint32_defs_->Contains(defn->ssa_temp_index())) {
+      // Not a candidate.
+      continue;
+    }
+    Definition* replacement = ConstructReplacementFor(defn);
+    ASSERT(replacement != NULL);
+    if (FLAG_trace_integer_ir_selection) {
+      OS::Print("Replacing %s with %s\n", defn->ToCString(),
+                                          replacement->ToCString());
+    }
+    defn->ReplaceWith(replacement, NULL);
+    ASSERT(flow_graph_->VerifyUseLists());
+  }
+}
+
+
+RangeBoundary RangeBoundary::FromDefinition(Definition* defn, int64_t offs) {
+  if (defn->IsConstant() && defn->AsConstant()->value().IsSmi()) {
+    return FromConstant(Smi::Cast(defn->AsConstant()->value()).Value() + offs);
+  }
+  return RangeBoundary(kSymbol, reinterpret_cast<intptr_t>(defn), offs);
+}
+
+
+RangeBoundary RangeBoundary::LowerBound() const {
+  if (IsInfinity()) {
+    return NegativeInfinity();
+  }
+  if (IsConstant()) return *this;
+  return Add(Range::ConstantMin(symbol()->range()),
+             RangeBoundary::FromConstant(offset_),
+             NegativeInfinity());
+}
+
+
+RangeBoundary RangeBoundary::UpperBound() const {
+  if (IsInfinity()) {
+    return PositiveInfinity();
+  }
+  if (IsConstant()) return *this;
+  return Add(Range::ConstantMax(symbol()->range()),
+             RangeBoundary::FromConstant(offset_),
+             PositiveInfinity());
+}
+
+
+RangeBoundary RangeBoundary::Add(const RangeBoundary& a,
+                                 const RangeBoundary& b,
+                                 const RangeBoundary& overflow) {
+  if (a.IsInfinity() || b.IsInfinity()) return overflow;
+
+  ASSERT(a.IsConstant() && b.IsConstant());
+  if (Utils::WillAddOverflow(a.ConstantValue(), b.ConstantValue())) {
+    return overflow;
+  }
+
+  int64_t result = a.ConstantValue() + b.ConstantValue();
+
+  return RangeBoundary::FromConstant(result);
+}
+
+
+RangeBoundary RangeBoundary::Sub(const RangeBoundary& a,
+                                 const RangeBoundary& b,
+                                 const RangeBoundary& overflow) {
+  if (a.IsInfinity() || b.IsInfinity()) return overflow;
+  ASSERT(a.IsConstant() && b.IsConstant());
+  if (Utils::WillSubOverflow(a.ConstantValue(), b.ConstantValue())) {
+    return overflow;
+  }
+
+  int64_t result = a.ConstantValue() - b.ConstantValue();
+
+  return RangeBoundary::FromConstant(result);
+}
+
+
+bool RangeBoundary::SymbolicAdd(const RangeBoundary& a,
+                                const RangeBoundary& b,
+                                RangeBoundary* result) {
+  if (a.IsSymbol() && b.IsConstant()) {
+    if (Utils::WillAddOverflow(a.offset(), b.ConstantValue())) {
+      return false;
+    }
+
+    const int64_t offset = a.offset() + b.ConstantValue();
+
+    *result = RangeBoundary::FromDefinition(a.symbol(), offset);
+    return true;
+  } else if (b.IsSymbol() && a.IsConstant()) {
+    return SymbolicAdd(b, a, result);
+  }
+  return false;
+}
+
+
+bool RangeBoundary::SymbolicSub(const RangeBoundary& a,
+                                const RangeBoundary& b,
+                                RangeBoundary* result) {
+  if (a.IsSymbol() && b.IsConstant()) {
+    if (Utils::WillSubOverflow(a.offset(), b.ConstantValue())) {
+      return false;
+    }
+
+    const int64_t offset = a.offset() - b.ConstantValue();
+
+    *result = RangeBoundary::FromDefinition(a.symbol(), offset);
+    return true;
+  }
+  return false;
+}
+
+
+static Definition* UnwrapConstraint(Definition* defn) {
+  while (defn->IsConstraint()) {
+    defn = defn->AsConstraint()->value()->definition();
+  }
+  return defn;
+}
+
+
+static bool AreEqualDefinitions(Definition* a, Definition* b) {
+  a = UnwrapConstraint(a);
+  b = UnwrapConstraint(b);
+  return (a == b) ||
+      (a->AllowsCSE() &&
+       a->Dependencies().IsNone() &&
+       b->AllowsCSE() &&
+       b->Dependencies().IsNone() &&
+       a->Equals(b));
+}
+
+
+// Returns true if two range boundaries refer to the same symbol.
+static bool DependOnSameSymbol(const RangeBoundary& a, const RangeBoundary& b) {
+  return a.IsSymbol() && b.IsSymbol() &&
+      AreEqualDefinitions(a.symbol(), b.symbol());
+}
+
+
+bool RangeBoundary::Equals(const RangeBoundary& other) const {
+  if (IsConstant() && other.IsConstant()) {
+    return ConstantValue() == other.ConstantValue();
+  } else if (IsInfinity() && other.IsInfinity()) {
+    return kind() == other.kind();
+  } else if (IsSymbol() && other.IsSymbol()) {
+    return (offset() == other.offset()) && DependOnSameSymbol(*this, other);
+  } else if (IsUnknown() && other.IsUnknown()) {
+    return true;
+  }
+  return false;
+}
+
+
+RangeBoundary RangeBoundary::Shl(const RangeBoundary& value_boundary,
+                                 int64_t shift_count,
+                                 const RangeBoundary& overflow) {
+  ASSERT(value_boundary.IsConstant());
+  ASSERT(shift_count >= 0);
+  int64_t limit = 64 - shift_count;
+  int64_t value = value_boundary.ConstantValue();
+
+  if ((value == 0) ||
+      (shift_count == 0) ||
+      ((limit > 0) && Utils::IsInt(static_cast<int>(limit), value))) {
+    // Result stays in 64 bit range.
+    int64_t result = value << shift_count;
+    return RangeBoundary(result);
+  }
+
+  return overflow;
+}
+
+
+static RangeBoundary CanonicalizeBoundary(const RangeBoundary& a,
+                                          const RangeBoundary& overflow) {
+  if (a.IsConstant() || a.IsInfinity()) {
+    return a;
+  }
+
+  int64_t offset = a.offset();
+  Definition* symbol = a.symbol();
+
+  bool changed;
+  do {
+    changed = false;
+    if (symbol->IsConstraint()) {
+      symbol = symbol->AsConstraint()->value()->definition();
+      changed = true;
+    } else if (symbol->IsBinarySmiOp()) {
+      BinarySmiOpInstr* op = symbol->AsBinarySmiOp();
+      Definition* left = op->left()->definition();
+      Definition* right = op->right()->definition();
+      switch (op->op_kind()) {
+        case Token::kADD:
+          if (right->IsConstant()) {
+            int64_t rhs = Smi::Cast(right->AsConstant()->value()).Value();
+            if (Utils::WillAddOverflow(offset, rhs)) {
+              return overflow;
+            }
+            offset += rhs;
+            symbol = left;
+            changed = true;
+          } else if (left->IsConstant()) {
+            int64_t rhs = Smi::Cast(left->AsConstant()->value()).Value();
+            if (Utils::WillAddOverflow(offset, rhs)) {
+              return overflow;
+            }
+            offset += rhs;
+            symbol = right;
+            changed = true;
+          }
+          break;
+
+        case Token::kSUB:
+          if (right->IsConstant()) {
+            int64_t rhs = Smi::Cast(right->AsConstant()->value()).Value();
+            if (Utils::WillSubOverflow(offset, rhs)) {
+              return overflow;
+            }
+            offset -= rhs;
+            symbol = left;
+            changed = true;
+          }
+          break;
+
+        default:
+          break;
+      }
+    }
+  } while (changed);
+
+  return RangeBoundary::FromDefinition(symbol, offset);
+}
+
+
+static bool CanonicalizeMaxBoundary(RangeBoundary* a) {
+  if (!a->IsSymbol()) return false;
+
+  Range* range = a->symbol()->range();
+  if ((range == NULL) || !range->max().IsSymbol()) return false;
+
+
+  if (Utils::WillAddOverflow(range->max().offset(), a->offset())) {
+    *a = RangeBoundary::PositiveInfinity();
+    return true;
+  }
+
+  const int64_t offset = range->max().offset() + a->offset();
+
+
+  *a = CanonicalizeBoundary(
+      RangeBoundary::FromDefinition(range->max().symbol(), offset),
+      RangeBoundary::PositiveInfinity());
+
+  return true;
+}
+
+
+static bool CanonicalizeMinBoundary(RangeBoundary* a) {
+  if (!a->IsSymbol()) return false;
+
+  Range* range = a->symbol()->range();
+  if ((range == NULL) || !range->min().IsSymbol()) return false;
+
+  if (Utils::WillAddOverflow(range->min().offset(), a->offset())) {
+    *a = RangeBoundary::NegativeInfinity();
+    return true;
+  }
+
+  const int64_t offset = range->min().offset() + a->offset();
+
+  *a = CanonicalizeBoundary(
+      RangeBoundary::FromDefinition(range->min().symbol(), offset),
+      RangeBoundary::NegativeInfinity());
+
+  return true;
+}
+
+
+RangeBoundary RangeBoundary::Min(RangeBoundary a, RangeBoundary b,
+                                 RangeSize size) {
+  ASSERT(!(a.IsNegativeInfinity() || b.IsNegativeInfinity()));
+  ASSERT(!a.IsUnknown() || !b.IsUnknown());
+  if (a.IsUnknown() && !b.IsUnknown()) {
+    return b;
+  }
+  if (!a.IsUnknown() && b.IsUnknown()) {
+    return a;
+  }
+  if (size == kRangeBoundarySmi) {
+    if (a.IsSmiMaximumOrAbove() && !b.IsSmiMaximumOrAbove()) {
+      return b;
+    }
+    if (!a.IsSmiMaximumOrAbove() && b.IsSmiMaximumOrAbove()) {
+      return a;
+    }
+  } else {
+    ASSERT(size == kRangeBoundaryInt64);
+    if (a.IsMaximumOrAbove() && !b.IsMaximumOrAbove()) {
+      return b;
+    }
+    if (!a.IsMaximumOrAbove() && b.IsMaximumOrAbove()) {
+      return a;
+    }
+  }
+
+  if (a.Equals(b)) {
+    return b;
+  }
+
+  {
+    RangeBoundary canonical_a =
+        CanonicalizeBoundary(a, RangeBoundary::PositiveInfinity());
+    RangeBoundary canonical_b =
+        CanonicalizeBoundary(b, RangeBoundary::PositiveInfinity());
+    do {
+      if (DependOnSameSymbol(canonical_a, canonical_b)) {
+        a = canonical_a;
+        b = canonical_b;
+        break;
+      }
+    } while (CanonicalizeMaxBoundary(&canonical_a) ||
+             CanonicalizeMaxBoundary(&canonical_b));
+  }
+
+  if (DependOnSameSymbol(a, b)) {
+    return (a.offset() <= b.offset()) ? a : b;
+  }
+
+  const int64_t min_a = a.UpperBound().Clamp(size).ConstantValue();
+  const int64_t min_b = b.UpperBound().Clamp(size).ConstantValue();
+
+  return RangeBoundary::FromConstant(Utils::Minimum(min_a, min_b));
+}
+
+
+RangeBoundary RangeBoundary::Max(RangeBoundary a, RangeBoundary b,
+                                 RangeSize size) {
+  ASSERT(!(a.IsPositiveInfinity() || b.IsPositiveInfinity()));
+  ASSERT(!a.IsUnknown() || !b.IsUnknown());
+  if (a.IsUnknown() && !b.IsUnknown()) {
+    return b;
+  }
+  if (!a.IsUnknown() && b.IsUnknown()) {
+    return a;
+  }
+  if (size == kRangeBoundarySmi) {
+    if (a.IsSmiMinimumOrBelow() && !b.IsSmiMinimumOrBelow()) {
+      return b;
+    }
+    if (!a.IsSmiMinimumOrBelow() && b.IsSmiMinimumOrBelow()) {
+      return a;
+    }
+  } else {
+     ASSERT(size == kRangeBoundaryInt64);
+    if (a.IsMinimumOrBelow() && !b.IsMinimumOrBelow()) {
+      return b;
+    }
+    if (!a.IsMinimumOrBelow() && b.IsMinimumOrBelow()) {
+      return a;
+    }
+  }
+  if (a.Equals(b)) {
+    return b;
+  }
+
+  {
+    RangeBoundary canonical_a =
+        CanonicalizeBoundary(a, RangeBoundary::NegativeInfinity());
+    RangeBoundary canonical_b =
+        CanonicalizeBoundary(b, RangeBoundary::NegativeInfinity());
+
+    do {
+      if (DependOnSameSymbol(canonical_a, canonical_b)) {
+        a = canonical_a;
+        b = canonical_b;
+        break;
+      }
+    } while (CanonicalizeMinBoundary(&canonical_a) ||
+             CanonicalizeMinBoundary(&canonical_b));
+  }
+
+  if (DependOnSameSymbol(a, b)) {
+    return (a.offset() <= b.offset()) ? b : a;
+  }
+
+  const int64_t max_a = a.LowerBound().Clamp(size).ConstantValue();
+  const int64_t max_b = b.LowerBound().Clamp(size).ConstantValue();
+
+  return RangeBoundary::FromConstant(Utils::Maximum(max_a, max_b));
+}
+
+
+int64_t RangeBoundary::ConstantValue() const {
+  ASSERT(IsConstant());
+  return value_;
+}
+
+
+bool Range::IsPositive() const {
+  if (min().IsNegativeInfinity()) {
+    return false;
+  }
+  if (min().LowerBound().ConstantValue() < 0) {
+    return false;
+  }
+  if (max().IsPositiveInfinity()) {
+    return true;
+  }
+  return max().UpperBound().ConstantValue() >= 0;
+}
+
+
+bool Range::OnlyLessThanOrEqualTo(int64_t val) const {
+  if (max().IsPositiveInfinity()) {
+    // Cannot be true.
+    return false;
+  }
+  if (max().UpperBound().ConstantValue() > val) {
+    // Not true.
+    return false;
+  }
+  return true;
+}
+
+
+bool Range::OnlyGreaterThanOrEqualTo(int64_t val) const {
+  if (min().IsNegativeInfinity()) {
+    return false;
+  }
+  if (min().LowerBound().ConstantValue() < val) {
+    return false;
+  }
+  return true;
+}
+
+
+// Inclusive.
+bool Range::IsWithin(int64_t min_int, int64_t max_int) const {
+  RangeBoundary lower_min = min().LowerBound();
+  if (lower_min.IsNegativeInfinity() || (lower_min.ConstantValue() < min_int)) {
+    return false;
+  }
+  RangeBoundary upper_max = max().UpperBound();
+  if (upper_max.IsPositiveInfinity() || (upper_max.ConstantValue() > max_int)) {
+    return false;
+  }
+  return true;
+}
+
+
+bool Range::Overlaps(int64_t min_int, int64_t max_int) const {
+  RangeBoundary lower = min().LowerBound();
+  RangeBoundary upper = max().UpperBound();
+  const int64_t this_min = lower.IsNegativeInfinity() ?
+      RangeBoundary::kMin : lower.ConstantValue();
+  const int64_t this_max = upper.IsPositiveInfinity() ?
+      RangeBoundary::kMax : upper.ConstantValue();
+  if ((this_min <= min_int) && (min_int <= this_max)) return true;
+  if ((this_min <= max_int) && (max_int <= this_max)) return true;
+  if ((min_int < this_min) && (max_int > this_max)) return true;
+  return false;
+}
+
+
+bool Range::IsUnsatisfiable() const {
+  // Infinity case: [+inf, ...] || [..., -inf]
+  if (min().IsPositiveInfinity() || max().IsNegativeInfinity()) {
+    return true;
+  }
+  // Constant case: For example [0, -1].
+  if (Range::ConstantMin(this).ConstantValue() >
+      Range::ConstantMax(this).ConstantValue()) {
+    return true;
+  }
+  // Symbol case: For example [v+1, v].
+  if (DependOnSameSymbol(min(), max()) && min().offset() > max().offset()) {
+    return true;
+  }
+  return false;
+}
+
+
+void Range::Clamp(RangeBoundary::RangeSize size) {
+  min_ = min_.Clamp(size);
+  max_ = max_.Clamp(size);
+}
+
+
+void Range::Shl(const Range* left,
+                const Range* right,
+                RangeBoundary* result_min,
+                RangeBoundary* result_max) {
+  ASSERT(left != NULL);
+  ASSERT(right != NULL);
+  ASSERT(result_min != NULL);
+  ASSERT(result_max != NULL);
+  RangeBoundary left_max = Range::ConstantMax(left);
+  RangeBoundary left_min = Range::ConstantMin(left);
+  // A negative shift count always deoptimizes (and throws), so the minimum
+  // shift count is zero.
+  int64_t right_max = Utils::Maximum(Range::ConstantMax(right).ConstantValue(),
+                                     static_cast<int64_t>(0));
+  int64_t right_min = Utils::Maximum(Range::ConstantMin(right).ConstantValue(),
+                                     static_cast<int64_t>(0));
+
+  *result_min = RangeBoundary::Shl(
+      left_min,
+      left_min.ConstantValue() > 0 ? right_min : right_max,
+      left_min.ConstantValue() > 0
+          ? RangeBoundary::PositiveInfinity()
+          : RangeBoundary::NegativeInfinity());
+
+  *result_max = RangeBoundary::Shl(
+      left_max,
+      left_max.ConstantValue() > 0 ? right_max : right_min,
+      left_max.ConstantValue() > 0
+          ? RangeBoundary::PositiveInfinity()
+          : RangeBoundary::NegativeInfinity());
+}
+
+
+void Range::Shr(const Range* left,
+                const Range* right,
+                RangeBoundary* result_min,
+                RangeBoundary* result_max) {
+  RangeBoundary left_max = Range::ConstantMax(left);
+  RangeBoundary left_min = Range::ConstantMin(left);
+  // A negative shift count always deoptimizes (and throws), so the minimum
+  // shift count is zero.
+  int64_t right_max = Utils::Maximum(Range::ConstantMax(right).ConstantValue(),
+                                     static_cast<int64_t>(0));
+  int64_t right_min = Utils::Maximum(Range::ConstantMin(right).ConstantValue(),
+                                     static_cast<int64_t>(0));
+
+  *result_min = RangeBoundary::Shr(
+      left_min,
+      left_min.ConstantValue() > 0 ? right_max : right_min);
+
+  *result_max = RangeBoundary::Shr(
+      left_max,
+      left_max.ConstantValue() > 0 ? right_min : right_max);
+}
+
+
+bool Range::And(const Range* left_range,
+                const Range* right_range,
+                RangeBoundary* result_min,
+                RangeBoundary* result_max) {
+  ASSERT(left_range != NULL);
+  ASSERT(right_range != NULL);
+  ASSERT(result_min != NULL);
+  ASSERT(result_max != NULL);
+
+  if (Range::ConstantMin(right_range).ConstantValue() >= 0) {
+    *result_min = RangeBoundary::FromConstant(0);
+    *result_max = Range::ConstantMax(right_range);
+    return true;
+  }
+
+  if (Range::ConstantMin(left_range).ConstantValue() >= 0) {
+    *result_min = RangeBoundary::FromConstant(0);
+    *result_max = Range::ConstantMax(left_range);
+    return true;
+  }
+
+  return false;
+}
+
+
+static bool IsArrayLength(Definition* defn) {
+  if (defn == NULL) {
+    return false;
+  }
+  LoadFieldInstr* load = defn->AsLoadField();
+  return (load != NULL) && load->IsImmutableLengthLoad();
+}
+
+
+void Range::Add(const Range* left_range,
+                const Range* right_range,
+                RangeBoundary* result_min,
+                RangeBoundary* result_max,
+                Definition* left_defn) {
+  ASSERT(left_range != NULL);
+  ASSERT(right_range != NULL);
+  ASSERT(result_min != NULL);
+  ASSERT(result_max != NULL);
+
+  RangeBoundary left_min =
+    IsArrayLength(left_defn) ?
+        RangeBoundary::FromDefinition(left_defn) : left_range->min();
+
+  RangeBoundary left_max =
+    IsArrayLength(left_defn) ?
+        RangeBoundary::FromDefinition(left_defn) : left_range->max();
+
+  if (!RangeBoundary::SymbolicAdd(left_min, right_range->min(), result_min)) {
+    *result_min = RangeBoundary::Add(left_range->min().LowerBound(),
+                                     right_range->min().LowerBound(),
+                                     RangeBoundary::NegativeInfinity());
+  }
+  if (!RangeBoundary::SymbolicAdd(left_max, right_range->max(), result_max)) {
+    *result_max = RangeBoundary::Add(right_range->max().UpperBound(),
+                                     left_range->max().UpperBound(),
+                                     RangeBoundary::PositiveInfinity());
+  }
+}
+
+
+void Range::Sub(const Range* left_range,
+                const Range* right_range,
+                RangeBoundary* result_min,
+                RangeBoundary* result_max,
+                Definition* left_defn) {
+  ASSERT(left_range != NULL);
+  ASSERT(right_range != NULL);
+  ASSERT(result_min != NULL);
+  ASSERT(result_max != NULL);
+
+  RangeBoundary left_min =
+    IsArrayLength(left_defn) ?
+        RangeBoundary::FromDefinition(left_defn) : left_range->min();
+
+  RangeBoundary left_max =
+    IsArrayLength(left_defn) ?
+        RangeBoundary::FromDefinition(left_defn) : left_range->max();
+
+  if (!RangeBoundary::SymbolicSub(left_min, right_range->max(), result_min)) {
+    *result_min = RangeBoundary::Sub(left_range->min().LowerBound(),
+                              right_range->max().UpperBound(),
+                              RangeBoundary::NegativeInfinity());
+  }
+  if (!RangeBoundary::SymbolicSub(left_max, right_range->min(), result_max)) {
+    *result_max = RangeBoundary::Sub(left_range->max().UpperBound(),
+                                     right_range->min().LowerBound(),
+                                     RangeBoundary::PositiveInfinity());
+  }
+}
+
+
+bool Range::Mul(const Range* left_range,
+                const Range* right_range,
+                RangeBoundary* result_min,
+                RangeBoundary* result_max) {
+  ASSERT(left_range != NULL);
+  ASSERT(right_range != NULL);
+  ASSERT(result_min != NULL);
+  ASSERT(result_max != NULL);
+
+  const int64_t left_max = ConstantAbsMax(left_range);
+  const int64_t right_max = ConstantAbsMax(right_range);
+  if ((left_max <= -kSmiMin) && (right_max <= -kSmiMin) &&
+      ((left_max == 0) || (right_max <= kMaxInt64 / left_max))) {
+    // Product of left and right max values stays in 64 bit range.
+    const int64_t mul_max = left_max * right_max;
+    if (Smi::IsValid(mul_max) && Smi::IsValid(-mul_max)) {
+      const int64_t r_min =
+          OnlyPositiveOrZero(*left_range, *right_range) ? 0 : -mul_max;
+      *result_min = RangeBoundary::FromConstant(r_min);
+      const int64_t r_max =
+          OnlyNegativeOrZero(*left_range, *right_range) ? 0 : mul_max;
+      *result_max = RangeBoundary::FromConstant(r_max);
+      return true;
+    }
+  }
+  return false;
+}
+
+
+// Both the a and b ranges are >= 0.
+bool Range::OnlyPositiveOrZero(const Range& a, const Range& b) {
+  return a.OnlyGreaterThanOrEqualTo(0) && b.OnlyGreaterThanOrEqualTo(0);
+}
+
+
+// Both the a and b ranges are <= 0.
+bool Range::OnlyNegativeOrZero(const Range& a, const Range& b) {
+  return a.OnlyLessThanOrEqualTo(0) && b.OnlyLessThanOrEqualTo(0);
+}
+
+
+// Return the maximum absolute value included in range.
+int64_t Range::ConstantAbsMax(const Range* range) {
+  if (range == NULL) {
+    return RangeBoundary::kMax;
+  }
+  const int64_t abs_min = Utils::Abs(Range::ConstantMin(range).ConstantValue());
+  const int64_t abs_max = Utils::Abs(Range::ConstantMax(range).ConstantValue());
+  return Utils::Maximum(abs_min, abs_max);
+}
+
+
+Range* Range::BinaryOp(const Token::Kind op,
+                       const Range* left_range,
+                       const Range* right_range,
+                       Definition* left_defn) {
+  ASSERT(left_range != NULL);
+  ASSERT(right_range != NULL);
+
+  // Both left and right ranges are finite.
+  ASSERT(left_range->IsFinite());
+  ASSERT(right_range->IsFinite());
+
+  RangeBoundary min;
+  RangeBoundary max;
+  ASSERT(min.IsUnknown() && max.IsUnknown());
+
+  switch (op) {
+    case Token::kADD:
+      Range::Add(left_range, right_range, &min, &max, left_defn);
+      break;
+    case Token::kSUB:
+      Range::Sub(left_range, right_range, &min, &max, left_defn);
+      break;
+    case Token::kMUL: {
+      if (!Range::Mul(left_range, right_range, &min, &max)) {
+        return NULL;
+      }
+      break;
+    }
+    case Token::kSHL: {
+      Range::Shl(left_range, right_range, &min, &max);
+      break;
+    }
+    case Token::kSHR: {
+      Range::Shr(left_range, right_range, &min, &max);
+      break;
+    }
+    case Token::kBIT_AND:
+      if (!Range::And(left_range, right_range, &min, &max)) {
+        return NULL;
+      }
+      break;
+    default:
+      return NULL;
+      break;
+  }
+
+  ASSERT(!min.IsUnknown() && !max.IsUnknown());
+
+  return new Range(min, max);
+}
+
+
+void Definition::InferRange() {
+  if (Type()->ToCid() == kSmiCid) {
+    if (range_ == NULL) {
+      range_ = Range::UnknownSmi();
+    }
+  } else if (IsMintDefinition()) {
+    if (range_ == NULL) {
+      range_ = Range::Unknown();
+    }
+  } else {
+    // Only Smi and Mint supported.
+    UNREACHABLE();
+  }
+}
+
+
+void PhiInstr::InferRange() {
+  RangeBoundary new_min;
+  RangeBoundary new_max;
+
+  ASSERT(Type()->ToCid() == kSmiCid);
+
+  for (intptr_t i = 0; i < InputCount(); i++) {
+    Range* input_range = InputAt(i)->definition()->range();
+    if (input_range == NULL) {
+      range_ = Range::UnknownSmi();
+      return;
+    }
+
+    if (new_min.IsUnknown()) {
+      new_min = Range::ConstantMin(input_range);
+    } else {
+      new_min = RangeBoundary::Min(new_min,
+                                   Range::ConstantMinSmi(input_range),
+                                   RangeBoundary::kRangeBoundarySmi);
+    }
+
+    if (new_max.IsUnknown()) {
+      new_max = Range::ConstantMax(input_range);
+    } else {
+      new_max = RangeBoundary::Max(new_max,
+                                   Range::ConstantMaxSmi(input_range),
+                                   RangeBoundary::kRangeBoundarySmi);
+    }
+  }
+
+  ASSERT(new_min.IsUnknown() == new_max.IsUnknown());
+  if (new_min.IsUnknown()) {
+    range_ = Range::UnknownSmi();
+    return;
+  }
+
+  range_ = new Range(new_min, new_max);
+}
+
+
+void ConstantInstr::InferRange() {
+  if (value_.IsSmi()) {
+    if (range_ == NULL) {
+      int64_t value = Smi::Cast(value_).Value();
+      range_ = new Range(RangeBoundary::FromConstant(value),
+                         RangeBoundary::FromConstant(value));
+    }
+  } else if (value_.IsMint()) {
+    if (range_ == NULL) {
+      int64_t value = Mint::Cast(value_).value();
+      range_ = new Range(RangeBoundary::FromConstant(value),
+                         RangeBoundary::FromConstant(value));
+    }
+  } else {
+    // Only Smi and Mint supported.
+    UNREACHABLE();
+  }
+}
+
+
+void UnboxIntegerInstr::InferRange() {
+  if (range_ == NULL) {
+    Definition* unboxed = value()->definition();
+    ASSERT(unboxed != NULL);
+    Range* range = unboxed->range();
+    if (range == NULL) {
+      range_ = Range::Unknown();
+      return;
+    }
+    range_ = new Range(range->min(), range->max());
+  }
+}
+
+
+void ConstraintInstr::InferRange() {
+  Range* value_range = value()->definition()->range();
+
+  // Only constraining smi values.
+  ASSERT(value()->IsSmiValue());
+
+  RangeBoundary min;
+  RangeBoundary max;
+
+  {
+    RangeBoundary value_min = (value_range == NULL) ?
+        RangeBoundary() : value_range->min();
+    RangeBoundary constraint_min = constraint()->min();
+    min = RangeBoundary::Max(value_min, constraint_min,
+                             RangeBoundary::kRangeBoundarySmi);
+  }
+
+  ASSERT(!min.IsUnknown());
+
+  {
+    RangeBoundary value_max = (value_range == NULL) ?
+        RangeBoundary() : value_range->max();
+    RangeBoundary constraint_max = constraint()->max();
+    max = RangeBoundary::Min(value_max, constraint_max,
+                             RangeBoundary::kRangeBoundarySmi);
+  }
+
+  ASSERT(!max.IsUnknown());
+
+  range_ = new Range(min, max);
+
+  // Mark branches that generate unsatisfiable constraints as constant.
+  if (target() != NULL && range_->IsUnsatisfiable()) {
+    BranchInstr* branch =
+        target()->PredecessorAt(0)->last_instruction()->AsBranch();
+    if (target() == branch->true_successor()) {
+      // True unreachable.
+      if (FLAG_trace_constant_propagation) {
+        OS::Print("Range analysis: True unreachable (B%" Pd ")\n",
+                  branch->true_successor()->block_id());
+      }
+      branch->set_constant_target(branch->false_successor());
+    } else {
+      ASSERT(target() == branch->false_successor());
+      // False unreachable.
+      if (FLAG_trace_constant_propagation) {
+        OS::Print("Range analysis: False unreachable (B%" Pd ")\n",
+                  branch->false_successor()->block_id());
+      }
+      branch->set_constant_target(branch->true_successor());
+    }
+  }
+}
+
+
+void LoadFieldInstr::InferRange() {
+  if ((range_ == NULL) &&
+      ((recognized_kind() == MethodRecognizer::kObjectArrayLength) ||
+       (recognized_kind() == MethodRecognizer::kImmutableArrayLength))) {
+    range_ = new Range(RangeBoundary::FromConstant(0),
+                       RangeBoundary::FromConstant(Array::kMaxElements));
+    return;
+  }
+  if ((range_ == NULL) &&
+      (recognized_kind() == MethodRecognizer::kTypedDataLength)) {
+    range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());
+    return;
+  }
+  if ((range_ == NULL) &&
+      (recognized_kind() == MethodRecognizer::kStringBaseLength)) {
+    range_ = new Range(RangeBoundary::FromConstant(0),
+                       RangeBoundary::FromConstant(String::kMaxElements));
+    return;
+  }
+  Definition::InferRange();
+}
+
+
+
+void LoadIndexedInstr::InferRange() {
+  switch (class_id()) {
+    case kTypedDataInt8ArrayCid:
+      range_ = new Range(RangeBoundary::FromConstant(-128),
+                         RangeBoundary::FromConstant(127));
+      break;
+    case kTypedDataUint8ArrayCid:
+    case kTypedDataUint8ClampedArrayCid:
+    case kExternalTypedDataUint8ArrayCid:
+    case kExternalTypedDataUint8ClampedArrayCid:
+      range_ = new Range(RangeBoundary::FromConstant(0),
+                         RangeBoundary::FromConstant(255));
+      break;
+    case kTypedDataInt16ArrayCid:
+      range_ = new Range(RangeBoundary::FromConstant(-32768),
+                         RangeBoundary::FromConstant(32767));
+      break;
+    case kTypedDataUint16ArrayCid:
+      range_ = new Range(RangeBoundary::FromConstant(0),
+                         RangeBoundary::FromConstant(65535));
+      break;
+    case kTypedDataInt32ArrayCid:
+      if (Typed32BitIsSmi()) {
+        range_ = Range::UnknownSmi();
+      } else {
+        range_ = new Range(RangeBoundary::FromConstant(kMinInt32),
+                           RangeBoundary::FromConstant(kMaxInt32));
+      }
+      break;
+    case kTypedDataUint32ArrayCid:
+      if (Typed32BitIsSmi()) {
+        range_ = Range::UnknownSmi();
+      } else {
+        range_ = new Range(RangeBoundary::FromConstant(0),
+                           RangeBoundary::FromConstant(kMaxUint32));
+      }
+      break;
+    case kOneByteStringCid:
+      range_ = new Range(RangeBoundary::FromConstant(0),
+                         RangeBoundary::FromConstant(0xFF));
+      break;
+    case kTwoByteStringCid:
+      range_ = new Range(RangeBoundary::FromConstant(0),
+                         RangeBoundary::FromConstant(0xFFFF));
+      break;
+    default:
+      Definition::InferRange();
+      break;
+  }
+}
+
+
+void IfThenElseInstr::InferRange() {
+  const intptr_t min = Utils::Minimum(if_true_, if_false_);
+  const intptr_t max = Utils::Maximum(if_true_, if_false_);
+  range_ = new Range(RangeBoundary::FromConstant(min),
+                     RangeBoundary::FromConstant(max));
+}
+
+
+void BinarySmiOpInstr::InferRange() {
+  // TODO(vegorov): canonicalize BinarySmiOp to always have constant on the
+  // right and a non-constant on the left.
+  Definition* left_defn = left()->definition();
+
+  Range* left_range = left_defn->range();
+  Range* right_range = right()->definition()->range();
+
+  if ((left_range == NULL) || (right_range == NULL)) {
+    range_ = Range::UnknownSmi();
+    return;
+  }
+
+  Range* possible_range = Range::BinaryOp(op_kind(),
+                                          left_range,
+                                          right_range,
+                                          left_defn);
+
+  if ((range_ == NULL) && (possible_range == NULL)) {
+    // Initialize.
+    range_ = Range::UnknownSmi();
+    return;
+  }
+
+  if (possible_range == NULL) {
+    // Nothing new.
+    return;
+  }
+
+  range_ = possible_range;
+
+  ASSERT(!range_->min().IsUnknown() && !range_->max().IsUnknown());
+  // Calculate overflowed status before clamping.
+  const bool overflowed = range_->min().LowerBound().OverflowedSmi() ||
+                          range_->max().UpperBound().OverflowedSmi();
+  set_overflow(overflowed);
+
+  // Clamp value to be within smi range.
+  range_->Clamp(RangeBoundary::kRangeBoundarySmi);
+}
+
+
+void BinaryMintOpInstr::InferRange() {
+  // TODO(vegorov): canonicalize BinaryMintOpInstr to always have constant on
+  // the right and a non-constant on the left.
+  Definition* left_defn = left()->definition();
+
+  Range* left_range = left_defn->range();
+  Range* right_range = right()->definition()->range();
+
+  if ((left_range == NULL) || (right_range == NULL)) {
+    range_ = Range::Unknown();
+    return;
+  }
+
+  Range* possible_range = Range::BinaryOp(op_kind(),
+                                          left_range,
+                                          right_range,
+                                          left_defn);
+
+  if ((range_ == NULL) && (possible_range == NULL)) {
+    // Initialize.
+    range_ = Range::Unknown();
+    return;
+  }
+
+  if (possible_range == NULL) {
+    // Nothing new.
+    return;
+  }
+
+  range_ = possible_range;
+
+  ASSERT(!range_->min().IsUnknown() && !range_->max().IsUnknown());
+
+  // Calculate overflowed status before clamping.
+  const bool overflowed = range_->min().LowerBound().OverflowedMint() ||
+                          range_->max().UpperBound().OverflowedMint();
+  set_can_overflow(overflowed);
+
+  // Clamp value to be within mint range.
+  range_->Clamp(RangeBoundary::kRangeBoundaryInt64);
+}
+
+
+void ShiftMintOpInstr::InferRange() {
+  Definition* left_defn = left()->definition();
+
+  Range* left_range = left_defn->range();
+  Range* right_range = right()->definition()->range();
+
+  if ((left_range == NULL) || (right_range == NULL)) {
+    range_ = Range::Unknown();
+    return;
+  }
+
+  Range* possible_range = Range::BinaryOp(op_kind(),
+                                          left_range,
+                                          right_range,
+                                          left_defn);
+
+  if ((range_ == NULL) && (possible_range == NULL)) {
+    // Initialize.
+    range_ = Range::Unknown();
+    return;
+  }
+
+  if (possible_range == NULL) {
+    // Nothing new.
+    return;
+  }
+
+  range_ = possible_range;
+
+  ASSERT(!range_->min().IsUnknown() && !range_->max().IsUnknown());
+
+  // Calculate overflowed status before clamping.
+  const bool overflowed = range_->min().LowerBound().OverflowedMint() ||
+                          range_->max().UpperBound().OverflowedMint();
+  set_can_overflow(overflowed);
+
+  // Clamp value to be within mint range.
+  range_->Clamp(RangeBoundary::kRangeBoundaryInt64);
+}
+
+
+void BoxIntegerInstr::InferRange() {
+  Range* input_range = value()->definition()->range();
+  if (input_range != NULL) {
+    bool is_smi = !input_range->min().LowerBound().OverflowedSmi() &&
+                  !input_range->max().UpperBound().OverflowedSmi();
+    set_is_smi(is_smi);
+    // The output range is the same as the input range.
+    range_ = input_range;
+  }
+}
+
+
+bool CheckArrayBoundInstr::IsRedundant(const RangeBoundary& length) {
+  Range* index_range = index()->definition()->range();
+
+  // Range of the index is unknown can't decide if the check is redundant.
+  if (index_range == NULL) {
+    return false;
+  }
+
+  // Range of the index is not positive. Check can't be redundant.
+  if (Range::ConstantMinSmi(index_range).ConstantValue() < 0) {
+    return false;
+  }
+
+  RangeBoundary max = CanonicalizeBoundary(index_range->max(),
+                                           RangeBoundary::PositiveInfinity());
+
+  if (max.OverflowedSmi()) {
+    return false;
+  }
+
+
+  RangeBoundary max_upper = max.UpperBound();
+  RangeBoundary length_lower = length.LowerBound();
+
+  if (max_upper.OverflowedSmi() || length_lower.OverflowedSmi()) {
+    return false;
+  }
+
+  // Try to compare constant boundaries.
+  if (max_upper.ConstantValue() < length_lower.ConstantValue()) {
+    return true;
+  }
+
+  RangeBoundary canonical_length =
+      CanonicalizeBoundary(length, RangeBoundary::PositiveInfinity());
+  if (canonical_length.OverflowedSmi()) {
+    return false;
+  }
+
+  // Try symbolic comparison.
+  do {
+    if (DependOnSameSymbol(max, canonical_length)) {
+      return max.offset() < canonical_length.offset();
+    }
+  } while (CanonicalizeMaxBoundary(&max) ||
+           CanonicalizeMinBoundary(&canonical_length));
+
+  // Failed to prove that maximum is bounded with array length.
+  return false;
+}
+
+
+}  // namespace dart
diff --git a/runtime/vm/flow_graph_range_analysis.h b/runtime/vm/flow_graph_range_analysis.h
new file mode 100644
index 0000000..f9ae036
--- /dev/null
+++ b/runtime/vm/flow_graph_range_analysis.h
@@ -0,0 +1,495 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef VM_FLOW_GRAPH_RANGE_ANALYSIS_H_
+#define VM_FLOW_GRAPH_RANGE_ANALYSIS_H_
+
+#include "vm/flow_graph.h"
+#include "vm/intermediate_language.h"
+
+namespace dart {
+
+class RangeBoundary : public ValueObject {
+ public:
+  enum Kind {
+    kUnknown,
+    kNegativeInfinity,
+    kPositiveInfinity,
+    kSymbol,
+    kConstant,
+  };
+
+  enum RangeSize {
+    kRangeBoundarySmi,
+    kRangeBoundaryInt64,
+  };
+
+  RangeBoundary() : kind_(kUnknown), value_(0), offset_(0) { }
+
+  RangeBoundary(const RangeBoundary& other)
+      : ValueObject(),
+        kind_(other.kind_),
+        value_(other.value_),
+        offset_(other.offset_) { }
+
+  explicit RangeBoundary(int64_t val)
+      : kind_(kConstant), value_(val), offset_(0) { }
+
+  RangeBoundary& operator=(const RangeBoundary& other) {
+    kind_ = other.kind_;
+    value_ = other.value_;
+    offset_ = other.offset_;
+    return *this;
+  }
+
+  static const int64_t kMin = kMinInt64;
+  static const int64_t kMax = kMaxInt64;
+
+  // Construct a RangeBoundary for a constant value.
+  static RangeBoundary FromConstant(int64_t val) {
+    return RangeBoundary(val);
+  }
+
+  // Construct a RangeBoundary for -inf.
+  static RangeBoundary NegativeInfinity() {
+    return RangeBoundary(kNegativeInfinity, 0, 0);
+  }
+
+  // Construct a RangeBoundary for +inf.
+  static RangeBoundary PositiveInfinity() {
+    return RangeBoundary(kPositiveInfinity, 0, 0);
+  }
+
+  // Construct a RangeBoundary from a definition and offset.
+  static RangeBoundary FromDefinition(Definition* defn, int64_t offs = 0);
+
+  // Construct a RangeBoundary for the constant MinSmi value.
+  static RangeBoundary MinSmi() {
+    return FromConstant(Smi::kMinValue);
+  }
+
+  // Construct a RangeBoundary for the constant MaxSmi value.
+  static RangeBoundary MaxSmi() {
+    return FromConstant(Smi::kMaxValue);
+  }
+
+    // Construct a RangeBoundary for the constant kMin value.
+  static RangeBoundary MinConstant() {
+    return FromConstant(kMin);
+  }
+
+  // Construct a RangeBoundary for the constant kMax value.
+  static RangeBoundary MaxConstant() {
+    return FromConstant(kMax);
+  }
+
+  // Calculate the minimum of a and b within the given range.
+  static RangeBoundary Min(RangeBoundary a, RangeBoundary b, RangeSize size);
+  static RangeBoundary Max(RangeBoundary a, RangeBoundary b, RangeSize size);
+
+  // Returns true when this is a constant that is outside of Smi range.
+  bool OverflowedSmi() const {
+    return (IsConstant() && !Smi::IsValid(ConstantValue())) || IsInfinity();
+  }
+
+  // Returns true if this outside mint range.
+  bool OverflowedMint() const {
+    return IsInfinity();
+  }
+
+  // -/+ infinity are clamped to MinConstant/MaxConstant of the given type.
+  RangeBoundary Clamp(RangeSize size) const {
+    if (IsNegativeInfinity()) {
+      return (size == kRangeBoundaryInt64) ? MinConstant() : MinSmi();
+    }
+    if (IsPositiveInfinity()) {
+      return (size == kRangeBoundaryInt64) ? MaxConstant() : MaxSmi();
+    }
+    if ((size == kRangeBoundarySmi) && IsConstant()) {
+      if (ConstantValue() <= Smi::kMinValue) {
+        return MinSmi();
+      }
+      if (ConstantValue() >= Smi::kMaxValue) {
+        return MaxSmi();
+      }
+    }
+    // If this range is a symbolic range, we do not clamp it.
+    // This could lead to some imprecision later on.
+    return *this;
+  }
+
+
+  bool IsSmiMinimumOrBelow() const {
+    return IsNegativeInfinity() ||
+           (IsConstant() && (ConstantValue() <= Smi::kMinValue));
+  }
+
+  bool IsSmiMaximumOrAbove() const {
+    return IsPositiveInfinity() ||
+           (IsConstant() && (ConstantValue() >= Smi::kMaxValue));
+  }
+
+  bool IsMinimumOrBelow() const {
+    return IsNegativeInfinity() || (IsConstant() && (ConstantValue() == kMin));
+  }
+
+  bool IsMaximumOrAbove() const {
+    return IsPositiveInfinity() || (IsConstant() && (ConstantValue() == kMax));
+  }
+
+  intptr_t kind() const {
+    return kind_;
+  }
+
+  // Kind tests.
+  bool IsUnknown() const { return kind_ == kUnknown; }
+  bool IsConstant() const { return kind_ == kConstant; }
+  bool IsSymbol() const { return kind_ == kSymbol; }
+  bool IsNegativeInfinity() const { return kind_ == kNegativeInfinity; }
+  bool IsPositiveInfinity() const { return kind_ == kPositiveInfinity; }
+  bool IsInfinity() const {
+    return IsNegativeInfinity() || IsPositiveInfinity();
+  }
+  bool IsConstantOrInfinity() const {
+    return IsConstant() || IsInfinity();
+  }
+
+  // Returns the value of a kConstant RangeBoundary.
+  int64_t ConstantValue() const;
+
+  // Returns the Definition associated with a kSymbol RangeBoundary.
+  Definition* symbol() const {
+    ASSERT(IsSymbol());
+    return reinterpret_cast<Definition*>(value_);
+  }
+
+  // Offset from symbol.
+  int64_t offset() const {
+    return offset_;
+  }
+
+  // Computes the LowerBound of this. Three cases:
+  // IsInfinity() -> NegativeInfinity().
+  // IsConstant() -> value().
+  // IsSymbol() -> lower bound computed from definition + offset.
+  RangeBoundary LowerBound() const;
+
+  // Computes the UpperBound of this. Three cases:
+  // IsInfinity() -> PositiveInfinity().
+  // IsConstant() -> value().
+  // IsSymbol() -> upper bound computed from definition + offset.
+  RangeBoundary UpperBound() const;
+
+  void PrintTo(BufferFormatter* f) const;
+  const char* ToCString() const;
+
+  static RangeBoundary Add(const RangeBoundary& a,
+                           const RangeBoundary& b,
+                           const RangeBoundary& overflow);
+
+  static RangeBoundary Sub(const RangeBoundary& a,
+                           const RangeBoundary& b,
+                           const RangeBoundary& overflow);
+
+  static RangeBoundary Shl(const RangeBoundary& value_boundary,
+                           int64_t shift_count,
+                           const RangeBoundary& overflow);
+
+  static RangeBoundary Shr(const RangeBoundary& value_boundary,
+                           int64_t shift_count) {
+    ASSERT(value_boundary.IsConstant());
+    ASSERT(shift_count >= 0);
+    int64_t value = static_cast<int64_t>(value_boundary.ConstantValue());
+    int64_t result = value >> shift_count;
+    return RangeBoundary(result);
+  }
+
+  // Attempts to calculate a + b when:
+  // a is a symbol and b is a constant OR
+  // a is a constant and b is a symbol
+  // returns true if it succeeds, output is in result.
+  static bool SymbolicAdd(const RangeBoundary& a,
+                          const RangeBoundary& b,
+                          RangeBoundary* result);
+
+  // Attempts to calculate a - b when:
+  // a is a symbol and b is a constant
+  // returns true if it succeeds, output is in result.
+  static bool SymbolicSub(const RangeBoundary& a,
+                          const RangeBoundary& b,
+                          RangeBoundary* result);
+
+  bool Equals(const RangeBoundary& other) const;
+
+ private:
+  RangeBoundary(Kind kind, int64_t value, int64_t offset)
+      : kind_(kind), value_(value), offset_(offset) { }
+
+  Kind kind_;
+  int64_t value_;
+  int64_t offset_;
+};
+
+
+class Range : public ZoneAllocated {
+ public:
+  Range(RangeBoundary min, RangeBoundary max) : min_(min), max_(max) { }
+
+  static Range* Unknown() {
+    return new Range(RangeBoundary::MinConstant(),
+                     RangeBoundary::MaxConstant());
+  }
+
+  static Range* UnknownSmi() {
+    return new Range(RangeBoundary::MinSmi(),
+                     RangeBoundary::MaxSmi());
+  }
+
+  void PrintTo(BufferFormatter* f) const;
+  static const char* ToCString(const Range* range);
+
+  const RangeBoundary& min() const { return min_; }
+  const RangeBoundary& max() const { return max_; }
+
+  static RangeBoundary ConstantMinSmi(const Range* range) {
+    if (range == NULL) {
+      return RangeBoundary::MinSmi();
+    }
+    return range->min().LowerBound().Clamp(RangeBoundary::kRangeBoundarySmi);
+  }
+
+  static RangeBoundary ConstantMaxSmi(const Range* range) {
+    if (range == NULL) {
+      return RangeBoundary::MaxSmi();
+    }
+    return range->max().UpperBound().Clamp(RangeBoundary::kRangeBoundarySmi);
+  }
+
+  static RangeBoundary ConstantMin(const Range* range) {
+    if (range == NULL) {
+      return RangeBoundary::MinConstant();
+    }
+    return range->min().LowerBound().Clamp(RangeBoundary::kRangeBoundaryInt64);
+  }
+
+  static RangeBoundary ConstantMax(const Range* range) {
+    if (range == NULL) {
+      return RangeBoundary::MaxConstant();
+    }
+    return range->max().UpperBound().Clamp(RangeBoundary::kRangeBoundaryInt64);
+  }
+
+  // [0, +inf]
+  bool IsPositive() const;
+
+  // [-inf, val].
+  bool OnlyLessThanOrEqualTo(int64_t val) const;
+
+  // [val, +inf].
+  bool OnlyGreaterThanOrEqualTo(int64_t val) const;
+
+  // Inclusive.
+  bool IsWithin(int64_t min_int, int64_t max_int) const;
+
+  // Inclusive.
+  bool Overlaps(int64_t min_int, int64_t max_int) const;
+
+  bool IsUnsatisfiable() const;
+
+  bool IsFinite() const {
+    return !min_.IsInfinity() && !max_.IsInfinity();
+  }
+
+  // Clamp this to be within size.
+  void Clamp(RangeBoundary::RangeSize size);
+
+  static void Add(const Range* left_range,
+                  const Range* right_range,
+                  RangeBoundary* min,
+                  RangeBoundary* max,
+                  Definition* left_defn);
+
+  static void Sub(const Range* left_range,
+                  const Range* right_range,
+                  RangeBoundary* min,
+                  RangeBoundary* max,
+                  Definition* left_defn);
+
+  static bool Mul(const Range* left_range,
+                  const Range* right_range,
+                  RangeBoundary* min,
+                  RangeBoundary* max);
+  static void Shr(const Range* left_range,
+                  const Range* right_range,
+                  RangeBoundary* min,
+                  RangeBoundary* max);
+
+  static void Shl(const Range* left_range,
+                  const Range* right_range,
+                  RangeBoundary* min,
+                  RangeBoundary* max);
+
+  static bool And(const Range* left_range,
+                  const Range* right_range,
+                  RangeBoundary* min,
+                  RangeBoundary* max);
+
+
+  // Both the a and b ranges are >= 0.
+  static bool OnlyPositiveOrZero(const Range& a, const Range& b);
+
+  // Both the a and b ranges are <= 0.
+  static bool OnlyNegativeOrZero(const Range& a, const Range& b);
+
+  // Return the maximum absolute value included in range.
+  static int64_t ConstantAbsMax(const Range* range);
+
+  static Range* BinaryOp(const Token::Kind op,
+                         const Range* left_range,
+                         const Range* right_range,
+                         Definition* left_defn);
+
+ private:
+  RangeBoundary min_;
+  RangeBoundary max_;
+};
+
+
+// Range analysis for integer values.
+class RangeAnalysis : public ValueObject {
+ public:
+  explicit RangeAnalysis(FlowGraph* flow_graph)
+      : flow_graph_(flow_graph),
+        marked_defns_(NULL) { }
+
+  // Infer ranges for all values and remove overflow checks from binary smi
+  // operations when proven redundant.
+  void Analyze();
+
+ private:
+  // Collect all values that were proven to be smi in smi_values_ array and all
+  // CheckSmi instructions in smi_check_ array.
+  void CollectValues();
+
+  // Iterate over smi values and constrain them at branch successors.
+  // Additionally constraint values after CheckSmi instructions.
+  void InsertConstraints();
+
+  // Iterate over uses of the given definition and discover branches that
+  // constrain it. Insert appropriate Constraint instructions at true
+  // and false successor and rename all dominated uses to refer to a
+  // Constraint instead of this definition.
+  void InsertConstraintsFor(Definition* defn);
+
+  // Create a constraint for defn, insert it after given instruction and
+  // rename all uses that are dominated by it.
+  ConstraintInstr* InsertConstraintFor(Definition* defn,
+                                       Range* constraint,
+                                       Instruction* after);
+
+  void ConstrainValueAfterBranch(Definition* defn, Value* use);
+  void ConstrainValueAfterCheckArrayBound(Definition* defn,
+                                          CheckArrayBoundInstr* check,
+                                          intptr_t use_index);
+
+  // Replace uses of the definition def that are dominated by instruction dom
+  // with uses of other definition.
+  void RenameDominatedUses(Definition* def,
+                           Instruction* dom,
+                           Definition* other);
+
+
+  // Walk the dominator tree and infer ranges for smi values.
+  void InferRanges();
+  void InferRangesRecursive(BlockEntryInstr* block);
+
+  enum Direction {
+    kUnknown,
+    kPositive,
+    kNegative,
+    kBoth
+  };
+
+  Range* InferInductionVariableRange(JoinEntryInstr* loop_header,
+                                     PhiInstr* var);
+
+  void ResetWorklist();
+  void MarkDefinition(Definition* defn);
+
+  static Direction ToDirection(Value* val);
+
+  static Direction Invert(Direction direction) {
+    return (direction == kPositive) ? kNegative : kPositive;
+  }
+
+  static void UpdateDirection(Direction* direction,
+                              Direction new_direction) {
+    if (*direction != new_direction) {
+      if (*direction != kUnknown) new_direction = kBoth;
+      *direction = new_direction;
+    }
+  }
+
+  // Remove artificial Constraint instructions and replace them with actual
+  // unconstrained definitions.
+  void RemoveConstraints();
+
+  Range* ConstraintRange(Token::Kind op, Definition* boundary);
+
+  Isolate* isolate() const { return flow_graph_->isolate(); }
+
+  FlowGraph* flow_graph_;
+
+  // Value that are known to be smi or mint.
+  GrowableArray<Definition*> values_;
+  // All CheckSmi instructions.
+  GrowableArray<CheckSmiInstr*> smi_checks_;
+
+  // All Constraints inserted during InsertConstraints phase. They are treated
+  // as smi values.
+  GrowableArray<ConstraintInstr*> constraints_;
+
+  // Bitvector for a quick filtering of known smi or mint values.
+  BitVector* definitions_;
+
+  // Worklist for induction variables analysis.
+  GrowableArray<Definition*> worklist_;
+  BitVector* marked_defns_;
+
+  DISALLOW_COPY_AND_ASSIGN(RangeAnalysis);
+};
+
+
+// Replaces Mint IL instructions with Uint32 IL instructions
+// when possible. Uses output of RangeAnalysis.
+class IntegerInstructionSelector : public ValueObject {
+ public:
+  explicit IntegerInstructionSelector(FlowGraph* flow_graph);
+
+  void Select();
+
+ private:
+  bool IsPotentialUint32Definition(Definition* def);
+  void FindPotentialUint32Definitions();
+  bool IsUint32NarrowingDefinition(Definition* def);
+  void FindUint32NarrowingDefinitions();
+  bool AllUsesAreUint32Narrowing(Value* list_head);
+  bool CanBecomeUint32(Definition* def);
+  void Propagate();
+  Definition* ConstructReplacementFor(Definition* def);
+  void ReplaceInstructions();
+
+  Isolate* isolate() const { return isolate_; }
+
+  GrowableArray<Definition*> potential_uint32_defs_;
+  BitVector* selected_uint32_defs_;
+
+  FlowGraph* flow_graph_;
+  Isolate* isolate_;
+};
+
+
+}  // namespace dart
+
+#endif  // VM_FLOW_GRAPH_RANGE_ANALYSIS_H_
diff --git a/runtime/vm/flow_graph_range_analysis_test.cc b/runtime/vm/flow_graph_range_analysis_test.cc
new file mode 100644
index 0000000..0471748
--- /dev/null
+++ b/runtime/vm/flow_graph_range_analysis_test.cc
@@ -0,0 +1,642 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "vm/flow_graph_range_analysis.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+
+TEST_CASE(RangeTests) {
+  Range* zero = new Range(
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::FromConstant(0));
+  Range* positive = new Range(
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::FromConstant(100));
+  Range* negative = new Range(
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::FromConstant(-100));
+  Range* range_x = new Range(
+      RangeBoundary::FromConstant(-15),
+      RangeBoundary::FromConstant(100));
+  EXPECT(positive->IsPositive());
+  EXPECT(zero->Overlaps(0, 0));
+  EXPECT(positive->Overlaps(0, 0));
+  EXPECT(!negative->Overlaps(0, 0));
+  EXPECT(range_x->Overlaps(0, 0));
+  EXPECT(range_x->IsWithin(-15, 100));
+  EXPECT(!range_x->IsWithin(-15, 99));
+  EXPECT(!range_x->IsWithin(-14, 100));
+
+#define TEST_RANGE_OP_(Op, l_min, l_max, r_min, r_max, Clamp, res_min, res_max)\
+  {                                                                            \
+    RangeBoundary min, max;                                                    \
+    Range* left_range = new Range(                                             \
+      RangeBoundary::FromConstant(l_min),                                      \
+      RangeBoundary::FromConstant(l_max));                                     \
+    Range* shift_range = new Range(                                            \
+      RangeBoundary::FromConstant(r_min),                                      \
+      RangeBoundary::FromConstant(r_max));                                     \
+    Op(left_range, shift_range, &min, &max);                                   \
+    min = Clamp(min);                                                          \
+    max = Clamp(max);                                                          \
+    EXPECT(min.Equals(res_min));                                               \
+    if (!min.Equals(res_min)) OS::Print("%s\n", min.ToCString());              \
+    EXPECT(max.Equals(res_max));                                               \
+    if (!max.Equals(res_max)) OS::Print("%s\n", max.ToCString());              \
+  }
+
+#define NO_CLAMP(b) (b)
+#define TEST_RANGE_OP(Op, l_min, l_max, r_min, r_max, result_min, result_max)  \
+  TEST_RANGE_OP_(Op, l_min, l_max, r_min, r_max,                               \
+                 NO_CLAMP, result_min, result_max)
+
+#define CLAMP_TO_SMI(b) (b.Clamp(RangeBoundary::kRangeBoundarySmi))
+#define TEST_RANGE_OP_SMI(Op, l_min, l_max, r_min, r_max, res_min, res_max)    \
+  TEST_RANGE_OP_(Op, l_min, l_max, r_min, r_max,                               \
+                 CLAMP_TO_SMI, res_min, res_max)
+
+  TEST_RANGE_OP(Range::Shl, -15, 100, 0, 2,
+                RangeBoundary(-60), RangeBoundary(400));
+  TEST_RANGE_OP(Range::Shl, -15, 100, -2, 2,
+                RangeBoundary(-60), RangeBoundary(400));
+  TEST_RANGE_OP(Range::Shl, -15, -10, 1, 2,
+                RangeBoundary(-60), RangeBoundary(-20));
+  TEST_RANGE_OP(Range::Shl, 5, 10, -2, 2,
+                RangeBoundary(5), RangeBoundary(40));
+  TEST_RANGE_OP(Range::Shl, -15, 100, 0, 64,
+                RangeBoundary::NegativeInfinity(),
+                RangeBoundary::PositiveInfinity());
+  TEST_RANGE_OP(Range::Shl, -1, 1, 63, 63,
+                RangeBoundary(kMinInt64),
+                RangeBoundary::PositiveInfinity());
+  if (kBitsPerWord == 64) {
+    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 62, 62,
+                  RangeBoundary(kSmiMin),
+                  RangeBoundary(kSmiMax));
+    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 30, 30,
+                  RangeBoundary(-1 << 30),
+                  RangeBoundary(1 << 30));
+  } else {
+    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 30, 30,
+                  RangeBoundary(kSmiMin),
+                  RangeBoundary(kSmiMax));
+    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 62, 62,
+                  RangeBoundary(kSmiMin),
+                  RangeBoundary(kSmiMax));
+  }
+  TEST_RANGE_OP(Range::Shl, 0, 100, 0, 64,
+                RangeBoundary(0), RangeBoundary::PositiveInfinity());
+  TEST_RANGE_OP(Range::Shl, -100, 0, 0, 64,
+                RangeBoundary::NegativeInfinity(), RangeBoundary(0));
+
+  TEST_RANGE_OP(Range::Shr, -8, 8, 1, 2, RangeBoundary(-4), RangeBoundary(4));
+  TEST_RANGE_OP(Range::Shr, 1, 8, 1, 2, RangeBoundary(0), RangeBoundary(4));
+  TEST_RANGE_OP(Range::Shr, -16, -8, 1, 2,
+                RangeBoundary(-8), RangeBoundary(-2));
+  TEST_RANGE_OP(Range::Shr, 2, 4, -1, 1, RangeBoundary(1), RangeBoundary(4));
+  TEST_RANGE_OP(Range::Shr, kMaxInt64, kMaxInt64, 0, 1,
+                RangeBoundary(kMaxInt64 >> 1), RangeBoundary(kMaxInt64));
+  TEST_RANGE_OP(Range::Shr, kMinInt64, kMinInt64, 0, 1,
+                RangeBoundary(kMinInt64), RangeBoundary(kMinInt64 >> 1));
+#undef TEST_RANGE_OP
+}
+
+
+TEST_CASE(RangeTestsInfinity) {
+  // +/- inf overflowed.
+  EXPECT(RangeBoundary::NegativeInfinity().OverflowedSmi());
+  EXPECT(RangeBoundary::PositiveInfinity().OverflowedSmi());
+
+  EXPECT(RangeBoundary::NegativeInfinity().OverflowedMint());
+  EXPECT(RangeBoundary::PositiveInfinity().OverflowedMint());
+
+  Range* all = new Range(RangeBoundary::NegativeInfinity(),
+                         RangeBoundary::PositiveInfinity());
+  EXPECT(all->Overlaps(0, 0));
+  EXPECT(all->Overlaps(-1, 1));
+  EXPECT(!all->IsWithin(0, 100));
+  Range* positive = new Range(RangeBoundary::FromConstant(0),
+                              RangeBoundary::PositiveInfinity());
+  EXPECT(positive->IsPositive());
+  EXPECT(positive->Overlaps(0, 1));
+  EXPECT(positive->Overlaps(1, 100));
+  EXPECT(positive->Overlaps(-1, 0));
+  EXPECT(!positive->Overlaps(-2, -1));
+  Range* negative = new Range(RangeBoundary::NegativeInfinity(),
+                              RangeBoundary::FromConstant(-1));
+  EXPECT(!negative->IsPositive());
+  EXPECT(!negative->Overlaps(0, 1));
+  EXPECT(!negative->Overlaps(1, 100));
+  EXPECT(negative->Overlaps(-1, 0));
+  EXPECT(negative->Overlaps(-2, -1));
+  Range* negpos = new Range(RangeBoundary::NegativeInfinity(),
+                            RangeBoundary::FromConstant(0));
+  EXPECT(!negpos->IsPositive());
+
+  Range* a = new Range(RangeBoundary::NegativeInfinity(),
+                       RangeBoundary::FromConstant(1));
+
+  Range* b = new Range(RangeBoundary::NegativeInfinity(),
+                       RangeBoundary::FromConstant(31));
+
+  Range* c = new Range(RangeBoundary::NegativeInfinity(),
+                       RangeBoundary::FromConstant(32));
+
+  EXPECT(a->OnlyLessThanOrEqualTo(31));
+  EXPECT(b->OnlyLessThanOrEqualTo(31));
+  EXPECT(!c->OnlyLessThanOrEqualTo(31));
+
+  Range* unsatisfiable = new Range(RangeBoundary::PositiveInfinity(),
+                                   RangeBoundary::NegativeInfinity());
+  EXPECT(unsatisfiable->IsUnsatisfiable());
+
+  Range* unsatisfiable_right = new Range(RangeBoundary::PositiveInfinity(),
+                                         RangeBoundary::FromConstant(0));
+  EXPECT(unsatisfiable_right->IsUnsatisfiable());
+
+  Range* unsatisfiable_left = new Range(RangeBoundary::FromConstant(0),
+                                        RangeBoundary::NegativeInfinity());
+  EXPECT(unsatisfiable_left->IsUnsatisfiable());
+}
+
+
+TEST_CASE(RangeUtils) {
+  // [-inf, +inf].
+  const Range& range_0 = *(new Range(RangeBoundary::NegativeInfinity(),
+                                     RangeBoundary::PositiveInfinity()));
+  // [-inf, -1].
+  const Range& range_a = *(new Range(RangeBoundary::NegativeInfinity(),
+                                     RangeBoundary::FromConstant(-1)));
+  // [-inf, 0].
+  const Range& range_b = *(new Range(RangeBoundary::NegativeInfinity(),
+                                     RangeBoundary::FromConstant(0)));
+  // [-inf, 1].
+  const Range& range_c = *(new Range(RangeBoundary::NegativeInfinity(),
+                                     RangeBoundary::FromConstant(1)));
+  // [-1, +inf]
+  const Range& range_d = *(new Range(RangeBoundary::FromConstant(-1),
+                                     RangeBoundary::PositiveInfinity()));
+  // [0, +inf]
+  const Range& range_e = *(new Range(RangeBoundary::FromConstant(0),
+                                     RangeBoundary::PositiveInfinity()));
+  // [1, +inf].
+  const Range& range_f = *(new Range(RangeBoundary::FromConstant(1),
+                                     RangeBoundary::PositiveInfinity()));
+  // [1, 2].
+  const Range& range_g = *(new Range(RangeBoundary::FromConstant(1),
+                                     RangeBoundary::FromConstant(2)));
+  // [-1, -2].
+  const Range& range_h = *(new Range(RangeBoundary::FromConstant(-1),
+                                     RangeBoundary::FromConstant(-2)));
+  // [-1, 1].
+  const Range& range_i = *(new Range(RangeBoundary::FromConstant(-1),
+                                     RangeBoundary::FromConstant(1)));
+
+  // OnlyPositiveOrZero.
+  EXPECT(!Range::OnlyPositiveOrZero(range_a, range_b));
+  EXPECT(!Range::OnlyPositiveOrZero(range_b, range_c));
+  EXPECT(!Range::OnlyPositiveOrZero(range_c, range_d));
+  EXPECT(!Range::OnlyPositiveOrZero(range_d, range_e));
+  EXPECT(Range::OnlyPositiveOrZero(range_e, range_f));
+  EXPECT(!Range::OnlyPositiveOrZero(range_d, range_d));
+  EXPECT(Range::OnlyPositiveOrZero(range_e, range_e));
+  EXPECT(Range::OnlyPositiveOrZero(range_f, range_g));
+  EXPECT(!Range::OnlyPositiveOrZero(range_g, range_h));
+  EXPECT(!Range::OnlyPositiveOrZero(range_i, range_i));
+
+  // OnlyNegativeOrZero.
+  EXPECT(Range::OnlyNegativeOrZero(range_a, range_b));
+  EXPECT(!Range::OnlyNegativeOrZero(range_b, range_c));
+  EXPECT(Range::OnlyNegativeOrZero(range_b, range_b));
+  EXPECT(!Range::OnlyNegativeOrZero(range_c, range_c));
+  EXPECT(!Range::OnlyNegativeOrZero(range_c, range_d));
+  EXPECT(!Range::OnlyNegativeOrZero(range_d, range_e));
+  EXPECT(!Range::OnlyNegativeOrZero(range_e, range_f));
+  EXPECT(!Range::OnlyNegativeOrZero(range_f, range_g));
+  EXPECT(!Range::OnlyNegativeOrZero(range_g, range_h));
+  EXPECT(Range::OnlyNegativeOrZero(range_h, range_h));
+  EXPECT(!Range::OnlyNegativeOrZero(range_i, range_i));
+
+  // [-inf, +inf].
+  EXPECT(!Range::OnlyNegativeOrZero(range_0, range_0));
+  EXPECT(!Range::OnlyPositiveOrZero(range_0, range_0));
+
+  EXPECT(Range::ConstantAbsMax(&range_0) == RangeBoundary::kMax);
+  EXPECT(Range::ConstantAbsMax(&range_h) == 2);
+  EXPECT(Range::ConstantAbsMax(&range_i) == 1);
+
+  // RangeBOundary.Equals.
+  EXPECT(RangeBoundary::FromConstant(1).Equals(
+      RangeBoundary::FromConstant(1)));
+  EXPECT(!RangeBoundary::FromConstant(2).Equals(
+      RangeBoundary::FromConstant(1)));
+  EXPECT(RangeBoundary::PositiveInfinity().Equals(
+      RangeBoundary::PositiveInfinity()));
+  EXPECT(!RangeBoundary::PositiveInfinity().Equals(
+      RangeBoundary::NegativeInfinity()));
+  EXPECT(RangeBoundary::NegativeInfinity().Equals(
+      RangeBoundary::NegativeInfinity()));
+  EXPECT(!RangeBoundary::NegativeInfinity().Equals(
+      RangeBoundary::PositiveInfinity()));
+  EXPECT(!RangeBoundary::FromConstant(1).Equals(
+      RangeBoundary::NegativeInfinity()));
+  EXPECT(!RangeBoundary::FromConstant(1).Equals(
+      RangeBoundary::NegativeInfinity()));
+  EXPECT(!RangeBoundary::FromConstant(2).Equals(
+      RangeBoundary::PositiveInfinity()));
+}
+
+
+TEST_CASE(RangeBinaryOp) {
+  Range* range_a = new Range(RangeBoundary::FromConstant(-1),
+                             RangeBoundary::PositiveInfinity());
+  range_a->Clamp(RangeBoundary::kRangeBoundaryInt64);
+  EXPECT(range_a->min().ConstantValue() == -1);
+  EXPECT(range_a->max().ConstantValue() == RangeBoundary::kMax);
+  Range* range_b = new Range(RangeBoundary::NegativeInfinity(),
+                             RangeBoundary::FromConstant(1));
+  range_b->Clamp(RangeBoundary::kRangeBoundaryInt64);
+  EXPECT(range_b->min().ConstantValue() == RangeBoundary::kMin);
+  EXPECT(range_b->max().ConstantValue() == 1);
+  Range* result = Range::BinaryOp(Token::kADD,
+                                  range_a,
+                                  range_b,
+                                  NULL);
+  ASSERT(result != NULL);
+  EXPECT(result->min().IsNegativeInfinity());
+  EXPECT(result->max().IsPositiveInfinity());
+
+  // Test that [5, 10] + [0, 5] = [5, 15].
+  Range* range_c = new Range(RangeBoundary::FromConstant(5),
+                             RangeBoundary::FromConstant(10));
+  Range* range_d = new Range(RangeBoundary::FromConstant(0),
+                             RangeBoundary::FromConstant(5));
+  result = Range::BinaryOp(Token::kADD,
+                           range_c,
+                           range_d,
+                           NULL);
+  ASSERT(result != NULL);
+  EXPECT(result->min().ConstantValue() == 5);
+  EXPECT(result->max().ConstantValue() == 15);
+
+
+  // Test that [0xff, 0xfff] & [0xf, 0xf] = [0x0, 0xf].
+  Range* range_e = new Range(RangeBoundary::FromConstant(0xff),
+                             RangeBoundary::FromConstant(0xfff));
+  Range* range_f = new Range(RangeBoundary::FromConstant(0xf),
+                             RangeBoundary::FromConstant(0xf));
+  result = Range::BinaryOp(Token::kBIT_AND,
+                           range_e,
+                           range_f,
+                           NULL);
+  ASSERT(result != NULL);
+  EXPECT(result->min().ConstantValue() == 0x0);
+  EXPECT(result->max().ConstantValue() == 0xf);
+}
+
+
+TEST_CASE(RangeAdd) {
+#define TEST_RANGE_ADD(l_min, l_max, r_min, r_max, result_min, result_max)     \
+  {                                                                            \
+    RangeBoundary min, max;                                                    \
+    Range* left_range = new Range(                                             \
+      RangeBoundary::FromConstant(l_min),                                      \
+      RangeBoundary::FromConstant(l_max));                                     \
+    Range* right_range = new Range(                                            \
+      RangeBoundary::FromConstant(r_min),                                      \
+      RangeBoundary::FromConstant(r_max));                                     \
+    EXPECT(left_range->min().ConstantValue() == l_min);                        \
+    EXPECT(left_range->max().ConstantValue() == l_max);                        \
+    EXPECT(right_range->min().ConstantValue() == r_min);                       \
+    EXPECT(right_range->max().ConstantValue() == r_max);                       \
+    Range::Add(left_range, right_range, &min, &max, NULL);                     \
+    EXPECT(min.Equals(result_min));                                            \
+    if (!min.Equals(result_min)) {                                             \
+      OS::Print("%s != %s\n", min.ToCString(), result_min.ToCString());        \
+    }                                                                          \
+    EXPECT(max.Equals(result_max));                                            \
+    if (!max.Equals(result_max)) {                                             \
+      OS::Print("%s != %s\n", max.ToCString(), result_max.ToCString());        \
+    }                                                                          \
+  }
+
+  // [kMaxInt32, kMaxInt32 + 15] + [10, 20] = [kMaxInt32 + 10, kMaxInt32 + 35].
+  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt32),
+                 static_cast<int64_t>(kMaxInt32) + 15,
+                 static_cast<int64_t>(10),
+                 static_cast<int64_t>(20),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 10),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 35));
+
+  // [kMaxInt32 - 15, kMaxInt32 + 15] + [15, -15] = [kMaxInt32, kMaxInt32].
+  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt32) - 15,
+                 static_cast<int64_t>(kMaxInt32) + 15,
+                 static_cast<int64_t>(15),
+                 static_cast<int64_t>(-15),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32)),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32)));
+
+  // [kMaxInt32, kMaxInt32 + 15] + [10, kMaxInt64] = [kMaxInt32 + 10, +inf].
+  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt32),
+                 static_cast<int64_t>(kMaxInt32) + 15,
+                 static_cast<int64_t>(10),
+                 static_cast<int64_t>(kMaxInt64),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 10),
+                 RangeBoundary::PositiveInfinity());
+
+  // [kMinInt64, kMaxInt32 + 15] + [10, 20] = [kMinInt64 + 10, kMaxInt32 + 35].
+  TEST_RANGE_ADD(static_cast<int64_t>(kMinInt64),
+                 static_cast<int64_t>(kMaxInt32) + 15,
+                 static_cast<int64_t>(10),
+                 static_cast<int64_t>(20),
+                 RangeBoundary(static_cast<int64_t>(kMinInt64) + 10),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 35));
+
+  // [0, 0] + [kMinInt64, kMaxInt64] = [kMinInt64, kMaxInt64].
+  TEST_RANGE_ADD(static_cast<int64_t>(0),
+                 static_cast<int64_t>(0),
+                 static_cast<int64_t>(kMinInt64),
+                 static_cast<int64_t>(kMaxInt64),
+                 RangeBoundary(kMinInt64),
+                 RangeBoundary(kMaxInt64));
+
+  // Overflows.
+
+  // [-1, 1] + [kMinInt64, kMaxInt64] = [-inf, +inf].
+  TEST_RANGE_ADD(static_cast<int64_t>(-1),
+                 static_cast<int64_t>(1),
+                 static_cast<int64_t>(kMinInt64),
+                 static_cast<int64_t>(kMaxInt64),
+                 RangeBoundary::NegativeInfinity(),
+                 RangeBoundary::PositiveInfinity());
+
+  // [kMaxInt64, kMaxInt64] + [kMaxInt64, kMaxInt64] = [-inf, +inf].
+  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt64),
+                 static_cast<int64_t>(kMaxInt64),
+                 static_cast<int64_t>(kMaxInt64),
+                 static_cast<int64_t>(kMaxInt64),
+                 RangeBoundary::NegativeInfinity(),
+                 RangeBoundary::PositiveInfinity());
+
+  // [kMaxInt64, kMaxInt64] + [1, 1] = [-inf, +inf].
+  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt64),
+                 static_cast<int64_t>(kMaxInt64),
+                 static_cast<int64_t>(1),
+                 static_cast<int64_t>(1),
+                 RangeBoundary::NegativeInfinity(),
+                 RangeBoundary::PositiveInfinity());
+
+#undef TEST_RANGE_ADD
+}
+
+
+TEST_CASE(RangeSub) {
+#define TEST_RANGE_SUB(l_min, l_max, r_min, r_max, result_min, result_max)     \
+  {                                                                            \
+    RangeBoundary min, max;                                                    \
+    Range* left_range = new Range(                                             \
+      RangeBoundary::FromConstant(l_min),                                      \
+      RangeBoundary::FromConstant(l_max));                                     \
+    Range* right_range = new Range(                                            \
+      RangeBoundary::FromConstant(r_min),                                      \
+      RangeBoundary::FromConstant(r_max));                                     \
+    EXPECT(left_range->min().ConstantValue() == l_min);                        \
+    EXPECT(left_range->max().ConstantValue() == l_max);                        \
+    EXPECT(right_range->min().ConstantValue() == r_min);                       \
+    EXPECT(right_range->max().ConstantValue() == r_max);                       \
+    Range::Sub(left_range, right_range, &min, &max, NULL);                     \
+    EXPECT(min.Equals(result_min));                                            \
+    if (!min.Equals(result_min)) {                                             \
+      OS::Print("%s != %s\n", min.ToCString(), result_min.ToCString());        \
+    }                                                                          \
+    EXPECT(max.Equals(result_max));                                            \
+    if (!max.Equals(result_max)) {                                             \
+      OS::Print("%s != %s\n", max.ToCString(), result_max.ToCString());        \
+    }                                                                          \
+  }
+
+  // [kMaxInt32, kMaxInt32 + 15] - [10, 20] = [kMaxInt32 - 20, kMaxInt32 + 5].
+  TEST_RANGE_SUB(static_cast<int64_t>(kMaxInt32),
+                 static_cast<int64_t>(kMaxInt32) + 15,
+                 static_cast<int64_t>(10),
+                 static_cast<int64_t>(20),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) - 20),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 5));
+
+  // [kMintInt64, kMintInt64] - [1, 1] = [-inf, +inf].
+  TEST_RANGE_SUB(static_cast<int64_t>(kMinInt64),
+                 static_cast<int64_t>(kMinInt64),
+                 static_cast<int64_t>(1),
+                 static_cast<int64_t>(1),
+                 RangeBoundary::NegativeInfinity(),
+                 RangeBoundary::PositiveInfinity());
+
+  // [1, 1] - [kMintInt64, kMintInt64] = [-inf, +inf].
+  TEST_RANGE_SUB(static_cast<int64_t>(1),
+                 static_cast<int64_t>(1),
+                 static_cast<int64_t>(kMinInt64),
+                 static_cast<int64_t>(kMinInt64),
+                 RangeBoundary::NegativeInfinity(),
+                 RangeBoundary::PositiveInfinity());
+
+  // [kMaxInt32 + 10, kMaxInt32 + 20] - [-20, -20] =
+  //     [kMaxInt32 + 30, kMaxInt32 + 40].
+  TEST_RANGE_SUB(static_cast<int64_t>(kMaxInt32) + 10,
+                 static_cast<int64_t>(kMaxInt32) + 20,
+                 static_cast<int64_t>(-20),
+                 static_cast<int64_t>(-20),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 30),
+                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 40));
+
+
+#undef TEST_RANGE_SUB
+}
+
+
+TEST_CASE(RangeAnd) {
+#define TEST_RANGE_AND(l_min, l_max, r_min, r_max, result_min, result_max)     \
+  {                                                                            \
+    RangeBoundary min, max;                                                    \
+    Range* left_range = new Range(                                             \
+      RangeBoundary::FromConstant(l_min),                                      \
+      RangeBoundary::FromConstant(l_max));                                     \
+    Range* right_range = new Range(                                            \
+      RangeBoundary::FromConstant(r_min),                                      \
+      RangeBoundary::FromConstant(r_max));                                     \
+    EXPECT(left_range->min().ConstantValue() == l_min);                        \
+    EXPECT(left_range->max().ConstantValue() == l_max);                        \
+    EXPECT(right_range->min().ConstantValue() == r_min);                       \
+    EXPECT(right_range->max().ConstantValue() == r_max);                       \
+    Range::And(left_range, right_range, &min, &max);                           \
+    EXPECT(min.Equals(result_min));                                            \
+    if (!min.Equals(result_min)) {                                             \
+      OS::Print("%s != %s\n", min.ToCString(), result_min.ToCString());        \
+    }                                                                          \
+    EXPECT(max.Equals(result_max));                                            \
+    if (!max.Equals(result_max)) {                                             \
+      OS::Print("%s != %s\n", max.ToCString(), result_max.ToCString());        \
+    }                                                                          \
+  }
+
+  // [0xff, 0xfff] & [0xf, 0xf] = [0x0, 0xf].
+  TEST_RANGE_AND(static_cast<int64_t>(0xff),
+                 static_cast<int64_t>(0xfff),
+                 static_cast<int64_t>(0xf),
+                 static_cast<int64_t>(0xf),
+                 RangeBoundary(0),
+                 RangeBoundary(0xf));
+
+  // [0xffffffff, 0xffffffff] & [0xfffffffff, 0xfffffffff] = [0x0, 0xfffffffff].
+  TEST_RANGE_AND(static_cast<int64_t>(0xffffffff),
+                 static_cast<int64_t>(0xffffffff),
+                 static_cast<int64_t>(0xfffffffff),
+                 static_cast<int64_t>(0xfffffffff),
+                 RangeBoundary(0),
+                 RangeBoundary(static_cast<int64_t>(0xfffffffff)));
+
+  // [0xffffffff, 0xffffffff] & [-20, 20] = [0x0, 0xffffffff].
+  TEST_RANGE_AND(static_cast<int64_t>(0xffffffff),
+                 static_cast<int64_t>(0xffffffff),
+                 static_cast<int64_t>(-20),
+                 static_cast<int64_t>(20),
+                 RangeBoundary(0),
+                 RangeBoundary(static_cast<int64_t>(0xffffffff)));
+
+  // [-20, 20] & [0xffffffff, 0xffffffff] = [0x0, 0xffffffff].
+  TEST_RANGE_AND(static_cast<int64_t>(-20),
+                 static_cast<int64_t>(20),
+                 static_cast<int64_t>(0xffffffff),
+                 static_cast<int64_t>(0xffffffff),
+                 RangeBoundary(0),
+                 RangeBoundary(static_cast<int64_t>(0xffffffff)));
+
+  // Test that [-20, 20] & [-20, 20] = [Unknown, Unknown].
+  TEST_RANGE_AND(static_cast<int64_t>(-20),
+                 static_cast<int64_t>(20),
+                 static_cast<int64_t>(-20),
+                 static_cast<int64_t>(20),
+                 RangeBoundary(),
+                 RangeBoundary());
+
+#undef TEST_RANGE_AND
+}
+
+
+TEST_CASE(RangeMinMax) {
+  // Constants.
+  // MIN(0, 1) == 0
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::FromConstant(1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
+  // MIN(0, -1) == -1
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
+
+  // MIN(1, 0) == 0
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary::FromConstant(1),
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
+  // MIN(-1, 0) == -1
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
+
+  // MAX(0, 1) == 1
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::FromConstant(1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
+
+  // MAX(0, -1) == 0
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
+
+  // MAX(1, 0) == 1
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary::FromConstant(1),
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
+  // MAX(-1, 0) == 0
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::FromConstant(0),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
+
+  RangeBoundary n_infinity = RangeBoundary::NegativeInfinity();
+  RangeBoundary p_infinity = RangeBoundary::PositiveInfinity();
+
+  // Constants vs. infinity.
+  EXPECT(RangeBoundary::Max(
+      n_infinity,
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
+
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary::FromConstant(-1),
+      n_infinity,
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
+
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary::FromConstant(1),
+      n_infinity,
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
+
+  EXPECT(RangeBoundary::Max(
+      n_infinity,
+      RangeBoundary::FromConstant(1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
+
+  EXPECT(RangeBoundary::Min(
+      p_infinity,
+      RangeBoundary::FromConstant(-1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
+
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary::FromConstant(-1),
+      p_infinity,
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
+
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary::FromConstant(1),
+      p_infinity,
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
+
+  EXPECT(RangeBoundary::Min(
+      p_infinity,
+      RangeBoundary::FromConstant(1),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
+
+  // 64-bit values.
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary(static_cast<int64_t>(kMinInt64)),
+      RangeBoundary(static_cast<int64_t>(kMinInt32)),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMinInt64);
+
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary(static_cast<int64_t>(kMinInt64)),
+      RangeBoundary(static_cast<int64_t>(kMinInt32)),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMinInt32);
+
+  EXPECT(RangeBoundary::Min(
+      RangeBoundary(static_cast<int64_t>(kMaxInt64)),
+      RangeBoundary(static_cast<int64_t>(kMaxInt32)),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMaxInt32);
+
+  EXPECT(RangeBoundary::Max(
+      RangeBoundary(static_cast<int64_t>(kMaxInt64)),
+      RangeBoundary(static_cast<int64_t>(kMaxInt32)),
+      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMaxInt64);
+}
+
+
+}  // namespace dart
diff --git a/runtime/vm/il_printer.cc b/runtime/vm/il_printer.cc
index b8664dc..5e3261e 100644
--- a/runtime/vm/il_printer.cc
+++ b/runtime/vm/il_printer.cc
@@ -4,6 +4,7 @@
 
 #include "vm/il_printer.h"
 
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/intermediate_language.h"
 #include "vm/os.h"
 #include "vm/parser.h"
diff --git a/runtime/vm/intermediate_language.cc b/runtime/vm/intermediate_language.cc
index c564930..118e652 100644
--- a/runtime/vm/intermediate_language.cc
+++ b/runtime/vm/intermediate_language.cc
@@ -12,6 +12,7 @@
 #include "vm/flow_graph_builder.h"
 #include "vm/flow_graph_compiler.h"
 #include "vm/flow_graph_optimizer.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/locations.h"
 #include "vm/object.h"
 #include "vm/object_store.h"
@@ -2582,579 +2583,6 @@
 }
 
 
-RangeBoundary RangeBoundary::FromDefinition(Definition* defn, int64_t offs) {
-  if (defn->IsConstant() && defn->AsConstant()->value().IsSmi()) {
-    return FromConstant(Smi::Cast(defn->AsConstant()->value()).Value() + offs);
-  }
-  return RangeBoundary(kSymbol, reinterpret_cast<intptr_t>(defn), offs);
-}
-
-
-RangeBoundary RangeBoundary::LowerBound() const {
-  if (IsInfinity()) {
-    return NegativeInfinity();
-  }
-  if (IsConstant()) return *this;
-  return Add(Range::ConstantMin(symbol()->range()),
-             RangeBoundary::FromConstant(offset_),
-             NegativeInfinity());
-}
-
-
-RangeBoundary RangeBoundary::UpperBound() const {
-  if (IsInfinity()) {
-    return PositiveInfinity();
-  }
-  if (IsConstant()) return *this;
-  return Add(Range::ConstantMax(symbol()->range()),
-             RangeBoundary::FromConstant(offset_),
-             PositiveInfinity());
-}
-
-
-RangeBoundary RangeBoundary::Add(const RangeBoundary& a,
-                                 const RangeBoundary& b,
-                                 const RangeBoundary& overflow) {
-  if (a.IsInfinity() || b.IsInfinity()) return overflow;
-
-  ASSERT(a.IsConstant() && b.IsConstant());
-  if (Utils::WillAddOverflow(a.ConstantValue(), b.ConstantValue())) {
-    return overflow;
-  }
-
-  int64_t result = a.ConstantValue() + b.ConstantValue();
-
-  return RangeBoundary::FromConstant(result);
-}
-
-
-RangeBoundary RangeBoundary::Sub(const RangeBoundary& a,
-                                 const RangeBoundary& b,
-                                 const RangeBoundary& overflow) {
-  if (a.IsInfinity() || b.IsInfinity()) return overflow;
-  ASSERT(a.IsConstant() && b.IsConstant());
-  if (Utils::WillSubOverflow(a.ConstantValue(), b.ConstantValue())) {
-    return overflow;
-  }
-
-  int64_t result = a.ConstantValue() - b.ConstantValue();
-
-  return RangeBoundary::FromConstant(result);
-}
-
-
-bool RangeBoundary::SymbolicAdd(const RangeBoundary& a,
-                                const RangeBoundary& b,
-                                RangeBoundary* result) {
-  if (a.IsSymbol() && b.IsConstant()) {
-    if (Utils::WillAddOverflow(a.offset(), b.ConstantValue())) {
-      return false;
-    }
-
-    const int64_t offset = a.offset() + b.ConstantValue();
-
-    *result = RangeBoundary::FromDefinition(a.symbol(), offset);
-    return true;
-  } else if (b.IsSymbol() && a.IsConstant()) {
-    return SymbolicAdd(b, a, result);
-  }
-  return false;
-}
-
-
-bool RangeBoundary::SymbolicSub(const RangeBoundary& a,
-                                const RangeBoundary& b,
-                                RangeBoundary* result) {
-  if (a.IsSymbol() && b.IsConstant()) {
-    if (Utils::WillSubOverflow(a.offset(), b.ConstantValue())) {
-      return false;
-    }
-
-    const int64_t offset = a.offset() - b.ConstantValue();
-
-    *result = RangeBoundary::FromDefinition(a.symbol(), offset);
-    return true;
-  }
-  return false;
-}
-
-
-static Definition* UnwrapConstraint(Definition* defn) {
-  while (defn->IsConstraint()) {
-    defn = defn->AsConstraint()->value()->definition();
-  }
-  return defn;
-}
-
-
-static bool AreEqualDefinitions(Definition* a, Definition* b) {
-  a = UnwrapConstraint(a);
-  b = UnwrapConstraint(b);
-  return (a == b) ||
-      (a->AllowsCSE() &&
-       a->Dependencies().IsNone() &&
-       b->AllowsCSE() &&
-       b->Dependencies().IsNone() &&
-       a->Equals(b));
-}
-
-
-// Returns true if two range boundaries refer to the same symbol.
-static bool DependOnSameSymbol(const RangeBoundary& a, const RangeBoundary& b) {
-  return a.IsSymbol() && b.IsSymbol() &&
-      AreEqualDefinitions(a.symbol(), b.symbol());
-}
-
-
-bool RangeBoundary::Equals(const RangeBoundary& other) const {
-  if (IsConstant() && other.IsConstant()) {
-    return ConstantValue() == other.ConstantValue();
-  } else if (IsInfinity() && other.IsInfinity()) {
-    return kind() == other.kind();
-  } else if (IsSymbol() && other.IsSymbol()) {
-    return (offset() == other.offset()) && DependOnSameSymbol(*this, other);
-  } else if (IsUnknown() && other.IsUnknown()) {
-    return true;
-  }
-  return false;
-}
-
-
-RangeBoundary RangeBoundary::Shl(const RangeBoundary& value_boundary,
-                                 int64_t shift_count,
-                                 const RangeBoundary& overflow) {
-  ASSERT(value_boundary.IsConstant());
-  ASSERT(shift_count >= 0);
-  int64_t limit = 64 - shift_count;
-  int64_t value = value_boundary.ConstantValue();
-
-  if ((value == 0) ||
-      (shift_count == 0) ||
-      ((limit > 0) && Utils::IsInt(static_cast<int>(limit), value))) {
-    // Result stays in 64 bit range.
-    int64_t result = value << shift_count;
-    return RangeBoundary(result);
-  }
-
-  return overflow;
-}
-
-
-static RangeBoundary CanonicalizeBoundary(const RangeBoundary& a,
-                                          const RangeBoundary& overflow) {
-  if (a.IsConstant() || a.IsInfinity()) {
-    return a;
-  }
-
-  int64_t offset = a.offset();
-  Definition* symbol = a.symbol();
-
-  bool changed;
-  do {
-    changed = false;
-    if (symbol->IsConstraint()) {
-      symbol = symbol->AsConstraint()->value()->definition();
-      changed = true;
-    } else if (symbol->IsBinarySmiOp()) {
-      BinarySmiOpInstr* op = symbol->AsBinarySmiOp();
-      Definition* left = op->left()->definition();
-      Definition* right = op->right()->definition();
-      switch (op->op_kind()) {
-        case Token::kADD:
-          if (right->IsConstant()) {
-            int64_t rhs = Smi::Cast(right->AsConstant()->value()).Value();
-            if (Utils::WillAddOverflow(offset, rhs)) {
-              return overflow;
-            }
-            offset += rhs;
-            symbol = left;
-            changed = true;
-          } else if (left->IsConstant()) {
-            int64_t rhs = Smi::Cast(left->AsConstant()->value()).Value();
-            if (Utils::WillAddOverflow(offset, rhs)) {
-              return overflow;
-            }
-            offset += rhs;
-            symbol = right;
-            changed = true;
-          }
-          break;
-
-        case Token::kSUB:
-          if (right->IsConstant()) {
-            int64_t rhs = Smi::Cast(right->AsConstant()->value()).Value();
-            if (Utils::WillSubOverflow(offset, rhs)) {
-              return overflow;
-            }
-            offset -= rhs;
-            symbol = left;
-            changed = true;
-          }
-          break;
-
-        default:
-          break;
-      }
-    }
-  } while (changed);
-
-  return RangeBoundary::FromDefinition(symbol, offset);
-}
-
-
-static bool CanonicalizeMaxBoundary(RangeBoundary* a) {
-  if (!a->IsSymbol()) return false;
-
-  Range* range = a->symbol()->range();
-  if ((range == NULL) || !range->max().IsSymbol()) return false;
-
-
-  if (Utils::WillAddOverflow(range->max().offset(), a->offset())) {
-    *a = RangeBoundary::PositiveInfinity();
-    return true;
-  }
-
-  const int64_t offset = range->max().offset() + a->offset();
-
-
-  *a = CanonicalizeBoundary(
-      RangeBoundary::FromDefinition(range->max().symbol(), offset),
-      RangeBoundary::PositiveInfinity());
-
-  return true;
-}
-
-
-static bool CanonicalizeMinBoundary(RangeBoundary* a) {
-  if (!a->IsSymbol()) return false;
-
-  Range* range = a->symbol()->range();
-  if ((range == NULL) || !range->min().IsSymbol()) return false;
-
-  if (Utils::WillAddOverflow(range->min().offset(), a->offset())) {
-    *a = RangeBoundary::NegativeInfinity();
-    return true;
-  }
-
-  const int64_t offset = range->min().offset() + a->offset();
-
-  *a = CanonicalizeBoundary(
-      RangeBoundary::FromDefinition(range->min().symbol(), offset),
-      RangeBoundary::NegativeInfinity());
-
-  return true;
-}
-
-
-RangeBoundary RangeBoundary::Min(RangeBoundary a, RangeBoundary b,
-                                 RangeSize size) {
-  ASSERT(!(a.IsNegativeInfinity() || b.IsNegativeInfinity()));
-  ASSERT(!a.IsUnknown() || !b.IsUnknown());
-  if (a.IsUnknown() && !b.IsUnknown()) {
-    return b;
-  }
-  if (!a.IsUnknown() && b.IsUnknown()) {
-    return a;
-  }
-  if (size == kRangeBoundarySmi) {
-    if (a.IsSmiMaximumOrAbove() && !b.IsSmiMaximumOrAbove()) {
-      return b;
-    }
-    if (!a.IsSmiMaximumOrAbove() && b.IsSmiMaximumOrAbove()) {
-      return a;
-    }
-  } else {
-    ASSERT(size == kRangeBoundaryInt64);
-    if (a.IsMaximumOrAbove() && !b.IsMaximumOrAbove()) {
-      return b;
-    }
-    if (!a.IsMaximumOrAbove() && b.IsMaximumOrAbove()) {
-      return a;
-    }
-  }
-
-  if (a.Equals(b)) {
-    return b;
-  }
-
-  {
-    RangeBoundary canonical_a =
-        CanonicalizeBoundary(a, RangeBoundary::PositiveInfinity());
-    RangeBoundary canonical_b =
-        CanonicalizeBoundary(b, RangeBoundary::PositiveInfinity());
-    do {
-      if (DependOnSameSymbol(canonical_a, canonical_b)) {
-        a = canonical_a;
-        b = canonical_b;
-        break;
-      }
-    } while (CanonicalizeMaxBoundary(&canonical_a) ||
-             CanonicalizeMaxBoundary(&canonical_b));
-  }
-
-  if (DependOnSameSymbol(a, b)) {
-    return (a.offset() <= b.offset()) ? a : b;
-  }
-
-  const int64_t min_a = a.UpperBound().Clamp(size).ConstantValue();
-  const int64_t min_b = b.UpperBound().Clamp(size).ConstantValue();
-
-  return RangeBoundary::FromConstant(Utils::Minimum(min_a, min_b));
-}
-
-
-RangeBoundary RangeBoundary::Max(RangeBoundary a, RangeBoundary b,
-                                 RangeSize size) {
-  ASSERT(!(a.IsPositiveInfinity() || b.IsPositiveInfinity()));
-  ASSERT(!a.IsUnknown() || !b.IsUnknown());
-  if (a.IsUnknown() && !b.IsUnknown()) {
-    return b;
-  }
-  if (!a.IsUnknown() && b.IsUnknown()) {
-    return a;
-  }
-  if (size == kRangeBoundarySmi) {
-    if (a.IsSmiMinimumOrBelow() && !b.IsSmiMinimumOrBelow()) {
-      return b;
-    }
-    if (!a.IsSmiMinimumOrBelow() && b.IsSmiMinimumOrBelow()) {
-      return a;
-    }
-  } else {
-     ASSERT(size == kRangeBoundaryInt64);
-    if (a.IsMinimumOrBelow() && !b.IsMinimumOrBelow()) {
-      return b;
-    }
-    if (!a.IsMinimumOrBelow() && b.IsMinimumOrBelow()) {
-      return a;
-    }
-  }
-  if (a.Equals(b)) {
-    return b;
-  }
-
-  {
-    RangeBoundary canonical_a =
-        CanonicalizeBoundary(a, RangeBoundary::NegativeInfinity());
-    RangeBoundary canonical_b =
-        CanonicalizeBoundary(b, RangeBoundary::NegativeInfinity());
-
-    do {
-      if (DependOnSameSymbol(canonical_a, canonical_b)) {
-        a = canonical_a;
-        b = canonical_b;
-        break;
-      }
-    } while (CanonicalizeMinBoundary(&canonical_a) ||
-             CanonicalizeMinBoundary(&canonical_b));
-  }
-
-  if (DependOnSameSymbol(a, b)) {
-    return (a.offset() <= b.offset()) ? b : a;
-  }
-
-  const int64_t max_a = a.LowerBound().Clamp(size).ConstantValue();
-  const int64_t max_b = b.LowerBound().Clamp(size).ConstantValue();
-
-  return RangeBoundary::FromConstant(Utils::Maximum(max_a, max_b));
-}
-
-
-int64_t RangeBoundary::ConstantValue() const {
-  ASSERT(IsConstant());
-  return value_;
-}
-
-
-void Definition::InferRange() {
-  if (Type()->ToCid() == kSmiCid) {
-    if (range_ == NULL) {
-      range_ = Range::UnknownSmi();
-    }
-  } else if (IsMintDefinition()) {
-    if (range_ == NULL) {
-      range_ = Range::Unknown();
-    }
-  } else {
-    // Only Smi and Mint supported.
-    UNREACHABLE();
-  }
-}
-
-
-void ConstantInstr::InferRange() {
-  if (value_.IsSmi()) {
-    if (range_ == NULL) {
-      int64_t value = Smi::Cast(value_).Value();
-      range_ = new Range(RangeBoundary::FromConstant(value),
-                         RangeBoundary::FromConstant(value));
-    }
-  } else if (value_.IsMint()) {
-    if (range_ == NULL) {
-      int64_t value = Mint::Cast(value_).value();
-      range_ = new Range(RangeBoundary::FromConstant(value),
-                         RangeBoundary::FromConstant(value));
-    }
-  } else {
-    // Only Smi and Mint supported.
-    UNREACHABLE();
-  }
-}
-
-
-void UnboxIntegerInstr::InferRange() {
-  if (range_ == NULL) {
-    Definition* unboxed = value()->definition();
-    ASSERT(unboxed != NULL);
-    Range* range = unboxed->range();
-    if (range == NULL) {
-      range_ = Range::Unknown();
-      return;
-    }
-    range_ = new Range(range->min(), range->max());
-  }
-}
-
-
-void ConstraintInstr::InferRange() {
-  Range* value_range = value()->definition()->range();
-
-  // Only constraining smi values.
-  ASSERT(value()->IsSmiValue());
-
-  RangeBoundary min;
-  RangeBoundary max;
-
-  {
-    RangeBoundary value_min = (value_range == NULL) ?
-        RangeBoundary() : value_range->min();
-    RangeBoundary constraint_min = constraint()->min();
-    min = RangeBoundary::Max(value_min, constraint_min,
-                             RangeBoundary::kRangeBoundarySmi);
-  }
-
-  ASSERT(!min.IsUnknown());
-
-  {
-    RangeBoundary value_max = (value_range == NULL) ?
-        RangeBoundary() : value_range->max();
-    RangeBoundary constraint_max = constraint()->max();
-    max = RangeBoundary::Min(value_max, constraint_max,
-                             RangeBoundary::kRangeBoundarySmi);
-  }
-
-  ASSERT(!max.IsUnknown());
-
-  range_ = new Range(min, max);
-
-  // Mark branches that generate unsatisfiable constraints as constant.
-  if (target() != NULL && range_->IsUnsatisfiable()) {
-    BranchInstr* branch =
-        target()->PredecessorAt(0)->last_instruction()->AsBranch();
-    if (target() == branch->true_successor()) {
-      // True unreachable.
-      if (FLAG_trace_constant_propagation) {
-        OS::Print("Range analysis: True unreachable (B%" Pd ")\n",
-                  branch->true_successor()->block_id());
-      }
-      branch->set_constant_target(branch->false_successor());
-    } else {
-      ASSERT(target() == branch->false_successor());
-      // False unreachable.
-      if (FLAG_trace_constant_propagation) {
-        OS::Print("Range analysis: False unreachable (B%" Pd ")\n",
-                  branch->false_successor()->block_id());
-      }
-      branch->set_constant_target(branch->true_successor());
-    }
-  }
-}
-
-
-void LoadFieldInstr::InferRange() {
-  if ((range_ == NULL) &&
-      ((recognized_kind() == MethodRecognizer::kObjectArrayLength) ||
-       (recognized_kind() == MethodRecognizer::kImmutableArrayLength))) {
-    range_ = new Range(RangeBoundary::FromConstant(0),
-                       RangeBoundary::FromConstant(Array::kMaxElements));
-    return;
-  }
-  if ((range_ == NULL) &&
-      (recognized_kind() == MethodRecognizer::kTypedDataLength)) {
-    range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());
-    return;
-  }
-  if ((range_ == NULL) &&
-      (recognized_kind() == MethodRecognizer::kStringBaseLength)) {
-    range_ = new Range(RangeBoundary::FromConstant(0),
-                       RangeBoundary::FromConstant(String::kMaxElements));
-    return;
-  }
-  Definition::InferRange();
-}
-
-
-
-void LoadIndexedInstr::InferRange() {
-  switch (class_id()) {
-    case kTypedDataInt8ArrayCid:
-      range_ = new Range(RangeBoundary::FromConstant(-128),
-                         RangeBoundary::FromConstant(127));
-      break;
-    case kTypedDataUint8ArrayCid:
-    case kTypedDataUint8ClampedArrayCid:
-    case kExternalTypedDataUint8ArrayCid:
-    case kExternalTypedDataUint8ClampedArrayCid:
-      range_ = new Range(RangeBoundary::FromConstant(0),
-                         RangeBoundary::FromConstant(255));
-      break;
-    case kTypedDataInt16ArrayCid:
-      range_ = new Range(RangeBoundary::FromConstant(-32768),
-                         RangeBoundary::FromConstant(32767));
-      break;
-    case kTypedDataUint16ArrayCid:
-      range_ = new Range(RangeBoundary::FromConstant(0),
-                         RangeBoundary::FromConstant(65535));
-      break;
-    case kTypedDataInt32ArrayCid:
-      if (Typed32BitIsSmi()) {
-        range_ = Range::UnknownSmi();
-      } else {
-        range_ = new Range(RangeBoundary::FromConstant(kMinInt32),
-                           RangeBoundary::FromConstant(kMaxInt32));
-      }
-      break;
-    case kTypedDataUint32ArrayCid:
-      if (Typed32BitIsSmi()) {
-        range_ = Range::UnknownSmi();
-      } else {
-        range_ = new Range(RangeBoundary::FromConstant(0),
-                           RangeBoundary::FromConstant(kMaxUint32));
-      }
-      break;
-    case kOneByteStringCid:
-      range_ = new Range(RangeBoundary::FromConstant(0),
-                         RangeBoundary::FromConstant(0xFF));
-      break;
-    case kTwoByteStringCid:
-      range_ = new Range(RangeBoundary::FromConstant(0),
-                         RangeBoundary::FromConstant(0xFFFF));
-      break;
-    default:
-      Definition::InferRange();
-      break;
-  }
-}
-
-
-void IfThenElseInstr::InferRange() {
-  const intptr_t min = Utils::Minimum(if_true_, if_false_);
-  const intptr_t max = Utils::Maximum(if_true_, if_false_);
-  range_ = new Range(RangeBoundary::FromConstant(min),
-                     RangeBoundary::FromConstant(max));
-}
-
-
 static bool BindsToSmiConstant(Value* value) {
   return value->BindsToConstant() && value->BoundConstant().IsSmi();
 }
@@ -3246,46 +2674,6 @@
 }
 
 
-void PhiInstr::InferRange() {
-  RangeBoundary new_min;
-  RangeBoundary new_max;
-
-  ASSERT(Type()->ToCid() == kSmiCid);
-
-  for (intptr_t i = 0; i < InputCount(); i++) {
-    Range* input_range = InputAt(i)->definition()->range();
-    if (input_range == NULL) {
-      range_ = Range::UnknownSmi();
-      return;
-    }
-
-    if (new_min.IsUnknown()) {
-      new_min = Range::ConstantMin(input_range);
-    } else {
-      new_min = RangeBoundary::Min(new_min,
-                                   Range::ConstantMinSmi(input_range),
-                                   RangeBoundary::kRangeBoundarySmi);
-    }
-
-    if (new_max.IsUnknown()) {
-      new_max = Range::ConstantMax(input_range);
-    } else {
-      new_max = RangeBoundary::Max(new_max,
-                                   Range::ConstantMaxSmi(input_range),
-                                   RangeBoundary::kRangeBoundarySmi);
-    }
-  }
-
-  ASSERT(new_min.IsUnknown() == new_max.IsUnknown());
-  if (new_min.IsUnknown()) {
-    range_ = Range::UnknownSmi();
-    return;
-  }
-
-  range_ = new Range(new_min, new_max);
-}
-
-
 bool PhiInstr::IsRedundant() const {
   ASSERT(InputCount() > 1);
   Definition* first = InputAt(0)->definition();
@@ -3297,543 +2685,11 @@
 }
 
 
-static bool IsArrayLength(Definition* defn) {
-  if (defn == NULL) {
-    return false;
-  }
-  LoadFieldInstr* load = defn->AsLoadField();
-  return (load != NULL) && load->IsImmutableLengthLoad();
-}
-
-
-void BinarySmiOpInstr::InferRange() {
-  // TODO(vegorov): canonicalize BinarySmiOp to always have constant on the
-  // right and a non-constant on the left.
-  Definition* left_defn = left()->definition();
-
-  Range* left_range = left_defn->range();
-  Range* right_range = right()->definition()->range();
-
-  if ((left_range == NULL) || (right_range == NULL)) {
-    range_ = Range::UnknownSmi();
-    return;
-  }
-
-  Range* possible_range = Range::BinaryOp(op_kind(),
-                                          left_range,
-                                          right_range,
-                                          left_defn);
-
-  if ((range_ == NULL) && (possible_range == NULL)) {
-    // Initialize.
-    range_ = Range::UnknownSmi();
-    return;
-  }
-
-  if (possible_range == NULL) {
-    // Nothing new.
-    return;
-  }
-
-  range_ = possible_range;
-
-  ASSERT(!range_->min().IsUnknown() && !range_->max().IsUnknown());
-  // Calculate overflowed status before clamping.
-  const bool overflowed = range_->min().LowerBound().OverflowedSmi() ||
-                          range_->max().UpperBound().OverflowedSmi();
-  set_overflow(overflowed);
-
-  // Clamp value to be within smi range.
-  range_->Clamp(RangeBoundary::kRangeBoundarySmi);
-}
-
-
-void BinaryMintOpInstr::InferRange() {
-  // TODO(vegorov): canonicalize BinaryMintOpInstr to always have constant on
-  // the right and a non-constant on the left.
-  Definition* left_defn = left()->definition();
-
-  Range* left_range = left_defn->range();
-  Range* right_range = right()->definition()->range();
-
-  if ((left_range == NULL) || (right_range == NULL)) {
-    range_ = Range::Unknown();
-    return;
-  }
-
-  Range* possible_range = Range::BinaryOp(op_kind(),
-                                          left_range,
-                                          right_range,
-                                          left_defn);
-
-  if ((range_ == NULL) && (possible_range == NULL)) {
-    // Initialize.
-    range_ = Range::Unknown();
-    return;
-  }
-
-  if (possible_range == NULL) {
-    // Nothing new.
-    return;
-  }
-
-  range_ = possible_range;
-
-  ASSERT(!range_->min().IsUnknown() && !range_->max().IsUnknown());
-
-  // Calculate overflowed status before clamping.
-  const bool overflowed = range_->min().LowerBound().OverflowedMint() ||
-                          range_->max().UpperBound().OverflowedMint();
-  set_can_overflow(overflowed);
-
-  // Clamp value to be within mint range.
-  range_->Clamp(RangeBoundary::kRangeBoundaryInt64);
-}
-
-
-void ShiftMintOpInstr::InferRange() {
-  Definition* left_defn = left()->definition();
-
-  Range* left_range = left_defn->range();
-  Range* right_range = right()->definition()->range();
-
-  if ((left_range == NULL) || (right_range == NULL)) {
-    range_ = Range::Unknown();
-    return;
-  }
-
-  Range* possible_range = Range::BinaryOp(op_kind(),
-                                          left_range,
-                                          right_range,
-                                          left_defn);
-
-  if ((range_ == NULL) && (possible_range == NULL)) {
-    // Initialize.
-    range_ = Range::Unknown();
-    return;
-  }
-
-  if (possible_range == NULL) {
-    // Nothing new.
-    return;
-  }
-
-  range_ = possible_range;
-
-  ASSERT(!range_->min().IsUnknown() && !range_->max().IsUnknown());
-
-  // Calculate overflowed status before clamping.
-  const bool overflowed = range_->min().LowerBound().OverflowedMint() ||
-                          range_->max().UpperBound().OverflowedMint();
-  set_can_overflow(overflowed);
-
-  // Clamp value to be within mint range.
-  range_->Clamp(RangeBoundary::kRangeBoundaryInt64);
-}
-
-
-void BoxIntegerInstr::InferRange() {
-  Range* input_range = value()->definition()->range();
-  if (input_range != NULL) {
-    bool is_smi = !input_range->min().LowerBound().OverflowedSmi() &&
-                  !input_range->max().UpperBound().OverflowedSmi();
-    set_is_smi(is_smi);
-    // The output range is the same as the input range.
-    range_ = input_range;
-  }
-}
-
-
-bool Range::IsPositive() const {
-  if (min().IsNegativeInfinity()) {
-    return false;
-  }
-  if (min().LowerBound().ConstantValue() < 0) {
-    return false;
-  }
-  if (max().IsPositiveInfinity()) {
-    return true;
-  }
-  return max().UpperBound().ConstantValue() >= 0;
-}
-
-
-bool Range::OnlyLessThanOrEqualTo(int64_t val) const {
-  if (max().IsPositiveInfinity()) {
-    // Cannot be true.
-    return false;
-  }
-  if (max().UpperBound().ConstantValue() > val) {
-    // Not true.
-    return false;
-  }
-  return true;
-}
-
-
-bool Range::OnlyGreaterThanOrEqualTo(int64_t val) const {
-  if (min().IsNegativeInfinity()) {
-    return false;
-  }
-  if (min().LowerBound().ConstantValue() < val) {
-    return false;
-  }
-  return true;
-}
-
-
-// Inclusive.
-bool Range::IsWithin(int64_t min_int, int64_t max_int) const {
-  RangeBoundary lower_min = min().LowerBound();
-  if (lower_min.IsNegativeInfinity() || (lower_min.ConstantValue() < min_int)) {
-    return false;
-  }
-  RangeBoundary upper_max = max().UpperBound();
-  if (upper_max.IsPositiveInfinity() || (upper_max.ConstantValue() > max_int)) {
-    return false;
-  }
-  return true;
-}
-
-
-bool Range::Overlaps(int64_t min_int, int64_t max_int) const {
-  RangeBoundary lower = min().LowerBound();
-  RangeBoundary upper = max().UpperBound();
-  const int64_t this_min = lower.IsNegativeInfinity() ?
-      RangeBoundary::kMin : lower.ConstantValue();
-  const int64_t this_max = upper.IsPositiveInfinity() ?
-      RangeBoundary::kMax : upper.ConstantValue();
-  if ((this_min <= min_int) && (min_int <= this_max)) return true;
-  if ((this_min <= max_int) && (max_int <= this_max)) return true;
-  if ((min_int < this_min) && (max_int > this_max)) return true;
-  return false;
-}
-
-
-bool Range::IsUnsatisfiable() const {
-  // Infinity case: [+inf, ...] || [..., -inf]
-  if (min().IsPositiveInfinity() || max().IsNegativeInfinity()) {
-    return true;
-  }
-  // Constant case: For example [0, -1].
-  if (Range::ConstantMin(this).ConstantValue() >
-      Range::ConstantMax(this).ConstantValue()) {
-    return true;
-  }
-  // Symbol case: For example [v+1, v].
-  if (DependOnSameSymbol(min(), max()) && min().offset() > max().offset()) {
-    return true;
-  }
-  return false;
-}
-
-
-void Range::Clamp(RangeBoundary::RangeSize size) {
-  min_ = min_.Clamp(size);
-  max_ = max_.Clamp(size);
-}
-
-
-void Range::Shl(const Range* left,
-                const Range* right,
-                RangeBoundary* result_min,
-                RangeBoundary* result_max) {
-  ASSERT(left != NULL);
-  ASSERT(right != NULL);
-  ASSERT(result_min != NULL);
-  ASSERT(result_max != NULL);
-  RangeBoundary left_max = Range::ConstantMax(left);
-  RangeBoundary left_min = Range::ConstantMin(left);
-  // A negative shift count always deoptimizes (and throws), so the minimum
-  // shift count is zero.
-  int64_t right_max = Utils::Maximum(Range::ConstantMax(right).ConstantValue(),
-                                     static_cast<int64_t>(0));
-  int64_t right_min = Utils::Maximum(Range::ConstantMin(right).ConstantValue(),
-                                     static_cast<int64_t>(0));
-
-  *result_min = RangeBoundary::Shl(
-      left_min,
-      left_min.ConstantValue() > 0 ? right_min : right_max,
-      left_min.ConstantValue() > 0
-          ? RangeBoundary::PositiveInfinity()
-          : RangeBoundary::NegativeInfinity());
-
-  *result_max = RangeBoundary::Shl(
-      left_max,
-      left_max.ConstantValue() > 0 ? right_max : right_min,
-      left_max.ConstantValue() > 0
-          ? RangeBoundary::PositiveInfinity()
-          : RangeBoundary::NegativeInfinity());
-}
-
-
-void Range::Shr(const Range* left,
-                const Range* right,
-                RangeBoundary* result_min,
-                RangeBoundary* result_max) {
-  RangeBoundary left_max = Range::ConstantMax(left);
-  RangeBoundary left_min = Range::ConstantMin(left);
-  // A negative shift count always deoptimizes (and throws), so the minimum
-  // shift count is zero.
-  int64_t right_max = Utils::Maximum(Range::ConstantMax(right).ConstantValue(),
-                                     static_cast<int64_t>(0));
-  int64_t right_min = Utils::Maximum(Range::ConstantMin(right).ConstantValue(),
-                                     static_cast<int64_t>(0));
-
-  *result_min = RangeBoundary::Shr(
-      left_min,
-      left_min.ConstantValue() > 0 ? right_max : right_min);
-
-  *result_max = RangeBoundary::Shr(
-      left_max,
-      left_max.ConstantValue() > 0 ? right_min : right_max);
-}
-
-
-bool Range::And(const Range* left_range,
-                const Range* right_range,
-                RangeBoundary* result_min,
-                RangeBoundary* result_max) {
-  ASSERT(left_range != NULL);
-  ASSERT(right_range != NULL);
-  ASSERT(result_min != NULL);
-  ASSERT(result_max != NULL);
-
-  if (Range::ConstantMin(right_range).ConstantValue() >= 0) {
-    *result_min = RangeBoundary::FromConstant(0);
-    *result_max = Range::ConstantMax(right_range);
-    return true;
-  }
-
-  if (Range::ConstantMin(left_range).ConstantValue() >= 0) {
-    *result_min = RangeBoundary::FromConstant(0);
-    *result_max = Range::ConstantMax(left_range);
-    return true;
-  }
-
-  return false;
-}
-
-
-void Range::Add(const Range* left_range,
-                const Range* right_range,
-                RangeBoundary* result_min,
-                RangeBoundary* result_max,
-                Definition* left_defn) {
-  ASSERT(left_range != NULL);
-  ASSERT(right_range != NULL);
-  ASSERT(result_min != NULL);
-  ASSERT(result_max != NULL);
-
-  RangeBoundary left_min =
-    IsArrayLength(left_defn) ?
-        RangeBoundary::FromDefinition(left_defn) : left_range->min();
-
-  RangeBoundary left_max =
-    IsArrayLength(left_defn) ?
-        RangeBoundary::FromDefinition(left_defn) : left_range->max();
-
-  if (!RangeBoundary::SymbolicAdd(left_min, right_range->min(), result_min)) {
-    *result_min = RangeBoundary::Add(left_range->min().LowerBound(),
-                                     right_range->min().LowerBound(),
-                                     RangeBoundary::NegativeInfinity());
-  }
-  if (!RangeBoundary::SymbolicAdd(left_max, right_range->max(), result_max)) {
-    *result_max = RangeBoundary::Add(right_range->max().UpperBound(),
-                                     left_range->max().UpperBound(),
-                                     RangeBoundary::PositiveInfinity());
-  }
-}
-
-
-void Range::Sub(const Range* left_range,
-                const Range* right_range,
-                RangeBoundary* result_min,
-                RangeBoundary* result_max,
-                Definition* left_defn) {
-  ASSERT(left_range != NULL);
-  ASSERT(right_range != NULL);
-  ASSERT(result_min != NULL);
-  ASSERT(result_max != NULL);
-
-  RangeBoundary left_min =
-    IsArrayLength(left_defn) ?
-        RangeBoundary::FromDefinition(left_defn) : left_range->min();
-
-  RangeBoundary left_max =
-    IsArrayLength(left_defn) ?
-        RangeBoundary::FromDefinition(left_defn) : left_range->max();
-
-  if (!RangeBoundary::SymbolicSub(left_min, right_range->max(), result_min)) {
-    *result_min = RangeBoundary::Sub(left_range->min().LowerBound(),
-                              right_range->max().UpperBound(),
-                              RangeBoundary::NegativeInfinity());
-  }
-  if (!RangeBoundary::SymbolicSub(left_max, right_range->min(), result_max)) {
-    *result_max = RangeBoundary::Sub(left_range->max().UpperBound(),
-                                     right_range->min().LowerBound(),
-                                     RangeBoundary::PositiveInfinity());
-  }
-}
-
-
-bool Range::Mul(const Range* left_range,
-                const Range* right_range,
-                RangeBoundary* result_min,
-                RangeBoundary* result_max) {
-  ASSERT(left_range != NULL);
-  ASSERT(right_range != NULL);
-  ASSERT(result_min != NULL);
-  ASSERT(result_max != NULL);
-
-  const int64_t left_max = ConstantAbsMax(left_range);
-  const int64_t right_max = ConstantAbsMax(right_range);
-  if ((left_max <= -kSmiMin) && (right_max <= -kSmiMin) &&
-      ((left_max == 0) || (right_max <= kMaxInt64 / left_max))) {
-    // Product of left and right max values stays in 64 bit range.
-    const int64_t mul_max = left_max * right_max;
-    if (Smi::IsValid(mul_max) && Smi::IsValid(-mul_max)) {
-      const int64_t r_min =
-          OnlyPositiveOrZero(*left_range, *right_range) ? 0 : -mul_max;
-      *result_min = RangeBoundary::FromConstant(r_min);
-      const int64_t r_max =
-          OnlyNegativeOrZero(*left_range, *right_range) ? 0 : mul_max;
-      *result_max = RangeBoundary::FromConstant(r_max);
-      return true;
-    }
-  }
-  return false;
-}
-
-
-// Both the a and b ranges are >= 0.
-bool Range::OnlyPositiveOrZero(const Range& a, const Range& b) {
-  return a.OnlyGreaterThanOrEqualTo(0) && b.OnlyGreaterThanOrEqualTo(0);
-}
-
-
-// Both the a and b ranges are <= 0.
-bool Range::OnlyNegativeOrZero(const Range& a, const Range& b) {
-  return a.OnlyLessThanOrEqualTo(0) && b.OnlyLessThanOrEqualTo(0);
-}
-
-
-// Return the maximum absolute value included in range.
-int64_t Range::ConstantAbsMax(const Range* range) {
-  if (range == NULL) {
-    return RangeBoundary::kMax;
-  }
-  const int64_t abs_min = Utils::Abs(Range::ConstantMin(range).ConstantValue());
-  const int64_t abs_max = Utils::Abs(Range::ConstantMax(range).ConstantValue());
-  return Utils::Maximum(abs_min, abs_max);
-}
-
-
-Range* Range::BinaryOp(const Token::Kind op,
-                       const Range* left_range,
-                       const Range* right_range,
-                       Definition* left_defn) {
-  ASSERT(left_range != NULL);
-  ASSERT(right_range != NULL);
-
-  // Both left and right ranges are finite.
-  ASSERT(left_range->IsFinite());
-  ASSERT(right_range->IsFinite());
-
-  RangeBoundary min;
-  RangeBoundary max;
-  ASSERT(min.IsUnknown() && max.IsUnknown());
-
-  switch (op) {
-    case Token::kADD:
-      Range::Add(left_range, right_range, &min, &max, left_defn);
-      break;
-    case Token::kSUB:
-      Range::Sub(left_range, right_range, &min, &max, left_defn);
-      break;
-    case Token::kMUL: {
-      if (!Range::Mul(left_range, right_range, &min, &max)) {
-        return NULL;
-      }
-      break;
-    }
-    case Token::kSHL: {
-      Range::Shl(left_range, right_range, &min, &max);
-      break;
-    }
-    case Token::kSHR: {
-      Range::Shr(left_range, right_range, &min, &max);
-      break;
-    }
-    case Token::kBIT_AND:
-      if (!Range::And(left_range, right_range, &min, &max)) {
-        return NULL;
-      }
-      break;
-    default:
-      return NULL;
-      break;
-  }
-
-  ASSERT(!min.IsUnknown() && !max.IsUnknown());
-
-  return new Range(min, max);
-}
-
-
 bool CheckArrayBoundInstr::IsFixedLengthArrayType(intptr_t cid) {
   return LoadFieldInstr::IsFixedLengthArrayCid(cid);
 }
 
 
-bool CheckArrayBoundInstr::IsRedundant(RangeBoundary length) {
-  Range* index_range = index()->definition()->range();
-
-  // Range of the index is unknown can't decide if the check is redundant.
-  if (index_range == NULL) {
-    return false;
-  }
-
-  // Range of the index is not positive. Check can't be redundant.
-  if (Range::ConstantMinSmi(index_range).ConstantValue() < 0) {
-    return false;
-  }
-
-  RangeBoundary max = CanonicalizeBoundary(index_range->max(),
-                                           RangeBoundary::PositiveInfinity());
-
-  if (max.OverflowedSmi()) {
-    return false;
-  }
-
-
-  RangeBoundary max_upper = max.UpperBound();
-  RangeBoundary length_lower = length.LowerBound();
-
-  if (max_upper.OverflowedSmi() || length_lower.OverflowedSmi()) {
-    return false;
-  }
-
-  // Try to compare constant boundaries.
-  if (max_upper.ConstantValue() < length_lower.ConstantValue()) {
-    return true;
-  }
-
-  length = CanonicalizeBoundary(length, RangeBoundary::PositiveInfinity());
-  if (length.OverflowedSmi()) {
-    return false;
-  }
-
-  // Try symbolic comparison.
-  do {
-    if (DependOnSameSymbol(max, length)) return max.offset() < length.offset();
-  } while (CanonicalizeMaxBoundary(&max) || CanonicalizeMinBoundary(&length));
-
-  // Failed to prove that maximum is bounded with array length.
-  return false;
-}
-
-
 Instruction* CheckArrayBoundInstr::Canonicalize(FlowGraph* flow_graph) {
   return IsRedundant(RangeBoundary::FromDefinition(length()->definition())) ?
       NULL : this;
diff --git a/runtime/vm/intermediate_language.h b/runtime/vm/intermediate_language.h
index 55bc22d..ade4498 100644
--- a/runtime/vm/intermediate_language.h
+++ b/runtime/vm/intermediate_language.h
@@ -32,6 +32,7 @@
 class LocalVariable;
 class ParsedFunction;
 class Range;
+class RangeBoundary;
 
 
 // TODO(srdjan): Unify with INTRINSIC_LIST.
@@ -2563,352 +2564,6 @@
 };
 
 
-class RangeBoundary : public ValueObject {
- public:
-  enum Kind {
-    kUnknown,
-    kNegativeInfinity,
-    kPositiveInfinity,
-    kSymbol,
-    kConstant,
-  };
-
-  enum RangeSize {
-    kRangeBoundarySmi,
-    kRangeBoundaryInt64,
-  };
-
-  RangeBoundary() : kind_(kUnknown), value_(0), offset_(0) { }
-
-  RangeBoundary(const RangeBoundary& other)
-      : ValueObject(),
-        kind_(other.kind_),
-        value_(other.value_),
-        offset_(other.offset_) { }
-
-  explicit RangeBoundary(int64_t val)
-      : kind_(kConstant), value_(val), offset_(0) { }
-
-  RangeBoundary& operator=(const RangeBoundary& other) {
-    kind_ = other.kind_;
-    value_ = other.value_;
-    offset_ = other.offset_;
-    return *this;
-  }
-
-  static const int64_t kMin = kMinInt64;
-  static const int64_t kMax = kMaxInt64;
-
-  // Construct a RangeBoundary for a constant value.
-  static RangeBoundary FromConstant(int64_t val) {
-    return RangeBoundary(val);
-  }
-
-  // Construct a RangeBoundary for -inf.
-  static RangeBoundary NegativeInfinity() {
-    return RangeBoundary(kNegativeInfinity, 0, 0);
-  }
-
-  // Construct a RangeBoundary for +inf.
-  static RangeBoundary PositiveInfinity() {
-    return RangeBoundary(kPositiveInfinity, 0, 0);
-  }
-
-  // Construct a RangeBoundary from a definition and offset.
-  static RangeBoundary FromDefinition(Definition* defn, int64_t offs = 0);
-
-  // Construct a RangeBoundary for the constant MinSmi value.
-  static RangeBoundary MinSmi() {
-    return FromConstant(Smi::kMinValue);
-  }
-
-  // Construct a RangeBoundary for the constant MaxSmi value.
-  static RangeBoundary MaxSmi() {
-    return FromConstant(Smi::kMaxValue);
-  }
-
-    // Construct a RangeBoundary for the constant kMin value.
-  static RangeBoundary MinConstant() {
-    return FromConstant(kMin);
-  }
-
-  // Construct a RangeBoundary for the constant kMax value.
-  static RangeBoundary MaxConstant() {
-    return FromConstant(kMax);
-  }
-
-  // Calculate the minimum of a and b within the given range.
-  static RangeBoundary Min(RangeBoundary a, RangeBoundary b, RangeSize size);
-  static RangeBoundary Max(RangeBoundary a, RangeBoundary b, RangeSize size);
-
-  // Returns true when this is a constant that is outside of Smi range.
-  bool OverflowedSmi() const {
-    return (IsConstant() && !Smi::IsValid(ConstantValue())) || IsInfinity();
-  }
-
-  // Returns true if this outside mint range.
-  bool OverflowedMint() const {
-    return IsInfinity();
-  }
-
-  // -/+ infinity are clamped to MinConstant/MaxConstant of the given type.
-  RangeBoundary Clamp(RangeSize size) const {
-    if (IsNegativeInfinity()) {
-      return (size == kRangeBoundaryInt64) ? MinConstant() : MinSmi();
-    }
-    if (IsPositiveInfinity()) {
-      return (size == kRangeBoundaryInt64) ? MaxConstant() : MaxSmi();
-    }
-    if ((size == kRangeBoundarySmi) && IsConstant()) {
-      if (ConstantValue() <= Smi::kMinValue) {
-        return MinSmi();
-      }
-      if (ConstantValue() >= Smi::kMaxValue) {
-        return MaxSmi();
-      }
-    }
-    // If this range is a symbolic range, we do not clamp it.
-    // This could lead to some imprecision later on.
-    return *this;
-  }
-
-
-  bool IsSmiMinimumOrBelow() const {
-    return IsNegativeInfinity() ||
-           (IsConstant() && (ConstantValue() <= Smi::kMinValue));
-  }
-
-  bool IsSmiMaximumOrAbove() const {
-    return IsPositiveInfinity() ||
-           (IsConstant() && (ConstantValue() >= Smi::kMaxValue));
-  }
-
-  bool IsMinimumOrBelow() const {
-    return IsNegativeInfinity() || (IsConstant() && (ConstantValue() == kMin));
-  }
-
-  bool IsMaximumOrAbove() const {
-    return IsPositiveInfinity() || (IsConstant() && (ConstantValue() == kMax));
-  }
-
-  intptr_t kind() const {
-    return kind_;
-  }
-
-  // Kind tests.
-  bool IsUnknown() const { return kind_ == kUnknown; }
-  bool IsConstant() const { return kind_ == kConstant; }
-  bool IsSymbol() const { return kind_ == kSymbol; }
-  bool IsNegativeInfinity() const { return kind_ == kNegativeInfinity; }
-  bool IsPositiveInfinity() const { return kind_ == kPositiveInfinity; }
-  bool IsInfinity() const {
-    return IsNegativeInfinity() || IsPositiveInfinity();
-  }
-  bool IsConstantOrInfinity() const {
-    return IsConstant() || IsInfinity();
-  }
-
-  // Returns the value of a kConstant RangeBoundary.
-  int64_t ConstantValue() const;
-
-  // Returns the Definition associated with a kSymbol RangeBoundary.
-  Definition* symbol() const {
-    ASSERT(IsSymbol());
-    return reinterpret_cast<Definition*>(value_);
-  }
-
-  // Offset from symbol.
-  int64_t offset() const {
-    return offset_;
-  }
-
-  // Computes the LowerBound of this. Three cases:
-  // IsInfinity() -> NegativeInfinity().
-  // IsConstant() -> value().
-  // IsSymbol() -> lower bound computed from definition + offset.
-  RangeBoundary LowerBound() const;
-
-  // Computes the UpperBound of this. Three cases:
-  // IsInfinity() -> PositiveInfinity().
-  // IsConstant() -> value().
-  // IsSymbol() -> upper bound computed from definition + offset.
-  RangeBoundary UpperBound() const;
-
-  void PrintTo(BufferFormatter* f) const;
-  const char* ToCString() const;
-
-  static RangeBoundary Add(const RangeBoundary& a,
-                           const RangeBoundary& b,
-                           const RangeBoundary& overflow);
-
-  static RangeBoundary Sub(const RangeBoundary& a,
-                           const RangeBoundary& b,
-                           const RangeBoundary& overflow);
-
-  static RangeBoundary Shl(const RangeBoundary& value_boundary,
-                           int64_t shift_count,
-                           const RangeBoundary& overflow);
-
-  static RangeBoundary Shr(const RangeBoundary& value_boundary,
-                           int64_t shift_count) {
-    ASSERT(value_boundary.IsConstant());
-    ASSERT(shift_count >= 0);
-    int64_t value = static_cast<int64_t>(value_boundary.ConstantValue());
-    int64_t result = value >> shift_count;
-    return RangeBoundary(result);
-  }
-
-  // Attempts to calculate a + b when:
-  // a is a symbol and b is a constant OR
-  // a is a constant and b is a symbol
-  // returns true if it succeeds, output is in result.
-  static bool SymbolicAdd(const RangeBoundary& a,
-                          const RangeBoundary& b,
-                          RangeBoundary* result);
-
-  // Attempts to calculate a - b when:
-  // a is a symbol and b is a constant
-  // returns true if it succeeds, output is in result.
-  static bool SymbolicSub(const RangeBoundary& a,
-                          const RangeBoundary& b,
-                          RangeBoundary* result);
-
-  bool Equals(const RangeBoundary& other) const;
-
- private:
-  RangeBoundary(Kind kind, int64_t value, int64_t offset)
-      : kind_(kind), value_(value), offset_(offset) { }
-
-  Kind kind_;
-  int64_t value_;
-  int64_t offset_;
-};
-
-
-class Range : public ZoneAllocated {
- public:
-  Range(RangeBoundary min, RangeBoundary max) : min_(min), max_(max) { }
-
-  static Range* Unknown() {
-    return new Range(RangeBoundary::MinConstant(),
-                     RangeBoundary::MaxConstant());
-  }
-
-  static Range* UnknownSmi() {
-    return new Range(RangeBoundary::MinSmi(),
-                     RangeBoundary::MaxSmi());
-  }
-
-  void PrintTo(BufferFormatter* f) const;
-  static const char* ToCString(const Range* range);
-
-  const RangeBoundary& min() const { return min_; }
-  const RangeBoundary& max() const { return max_; }
-
-  static RangeBoundary ConstantMinSmi(const Range* range) {
-    if (range == NULL) {
-      return RangeBoundary::MinSmi();
-    }
-    return range->min().LowerBound().Clamp(RangeBoundary::kRangeBoundarySmi);
-  }
-
-  static RangeBoundary ConstantMaxSmi(const Range* range) {
-    if (range == NULL) {
-      return RangeBoundary::MaxSmi();
-    }
-    return range->max().UpperBound().Clamp(RangeBoundary::kRangeBoundarySmi);
-  }
-
-  static RangeBoundary ConstantMin(const Range* range) {
-    if (range == NULL) {
-      return RangeBoundary::MinConstant();
-    }
-    return range->min().LowerBound().Clamp(RangeBoundary::kRangeBoundaryInt64);
-  }
-
-  static RangeBoundary ConstantMax(const Range* range) {
-    if (range == NULL) {
-      return RangeBoundary::MaxConstant();
-    }
-    return range->max().UpperBound().Clamp(RangeBoundary::kRangeBoundaryInt64);
-  }
-
-  // [0, +inf]
-  bool IsPositive() const;
-
-  // [-inf, val].
-  bool OnlyLessThanOrEqualTo(int64_t val) const;
-
-  // [val, +inf].
-  bool OnlyGreaterThanOrEqualTo(int64_t val) const;
-
-  // Inclusive.
-  bool IsWithin(int64_t min_int, int64_t max_int) const;
-
-  // Inclusive.
-  bool Overlaps(int64_t min_int, int64_t max_int) const;
-
-  bool IsUnsatisfiable() const;
-
-  bool IsFinite() const {
-    return !min_.IsInfinity() && !max_.IsInfinity();
-  }
-
-  // Clamp this to be within size.
-  void Clamp(RangeBoundary::RangeSize size);
-
-  static void Add(const Range* left_range,
-                  const Range* right_range,
-                  RangeBoundary* min,
-                  RangeBoundary* max,
-                  Definition* left_defn);
-
-  static void Sub(const Range* left_range,
-                  const Range* right_range,
-                  RangeBoundary* min,
-                  RangeBoundary* max,
-                  Definition* left_defn);
-
-  static bool Mul(const Range* left_range,
-                  const Range* right_range,
-                  RangeBoundary* min,
-                  RangeBoundary* max);
-  static void Shr(const Range* left_range,
-                  const Range* right_range,
-                  RangeBoundary* min,
-                  RangeBoundary* max);
-
-  static void Shl(const Range* left_range,
-                  const Range* right_range,
-                  RangeBoundary* min,
-                  RangeBoundary* max);
-
-  static bool And(const Range* left_range,
-                  const Range* right_range,
-                  RangeBoundary* min,
-                  RangeBoundary* max);
-
-
-  // Both the a and b ranges are >= 0.
-  static bool OnlyPositiveOrZero(const Range& a, const Range& b);
-
-  // Both the a and b ranges are <= 0.
-  static bool OnlyNegativeOrZero(const Range& a, const Range& b);
-
-  // Return the maximum absolute value included in range.
-  static int64_t ConstantAbsMax(const Range* range);
-
-  static Range* BinaryOp(const Token::Kind op,
-                         const Range* left_range,
-                         const Range* right_range,
-                         Definition* left_defn);
-
- private:
-  RangeBoundary min_;
-  RangeBoundary max_;
-};
-
-
 class ConstraintInstr : public TemplateDefinition<2> {
  public:
   ConstraintInstr(Value* value, Range* constraint)
@@ -8086,7 +7741,7 @@
 
   virtual bool CanDeoptimize() const { return true; }
 
-  bool IsRedundant(RangeBoundary length);
+  bool IsRedundant(const RangeBoundary& length);
 
   virtual Instruction* Canonicalize(FlowGraph* flow_graph);
 
diff --git a/runtime/vm/intermediate_language_arm.cc b/runtime/vm/intermediate_language_arm.cc
index a514a3b..ab28475 100644
--- a/runtime/vm/intermediate_language_arm.cc
+++ b/runtime/vm/intermediate_language_arm.cc
@@ -11,6 +11,7 @@
 #include "vm/dart_entry.h"
 #include "vm/flow_graph.h"
 #include "vm/flow_graph_compiler.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/locations.h"
 #include "vm/object_store.h"
 #include "vm/parser.h"
diff --git a/runtime/vm/intermediate_language_arm64.cc b/runtime/vm/intermediate_language_arm64.cc
index 9c06aeb..63bce94 100644
--- a/runtime/vm/intermediate_language_arm64.cc
+++ b/runtime/vm/intermediate_language_arm64.cc
@@ -10,6 +10,7 @@
 #include "vm/dart_entry.h"
 #include "vm/flow_graph.h"
 #include "vm/flow_graph_compiler.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/locations.h"
 #include "vm/object_store.h"
 #include "vm/parser.h"
diff --git a/runtime/vm/intermediate_language_ia32.cc b/runtime/vm/intermediate_language_ia32.cc
index 766cfee..c7fe804 100644
--- a/runtime/vm/intermediate_language_ia32.cc
+++ b/runtime/vm/intermediate_language_ia32.cc
@@ -10,6 +10,7 @@
 #include "vm/dart_entry.h"
 #include "vm/flow_graph.h"
 #include "vm/flow_graph_compiler.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/locations.h"
 #include "vm/object_store.h"
 #include "vm/parser.h"
diff --git a/runtime/vm/intermediate_language_mips.cc b/runtime/vm/intermediate_language_mips.cc
index ac60f82..0ab34fc 100644
--- a/runtime/vm/intermediate_language_mips.cc
+++ b/runtime/vm/intermediate_language_mips.cc
@@ -10,6 +10,7 @@
 #include "vm/dart_entry.h"
 #include "vm/flow_graph.h"
 #include "vm/flow_graph_compiler.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/locations.h"
 #include "vm/object_store.h"
 #include "vm/parser.h"
diff --git a/runtime/vm/intermediate_language_test.cc b/runtime/vm/intermediate_language_test.cc
index 67f304e..61b9e05 100644
--- a/runtime/vm/intermediate_language_test.cc
+++ b/runtime/vm/intermediate_language_test.cc
@@ -40,634 +40,4 @@
 }
 
 
-TEST_CASE(RangeTests) {
-  Range* zero = new Range(
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::FromConstant(0));
-  Range* positive = new Range(
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::FromConstant(100));
-  Range* negative = new Range(
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::FromConstant(-100));
-  Range* range_x = new Range(
-      RangeBoundary::FromConstant(-15),
-      RangeBoundary::FromConstant(100));
-  EXPECT(positive->IsPositive());
-  EXPECT(zero->Overlaps(0, 0));
-  EXPECT(positive->Overlaps(0, 0));
-  EXPECT(!negative->Overlaps(0, 0));
-  EXPECT(range_x->Overlaps(0, 0));
-  EXPECT(range_x->IsWithin(-15, 100));
-  EXPECT(!range_x->IsWithin(-15, 99));
-  EXPECT(!range_x->IsWithin(-14, 100));
-
-#define TEST_RANGE_OP_(Op, l_min, l_max, r_min, r_max, Clamp, res_min, res_max)\
-  {                                                                            \
-    RangeBoundary min, max;                                                    \
-    Range* left_range = new Range(                                             \
-      RangeBoundary::FromConstant(l_min),                                      \
-      RangeBoundary::FromConstant(l_max));                                     \
-    Range* shift_range = new Range(                                            \
-      RangeBoundary::FromConstant(r_min),                                      \
-      RangeBoundary::FromConstant(r_max));                                     \
-    Op(left_range, shift_range, &min, &max);                                   \
-    min = Clamp(min);                                                          \
-    max = Clamp(max);                                                          \
-    EXPECT(min.Equals(res_min));                                               \
-    if (!min.Equals(res_min)) OS::Print("%s\n", min.ToCString());              \
-    EXPECT(max.Equals(res_max));                                               \
-    if (!max.Equals(res_max)) OS::Print("%s\n", max.ToCString());              \
-  }
-
-#define NO_CLAMP(b) (b)
-#define TEST_RANGE_OP(Op, l_min, l_max, r_min, r_max, result_min, result_max)  \
-  TEST_RANGE_OP_(Op, l_min, l_max, r_min, r_max,                               \
-                 NO_CLAMP, result_min, result_max)
-
-#define CLAMP_TO_SMI(b) (b.Clamp(RangeBoundary::kRangeBoundarySmi))
-#define TEST_RANGE_OP_SMI(Op, l_min, l_max, r_min, r_max, res_min, res_max)    \
-  TEST_RANGE_OP_(Op, l_min, l_max, r_min, r_max,                               \
-                 CLAMP_TO_SMI, res_min, res_max)
-
-  TEST_RANGE_OP(Range::Shl, -15, 100, 0, 2,
-                RangeBoundary(-60), RangeBoundary(400));
-  TEST_RANGE_OP(Range::Shl, -15, 100, -2, 2,
-                RangeBoundary(-60), RangeBoundary(400));
-  TEST_RANGE_OP(Range::Shl, -15, -10, 1, 2,
-                RangeBoundary(-60), RangeBoundary(-20));
-  TEST_RANGE_OP(Range::Shl, 5, 10, -2, 2,
-                RangeBoundary(5), RangeBoundary(40));
-  TEST_RANGE_OP(Range::Shl, -15, 100, 0, 64,
-                RangeBoundary::NegativeInfinity(),
-                RangeBoundary::PositiveInfinity());
-  TEST_RANGE_OP(Range::Shl, -1, 1, 63, 63,
-                RangeBoundary(kMinInt64),
-                RangeBoundary::PositiveInfinity());
-  if (kBitsPerWord == 64) {
-    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 62, 62,
-                  RangeBoundary(kSmiMin),
-                  RangeBoundary(kSmiMax));
-    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 30, 30,
-                  RangeBoundary(-1 << 30),
-                  RangeBoundary(1 << 30));
-  } else {
-    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 30, 30,
-                  RangeBoundary(kSmiMin),
-                  RangeBoundary(kSmiMax));
-    TEST_RANGE_OP_SMI(Range::Shl, -1, 1, 62, 62,
-                  RangeBoundary(kSmiMin),
-                  RangeBoundary(kSmiMax));
-  }
-  TEST_RANGE_OP(Range::Shl, 0, 100, 0, 64,
-                RangeBoundary(0), RangeBoundary::PositiveInfinity());
-  TEST_RANGE_OP(Range::Shl, -100, 0, 0, 64,
-                RangeBoundary::NegativeInfinity(), RangeBoundary(0));
-
-  TEST_RANGE_OP(Range::Shr, -8, 8, 1, 2, RangeBoundary(-4), RangeBoundary(4));
-  TEST_RANGE_OP(Range::Shr, 1, 8, 1, 2, RangeBoundary(0), RangeBoundary(4));
-  TEST_RANGE_OP(Range::Shr, -16, -8, 1, 2,
-                RangeBoundary(-8), RangeBoundary(-2));
-  TEST_RANGE_OP(Range::Shr, 2, 4, -1, 1, RangeBoundary(1), RangeBoundary(4));
-  TEST_RANGE_OP(Range::Shr, kMaxInt64, kMaxInt64, 0, 1,
-                RangeBoundary(kMaxInt64 >> 1), RangeBoundary(kMaxInt64));
-  TEST_RANGE_OP(Range::Shr, kMinInt64, kMinInt64, 0, 1,
-                RangeBoundary(kMinInt64), RangeBoundary(kMinInt64 >> 1));
-#undef TEST_RANGE_OP
-}
-
-
-TEST_CASE(RangeTestsInfinity) {
-  // +/- inf overflowed.
-  EXPECT(RangeBoundary::NegativeInfinity().OverflowedSmi());
-  EXPECT(RangeBoundary::PositiveInfinity().OverflowedSmi());
-
-  EXPECT(RangeBoundary::NegativeInfinity().OverflowedMint());
-  EXPECT(RangeBoundary::PositiveInfinity().OverflowedMint());
-
-  Range* all = new Range(RangeBoundary::NegativeInfinity(),
-                         RangeBoundary::PositiveInfinity());
-  EXPECT(all->Overlaps(0, 0));
-  EXPECT(all->Overlaps(-1, 1));
-  EXPECT(!all->IsWithin(0, 100));
-  Range* positive = new Range(RangeBoundary::FromConstant(0),
-                              RangeBoundary::PositiveInfinity());
-  EXPECT(positive->IsPositive());
-  EXPECT(positive->Overlaps(0, 1));
-  EXPECT(positive->Overlaps(1, 100));
-  EXPECT(positive->Overlaps(-1, 0));
-  EXPECT(!positive->Overlaps(-2, -1));
-  Range* negative = new Range(RangeBoundary::NegativeInfinity(),
-                              RangeBoundary::FromConstant(-1));
-  EXPECT(!negative->IsPositive());
-  EXPECT(!negative->Overlaps(0, 1));
-  EXPECT(!negative->Overlaps(1, 100));
-  EXPECT(negative->Overlaps(-1, 0));
-  EXPECT(negative->Overlaps(-2, -1));
-  Range* negpos = new Range(RangeBoundary::NegativeInfinity(),
-                            RangeBoundary::FromConstant(0));
-  EXPECT(!negpos->IsPositive());
-
-  Range* a = new Range(RangeBoundary::NegativeInfinity(),
-                       RangeBoundary::FromConstant(1));
-
-  Range* b = new Range(RangeBoundary::NegativeInfinity(),
-                       RangeBoundary::FromConstant(31));
-
-  Range* c = new Range(RangeBoundary::NegativeInfinity(),
-                       RangeBoundary::FromConstant(32));
-
-  EXPECT(a->OnlyLessThanOrEqualTo(31));
-  EXPECT(b->OnlyLessThanOrEqualTo(31));
-  EXPECT(!c->OnlyLessThanOrEqualTo(31));
-
-  Range* unsatisfiable = new Range(RangeBoundary::PositiveInfinity(),
-                                   RangeBoundary::NegativeInfinity());
-  EXPECT(unsatisfiable->IsUnsatisfiable());
-
-  Range* unsatisfiable_right = new Range(RangeBoundary::PositiveInfinity(),
-                                         RangeBoundary::FromConstant(0));
-  EXPECT(unsatisfiable_right->IsUnsatisfiable());
-
-  Range* unsatisfiable_left = new Range(RangeBoundary::FromConstant(0),
-                                        RangeBoundary::NegativeInfinity());
-  EXPECT(unsatisfiable_left->IsUnsatisfiable());
-}
-
-
-TEST_CASE(RangeUtils) {
-  // [-inf, +inf].
-  const Range& range_0 = *(new Range(RangeBoundary::NegativeInfinity(),
-                                     RangeBoundary::PositiveInfinity()));
-  // [-inf, -1].
-  const Range& range_a = *(new Range(RangeBoundary::NegativeInfinity(),
-                                     RangeBoundary::FromConstant(-1)));
-  // [-inf, 0].
-  const Range& range_b = *(new Range(RangeBoundary::NegativeInfinity(),
-                                     RangeBoundary::FromConstant(0)));
-  // [-inf, 1].
-  const Range& range_c = *(new Range(RangeBoundary::NegativeInfinity(),
-                                     RangeBoundary::FromConstant(1)));
-  // [-1, +inf]
-  const Range& range_d = *(new Range(RangeBoundary::FromConstant(-1),
-                                     RangeBoundary::PositiveInfinity()));
-  // [0, +inf]
-  const Range& range_e = *(new Range(RangeBoundary::FromConstant(0),
-                                     RangeBoundary::PositiveInfinity()));
-  // [1, +inf].
-  const Range& range_f = *(new Range(RangeBoundary::FromConstant(1),
-                                     RangeBoundary::PositiveInfinity()));
-  // [1, 2].
-  const Range& range_g = *(new Range(RangeBoundary::FromConstant(1),
-                                     RangeBoundary::FromConstant(2)));
-  // [-1, -2].
-  const Range& range_h = *(new Range(RangeBoundary::FromConstant(-1),
-                                     RangeBoundary::FromConstant(-2)));
-  // [-1, 1].
-  const Range& range_i = *(new Range(RangeBoundary::FromConstant(-1),
-                                     RangeBoundary::FromConstant(1)));
-
-  // OnlyPositiveOrZero.
-  EXPECT(!Range::OnlyPositiveOrZero(range_a, range_b));
-  EXPECT(!Range::OnlyPositiveOrZero(range_b, range_c));
-  EXPECT(!Range::OnlyPositiveOrZero(range_c, range_d));
-  EXPECT(!Range::OnlyPositiveOrZero(range_d, range_e));
-  EXPECT(Range::OnlyPositiveOrZero(range_e, range_f));
-  EXPECT(!Range::OnlyPositiveOrZero(range_d, range_d));
-  EXPECT(Range::OnlyPositiveOrZero(range_e, range_e));
-  EXPECT(Range::OnlyPositiveOrZero(range_f, range_g));
-  EXPECT(!Range::OnlyPositiveOrZero(range_g, range_h));
-  EXPECT(!Range::OnlyPositiveOrZero(range_i, range_i));
-
-  // OnlyNegativeOrZero.
-  EXPECT(Range::OnlyNegativeOrZero(range_a, range_b));
-  EXPECT(!Range::OnlyNegativeOrZero(range_b, range_c));
-  EXPECT(Range::OnlyNegativeOrZero(range_b, range_b));
-  EXPECT(!Range::OnlyNegativeOrZero(range_c, range_c));
-  EXPECT(!Range::OnlyNegativeOrZero(range_c, range_d));
-  EXPECT(!Range::OnlyNegativeOrZero(range_d, range_e));
-  EXPECT(!Range::OnlyNegativeOrZero(range_e, range_f));
-  EXPECT(!Range::OnlyNegativeOrZero(range_f, range_g));
-  EXPECT(!Range::OnlyNegativeOrZero(range_g, range_h));
-  EXPECT(Range::OnlyNegativeOrZero(range_h, range_h));
-  EXPECT(!Range::OnlyNegativeOrZero(range_i, range_i));
-
-  // [-inf, +inf].
-  EXPECT(!Range::OnlyNegativeOrZero(range_0, range_0));
-  EXPECT(!Range::OnlyPositiveOrZero(range_0, range_0));
-
-  EXPECT(Range::ConstantAbsMax(&range_0) == RangeBoundary::kMax);
-  EXPECT(Range::ConstantAbsMax(&range_h) == 2);
-  EXPECT(Range::ConstantAbsMax(&range_i) == 1);
-
-  // RangeBOundary.Equals.
-  EXPECT(RangeBoundary::FromConstant(1).Equals(
-      RangeBoundary::FromConstant(1)));
-  EXPECT(!RangeBoundary::FromConstant(2).Equals(
-      RangeBoundary::FromConstant(1)));
-  EXPECT(RangeBoundary::PositiveInfinity().Equals(
-      RangeBoundary::PositiveInfinity()));
-  EXPECT(!RangeBoundary::PositiveInfinity().Equals(
-      RangeBoundary::NegativeInfinity()));
-  EXPECT(RangeBoundary::NegativeInfinity().Equals(
-      RangeBoundary::NegativeInfinity()));
-  EXPECT(!RangeBoundary::NegativeInfinity().Equals(
-      RangeBoundary::PositiveInfinity()));
-  EXPECT(!RangeBoundary::FromConstant(1).Equals(
-      RangeBoundary::NegativeInfinity()));
-  EXPECT(!RangeBoundary::FromConstant(1).Equals(
-      RangeBoundary::NegativeInfinity()));
-  EXPECT(!RangeBoundary::FromConstant(2).Equals(
-      RangeBoundary::PositiveInfinity()));
-}
-
-
-TEST_CASE(RangeBinaryOp) {
-  Range* range_a = new Range(RangeBoundary::FromConstant(-1),
-                             RangeBoundary::PositiveInfinity());
-  range_a->Clamp(RangeBoundary::kRangeBoundaryInt64);
-  EXPECT(range_a->min().ConstantValue() == -1);
-  EXPECT(range_a->max().ConstantValue() == RangeBoundary::kMax);
-  Range* range_b = new Range(RangeBoundary::NegativeInfinity(),
-                             RangeBoundary::FromConstant(1));
-  range_b->Clamp(RangeBoundary::kRangeBoundaryInt64);
-  EXPECT(range_b->min().ConstantValue() == RangeBoundary::kMin);
-  EXPECT(range_b->max().ConstantValue() == 1);
-  Range* result = Range::BinaryOp(Token::kADD,
-                                  range_a,
-                                  range_b,
-                                  NULL);
-  ASSERT(result != NULL);
-  EXPECT(result->min().IsNegativeInfinity());
-  EXPECT(result->max().IsPositiveInfinity());
-
-  // Test that [5, 10] + [0, 5] = [5, 15].
-  Range* range_c = new Range(RangeBoundary::FromConstant(5),
-                             RangeBoundary::FromConstant(10));
-  Range* range_d = new Range(RangeBoundary::FromConstant(0),
-                             RangeBoundary::FromConstant(5));
-  result = Range::BinaryOp(Token::kADD,
-                           range_c,
-                           range_d,
-                           NULL);
-  ASSERT(result != NULL);
-  EXPECT(result->min().ConstantValue() == 5);
-  EXPECT(result->max().ConstantValue() == 15);
-
-
-  // Test that [0xff, 0xfff] & [0xf, 0xf] = [0x0, 0xf].
-  Range* range_e = new Range(RangeBoundary::FromConstant(0xff),
-                             RangeBoundary::FromConstant(0xfff));
-  Range* range_f = new Range(RangeBoundary::FromConstant(0xf),
-                             RangeBoundary::FromConstant(0xf));
-  result = Range::BinaryOp(Token::kBIT_AND,
-                           range_e,
-                           range_f,
-                           NULL);
-  ASSERT(result != NULL);
-  EXPECT(result->min().ConstantValue() == 0x0);
-  EXPECT(result->max().ConstantValue() == 0xf);
-}
-
-
-TEST_CASE(RangeAdd) {
-#define TEST_RANGE_ADD(l_min, l_max, r_min, r_max, result_min, result_max)     \
-  {                                                                            \
-    RangeBoundary min, max;                                                    \
-    Range* left_range = new Range(                                             \
-      RangeBoundary::FromConstant(l_min),                                      \
-      RangeBoundary::FromConstant(l_max));                                     \
-    Range* right_range = new Range(                                            \
-      RangeBoundary::FromConstant(r_min),                                      \
-      RangeBoundary::FromConstant(r_max));                                     \
-    EXPECT(left_range->min().ConstantValue() == l_min);                        \
-    EXPECT(left_range->max().ConstantValue() == l_max);                        \
-    EXPECT(right_range->min().ConstantValue() == r_min);                       \
-    EXPECT(right_range->max().ConstantValue() == r_max);                       \
-    Range::Add(left_range, right_range, &min, &max, NULL);                     \
-    EXPECT(min.Equals(result_min));                                            \
-    if (!min.Equals(result_min)) {                                             \
-      OS::Print("%s != %s\n", min.ToCString(), result_min.ToCString());        \
-    }                                                                          \
-    EXPECT(max.Equals(result_max));                                            \
-    if (!max.Equals(result_max)) {                                             \
-      OS::Print("%s != %s\n", max.ToCString(), result_max.ToCString());        \
-    }                                                                          \
-  }
-
-  // [kMaxInt32, kMaxInt32 + 15] + [10, 20] = [kMaxInt32 + 10, kMaxInt32 + 35].
-  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt32),
-                 static_cast<int64_t>(kMaxInt32) + 15,
-                 static_cast<int64_t>(10),
-                 static_cast<int64_t>(20),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 10),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 35));
-
-  // [kMaxInt32 - 15, kMaxInt32 + 15] + [15, -15] = [kMaxInt32, kMaxInt32].
-  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt32) - 15,
-                 static_cast<int64_t>(kMaxInt32) + 15,
-                 static_cast<int64_t>(15),
-                 static_cast<int64_t>(-15),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32)),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32)));
-
-  // [kMaxInt32, kMaxInt32 + 15] + [10, kMaxInt64] = [kMaxInt32 + 10, +inf].
-  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt32),
-                 static_cast<int64_t>(kMaxInt32) + 15,
-                 static_cast<int64_t>(10),
-                 static_cast<int64_t>(kMaxInt64),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 10),
-                 RangeBoundary::PositiveInfinity());
-
-  // [kMinInt64, kMaxInt32 + 15] + [10, 20] = [kMinInt64 + 10, kMaxInt32 + 35].
-  TEST_RANGE_ADD(static_cast<int64_t>(kMinInt64),
-                 static_cast<int64_t>(kMaxInt32) + 15,
-                 static_cast<int64_t>(10),
-                 static_cast<int64_t>(20),
-                 RangeBoundary(static_cast<int64_t>(kMinInt64) + 10),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 35));
-
-  // [0, 0] + [kMinInt64, kMaxInt64] = [kMinInt64, kMaxInt64].
-  TEST_RANGE_ADD(static_cast<int64_t>(0),
-                 static_cast<int64_t>(0),
-                 static_cast<int64_t>(kMinInt64),
-                 static_cast<int64_t>(kMaxInt64),
-                 RangeBoundary(kMinInt64),
-                 RangeBoundary(kMaxInt64));
-
-  // Overflows.
-
-  // [-1, 1] + [kMinInt64, kMaxInt64] = [-inf, +inf].
-  TEST_RANGE_ADD(static_cast<int64_t>(-1),
-                 static_cast<int64_t>(1),
-                 static_cast<int64_t>(kMinInt64),
-                 static_cast<int64_t>(kMaxInt64),
-                 RangeBoundary::NegativeInfinity(),
-                 RangeBoundary::PositiveInfinity());
-
-  // [kMaxInt64, kMaxInt64] + [kMaxInt64, kMaxInt64] = [-inf, +inf].
-  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt64),
-                 static_cast<int64_t>(kMaxInt64),
-                 static_cast<int64_t>(kMaxInt64),
-                 static_cast<int64_t>(kMaxInt64),
-                 RangeBoundary::NegativeInfinity(),
-                 RangeBoundary::PositiveInfinity());
-
-  // [kMaxInt64, kMaxInt64] + [1, 1] = [-inf, +inf].
-  TEST_RANGE_ADD(static_cast<int64_t>(kMaxInt64),
-                 static_cast<int64_t>(kMaxInt64),
-                 static_cast<int64_t>(1),
-                 static_cast<int64_t>(1),
-                 RangeBoundary::NegativeInfinity(),
-                 RangeBoundary::PositiveInfinity());
-
-#undef TEST_RANGE_ADD
-}
-
-
-TEST_CASE(RangeSub) {
-#define TEST_RANGE_SUB(l_min, l_max, r_min, r_max, result_min, result_max)     \
-  {                                                                            \
-    RangeBoundary min, max;                                                    \
-    Range* left_range = new Range(                                             \
-      RangeBoundary::FromConstant(l_min),                                      \
-      RangeBoundary::FromConstant(l_max));                                     \
-    Range* right_range = new Range(                                            \
-      RangeBoundary::FromConstant(r_min),                                      \
-      RangeBoundary::FromConstant(r_max));                                     \
-    EXPECT(left_range->min().ConstantValue() == l_min);                        \
-    EXPECT(left_range->max().ConstantValue() == l_max);                        \
-    EXPECT(right_range->min().ConstantValue() == r_min);                       \
-    EXPECT(right_range->max().ConstantValue() == r_max);                       \
-    Range::Sub(left_range, right_range, &min, &max, NULL);                     \
-    EXPECT(min.Equals(result_min));                                            \
-    if (!min.Equals(result_min)) {                                             \
-      OS::Print("%s != %s\n", min.ToCString(), result_min.ToCString());        \
-    }                                                                          \
-    EXPECT(max.Equals(result_max));                                            \
-    if (!max.Equals(result_max)) {                                             \
-      OS::Print("%s != %s\n", max.ToCString(), result_max.ToCString());        \
-    }                                                                          \
-  }
-
-  // [kMaxInt32, kMaxInt32 + 15] - [10, 20] = [kMaxInt32 - 20, kMaxInt32 + 5].
-  TEST_RANGE_SUB(static_cast<int64_t>(kMaxInt32),
-                 static_cast<int64_t>(kMaxInt32) + 15,
-                 static_cast<int64_t>(10),
-                 static_cast<int64_t>(20),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) - 20),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 5));
-
-  // [kMintInt64, kMintInt64] - [1, 1] = [-inf, +inf].
-  TEST_RANGE_SUB(static_cast<int64_t>(kMinInt64),
-                 static_cast<int64_t>(kMinInt64),
-                 static_cast<int64_t>(1),
-                 static_cast<int64_t>(1),
-                 RangeBoundary::NegativeInfinity(),
-                 RangeBoundary::PositiveInfinity());
-
-  // [1, 1] - [kMintInt64, kMintInt64] = [-inf, +inf].
-  TEST_RANGE_SUB(static_cast<int64_t>(1),
-                 static_cast<int64_t>(1),
-                 static_cast<int64_t>(kMinInt64),
-                 static_cast<int64_t>(kMinInt64),
-                 RangeBoundary::NegativeInfinity(),
-                 RangeBoundary::PositiveInfinity());
-
-  // [kMaxInt32 + 10, kMaxInt32 + 20] - [-20, -20] =
-  //     [kMaxInt32 + 30, kMaxInt32 + 40].
-  TEST_RANGE_SUB(static_cast<int64_t>(kMaxInt32) + 10,
-                 static_cast<int64_t>(kMaxInt32) + 20,
-                 static_cast<int64_t>(-20),
-                 static_cast<int64_t>(-20),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 30),
-                 RangeBoundary(static_cast<int64_t>(kMaxInt32) + 40));
-
-
-#undef TEST_RANGE_SUB
-}
-
-
-TEST_CASE(RangeAnd) {
-#define TEST_RANGE_AND(l_min, l_max, r_min, r_max, result_min, result_max)     \
-  {                                                                            \
-    RangeBoundary min, max;                                                    \
-    Range* left_range = new Range(                                             \
-      RangeBoundary::FromConstant(l_min),                                      \
-      RangeBoundary::FromConstant(l_max));                                     \
-    Range* right_range = new Range(                                            \
-      RangeBoundary::FromConstant(r_min),                                      \
-      RangeBoundary::FromConstant(r_max));                                     \
-    EXPECT(left_range->min().ConstantValue() == l_min);                        \
-    EXPECT(left_range->max().ConstantValue() == l_max);                        \
-    EXPECT(right_range->min().ConstantValue() == r_min);                       \
-    EXPECT(right_range->max().ConstantValue() == r_max);                       \
-    Range::And(left_range, right_range, &min, &max);                           \
-    EXPECT(min.Equals(result_min));                                            \
-    if (!min.Equals(result_min)) {                                             \
-      OS::Print("%s != %s\n", min.ToCString(), result_min.ToCString());        \
-    }                                                                          \
-    EXPECT(max.Equals(result_max));                                            \
-    if (!max.Equals(result_max)) {                                             \
-      OS::Print("%s != %s\n", max.ToCString(), result_max.ToCString());        \
-    }                                                                          \
-  }
-
-  // [0xff, 0xfff] & [0xf, 0xf] = [0x0, 0xf].
-  TEST_RANGE_AND(static_cast<int64_t>(0xff),
-                 static_cast<int64_t>(0xfff),
-                 static_cast<int64_t>(0xf),
-                 static_cast<int64_t>(0xf),
-                 RangeBoundary(0),
-                 RangeBoundary(0xf));
-
-  // [0xffffffff, 0xffffffff] & [0xfffffffff, 0xfffffffff] = [0x0, 0xfffffffff].
-  TEST_RANGE_AND(static_cast<int64_t>(0xffffffff),
-                 static_cast<int64_t>(0xffffffff),
-                 static_cast<int64_t>(0xfffffffff),
-                 static_cast<int64_t>(0xfffffffff),
-                 RangeBoundary(0),
-                 RangeBoundary(static_cast<int64_t>(0xfffffffff)));
-
-  // [0xffffffff, 0xffffffff] & [-20, 20] = [0x0, 0xffffffff].
-  TEST_RANGE_AND(static_cast<int64_t>(0xffffffff),
-                 static_cast<int64_t>(0xffffffff),
-                 static_cast<int64_t>(-20),
-                 static_cast<int64_t>(20),
-                 RangeBoundary(0),
-                 RangeBoundary(static_cast<int64_t>(0xffffffff)));
-
-  // [-20, 20] & [0xffffffff, 0xffffffff] = [0x0, 0xffffffff].
-  TEST_RANGE_AND(static_cast<int64_t>(-20),
-                 static_cast<int64_t>(20),
-                 static_cast<int64_t>(0xffffffff),
-                 static_cast<int64_t>(0xffffffff),
-                 RangeBoundary(0),
-                 RangeBoundary(static_cast<int64_t>(0xffffffff)));
-
-  // Test that [-20, 20] & [-20, 20] = [Unknown, Unknown].
-  TEST_RANGE_AND(static_cast<int64_t>(-20),
-                 static_cast<int64_t>(20),
-                 static_cast<int64_t>(-20),
-                 static_cast<int64_t>(20),
-                 RangeBoundary(),
-                 RangeBoundary());
-
-#undef TEST_RANGE_AND
-}
-
-
-TEST_CASE(RangeMinMax) {
-  // Constants.
-  // MIN(0, 1) == 0
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::FromConstant(1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
-  // MIN(0, -1) == -1
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
-
-  // MIN(1, 0) == 0
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary::FromConstant(1),
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
-  // MIN(-1, 0) == -1
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
-
-  // MAX(0, 1) == 1
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::FromConstant(1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
-
-  // MAX(0, -1) == 0
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
-
-  // MAX(1, 0) == 1
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary::FromConstant(1),
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
-  // MAX(-1, 0) == 0
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::FromConstant(0),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 0);
-
-  RangeBoundary n_infinity = RangeBoundary::NegativeInfinity();
-  RangeBoundary p_infinity = RangeBoundary::PositiveInfinity();
-
-  // Constants vs. infinity.
-  EXPECT(RangeBoundary::Max(
-      n_infinity,
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
-
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary::FromConstant(-1),
-      n_infinity,
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
-
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary::FromConstant(1),
-      n_infinity,
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
-
-  EXPECT(RangeBoundary::Max(
-      n_infinity,
-      RangeBoundary::FromConstant(1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
-
-  EXPECT(RangeBoundary::Min(
-      p_infinity,
-      RangeBoundary::FromConstant(-1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
-
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary::FromConstant(-1),
-      p_infinity,
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == -1);
-
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary::FromConstant(1),
-      p_infinity,
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
-
-  EXPECT(RangeBoundary::Min(
-      p_infinity,
-      RangeBoundary::FromConstant(1),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == 1);
-
-  // 64-bit values.
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary(static_cast<int64_t>(kMinInt64)),
-      RangeBoundary(static_cast<int64_t>(kMinInt32)),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMinInt64);
-
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary(static_cast<int64_t>(kMinInt64)),
-      RangeBoundary(static_cast<int64_t>(kMinInt32)),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMinInt32);
-
-  EXPECT(RangeBoundary::Min(
-      RangeBoundary(static_cast<int64_t>(kMaxInt64)),
-      RangeBoundary(static_cast<int64_t>(kMaxInt32)),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMaxInt32);
-
-  EXPECT(RangeBoundary::Max(
-      RangeBoundary(static_cast<int64_t>(kMaxInt64)),
-      RangeBoundary(static_cast<int64_t>(kMaxInt32)),
-      RangeBoundary::kRangeBoundaryInt64).ConstantValue() == kMaxInt64);
-}
-
 }  // namespace dart
diff --git a/runtime/vm/intermediate_language_x64.cc b/runtime/vm/intermediate_language_x64.cc
index e6d3910..9a258bc 100644
--- a/runtime/vm/intermediate_language_x64.cc
+++ b/runtime/vm/intermediate_language_x64.cc
@@ -10,6 +10,7 @@
 #include "vm/dart_entry.h"
 #include "vm/flow_graph.h"
 #include "vm/flow_graph_compiler.h"
+#include "vm/flow_graph_range_analysis.h"
 #include "vm/locations.h"
 #include "vm/object_store.h"
 #include "vm/parser.h"
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 08adf59..5dbbcfb 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -6858,28 +6858,26 @@
 RawString* Field::GetterName(const String& field_name) {
   CompilerStats::make_accessor_name++;
   // TODO(koda): Avoid most of these allocations by adding prefix-based lookup
-  // to Symbols.
+  // to Class::Lookup*.
   return String::Concat(Symbols::GetterPrefix(), field_name);
 }
 
 
 RawString* Field::GetterSymbol(const String& field_name) {
-  const String& str = String::Handle(Field::GetterName(field_name));
-  return Symbols::New(str);
+  return Symbols::FromConcat(Symbols::GetterPrefix(), field_name);
 }
 
 
 RawString* Field::SetterName(const String& field_name) {
   CompilerStats::make_accessor_name++;
   // TODO(koda): Avoid most of these allocations by adding prefix-based lookup
-  // to Symbols.
+  // to Class::Lookup*.
   return String::Concat(Symbols::SetterPrefix(), field_name);
 }
 
 
 RawString* Field::SetterSymbol(const String& field_name) {
-  const String& str = String::Handle(Field::SetterName(field_name));
-  return Symbols::New(str);
+  return Symbols::FromConcat(Symbols::SetterPrefix(), field_name);
 }
 
 
@@ -16086,6 +16084,8 @@
   void Add(int32_t ch) {
     hash_ = CombineHashes(hash_, ch);
   }
+  void Add(const String& str, intptr_t begin_index, intptr_t len);
+
   // Return a non-zero hash of at most 'bits' bits.
   intptr_t Finalize(int bits) {
     ASSERT(1 <= bits && bits <= (kBitsPerWord - 1));
@@ -16099,25 +16099,46 @@
 };
 
 
-intptr_t String::Hash(const String& str, intptr_t begin_index, intptr_t len) {
+void StringHasher::Add(const String& str, intptr_t begin_index, intptr_t len) {
   ASSERT(begin_index >= 0);
   ASSERT(len >= 0);
   ASSERT((begin_index + len) <= str.Length());
-  StringHasher hasher;
   if (str.IsOneByteString()) {
     for (intptr_t i = 0; i < len; i++) {
-      hasher.Add(*OneByteString::CharAddr(str, i + begin_index));
+      Add(*OneByteString::CharAddr(str, i + begin_index));
     }
   } else {
-    CodePointIterator it(str, begin_index, len);
+    String::CodePointIterator it(str, begin_index, len);
     while (it.Next()) {
-      hasher.Add(it.Current());
+      Add(it.Current());
     }
   }
+}
+
+
+intptr_t String::Hash(const String& str, intptr_t begin_index, intptr_t len) {
+  StringHasher hasher;
+  hasher.Add(str, begin_index, len);
   return hasher.Finalize(String::kHashBits);
 }
 
 
+intptr_t String::HashConcat(const String& str1, const String& str2) {
+  intptr_t len1 = str1.Length();
+  // Since String::Hash works at the code point (rune) level, a surrogate pair
+  // that crosses the boundary between str1 and str2 must be composed.
+  if (str1.IsTwoByteString() && Utf16::IsLeadSurrogate(str1.CharAt(len1 - 1))) {
+    const String& temp = String::Handle(String::Concat(str1, str2));
+    return temp.Hash();
+  } else {
+    StringHasher hasher;
+    hasher.Add(str1, 0, len1);
+    hasher.Add(str2, 0, str2.Length());
+    return hasher.Finalize(String::kHashBits);
+  }
+}
+
+
 template<typename T>
 static intptr_t HashImpl(const T* characters, intptr_t len) {
   ASSERT(len >= 0);
@@ -16294,6 +16315,13 @@
 }
 
 
+bool String::EqualsConcat(const String& str1, const String& str2) const {
+  return (Length() == str1.Length() + str2.Length()) &&
+    str1.Equals(*this, 0, str1.Length()) &&
+    str2.Equals(*this, str1.Length(), str2.Length());
+}
+
+
 intptr_t String::CompareTo(const String& other) const {
   const intptr_t this_len = this->Length();
   const intptr_t other_len = other.IsNull() ? 0 : other.Length();
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 57bacda..f7fde3f 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -5402,6 +5402,9 @@
     return result;
   }
 
+  // Returns the hash of str1 + str2.
+  static intptr_t HashConcat(const String& str1, const String& str2);
+
   virtual RawObject* HashCode() const { return Integer::New(Hash()); }
 
   int32_t CharAt(intptr_t index) const;
@@ -5429,6 +5432,9 @@
   // Compares to an array of UTF-32 encoded characters.
   bool Equals(const int32_t* characters, intptr_t len) const;
 
+  // True iff this string equals str1 + str2.
+  bool EqualsConcat(const String& str1, const String& str2) const;
+
   virtual bool OperatorEquals(const Instance& other) const {
     return Equals(other);
   }
@@ -5627,6 +5633,7 @@
   friend class Symbols;
   friend class StringSlice;  // SetHash
   template<typename CharType> friend class CharArray;  // SetHash
+  friend class ConcatString;  // SetHash
   friend class OneByteString;
   friend class TwoByteString;
   friend class ExternalOneByteString;
@@ -5761,6 +5768,7 @@
   friend class String;
   friend class ExternalOneByteString;
   friend class SnapshotReader;
+  friend class StringHasher;
 };
 
 
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index 42b39ad..6833efe 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -1336,6 +1336,22 @@
 }
 
 
+TEST_CASE(StringHashConcat) {
+  EXPECT_EQ(String::Handle(String::New("onebyte")).Hash(),
+            String::HashConcat(String::Handle(String::New("one")),
+                               String::Handle(String::New("byte"))));
+  uint16_t clef_utf16[] = { 0xD834, 0xDD1E };
+  const String& clef = String::Handle(String::FromUTF16(clef_utf16, 2));
+  int32_t clef_utf32[] = { 0x1D11E };
+  EXPECT(clef.Equals(clef_utf32, 1));
+  intptr_t hash32 = String::Hash(clef_utf32, 1);
+  EXPECT_EQ(hash32, clef.Hash());
+  EXPECT_EQ(hash32, String::HashConcat(
+      String::Handle(String::FromUTF16(clef_utf16, 1)),
+      String::Handle(String::FromUTF16(clef_utf16 + 1, 1))));
+}
+
+
 TEST_CASE(StringSubStringDifferentWidth) {
   // Create 1-byte substring from a 1-byte source string.
   const char* onechars =
diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc
index 7812645..4d0e741 100644
--- a/runtime/vm/parser.cc
+++ b/runtime/vm/parser.cc
@@ -2379,7 +2379,9 @@
         ASSERT(IsIdentifier());
         ConsumeToken();
         ExpectToken(Token::kASSIGN);
-        if (field.is_const()) {
+        if (current_class().is_const()) {
+          // If the class has a const contructor, the initializer
+          // expression must be a compile-time constant.
           init_expr = ParseConstExpr();
         } else {
           intptr_t expr_pos = TokenPos();
diff --git a/runtime/vm/symbols.cc b/runtime/vm/symbols.cc
index 627fde6..401a5c2 100644
--- a/runtime/vm/symbols.cc
+++ b/runtime/vm/symbols.cc
@@ -170,6 +170,30 @@
 }
 
 
+class ConcatString {
+ public:
+  ConcatString(const String& str1, const String& str2)
+      : str1_(str1), str2_(str2), hash_(String::HashConcat(str1, str2)) {}
+  RawString* ToSymbol() const;
+  bool Equals(const String& other) const {
+    return other.EqualsConcat(str1_, str2_);
+  }
+  intptr_t Hash() const { return hash_; }
+ private:
+  const String& str1_;
+  const String& str2_;
+  intptr_t hash_;
+};
+
+
+RawString* ConcatString::ToSymbol() const {
+  String& result = String::Handle(String::Concat(str1_, str2_, Heap::kOld));
+  result.SetCanonical();
+  result.SetHash(hash_);
+  return result.raw();
+}
+
+
 class SymbolTraits {
  public:
   static bool IsMatch(const Object& a, const Object& b) {
@@ -182,6 +206,9 @@
   static bool IsMatch(const StringSlice& slice, const Object& obj) {
     return slice.Equals(String::Cast(obj));
   }
+  static bool IsMatch(const ConcatString& concat, const Object& obj) {
+    return concat.Equals(String::Cast(obj));
+  }
   static uword Hash(const Object& key) {
     return String::Cast(key).Hash();
   }
@@ -192,6 +219,9 @@
   static uword Hash(const StringSlice& slice) {
     return slice.Hash();
   }
+  static uword Hash(const ConcatString& concat) {
+    return concat.Hash();
+  }
   template<typename CharType>
   static RawObject* NewKey(const CharArray<CharType>& array) {
     return array.ToSymbol();
@@ -199,6 +229,9 @@
   static RawObject* NewKey(const StringSlice& slice) {
     return slice.ToSymbol();
   }
+  static RawObject* NewKey(const ConcatString& concat) {
+    return concat.ToSymbol();
+  }
 };
 typedef UnorderedHashSet<SymbolTraits> SymbolTable;
 
@@ -278,7 +311,12 @@
 }
 
 
-// StringType can be StringSlice, Latin1Array, UTF16Array or UTF32Array.
+RawString* Symbols::FromConcat(const String& str1, const String& str2) {
+  return NewSymbol(ConcatString(str1, str2));
+}
+
+
+// StringType can be StringSlice, ConcatString, or {Latin1,UTF16,UTF32}Array.
 template<typename StringType>
 RawString* Symbols::NewSymbol(const StringType& str) {
   Isolate* isolate = Isolate::Current();
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 319853f..f061c3c 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -475,6 +475,8 @@
                         intptr_t begin_index,
                         intptr_t length);
 
+  static RawString* FromConcat(const String& str1, const String& str2);
+
   // Returns char* of predefined symbol.
   static const char* Name(SymbolId symbol);
 
diff --git a/runtime/vm/vm_sources.gypi b/runtime/vm/vm_sources.gypi
index c77824d..f7a5088 100644
--- a/runtime/vm/vm_sources.gypi
+++ b/runtime/vm/vm_sources.gypi
@@ -182,6 +182,9 @@
     'flow_graph_inliner.h',
     'flow_graph_optimizer.cc',
     'flow_graph_optimizer.h',
+    'flow_graph_range_analysis.cc',
+    'flow_graph_range_analysis.h',
+    'flow_graph_range_analysis_test.cc',
     'flow_graph_type_propagator.cc',
     'flow_graph_type_propagator.h',
     'freelist.cc',
diff --git a/sdk/lib/_internal/compiler/implementation/closure.dart b/sdk/lib/_internal/compiler/implementation/closure.dart
index 1661366..5ad9384 100644
--- a/sdk/lib/_internal/compiler/implementation/closure.dart
+++ b/sdk/lib/_internal/compiler/implementation/closure.dart
@@ -440,12 +440,12 @@
   // List of encountered closures.
   List<Expression> closures = <Expression>[];
 
-  // The variables that have been declared in the current scope.
-  List<Local> scopeVariables;
+  // The local variables that have been declared in the current scope.
+  List<LocalVariableElement> scopeVariables;
 
-  // Keep track of the mutated variables so that we don't need to box
+  // Keep track of the mutated local variables so that we don't need to box
   // non-mutated variables.
-  Set<VariableElement> mutatedVariables = new Set<VariableElement>();
+  Set<LocalVariableElement> mutatedVariables = new Set<LocalVariableElement>();
 
   MemberElement outermostElement;
   ExecutableElement executableContext;
@@ -601,7 +601,7 @@
     useLocal(new TypeVariableLocal(typeVariable, outermostElement));
   }
 
-  void declareLocal(Local element) {
+  void declareLocal(LocalVariableElement element) {
     scopeVariables.add(element);
   }
 
@@ -793,13 +793,9 @@
       }
     }
 
-    bool isAssignable(var variable) {
-      return variable is Element && variable.isAssignable;
-    }
-
-    for (Local variable in scopeVariables) {
+    for (LocalVariableElement variable in scopeVariables) {
       // No need to box non-assignable elements.
-      if (!isAssignable(variable)) continue;
+      if (!variable.isAssignable) continue;
       if (!mutatedVariables.contains(variable)) continue;
       boxCapturedVariable(variable);
     }
@@ -810,8 +806,8 @@
   }
 
   void inNewScope(Node node, Function action) {
-    List<Local> oldScopeVariables = scopeVariables;
-    scopeVariables = <Local>[];
+    List<LocalVariableElement> oldScopeVariables = scopeVariables;
+    scopeVariables = <LocalVariableElement>[];
     action();
     attachCapturedScopeVariables(node);
     mutatedVariables.removeAll(scopeVariables);
@@ -929,28 +925,6 @@
     closureMappingCache[node] = closureData;
 
     inNewScope(node, () {
-      // We have to declare the implicit 'this' parameter.
-      if (!insideClosure && closureData.thisLocal != null) {
-        declareLocal(closureData.thisLocal);
-      }
-      // If we are inside a named closure we have to declare ourselve. For
-      // simplicity we declare the local even if the closure does not have a
-      // name.
-      // It will simply not be used.
-      if (insideClosure) {
-        declareLocal(closure);
-      }
-
-      if (executableContext.isFactoryConstructor &&
-          compiler.backend.classNeedsRti(executableContext.enclosingElement)) {
-        // Declare the type parameters in the scope. Generative
-        // constructors just use 'this'.
-        ClassElement cls = executableContext.enclosingClass;
-        cls.typeVariables.forEach((TypeVariableType typeVariable) {
-          declareLocal(new TypeVariableLocal(typeVariable, executableContext));
-        });
-      }
-
       DartType type = element.type;
       // If the method needs RTI, or checked mode is set, we need to
       // escape the potential type variables used in that closure.
@@ -1000,12 +974,6 @@
     });
   }
 
-  visitFunctionDeclaration(FunctionDeclaration node) {
-    node.visitChildren(this);
-    LocalFunctionElement localFunction = elements[node];
-    declareLocal(localFunction);
-  }
-
   visitTryStatement(TryStatement node) {
     // TODO(ngeoffray): implement finer grain state.
     bool oldInTryStatement = inTryStatement;
diff --git a/sdk/lib/_internal/compiler/implementation/compilation_info.dart b/sdk/lib/_internal/compiler/implementation/compilation_info.dart
deleted file mode 100644
index 3732338..0000000
--- a/sdk/lib/_internal/compiler/implementation/compilation_info.dart
+++ /dev/null
@@ -1,66 +0,0 @@
-library dart2js.compilation_info;
-
-import 'dart2jslib.dart';
-import 'elements/elements.dart';
-import 'tree/tree.dart';
-
-
-abstract class CompilationInformation {
-  factory CompilationInformation(Enqueuer enqueuer, bool dumpInfoEnabled) {
-    if (dumpInfoEnabled) {
-      return new _CompilationInformation(enqueuer);
-    } else {
-      return new _EmptyCompilationInformation();
-    }
-  }
-
-  Map<Element, Set<Element>> get enqueuesMap;
-  Map<Element, Set<Element>> get addsToWorkListMap;
-
-  void enqueues(Element function, Element source) {}
-  void addsToWorkList(Element context, Element element) {}
-  void registerCallSite(TreeElements context, Send node) {}
-}
-
-class _EmptyCompilationInformation implements CompilationInformation {
-  _EmptyCompilationInformation();
-  Map<Element, Set<Element>> get enqueuesMap => <Element, Set<Element>>{};
-  Map<Element, Set<Element>> get addsToWorkListMap => <Element, Set<Element>>{};
-
-  void enqueues(Element function, Element source) {}
-  void addsToWorkList(Element context, Element element) {}
-  void registerCallSite(TreeElements context, Send node) {}
-}
-
-
-class _CompilationInformation implements CompilationInformation {
-  final String prefix;
-
-  final Map<Element, Set<Element>> enqueuesMap = {};
-  final Map<Element, Set<Element>> addsToWorkListMap = {};
-
-  _CompilationInformation(Enqueuer enqueuer)
-    : prefix = enqueuer.isResolutionQueue ? 'resolution' : 'codegen';
-
-  Set<CallSite> callSites = new Set<CallSite>();
-
-  enqueues(Element function, Element source) {
-    enqueuesMap.putIfAbsent(function, () => new Set())
-    .add(source);
-  }
-
-  addsToWorkList(Element context, Element element) {
-    addsToWorkListMap.putIfAbsent(context, () => new Set())
-    .add(element);
-  }
-
-  registerCallSite(TreeElements context, Send node) {
-    callSites.add(new CallSite(context, node));
-  }
-}
-
-class CallSite {
-  final TreeElements context;
-  final Send node;
-  CallSite(this.context, this.node);
-}
diff --git a/sdk/lib/_internal/compiler/implementation/compiler.dart b/sdk/lib/_internal/compiler/implementation/compiler.dart
index 3b3cdcd..e84dd7ee 100644
--- a/sdk/lib/_internal/compiler/implementation/compiler.dart
+++ b/sdk/lib/_internal/compiler/implementation/compiler.dart
@@ -84,15 +84,18 @@
   }
 
   void registerDynamicInvocation(Selector selector) {
-    world.registerDynamicInvocation(currentElement, selector);
+    world.registerDynamicInvocation(selector);
+    compiler.dumpInfoTask.elementUsesSelector(currentElement, selector);
   }
 
   void registerDynamicSetter(Selector selector) {
-    world.registerDynamicSetter(currentElement, selector);
+    world.registerDynamicSetter(selector);
+    compiler.dumpInfoTask.elementUsesSelector(currentElement, selector);
   }
 
   void registerDynamicGetter(Selector selector) {
-    world.registerDynamicGetter(currentElement, selector);
+    world.registerDynamicGetter(selector);
+    compiler.dumpInfoTask.elementUsesSelector(currentElement, selector);
   }
 
   void registerGetterForSuperMethod(Element element) {
@@ -131,7 +134,7 @@
   }
 
   void registerSelectorUse(Selector selector) {
-    world.registerSelectorUse(currentElement, selector);
+    world.registerSelectorUse(selector);
   }
 
   void registerFactoryWithTypeArguments() {
@@ -313,7 +316,7 @@
    * backend.
    */
   void enableNoSuchMethod(Element context, Enqueuer enqueuer) {
-    enqueuer.registerInvocation(null, compiler.noSuchMethodSelector);
+    enqueuer.registerInvocation(compiler.noSuchMethodSelector);
   }
 
   /// Call this method to enable support for isolates.
@@ -1647,10 +1650,6 @@
 
   void reportWarning(Spannable node, MessageKind messageKind,
                      [Map arguments = const {}]) {
-    // TODO(ahe): Don't suppress these warning when the type checker
-    // is more complete.
-    if (messageKind == MessageKind.MISSING_RETURN) return;
-    if (messageKind == MessageKind.MAYBE_MISSING_RETURN) return;
     reportDiagnosticInternal(
         node, messageKind, arguments, api.Diagnostic.WARNING);
   }
diff --git a/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_builder.dart b/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_builder.dart
index d02c786..8de6518 100644
--- a/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_builder.dart
+++ b/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_builder.dart
@@ -77,8 +77,6 @@
             nodes[element] = function;
             compiler.tracer.traceCompilation(element.name, null, compiler);
             compiler.tracer.traceGraph("IR Builder", function);
-
-            new ir.RegisterAllocator().visit(function);
           }
         }
       });
@@ -140,6 +138,64 @@
 }
 
 /**
+ *
+ */
+class Environment {
+  final Map<Element, int> variable2index;
+  final List<Element> index2variable;
+  final List<ir.Primitive> index2value;
+
+  Environment.empty()
+      : variable2index = <Element, int>{},
+        index2variable = <Element>[],
+        index2value = <ir.Primitive>[];
+
+  Environment.from(Environment other)
+      : variable2index = other.variable2index,
+        index2variable = new List<Element>.from(other.index2variable),
+        index2value = new List<ir.Primitive>.from(other.index2value);
+
+  get length => index2variable.length;
+
+  ir.Primitive operator [](int index) => index2value[index];
+
+  void extend(Element element, ir.Primitive value) {
+    assert(!variable2index.containsKey(element));
+    variable2index[element] = index2variable.length;
+    index2variable.add(element);
+    index2value.add(value);
+  }
+
+  ir.Primitive lookup(Element element) {
+    assert(!element.isConst);
+    return index2value[variable2index[element]];
+  }
+
+  void update(Element element, ir.Primitive value) {
+    index2value[variable2index[element]] = value;
+  }
+
+  /// Verify that the variable2index and index2variable maps agree up to the
+  /// index [length] exclusive.
+  bool sameDomain(int length, Environment other) {
+    assert(this.length >= length);
+    assert(other.length >= length);
+    for (int i = 0; i < length; ++i) {
+      // An index maps to the same variable in both environments.
+      Element variable = index2variable[i];
+      if (variable != other.index2variable[i]) return false;
+
+      // The variable maps to the same index in both environments.
+      int index = variable2index[variable];
+      if (index == null || index != other.variable2index[variable]) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+/**
  * A tree visitor that builds [IrNodes]. The visit methods add statements using
  * to the [builder] and return the last added statement for trees that represent
  * an expression.
@@ -193,10 +249,9 @@
   // that is, the definition in effect at the hole in 'current'.  These are
   // used to determine if a join-point continuation needs to be passed
   // arguments, and what the arguments are.
-  final Map<Element, int> variableIndex;
-  final List<Element> index2variable;
-  final List<ir.Parameter> freeVars;
-  final List<ir.Primitive> assignedVars;
+
+  /// A map from variable indexes to their values.
+  Environment environment;
 
   ConstExpBuilder constantBuilder;
 
@@ -209,34 +264,55 @@
   IrBuilder(TreeElements elements, Compiler compiler, this.sourceFile)
       : returnContinuation = new ir.Continuation.retrn(),
         parameters = <ir.Parameter>[],
-        variableIndex = <Element, int>{},
-        freeVars = null,
-        assignedVars = <ir.Primitive>[],
-        index2variable = <Element>[],
+        environment = new Environment.empty(),
         localConstants = <ConstDeclaration>[],
         closureLocals = new DetectClosureVariables(elements),
         super(elements, compiler) {
-          constantBuilder = new ConstExpBuilder(this);
-        }
+    constantBuilder = new ConstExpBuilder(this);
+  }
 
-  /// Construct a delimited visitor.
+  /// Construct a delimited visitor for visiting a subtree.
+  ///
+  /// The delimited visitor has its own compile-time environment mapping
+  /// local variables to their values, which is initially a copy of the parent
+  /// environment.  It has its own context for building an IR expression, so
+  /// the built expression is not plugged into the parent's context.
   IrBuilder.delimited(IrBuilder parent)
       : sourceFile = parent.sourceFile,
         returnContinuation = parent.returnContinuation,
-        parameters = parent.parameters,
-        variableIndex = parent.variableIndex,
-        freeVars = new List<ir.Parameter>.generate(
-            parent.assignedVars.length, (_) => new ir.Parameter(null),
-            growable: false),
-        assignedVars = new List<ir.Primitive>.generate(
-            parent.assignedVars.length, (_) => null),
-        index2variable = new List<Element>.from(parent.index2variable),
+        parameters = <ir.Parameter>[],
+        environment = new Environment.from(parent.environment),
         constantBuilder = parent.constantBuilder,
         localConstants = parent.localConstants,
         currentFunction = parent.currentFunction,
         closureLocals = parent.closureLocals,
         super(parent.elements, parent.compiler);
 
+  /// Construct a visitor for a recursive continuation.
+  ///
+  /// The recursive continuation builder has fresh parameters (i.e. SSA phis)
+  /// for all the local variables in the parent, because the invocation sites
+  /// of the continuation are not all known when the builder is created.  The
+  /// recursive invocations will be passed values for all the local variables,
+  /// which may be eliminated later if they are redundant---if they take on
+  /// the same value at all invocation sites.
+  IrBuilder.recursive(IrBuilder parent)
+      : sourceFile = parent.sourceFile,
+        returnContinuation = parent.returnContinuation,
+        parameters = <ir.Parameter>[],
+        environment = new Environment.empty(),
+        constantBuilder = parent.constantBuilder,
+        localConstants = parent.localConstants,
+        currentFunction = parent.currentFunction,
+        closureLocals = parent.closureLocals,
+        super(parent.elements, parent.compiler) {
+    for (Element element in parent.environment.index2variable) {
+      ir.Parameter parameter = new ir.Parameter(element);
+      parameters.add(parameter);
+      environment.extend(element, parameter);
+    }
+  }
+
   /**
    * Builds the [ir.FunctionDefinition] for a function element. In case the
    * function uses features that cannot be expressed in the IR, this function
@@ -265,9 +341,7 @@
       if (isClosureVariable(parameterElement)) {
         add(new ir.SetClosureVariable(parameterElement, parameter));
       } else {
-        variableIndex[parameterElement] = assignedVars.length;
-        assignedVars.add(parameter);
-        index2variable.add(parameterElement);
+        environment.extend(parameterElement, parameter);
       }
     });
 
@@ -353,181 +427,121 @@
     return null;
   }
 
-  /// Create branch join continuation parameters and fill in arguments.
+
+  /// Create a non-recursive join-point continuation.
   ///
-  /// Given delimited builders for the arms of a branch, return a list of
-  /// fresh join-point continuation parameters for the join continuation.
-  /// Fill in [leftArguments] and [rightArguments] with the left and right
-  /// continuation invocation arguments.
-  List<ir.Parameter> createBranchJoinParametersAndFillArguments(
-      IrBuilder leftBuilder,
-      IrBuilder rightBuilder,
-      List<ir.Primitive> leftArguments,
-      List<ir.Primitive> rightArguments) {
-    // The sets of free and assigned variables for a delimited builder is
-    // initially the length of the assigned variables of the parent.  The free
-    // variables cannot grow because there cannot be free occurrences of
-    // variables that were not declared before the entrance to the delimited
-    // subgraph.  The assigned variables can grow when new variables are
-    // declared in the delimited graph, but we only inspect the prefix
-    // corresponding to the parent's declared variables.
-    assert(leftBuilder.isOpen);
-    assert(rightBuilder.isOpen);
-    assert(assignedVars.length <= leftBuilder.assignedVars.length);
-    assert(assignedVars.length <= rightBuilder.assignedVars.length);
-
-    List<ir.Parameter> parameters = <ir.Parameter>[];
-    // If a variable was assigned on either the left or the right (and control
-    // flow reaches the end of the corresponding subterm) then the variable has
-    // different values reaching the join point and needs to be passed as an
-    // argument to the join point continuation.
-    for (int i = 0; i < assignedVars.length; ++i) {
-      // The last assignments, if any, reaching the end of the two subterms.
-      ir.Primitive leftAssignment = leftBuilder.assignedVars[i];
-      ir.Primitive rightAssignment = rightBuilder.assignedVars[i];
-
-      if (leftAssignment != null || rightAssignment != null) {
-        // The corresponsing argument is the reaching definition if any, or a
-        // free occurrence.  In the case that control does not reach both the
-        // left and right subterms we will still have a join continuation with
-        // possibly arguments passed to it.  Such singly-used continuations
-        // are eliminated by the shrinking conversions.
-        parameters.add(new ir.Parameter(index2variable[i]));
-        ir.Primitive reachingDefinition =
-            assignedVars[i] == null ? freeVars[i] : assignedVars[i];
-        leftArguments.add(
-            leftAssignment == null ? reachingDefinition : leftAssignment);
-        rightArguments.add(
-            rightAssignment == null ? reachingDefinition : rightAssignment);
-      }
-    }
-    return parameters;
-  }
-
-  /// Allocate loop join continuation parameters and fill in arguments.
+  /// Given the environment length at the join point and a list of open
+  /// contexts that should reach the join point, create a join-point
+  /// continuation.  The join-point continuation has a parameter for each
+  /// variable that has different values reaching on different paths.
   ///
-  /// Given delimited builders for a test at the top (while, for, or for-in)
-  /// loop's condition and for the loop body, return a list of fresh
-  /// join-point continuation parameters for the loop join.  Fill in
-  /// [entryArguments] with the arguments to the non-recursive continuation
-  /// invocation and [loopArguments] with the arguments to the recursive
-  /// continuation invocation.
+  /// The holes in the contexts are filled with [ir.InvokeContinuation]
+  /// expressions passing the appropriate arguments.
   ///
-  /// The [bodyBuilder] is assumed to be open, otherwise there is no join
-  /// necessary.
-  List<ir.Parameter> createLoopJoinParametersAndFillArguments(
-      IrBuilder conditionBuilder,
-      IrBuilder bodyBuilder,
-      List<ir.Primitive> entryArguments,
-      List<ir.Primitive> loopArguments) {
-    assert(bodyBuilder.isOpen);
-    // The loop condition and body are delimited --- assignedVars are still
-    // those reaching the entry to the loop.
-    assert(assignedVars.length == conditionBuilder.freeVars.length);
-    assert(assignedVars.length == bodyBuilder.freeVars.length);
-    assert(assignedVars.length <= conditionBuilder.assignedVars.length);
-    assert(assignedVars.length <= bodyBuilder.assignedVars.length);
+  /// As a side effect, the environment of this builder is updated to include
+  /// the join-point continuation parameters.
+  ir.Continuation createJoin(int environmentLength,
+                             List<IrBuilder> contexts) {
+    assert(contexts.length >= 2);
 
-    List<ir.Parameter> parameters = <ir.Parameter>[];
-    // When the free variables in the loop body are computed later, the
-    // parameters are assumed to appear in the same order as they appear in
-    // the assignedVars list.
-    for (int i = 0; i < assignedVars.length; ++i) {
-      // Was there an assignment in the body?
-      ir.Definition reachingAssignment = bodyBuilder.assignedVars[i];
-      // If not, was there an assignment in the condition?
-      if (reachingAssignment == null) {
-        reachingAssignment = conditionBuilder.assignedVars[i];
-      }
-      // If not, no value needs to be passed to the join point.
-      if (reachingAssignment == null) continue;
-
-      parameters.add(new ir.Parameter(index2variable[i]));
-      ir.Definition entryAssignment = assignedVars[i];
-      entryArguments.add(
-          entryAssignment == null ? freeVars[i] : entryAssignment);
-      loopArguments.add(reachingAssignment);
-    }
-    return parameters;
-  }
-
-  /// Capture free variables in the arms of a branch.
-  ///
-  /// Capture the free variables in the left and right arms of a conditional
-  /// branch.  The free variables are captured by the current definition.
-  /// Also update the builder's assigned variables to be those reaching the
-  /// branch join.  If there is no join, [parameters] should be `null` and
-  /// at least one of [leftBuilder] or [rightBuilder] should not be open.
-  void captureFreeBranchVariables(IrBuilder leftBuilder,
-                                  IrBuilder rightBuilder,
-                                  List<ir.Parameter> parameters) {
-    // Parameters is non-null when there is a join, if and only if both left
-    // and right subterm contexts are open.
-    assert((leftBuilder.isOpen && rightBuilder.isOpen) ==
-           (parameters != null));
-    int parameterIndex = 0;
-    for (int i = 0; i < assignedVars.length; ++i) {
-      // This is the definition that reaches the left and right subterms.  All
-      // free uses in either term are uses of this definition.
-      ir.Primitive reachingDefinition =
-          assignedVars[i] == null ? freeVars[i] : assignedVars[i];
-      reachingDefinition
-          ..substituteFor(leftBuilder.freeVars[i])
-          ..substituteFor(rightBuilder.freeVars[i]);
-
-      // Also add join continuation parameters as assignments for the join
-      // body.  This is done last because the assigned variables are updated
-      // in place.
-      ir.Primitive leftAssignment = leftBuilder.assignedVars[i];
-      ir.Primitive rightAssignment = rightBuilder.assignedVars[i];
-      if (parameters != null) {
-        if (leftAssignment != null || rightAssignment != null) {
-          assignedVars[i] = parameters[parameterIndex++];
+    // Compute which values are identical on all paths reaching the join.
+    // Handle the common case of a pair of contexts efficiently.
+    Environment first = contexts[0].environment;
+    Environment second = contexts[1].environment;
+    assert(environmentLength <= first.length);
+    assert(environmentLength <= second.length);
+    assert(first.sameDomain(environmentLength, second));
+    // A running count of the join-point parameters.
+    int parameterCount = 0;
+    // The null elements of common correspond to required parameters of the
+    // join-point continuation.
+    List<ir.Primitive> common =
+        new List<ir.Primitive>.generate(environmentLength,
+            (i) {
+              ir.Primitive candidate = first[i];
+              if (second[i] == candidate) {
+                return candidate;
+              } else {
+                ++parameterCount;
+                return null;
+              }
+            });
+    // If there is already a parameter for each variable, the other
+    // environments do not need to be considered.
+    if (parameterCount < environmentLength) {
+      for (int i = 0; i < environmentLength; ++i) {
+        ir.Primitive candidate = common[i];
+        if (candidate == null) continue;
+        for (IrBuilder current in contexts.skip(2)) {
+          assert(environmentLength <= current.environment.length);
+          assert(first.sameDomain(environmentLength, current.environment));
+          if (candidate != current.environment[i]) {
+            common[i] = null;
+            ++parameterCount;
+            break;
+          }
         }
-      } else if (leftBuilder.isOpen) {
-        if (leftAssignment != null) assignedVars[i] = leftAssignment;
-      } else if (rightBuilder.isOpen) {
-        if (rightAssignment != null) assignedVars[i] = rightAssignment;
+        if (parameterCount >= environmentLength) break;
       }
     }
+
+    List<ir.Parameter> parameters = <ir.Parameter>[];
+    parameters.length = parameterCount;
+    int index = 0;
+    for (int i = 0; i < environmentLength; ++i) {
+      if (common[i] == null) {
+        ir.Parameter parameter = new ir.Parameter(first.index2variable[i]);
+        parameters[index++] = parameter;
+        if (i < environment.length) {
+          // The variable is bound to the join-point parameter in the
+          // continuation.  Variables outside the range of the environment are
+          // 'phantom' variables used for the values of expressions like
+          // &&, ||, and ?:.
+          environment.index2value[i] = parameter;
+        }
+      }
+    }
+    assert(index == parameterCount);
+    ir.Continuation join = new ir.Continuation(parameters);
+
+    // Plug all the contexts with continuation invocations.
+    for (IrBuilder context in contexts) {
+      // Passing `this` as one of the contexts will not do the right thing
+      // (this.environment has already been mutated).
+      assert(context != this);
+      List<ir.Primitive> arguments = <ir.Primitive>[];
+      arguments.length = parameterCount;
+      int index = 0;
+      for (int i = 0; i < environmentLength; ++i) {
+        if (common[i] == null) {
+          arguments[index++] = context.environment[i];
+        }
+      }
+      context.add(new ir.InvokeContinuation(join, arguments));
+      context.current = null;
+    }
+
+    return join;
   }
 
-  /// Capture free variables in a test at the top loop.
+  /// Invoke a recursive join-point continuation.
   ///
-  /// Capture the free variables in the condition and the body of a test at
-  /// the top loop (e.g., while, for, or for-in).  Also updates the
-  /// builder's assigned variables to be those reaching the loop successor
-  /// statement.
-  void captureFreeLoopVariables(IrBuilder condBuilder,
-                                IrBuilder bodyBuilder,
-                                List<ir.Parameter> parameters) {
-    // Capturing loop-body variables differs from capturing variables for
-    // the predecessors of a non-recursive join-point continuation.  The
-    // join point continuation parameters are in scope for the condition
-    // and body in the case of a loop.
-    int parameterIndex = 0;
-    // The parameters are assumed to be in the same order as the corresponding
-    // variables appear in the assignedVars list.
-    for (int i = 0; i < assignedVars.length; ++i) {
-      // Add recursive join continuation parameters as assignments for the
-      // join body, if there is a join continuation (parameters != null).
-      // This is done first because free occurrences in the loop should be
-      // captured by the join continuation parameters.
-      if (parameters != null &&
-          (condBuilder.assignedVars[i] != null ||
-           bodyBuilder.assignedVars[i] != null)) {
-        assignedVars[i] = parameters[parameterIndex++];
-      }
-      ir.Definition reachingDefinition =
-            assignedVars[i] == null ? freeVars[i] : assignedVars[i];
-      // Free variables in the body can be captured by assignments in the
-      // condition.
-      if (condBuilder.assignedVars[i] == null) {
-        reachingDefinition.substituteFor(bodyBuilder.freeVars[i]);
-      } else {
-        condBuilder.assignedVars[i].substituteFor(bodyBuilder.freeVars[i]);
-      }
-      reachingDefinition.substituteFor(condBuilder.freeVars[i]);
+  /// Given the continuation and a list of open contexts that should contain
+  /// recursive invocations, plug each context with an invocation of the
+  /// continuation.
+  void invokeRecursiveJoin(ir.Continuation join, List<IrBuilder> contexts) {
+    for (IrBuilder context in contexts) {
+      // Passing `this` as one of the contexts will not do the right thing
+      // (this.environment has already been mutated).
+      assert(context != this);
+      List<ir.Primitive> args =
+          context.environment.index2value.sublist(0, join.parameters.length);
+      context.add(new ir.InvokeContinuation(join, args, recursive: true));
+      context.current = null;
+    }
+    assert(environment.index2value.length <= join.parameters.length);
+    for (int i = 0; i < environment.index2value.length; ++i) {
+      environment.index2value[i] = join.parameters[i];
     }
   }
 
@@ -558,17 +572,17 @@
 
     if (node.initializer != null) visit(node.initializer);
 
-    // If the condition is empty then the body is entered unconditionally.
-    IrBuilder condBuilder = new IrBuilder.delimited(this);
+    IrBuilder condBuilder = new IrBuilder.recursive(this);
     ir.Primitive condition;
     if (node.condition == null) {
+      // If the condition is empty then the body is entered unconditionally.
       condition = makePrimConst(constantSystem.createBool(true));
       condBuilder.add(new ir.LetPrim(condition));
     } else {
       condition = condBuilder.visit(node.condition);
     }
 
-    IrBuilder bodyBuilder = new IrBuilder.delimited(this);
+    IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
     bodyBuilder.visit(node.body);
     for (ast.Node n in node.update) {
       if (!bodyBuilder.isOpen) break;
@@ -582,35 +596,25 @@
     condBuilder.add(new ir.Branch(new ir.IsTrue(condition),
                                   bodyContinuation,
                                   exitContinuation));
-    ir.Continuation loopContinuation;
-    List<ir.Parameter> parameters;
-    List<ir.Primitive> entryArguments = <ir.Primitive>[];
+    List<ir.Parameter> parameters = condBuilder.parameters;
+    ir.Continuation loopContinuation = new ir.Continuation(parameters);
+    // Copy the environment here because invokeJoin will update it for the
+    // join-point continuation.
+    List<ir.Primitive> entryArguments =
+        new List<ir.Primitive>.from(environment.index2value);
     if (bodyBuilder.isOpen) {
-      List<ir.Primitive> loopArguments = <ir.Primitive>[];
-      parameters =
-          createLoopJoinParametersAndFillArguments(
-              condBuilder, bodyBuilder, entryArguments, loopArguments);
-      loopContinuation = new ir.Continuation(parameters);
-      bodyBuilder.add(
-          new ir.InvokeContinuation(loopContinuation, loopArguments,
-                                    recursive:true));
+      invokeRecursiveJoin(loopContinuation, [bodyBuilder]);
     }
     bodyContinuation.body = bodyBuilder.root;
 
-    captureFreeLoopVariables(condBuilder, bodyBuilder, parameters);
-
     ir.Expression resultContext =
         new ir.LetCont(exitContinuation,
             new ir.LetCont(bodyContinuation,
                 condBuilder.root));
-    if (loopContinuation != null) {
-      loopContinuation.body = resultContext;
-      add(new ir.LetCont(loopContinuation,
-              new ir.InvokeContinuation(loopContinuation, entryArguments)));
-      current = resultContext;
-    } else {
-      add(resultContext);
-    }
+    loopContinuation.body = resultContext;
+    add(new ir.LetCont(loopContinuation,
+            new ir.InvokeContinuation(loopContinuation, entryArguments)));
+    current = resultContext;
     return null;
   }
 
@@ -632,38 +636,22 @@
     ir.Continuation elseContinuation = new ir.Continuation([]);
     ir.Expression letElse =
         new ir.LetCont(elseContinuation,
-                       new ir.Branch(new ir.IsTrue(condition),
-                                     thenContinuation,
-                                     elseContinuation));
+          new ir.Branch(new ir.IsTrue(condition),
+                        thenContinuation,
+                        elseContinuation));
     ir.Expression letThen = new ir.LetCont(thenContinuation, letElse);
     ir.Expression result = letThen;
 
-    List<ir.Parameter> parameters;  // Null if there is no join.
+    ir.Continuation joinContinuation;  // Null if there is no join.
     if (thenBuilder.isOpen && elseBuilder.isOpen) {
       // There is a join-point continuation.  Build the term
       // 'let cont join(x, ...) = [] in Result' and plug invocations of the
       // join-point continuation into the then and else continuations.
-      List<ir.Primitive> thenArguments = <ir.Primitive>[];
-      List<ir.Primitive> elseArguments = <ir.Primitive>[];
-
-      // Compute the join-point continuation parameters.  Fill in the
-      // arguments to the join-point continuation invocations.
-      parameters = createBranchJoinParametersAndFillArguments(
-          thenBuilder, elseBuilder, thenArguments, elseArguments);
-      ir.Continuation joinContinuation = new ir.Continuation(parameters);
-      thenBuilder.add(
-          new ir.InvokeContinuation(joinContinuation, thenArguments));
-      elseBuilder.add(
-          new ir.InvokeContinuation(joinContinuation, elseArguments));
+      joinContinuation =
+          createJoin(environment.length, [thenBuilder, elseBuilder]);
       result = new ir.LetCont(joinContinuation, result);
     }
 
-    // Capture free occurrences in the then and else bodies and update the
-    // assigned variables for the successor.  This is done after creating
-    // invocations of the join continuation so free join continuation
-    // arguments are properly captured.
-    captureFreeBranchVariables(thenBuilder, elseBuilder, parameters);
-
     // The then or else term root could be null, but not both.  If there is
     // a join then an InvokeContinuation was just added to both of them.  If
     // there is no join, then at least one of them is closed and thus has a
@@ -675,12 +663,14 @@
     elseContinuation.body = elseBuilder.root;
 
     add(result);
-    if (parameters == null) {
-      // At least one subter is closed.
+    if (joinContinuation == null) {
+      // At least one subexpression is closed.
       if (thenBuilder.isOpen) {
         current = (thenBuilder.root == null) ? letThen : thenBuilder.current;
+        environment = thenBuilder.environment;
       } else if (elseBuilder.isOpen) {
         current = (elseBuilder.root == null) ? letElse : elseBuilder.current;
+        environment = elseBuilder.environment;
       } else {
         current = null;
       }
@@ -692,7 +682,7 @@
     assert(isOpen);
     // While loops use three named continuations: the entry to the body,
     // the loop exit (break), and the loop back edge (continue).
-    // The CPS translation [[while (condition) body; successor]] is:
+    // The CPS translation of [[while (condition) body; successor]] is:
     //
     // let cont loop(x, ...) =
     //     let cont exit() = [[successor]] in
@@ -702,9 +692,10 @@
     // loop(v, ...)
 
     // The condition and body are delimited.
-    IrBuilder condBuilder = new IrBuilder.delimited(this);
-    IrBuilder bodyBuilder = new IrBuilder.delimited(this);
+    IrBuilder condBuilder = new IrBuilder.recursive(this);
     ir.Primitive condition = condBuilder.visit(node.condition);
+
+    IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
     bodyBuilder.visit(node.body);
 
     // Create body entry and loop exit continuations and a join-point
@@ -714,36 +705,25 @@
     condBuilder.add(new ir.Branch(new ir.IsTrue(condition),
                                   bodyContinuation,
                                   exitContinuation));
-    ir.Continuation loopContinuation;
-    List<ir.Parameter> parameters;
-    List<ir.Primitive> entryArguments = <ir.Primitive>[];  // The forward edge.
+    List<ir.Parameter> parameters = condBuilder.parameters;
+    ir.Continuation loopContinuation = new ir.Continuation(parameters);
+    // Copy the environment here because invokeJoin will update it for the
+    // join-point continuation.
+    List<ir.Primitive> entryArguments =
+        new List<ir.Primitive>.from(environment.index2value);
     if (bodyBuilder.isOpen) {
-      List<ir.Primitive> loopArguments = <ir.Primitive>[];  // The back edge.
-      parameters =
-          createLoopJoinParametersAndFillArguments(
-              condBuilder, bodyBuilder, entryArguments, loopArguments);
-      loopContinuation = new ir.Continuation(parameters);
-      bodyBuilder.add(
-          new ir.InvokeContinuation(loopContinuation, loopArguments,
-                                    recursive:true));
+      invokeRecursiveJoin(loopContinuation, [bodyBuilder]);
     }
     bodyContinuation.body = bodyBuilder.root;
 
-    // Capture free variable occurrences in the loop body.
-    captureFreeLoopVariables(condBuilder, bodyBuilder, parameters);
-
     ir.Expression resultContext =
         new ir.LetCont(exitContinuation,
             new ir.LetCont(bodyContinuation,
                 condBuilder.root));
-    if (loopContinuation != null) {
-      loopContinuation.body = resultContext;
-      add(new ir.LetCont(loopContinuation,
-              new ir.InvokeContinuation(loopContinuation, entryArguments)));
-      current = resultContext;
-    } else {
-      add(resultContext);
-    }
+    loopContinuation.body = resultContext;
+    add(new ir.LetCont(loopContinuation,
+            new ir.InvokeContinuation(loopContinuation, entryArguments)));
+    current = resultContext;
     return null;
   }
 
@@ -782,9 +762,7 @@
           // In case a primitive was introduced for the initializer expression,
           // use this variable element to help derive a good name for it.
           initialValue.useElementAsHint(element);
-          variableIndex[element] = assignedVars.length;
-          assignedVars.add(initialValue);
-          index2variable.add(element);
+          environment.extend(element, initialValue);
         }
       }
     }
@@ -822,38 +800,23 @@
     ir.Primitive thenValue = thenBuilder.visit(node.thenExpression);
     ir.Primitive elseValue = elseBuilder.visit(node.elseExpression);
 
-    // Compute the join-point continuation parameters.  Fill in the
-    // arguments to the join-point continuation invocations.
-    List<ir.Primitive> thenArguments = <ir.Primitive>[];
-    List<ir.Primitive> elseArguments = <ir.Primitive>[];
-    List<ir.Parameter> parameters =
-        createBranchJoinParametersAndFillArguments(
-            thenBuilder, elseBuilder, thenArguments, elseArguments);
-    // Add a continuation parameter for the result of the expression.
-    ir.Parameter resultParameter = new ir.Parameter(null);
-    parameters.add(resultParameter);
-    thenArguments.add(thenValue);
-    elseArguments.add(elseValue);
+    // Treat the values of the subexpressions as named values in the
+    // environment, so they will be treated as arguments to the join-point
+    // continuation.
+    assert(environment.length == thenBuilder.environment.length);
+    assert(environment.length == elseBuilder.environment.length);
+    thenBuilder.environment.extend(null, thenValue);
+    elseBuilder.environment.extend(null, elseValue);
+    ir.Continuation joinContinuation =
+        createJoin(environment.length + 1, [thenBuilder, elseBuilder]);
 
     // Build the term
     //   let cont join(x, ..., result) = [] in
     //   let cont then() = [[thenPart]]; join(v, ...) in
     //   let cont else() = [[elsePart]]; join(v, ...) in
     //     if condition (then, else)
-    ir.Continuation joinContinuation = new ir.Continuation(parameters);
     ir.Continuation thenContinuation = new ir.Continuation([]);
     ir.Continuation elseContinuation = new ir.Continuation([]);
-    thenBuilder.add(
-        new ir.InvokeContinuation(joinContinuation, thenArguments));
-    elseBuilder.add(
-        new ir.InvokeContinuation(joinContinuation, elseArguments));
-
-    // Capture free occurrences in the then and else bodies and update the
-    // assigned variables for the successor.  This is done after creating
-    // invocations of the join continuation so free join continuation
-    // arguments are properly captured.
-    captureFreeBranchVariables(thenBuilder, elseBuilder, parameters);
-
     thenContinuation.body = thenBuilder.root;
     elseContinuation.body = elseBuilder.root;
     add(new ir.LetCont(joinContinuation,
@@ -862,7 +825,9 @@
                     new ir.Branch(new ir.IsTrue(condition),
                                   thenContinuation,
                                   elseContinuation)))));
-    return resultParameter;
+    return (thenValue == elseValue)
+        ? thenValue
+        : joinContinuation.parameters.last;
   }
 
   // For all simple literals:
@@ -973,13 +938,6 @@
     return result;
   }
 
-  ir.Primitive lookupLocal(Element element) {
-    assert(!element.isConst);
-    int index = variableIndex[element];
-    ir.Primitive value = assignedVars[index];
-    return value == null ? freeVars[index] : value;
-  }
-
   // ==== Sends ====
   ir.Primitive visitAssert(ast.Send node) {
     assert(isOpen);
@@ -1024,7 +982,7 @@
       add(new ir.LetPrim(closureTarget));
     } else {
       assert(Elements.isLocal(element));
-      closureTarget = lookupLocal(element);
+      closureTarget = environment.lookup(element);
     }
     Selector closureSelector = elements.getSelector(node);
     return translateClosureCall(closureTarget, closureSelector,
@@ -1079,7 +1037,7 @@
       add(new ir.LetPrim(result));
     } else if (Elements.isLocal(element)) {
       // Reference to local variable
-      result = lookupLocal(element);
+      result = environment.lookup(element);
     } else if (element == null ||
                Elements.isInstanceField(element) ||
                Elements.isInstanceMethod(element) ||
@@ -1165,49 +1123,49 @@
     IrBuilder rightBuilder = new IrBuilder.delimited(this);
     ir.Primitive rightValue = rightBuilder.visit(right);
     // A dummy empty target for the branch on the left subexpression branch.
-    // This enables using the same infrastructure for continuation arguments
-    // and free variable capture as in visitIf and visitConditional.  It will
-    // hold an invocation of the join-point continuation.  It cannot have
-    // assigned variables but may have free variables as arguments to the
-    // join-point continuation.
+    // This enables using the same infrastructure for join-point continuations
+    // as in visitIf and visitConditional.  It will hold a definition of the
+    // appropriate constant and an invocation of the join-point continuation.
     IrBuilder emptyBuilder = new IrBuilder.delimited(this);
+    // Dummy empty targets for right true and right false.  They hold
+    // definitions of the appropriate constant and an invocation of the
+    // join-point continuation.
+    IrBuilder rightTrueBuilder = new IrBuilder.delimited(rightBuilder);
+    IrBuilder rightFalseBuilder = new IrBuilder.delimited(rightBuilder);
 
-    List <ir.Primitive> leftArguments = <ir.Primitive>[];
-    List <ir.Primitive> rightArguments = <ir.Primitive>[];
-    List <ir.Parameter> parameters =
-        createBranchJoinParametersAndFillArguments(
-            emptyBuilder, rightBuilder, leftArguments, rightArguments);
-
-    // Add a continuation parameter for the result of the expression.
-    ir.Parameter resultParameter = new ir.Parameter(null);
-    parameters.add(resultParameter);
     // If we don't evaluate the right subexpression, the value of the whole
     // expression is this constant.
-    ir.Constant leftBool =
-        makePrimConst(constantSystem.createBool(op.source == '||'));
-    leftArguments.add(leftBool);
+    ir.Constant leftBool = emptyBuilder.makePrimConst(
+        constantSystem.createBool(op.source == '||'));
     // If we do evaluate the right subexpression, the value of the expression
     // is a true or false constant.
-    ir.Constant rightTrue = makePrimConst(constantSystem.createBool(true));
-    ir.Constant rightFalse = makePrimConst(constantSystem.createBool(false));
+    ir.Constant rightTrue = rightTrueBuilder.makePrimConst(
+        constantSystem.createBool(true));
+    ir.Constant rightFalse = rightFalseBuilder.makePrimConst(
+        constantSystem.createBool(false));
+    emptyBuilder.add(new ir.LetPrim(leftBool));
+    rightTrueBuilder.add(new ir.LetPrim(rightTrue));
+    rightFalseBuilder.add(new ir.LetPrim(rightFalse));
+
+    // Treat the result values as named values in the environment, so they
+    // will be treated as arguments to the join-point continuation.
+    assert(environment.length == emptyBuilder.environment.length);
+    assert(environment.length == rightTrueBuilder.environment.length);
+    assert(environment.length == rightFalseBuilder.environment.length);
+    emptyBuilder.environment.extend(null, leftBool);
+    rightTrueBuilder.environment.extend(null, rightTrue);
+    rightFalseBuilder.environment.extend(null, rightFalse);
 
     // Wire up two continuations for the left subexpression, two continuations
     // for the right subexpression, and a three-way join continuation.
-    ir.Continuation joinContinuation = new ir.Continuation(parameters);
+    ir.Continuation joinContinuation = createJoin(environment.length + 1,
+        [emptyBuilder, rightTrueBuilder, rightFalseBuilder]);
     ir.Continuation leftTrueContinuation = new ir.Continuation([]);
     ir.Continuation leftFalseContinuation = new ir.Continuation([]);
     ir.Continuation rightTrueContinuation = new ir.Continuation([]);
     ir.Continuation rightFalseContinuation = new ir.Continuation([]);
-    // If right is true, invoke the join with a true value for the result.
-    rightArguments.add(rightTrue);
-    rightTrueContinuation.body = new ir.LetPrim(rightTrue)
-        ..plug(new ir.InvokeContinuation(joinContinuation, rightArguments));
-    // And if false, invoke the join continuation with a false value.  The
-    // argument list of definitions can be mutated, because fresh Reference
-    // objects are allocated by the InvokeContinuation constructor.
-    rightArguments[rightArguments.length - 1] = rightFalse;
-    rightFalseContinuation.body = new ir.LetPrim(rightFalse)
-        ..plug(new ir.InvokeContinuation(joinContinuation, rightArguments));
+    rightTrueContinuation.body = rightTrueBuilder.root;
+    rightFalseContinuation.body = rightFalseBuilder.root;
     // The right subexpression has two continuations.
     rightBuilder.add(
         new ir.LetCont(rightTrueContinuation,
@@ -1220,26 +1178,21 @@
     // continuation.
     if (op.source == '&&') {
       leftTrueContinuation.body = rightBuilder.root;
-      leftFalseContinuation.body = new ir.LetPrim(leftBool)
-          ..plug(new ir.InvokeContinuation(joinContinuation, leftArguments));
+      leftFalseContinuation.body = emptyBuilder.root;
     } else {
-      leftTrueContinuation.body = new ir.LetPrim(leftBool)
-          ..plug(new ir.InvokeContinuation(joinContinuation, leftArguments));
+      leftTrueContinuation.body = emptyBuilder.root;
       leftFalseContinuation.body = rightBuilder.root;
     }
 
-    // Capture free local variable occurrences in the right subexpression
-    // and update the reaching definitions for the join-point continuation
-    // body to include the continuation's parameters.
-    captureFreeBranchVariables(rightBuilder, emptyBuilder, parameters);
-
     add(new ir.LetCont(joinContinuation,
             new ir.LetCont(leftTrueContinuation,
                 new ir.LetCont(leftFalseContinuation,
                     new ir.Branch(new ir.IsTrue(leftValue),
                                   leftTrueContinuation,
                                   leftFalseContinuation)))));
-    return resultParameter;
+    // There is always a join parameter for the result value, because it
+    // is different on at least two paths.
+    return joinContinuation.parameters.last;
   }
 
   ir.Primitive visitOperatorSend(ast.Send node) {
@@ -1399,7 +1352,7 @@
       add(new ir.SetClosureVariable(local, valueToStore));
     } else if (Elements.isLocal(element)) {
       valueToStore.useElementAsHint(element);
-      assignedVars[variableIndex[element]] = valueToStore;
+      environment.update(element, valueToStore);
     } else if ((!node.isSuperCall && Elements.isErroneousElement(element)) ||
                 Elements.isStaticOrTopLevel(element)) {
       assert(element.isErroneous || element.isField || element.isSetter);
@@ -1494,9 +1447,7 @@
     } else {
       ir.CreateFunction prim = new ir.CreateFunction(inner);
       add(new ir.LetPrim(prim));
-      variableIndex[element] = assignedVars.length;
-      assignedVars.add(prim);
-      index2variable.add(element);
+      environment.extend(element, prim);
       prim.useElementAsHint(element);
     }
     return null;
diff --git a/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_nodes.dart b/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_nodes.dart
index 3d816c4..21aa559 100644
--- a/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_nodes.dart
+++ b/sdk/lib/_internal/compiler/implementation/cps_ir/cps_ir_nodes.dart
@@ -367,6 +367,8 @@
       : continuation = new Reference(cont),
         arguments = _referenceList(args),
         isRecursive = recursive {
+    assert(cont.parameters == null ||
+        cont.parameters.length == args.length);
     if (recursive) cont.isRecursive = true;
   }
 
@@ -855,7 +857,7 @@
         // Reorganize parameters and arguments in case of deletions.
         cont.parameters[dst] = cont.parameters[src];
         for (InvokeContinuation invoke in invokes) {
-            invoke.arguments[dst] = invoke.arguments[src];
+          invoke.arguments[dst] = invoke.arguments[src];
         }
 
         dst++;
diff --git a/sdk/lib/_internal/compiler/implementation/dart2js.dart b/sdk/lib/_internal/compiler/implementation/dart2js.dart
index 7fe9a7a..8531504 100644
--- a/sdk/lib/_internal/compiler/implementation/dart2js.dart
+++ b/sdk/lib/_internal/compiler/implementation/dart2js.dart
@@ -624,7 +624,10 @@
     all categories, use --categories=all.
 
   --dump-info
-    Generates an out.info.html file with information about the generated code.
+    Generates an out.info.json file with information about the generated code.
+    You can inspect the generated file with the viewer at:
+    http://dart-lang.github.io/dump-info-visualizer/build/web/viewer.html
+
 '''.trim());
 }
 
diff --git a/sdk/lib/_internal/compiler/implementation/dart2jslib.dart b/sdk/lib/_internal/compiler/implementation/dart2jslib.dart
index 7267a0e..69ead07 100644
--- a/sdk/lib/_internal/compiler/implementation/dart2jslib.dart
+++ b/sdk/lib/_internal/compiler/implementation/dart2jslib.dart
@@ -48,7 +48,6 @@
 import 'dump_info.dart';
 import 'tracer.dart' show Tracer;
 import 'cache_strategy.dart';
-import 'compilation_info.dart';
 
 export 'resolution/resolution.dart' show TreeElements, TreeElementMapping;
 export 'scanner/scannerlib.dart' show isUserDefinableOperator,
diff --git a/sdk/lib/_internal/compiler/implementation/dart_backend/backend.dart b/sdk/lib/_internal/compiler/implementation/dart_backend/backend.dart
index f761f63..8122603 100644
--- a/sdk/lib/_internal/compiler/implementation/dart_backend/backend.dart
+++ b/sdk/lib/_internal/compiler/implementation/dart_backend/backend.dart
@@ -132,14 +132,10 @@
     }
     // Enqueue the methods that the VM might invoke on user objects because
     // we don't trust the resolution to always get these included.
-    world.registerInvocation(
-        null, new Selector.call("toString", null, 0));
-    world.registerInvokedGetter(
-        null, new Selector.getter("hashCode", null));
-    world.registerInvocation(
-        null, new Selector.binaryOperator("=="));
-    world.registerInvocation(
-        null, new Selector.call("compareTo", null, 1));
+    world.registerInvocation(new Selector.call("toString", null, 0));
+    world.registerInvokedGetter(new Selector.getter("hashCode", null));
+    world.registerInvocation(new Selector.binaryOperator("=="));
+    world.registerInvocation(new Selector.call("compareTo", null, 1));
   }
 
   void codegen(CodegenWorkItem work) { }
@@ -231,14 +227,21 @@
         return new ElementAst(element);
       } else {
         cps_ir.FunctionDefinition function = compiler.irBuilder.getIr(element);
+        // Transformations on the CPS IR.
         new cps_ir.RedundantPhiEliminator().rewrite(function);
+        compiler.tracer.traceCompilation(element.name, null, compiler);
         compiler.tracer.traceGraph("Redundant phi elimination", function);
+        // Do not rewrite the IR after variable allocation.  Allocation
+        // makes decisions based on an approximation of IR variable live
+        // ranges that can be invalidated by transforming the IR.
+        new cps_ir.RegisterAllocator().visit(function);
+
         tree_builder.Builder builder = new tree_builder.Builder(compiler);
         tree_ir.FunctionDefinition definition = builder.build(function);
         assert(definition != null);
-        compiler.tracer.traceCompilation(element.name, null, compiler);
         compiler.tracer.traceGraph('Tree builder', definition);
-        TreeElementMapping treeElements = new TreeElementMapping(element);
+
+        // Transformations on the Tree IR.
         new StatementRewriter().rewrite(definition);
         compiler.tracer.traceGraph('Statement rewriter', definition);
         new CopyPropagator().rewrite(definition);
@@ -249,6 +252,8 @@
         compiler.tracer.traceGraph('Logical rewriter', definition);
         new backend_ast_emitter.UnshadowParameters().unshadow(definition);
         compiler.tracer.traceGraph('Unshadow parameters', definition);
+
+        TreeElementMapping treeElements = new TreeElementMapping(element);
         backend_ast.Node backendAst =
             backend_ast_emitter.emit(definition);
         Node frontend_ast = backend2frontend.emit(treeElements, backendAst);
diff --git a/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart b/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart
index d7279d5..40fb6ea 100644
--- a/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart
+++ b/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart
@@ -195,14 +195,16 @@
       ConstructorElement constructor = element;
       DartType type = element.enclosingClass.thisType.asRaw();
       makeConstructorPlaceholder(node.name, element, type);
-      Return bodyAsReturn = node.body.asReturn();
-      if (bodyAsReturn != null && bodyAsReturn.isRedirectingFactoryBody) {
+      RedirectingFactoryBody bodyAsRedirectingFactoryBody =
+          node.body.asRedirectingFactoryBody();
+      if (bodyAsRedirectingFactoryBody != null) {
         // Factory redirection.
         FunctionElement redirectTarget = constructor.immediateRedirectionTarget;
         assert(redirectTarget != null && redirectTarget != element);
         type = redirectTarget.enclosingClass.thisType.asRaw();
         makeConstructorPlaceholder(
-            bodyAsReturn.expression, redirectTarget, type);
+            bodyAsRedirectingFactoryBody.constructorReference,
+            redirectTarget, type);
       }
     } else if (Elements.isStaticOrTopLevel(element)) {
       // Note: this code should only rename private identifiers for class'
@@ -239,11 +241,7 @@
     } else if (element is VariableElement) {
       VariableDefinitions definitions = elementNode;
       Node definition = definitions.definitions.nodes.head;
-      final definitionElement = treeElements[elementNode];
-      // definitionElement == null if variable is actually unused.
-      if (definitionElement != null) {
-        collectFieldDeclarationPlaceholders(definitionElement, definition);
-      }
+      collectFieldDeclarationPlaceholders(element, definition);
       makeVarDeclarationTypePlaceholder(definitions);
     } else {
       assert(element is ClassElement || element is TypedefElement);
@@ -252,6 +250,9 @@
     compiler.withCurrentElement(element, () {
       elementNode.accept(this);
     });
+    if (element == backend.mirrorHelperSymbolsMap) {
+      backend.registerMirrorHelperElement(element, elementNode);
+    }
   }
 
   // TODO(karlklose): should we create placeholders for these?
@@ -484,10 +485,6 @@
   }
 
   visitVariableDefinitions(VariableDefinitions node) {
-    Element definitionElement = treeElements[node];
-    if (definitionElement == backend.mirrorHelperSymbolsMap) {
-      backend.registerMirrorHelperElement(definitionElement, node);
-    }
     // Collect only local placeholders.
     for (Node definition in node.definitions.nodes) {
       Element definitionElement = treeElements[definition];
diff --git a/sdk/lib/_internal/compiler/implementation/dart_types.dart b/sdk/lib/_internal/compiler/implementation/dart_types.dart
index 1df21c6..016ce5d 100644
--- a/sdk/lib/_internal/compiler/implementation/dart_types.dart
+++ b/sdk/lib/_internal/compiler/implementation/dart_types.dart
@@ -14,7 +14,7 @@
          TypeDeclarationElementX,
          TypedefElementX;
 import 'elements/elements.dart';
-import 'helpers/helpers.dart';
+import 'helpers/helpers.dart';  // Included for debug helpers.
 import 'ordered_typeset.dart' show OrderedTypeSet;
 import 'util/util.dart' show CURRENT_ELEMENT_SPANNABLE, equalElements;
 
@@ -231,48 +231,23 @@
   String toString() => name;
 }
 
-/**
- * A statement type tracks whether a statement returns or may return.
- */
+/// Internal type representing the result of analyzing a statement.
 class StatementType extends DartType {
-  final String stringName;
-
   Element get element => null;
 
   TypeKind get kind => TypeKind.STATEMENT;
 
-  String get name => stringName;
+  String get name => 'statement';
 
-  const StatementType(this.stringName);
+  const StatementType();
 
-  static const RETURNING = const StatementType('<returning>');
-  static const NOT_RETURNING = const StatementType('<not returning>');
-  static const MAYBE_RETURNING = const StatementType('<maybe returning>');
-
-  /** Combine the information about two control-flow edges that are joined. */
-  StatementType join(StatementType other) {
-    return (identical(this, other)) ? this : MAYBE_RETURNING;
-  }
-
-  DartType subst(List<DartType> arguments, List<DartType> parameters) {
-    // Statement types are not substitutable.
-    return this;
-  }
+  DartType subst(List<DartType> arguments, List<DartType> parameters) => this;
 
   DartType unalias(Compiler compiler) => this;
 
   accept(DartTypeVisitor visitor, var argument) {
     return visitor.visitStatementType(this, argument);
   }
-
-  int get hashCode => 17 * stringName.hashCode;
-
-  bool operator ==(other) {
-    if (other is !StatementType) return false;
-    return other.stringName == stringName;
-  }
-
-  String toString() => stringName;
 }
 
 class VoidType extends DartType {
@@ -1408,8 +1383,7 @@
     }
     if (a.kind == TypeKind.STATEMENT) {
       if (b.kind == TypeKind.STATEMENT) {
-        return (a as StatementType).stringName.compareTo(
-               (b as StatementType).stringName);
+        return 0;
       } else {
         // [b] is a malformed type => a > b.
         return 1;
diff --git a/sdk/lib/_internal/compiler/implementation/deferred_load.dart b/sdk/lib/_internal/compiler/implementation/deferred_load.dart
index 310799a..0c0aa7b 100644
--- a/sdk/lib/_internal/compiler/implementation/deferred_load.dart
+++ b/sdk/lib/_internal/compiler/implementation/deferred_load.dart
@@ -763,9 +763,10 @@
     }
     if (splitProgram && backend is DartBackend) {
       // TODO(sigurdm): Implement deferred loading for dart2dart.
-      compiler.reportFatalError(
+      compiler.reportWarning(
           lastDeferred,
           MessageKind.DEFERRED_LIBRARY_DART_2_DART);
+      splitProgram = false;
     }
   }
 
diff --git a/sdk/lib/_internal/compiler/implementation/dump_info.dart b/sdk/lib/_internal/compiler/implementation/dump_info.dart
index c5d0db8..a5e52e5 100644
--- a/sdk/lib/_internal/compiler/implementation/dump_info.dart
+++ b/sdk/lib/_internal/compiler/implementation/dump_info.dart
@@ -12,29 +12,26 @@
 
 import 'elements/elements.dart';
 import 'elements/visitor.dart';
-import 'dart2jslib.dart' show
-    Compiler,
-    CompilerTask,
-    CodeBuffer;
-import 'dart_types.dart' show DartType;
+import 'dart2jslib.dart' show Backend, CodeBuffer, Compiler, CompilerTask;
 import 'types/types.dart' show TypeMask;
-import 'util/util.dart' show modifiersToString;
 import 'deferred_load.dart' show OutputUnit;
 import 'js_backend/js_backend.dart' show JavaScriptBackend;
 import 'js/js.dart' as jsAst;
-import 'compilation_info.dart' show CompilationInformation;
+import 'universe/universe.dart' show Selector;
 
-/// Maps elements to an id.  Supports lookups in
+/// Maps objects to an id.  Supports lookups in
 /// both directions.
-class ElementMapper {
-  Map<int, Element> _idToElement = {};
-  Map<Element, int> _elementToId = {};
+class IdMapper<T>{
+  Map<int, T> _idToElement = {};
+  Map<T, int> _elementToId = {};
   int _idCounter = 0;
   String name;
 
-  ElementMapper(this.name);
+  IdMapper(this.name);
 
-  String add(Element e) {
+  Iterable<T> get elements => _elementToId.keys;
+
+  String add(T e) {
     if (_elementToId.containsKey(e)) {
       return name + "/${_elementToId[e]}";
     }
@@ -46,19 +43,22 @@
   }
 }
 
-class DividedElementMapper {
+class GroupedIdMapper {
   // Mappers for specific kinds of elements.
-  ElementMapper _library = new ElementMapper('library');
-  ElementMapper _typedef = new ElementMapper('typedef');
-  ElementMapper _field = new ElementMapper('field');
-  ElementMapper _class = new ElementMapper('class');
-  ElementMapper _function = new ElementMapper('function');
+  IdMapper<LibraryElement> _library = new IdMapper('library');
+  IdMapper<TypedefElement> _typedef = new IdMapper('typedef');
+  IdMapper<FieldElement> _field = new IdMapper('field');
+  IdMapper<ClassElement> _class = new IdMapper('class');
+  IdMapper<FunctionElement> _function = new IdMapper('function');
+  IdMapper<OutputUnit> _outputUnit = new IdMapper('outputUnit');
+
+  Iterable<Element> get functions => _function.elements;
 
   // Convert this database of elements into JSON for rendering
   Map<String, dynamic> _toJson(ElementToJsonVisitor elementToJson) {
     Map<String, dynamic> json = {};
     var m = [_library, _typedef, _field, _class, _function];
-    for (ElementMapper mapper in m) {
+    for (IdMapper mapper in m) {
       Map<String, dynamic> innerMapper = {};
       mapper._idToElement.forEach((k, v) {
         // All these elements are already cached in the
@@ -75,11 +75,9 @@
 }
 
 class ElementToJsonVisitor extends ElementVisitor<Map<String, dynamic>> {
-  DividedElementMapper mapper = new DividedElementMapper();
+  GroupedIdMapper mapper = new GroupedIdMapper();
   Compiler compiler;
 
-  CompilationInformation compilationInfo;
-
   Map<Element, Map<String, dynamic>> jsonCache = {};
   Map<Element, jsAst.Expression> codeCache;
 
@@ -91,9 +89,17 @@
 
   ElementToJsonVisitor(Compiler compiler) {
     this.compiler = compiler;
-    this.compilationInfo = compiler.enqueuer.codegen.compilationInfo;
 
-    programSize = compiler.assembledCode.length;
+    Backend backend = compiler.backend;
+    if (backend is JavaScriptBackend) {
+      // Add up the sizes of all output-buffers.
+      programSize = backend.emitter.outputBuffers.values.fold(0,
+          (a, b) => a + b.length);
+    } else {
+      programSize = compiler.assembledCode.length;
+    }
+
+
     compilationMoment = new DateTime.now();
     dart2jsVersion = compiler.hasBuildId ? compiler.buildId : null;
     compilationDuration = compiler.totalCompileTime.elapsed;
@@ -108,8 +114,7 @@
   // If keeping the element is in question (like if a function has a size
   // of zero), only keep it if it holds dependencies to elsewhere.
   bool shouldKeep(Element element) {
-    return compilationInfo.addsToWorkListMap.containsKey(element) ||
-           compilationInfo.enqueuesMap.containsKey(element);
+    return compiler.dumpInfoTask.selectorsFromElement.containsKey(element);
   }
 
   Map<String, dynamic> toJson() {
@@ -121,6 +126,17 @@
     return jsonCache.putIfAbsent(element, () => element.accept(this));
   }
 
+  // Returns the id of an [element] if it has already been processed.
+  // If the element has not been processed, this function does not
+  // process it, and simply returns null instead.
+  String idOf(Element element) {
+    if (jsonCache.containsKey(element) && jsonCache[element] != null) {
+      return jsonCache[element]['id'];
+    } else {
+      return null;
+    }
+  }
+
   Map<String, dynamic> visitElement(Element element) {
     return null;
   }
@@ -201,6 +217,9 @@
       }
     }
 
+    OutputUnit outputUnit =
+        compiler.deferredLoadTask.outputUnitForElement(element);
+
     return {
       'id': id,
       'kind': 'field',
@@ -209,7 +228,8 @@
       'name': element.name,
       'children': children,
       'size': size,
-      'code': code
+      'code': code,
+      'outputUnit': mapper._outputUnit.add(outputUnit)
     };
   }
 
@@ -228,16 +248,34 @@
       Map<String, dynamic> childJson = this.process(member);
       if (childJson != null) {
         children.add(childJson['id']);
+
+        // Closures are placed in the library namespace, but
+        // we want to attribute them to a function, and by
+        // extension, this class.  Process and add the sizes
+        // here.
+        if (member is MemberElement) {
+          for (Element closure in member.nestedClosures) {
+            Map<String, dynamic> child = this.process(closure);
+            if (child != null) {
+              size += child['size'];
+            }
+          }
+        }
       }
     });
 
+
+    OutputUnit outputUnit =
+        compiler.deferredLoadTask.outputUnitForElement(element);
+
     return {
       'name': element.name,
       'size': size,
       'kind': 'class',
       'modifiers': modifiers,
       'children': children,
-      'id': id
+      'id': id,
+      'outputUnit': mapper._outputUnit.add(outputUnit)
     };
   }
 
@@ -295,8 +333,10 @@
       sideEffects = compiler.world.getSideEffectsOfElement(element).toString();
       code = emittedCode.toString();
     }
-    if (element is MethodElement) {
-      for (Element closure in element.nestedClosures) {
+
+    if (element is MemberElement) {
+      MemberElement member = element as MemberElement;
+      for (Element closure in member.nestedClosures) {
         Map<String, dynamic> child = this.process(closure);
         if (child != null) {
           children.add(child['id']);
@@ -309,6 +349,9 @@
       return null;
     }
 
+    OutputUnit outputUnit =
+        compiler.deferredLoadTask.outputUnitForElement(element);
+
     return {
       'kind': kind,
       'name': name,
@@ -321,7 +364,8 @@
       'parameters': parameters,
       'sideEffects': sideEffects,
       'code': code,
-      'type': element.computeType(compiler).toString()
+      'type': element.type.toString(),
+      'outputUnit': mapper._outputUnit.add(outputUnit)
     };
   }
 }
@@ -348,6 +392,33 @@
   final Map<jsAst.Node, int> _nodeBeforeSize = <jsAst.Node, int>{};
   final Map<Element, int> _fieldNameToSize = <Element, int>{};
 
+  final Map<Element, Set<Selector>> selectorsFromElement = {};
+
+  /**
+   * Registers that a function uses a selector in the
+   * function body
+   */
+  void elementUsesSelector(Element element, Selector selector) {
+    if (compiler.dumpInfo) {
+      selectorsFromElement
+          .putIfAbsent(element, () => new Set<Selector>())
+          .add(selector);
+    }
+  }
+
+  /**
+   * Returns an iterable of [Element]s that are used by
+   * [element].
+   */
+  Iterable<Element> getRetaining(Element element) {
+    if (!selectorsFromElement.containsKey(element)) {
+      return const <Element>[];
+    } else {
+      return selectorsFromElement[element].expand(
+          (s) => compiler.world.allFunctions.filter(s));
+    }
+  }
+
   /**
    * A callback that can be called before a jsAst [node] is
    * pretty-printed. The size of the code buffer ([aftersize])
@@ -463,40 +534,46 @@
 
   void dumpInfoJson(StringSink buffer) {
     JsonEncoder encoder = const JsonEncoder();
-
-    // `A` uses and depends on the functions `Bs`.
-    //     A         Bs
-    Map<String, List<String>> holding = <String, List<String>>{};
-
     DateTime startToJsonTime = new DateTime.now();
 
-    CompilationInformation compilationInfo =
-      infoCollector.compiler.enqueuer.codegen.compilationInfo;
-    compilationInfo.addsToWorkListMap.forEach((func, deps) {
-      if (func != null) {
-        var funcJson = infoCollector.process(func);
-        if (funcJson != null) {
-          var funcId = funcJson['id'];
-
-          List<String> heldList = <String>[];
-
-          for (var held in deps) {
-            // "process" to get the ids of the elements.
-            var heldJson = infoCollector.process(held);
-            if (heldJson != null) {
-              var heldId = heldJson['id'];
-              heldList.add(heldId);
-            }
-          }
-          holding[funcId] = heldList;
+    Map<String, List<String>> holding = <String, List<String>>{};
+    for (Element fn in infoCollector.mapper.functions) {
+      Iterable<Element> pulling = getRetaining(fn);
+      // Don't bother recording an empty list of dependencies.
+      if (pulling.length > 0) {
+        String fnId = infoCollector.idOf(fn);
+        // Some dart2js builtin functions are not
+        // recorded.  Don't register these.
+        if (fnId != null) {
+          holding[fnId] = pulling
+            .map((a) => infoCollector.idOf(a))
+            // Filter non-null ids for the same reason as above.
+            .where((a) => a != null)
+            .toList();
         }
       }
-    });
+    }
+
+    List<Map<String, dynamic>> outputUnits =
+        new List<Map<String, dynamic>>();
+
+    JavaScriptBackend backend = compiler.backend;
+
+    for (OutputUnit outputUnit in
+        infoCollector.mapper._outputUnit._elementToId.keys) {
+      String id = infoCollector.mapper._outputUnit.add(outputUnit);
+      outputUnits.add(<String, dynamic> {
+        'id': id,
+        'name': outputUnit.name,
+        'size': backend.emitter.outputBuffers[outputUnit].length,
+      });
+    }
 
     Map<String, dynamic> outJson = {
       'elements': infoCollector.toJson(),
       'holding': holding,
-      'dump_version': 1,
+      'outputUnits': outputUnits,
+      'dump_version': 2,
     };
 
     Duration toJsonDuration = new DateTime.now().difference(startToJsonTime);
diff --git a/sdk/lib/_internal/compiler/implementation/enqueue.dart b/sdk/lib/_internal/compiler/implementation/enqueue.dart
index fbd3f93..e296258 100644
--- a/sdk/lib/_internal/compiler/implementation/enqueue.dart
+++ b/sdk/lib/_internal/compiler/implementation/enqueue.dart
@@ -49,8 +49,6 @@
   bool hasEnqueuedReflectiveElements = false;
   bool hasEnqueuedReflectiveStaticFields = false;
 
-  CompilationInformation get compilationInfo;
-
   Enqueuer(this.name, this.compiler, this.itemCompilationContextCreator);
 
   Queue<WorkItem> get queue;
@@ -71,9 +69,7 @@
    */
   void addToWorkList(Element element) {
     assert(invariant(element, element.isDeclaration));
-    if (internalAddToWorkList(element)) {
-      compilationInfo.addsToWorkList(compiler.currentElement, element);
-    }
+    internalAddToWorkList(element);
   }
 
   /**
@@ -189,7 +185,6 @@
           memberName, () => const Link<Element>());
       instanceFunctionsByName[memberName] = members.prepend(member);
       if (universe.hasInvocation(member, compiler)) {
-        compilationInfo.enqueues(getContext(), member);
         addToWorkList(member);
         return;
       }
@@ -221,8 +216,6 @@
   void enableNoSuchMethod(Element element) {}
   void enableIsolateSupport() {}
 
-  Element getContext() => compiler.currentElement;
-
   void onRegisterInstantiatedClass(ClassElement cls) {
     task.measure(() {
       if (seenClasses.contains(cls)) return;
@@ -254,33 +247,32 @@
     });
   }
 
-  void registerNewSelector(Element context,
-                           Selector selector,
+  void registerNewSelector(Selector selector,
                            Map<String, Set<Selector>> selectorsMap) {
     String name = selector.name;
     Set<Selector> selectors =
         selectorsMap.putIfAbsent(name, () => new Setlet<Selector>());
     if (!selectors.contains(selector)) {
       selectors.add(selector);
-      handleUnseenSelector(context, name, selector);
+      handleUnseenSelector(name, selector);
     }
   }
 
-  void registerInvocation(Element context, Selector selector) {
+  void registerInvocation(Selector selector) {
     task.measure(() {
-      registerNewSelector(context, selector, universe.invokedNames);
+      registerNewSelector(selector, universe.invokedNames);
     });
   }
 
-  void registerInvokedGetter(Element context, Selector selector) {
+  void registerInvokedGetter(Selector selector) {
     task.measure(() {
-      registerNewSelector(context, selector, universe.invokedGetters);
+      registerNewSelector(selector, universe.invokedGetters);
     });
   }
 
-  void registerInvokedSetter(Element context, Selector selector) {
+  void registerInvokedSetter(Selector selector) {
     task.measure(() {
-      registerNewSelector(context, selector, universe.invokedSetters);
+      registerNewSelector(selector, universe.invokedSetters);
     });
   }
 
@@ -322,22 +314,24 @@
   /// needed for reflection.
   void enqueueReflectiveMember(Element element, bool enclosingWasIncluded) {
     if (shouldIncludeElementDueToMirrors(element,
-            includedEnclosing: enclosingWasIncluded)
-        // Do not enqueue typedefs.
-        && !element.impliesType) {
+            includedEnclosing: enclosingWasIncluded)) {
       logEnqueueReflectiveAction(element);
-      if (Elements.isStaticOrTopLevel(element)) {
+      if (element.isTypedef) {
+        TypedefElement typedef = element;
+        typedef.ensureResolved(compiler);
+        compiler.world.allTypedefs.add(element);
+      } else if (Elements.isStaticOrTopLevel(element)) {
         registerStaticUse(element.declaration);
       } else if (element.isInstanceMember) {
         // We need to enqueue all members matching this one in subclasses, as
         // well.
         // TODO(herhut): Use TypedSelector.subtype for enqueueing
         Selector selector = new Selector.fromElement(element, compiler);
-        registerSelectorUse(element, selector);
+        registerSelectorUse(selector);
         if (element.isField) {
           Selector selector =
               new Selector.setter(element.name, element.library);
-          registerInvokedSetter(element, selector);
+          registerInvokedSetter(selector);
         }
       }
     }
@@ -474,11 +468,8 @@
     processLink(instanceFunctionsByName, n, f);
   }
 
-  void handleUnseenSelector(Element context,
-                            String methodName,
-                            Selector selector) {
+  void handleUnseenSelector(String methodName, Selector selector) {
     processInstanceMembers(methodName, (Element member) {
-      compilationInfo.enqueues(context, member);
       if (selector.appliesUnnamed(member, compiler)) {
         if (member.isFunction && selector.isGetter) {
           registerClosurizedMember(member, compiler.globalDependencies);
@@ -535,27 +526,27 @@
     universe.staticFunctionsNeedingGetter.add(element);
   }
 
-  void registerDynamicInvocation(Element context, Selector selector) {
+  void registerDynamicInvocation(Selector selector) {
     assert(selector != null);
-    registerInvocation(context, selector);
+    registerInvocation(selector);
   }
 
-  void registerSelectorUse(Element context, Selector selector) {
+  void registerSelectorUse(Selector selector) {
     if (selector.isGetter) {
-      registerInvokedGetter(context, selector);
+      registerInvokedGetter(selector);
     } else if (selector.isSetter) {
-      registerInvokedSetter(context, selector);
+      registerInvokedSetter(selector);
     } else {
-      registerInvocation(context, selector);
+      registerInvocation(selector);
     }
   }
 
-  void registerDynamicGetter(Element context, Selector selector) {
-    registerInvokedGetter(context, selector);
+  void registerDynamicGetter(Selector selector) {
+    registerInvokedGetter(selector);
   }
 
-  void registerDynamicSetter(Element context, Selector selector) {
-    registerInvokedSetter(context, selector);
+  void registerDynamicSetter(Selector selector) {
+    registerInvokedSetter(selector);
   }
 
   void registerGetterForSuperMethod(Element element) {
@@ -663,16 +654,12 @@
    */
   final Queue<DeferredTask> deferredTaskQueue;
 
-  CompilationInformation compilationInfo;
-
   ResolutionEnqueuer(Compiler compiler,
                      ItemCompilationContext itemCompilationContextCreator())
       : super('resolution enqueuer', compiler, itemCompilationContextCreator),
         resolvedElements = new Set<AstElement>(),
         queue = new Queue<ResolutionWorkItem>(),
-        deferredTaskQueue = new Queue<DeferredTask>() {
-    compilationInfo = new CompilationInformation(this, compiler.dumpInfo);
-  }
+        deferredTaskQueue = new Queue<DeferredTask>();
 
   bool get isResolutionQueue => true;
 
@@ -815,15 +802,11 @@
 
   final Set<Element> newlyEnqueuedElements;
 
-  CompilationInformation compilationInfo;
-
   CodegenEnqueuer(Compiler compiler,
                   ItemCompilationContext itemCompilationContextCreator())
       : queue = new Queue<CodegenWorkItem>(),
         newlyEnqueuedElements = compiler.cacheStrategy.newSet(),
-        super('codegen enqueuer', compiler, itemCompilationContextCreator) {
-    compilationInfo = new CompilationInformation(this, compiler.dumpInfo);
-  }
+        super('codegen enqueuer', compiler, itemCompilationContextCreator);
 
   bool isProcessed(Element member) =>
       member.isAbstract || generatedCode.containsKey(member);
diff --git a/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart b/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart
index 871859c..3d0941d 100644
--- a/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart
+++ b/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart
@@ -573,7 +573,7 @@
   }
 
   T visitFunctionExpression(ast.FunctionExpression node) {
-    Element element = elements[node];
+    LocalFunctionElement element = elements.getFunctionDefinition(node);
     // We don't put the closure in the work queue of the
     // inferrer, because it will share information with its enclosing
     // method, like for example the types of local variables.
@@ -607,7 +607,7 @@
   }
 
   T visitFunctionDeclaration(ast.FunctionDeclaration node) {
-    Element element = elements[node];
+    LocalFunctionElement element = elements.getFunctionDefinition(node.function);
     T type = inferrer.concreteTypes.putIfAbsent(node.function, () {
       return types.allocateClosure(node.function, element);
     });
@@ -1231,26 +1231,27 @@
                                           sideEffects,
                                           inLoop);
   }
+  T visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) {
+    Element element = elements.getRedirectingTargetConstructor(node);
+    if (Elements.isErroneousElement(element)) {
+      recordReturnType(types.dynamicType);
+    } else {
+      // We don't create a selector for redirecting factories, and
+      // the send is just a property access. Therefore we must
+      // manually create the [ArgumentsTypes] of the call, and
+      // manually register [analyzedElement] as a caller of [element].
+      T mask = synthesizeForwardingCall(node.constructorReference, element);
+      recordReturnType(mask);
+    }
+    locals.seenReturnOrThrow = true;
+    return null;
+  }
 
   T visitReturn(ast.Return node) {
-    if (node.isRedirectingFactoryBody) {
-      Element element = elements[node.expression];
-      if (Elements.isErroneousElement(element)) {
-        recordReturnType(types.dynamicType);
-      } else {
-        // We don't create a selector for redirecting factories, and
-        // the send is just a property access. Therefore we must
-        // manually create the [ArgumentsTypes] of the call, and
-        // manually register [analyzedElement] as a caller of [element].
-        T mask = synthesizeForwardingCall(node.expression, element);
-        recordReturnType(mask);
-      }
-    } else {
-      ast.Node expression = node.expression;
-      recordReturnType(expression == null
-          ? types.nullType
-          : expression.accept(this));
-    }
+    ast.Node expression = node.expression;
+    recordReturnType(expression == null
+        ? types.nullType
+        : expression.accept(this));
     locals.seenReturnOrThrow = true;
     return null;
   }
@@ -1275,7 +1276,7 @@
     }
 
     ast.Node identifier = node.declaredIdentifier;
-    Element element = elements[identifier];
+    Element element = elements.getForInVariable(node);
     Selector selector = elements.getSelector(identifier);
 
     T receiverType;
diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart
index ec4854d..b1ee024 100644
--- a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart
@@ -340,9 +340,6 @@
   /// List of elements that the backend may use.
   final Set<Element> helpersUsed = new Set<Element>();
 
-  /// Set of typedefs that are used as type literals.
-  final Set<TypedefElement> typedefTypeLiterals = new Set<TypedefElement>();
-
   /// All the checked mode helpers.
   static const checkedModeHelpers = CheckedModeHelper.helpers;
 
@@ -946,7 +943,7 @@
 
   void enableNoSuchMethod(context, Enqueuer world) {
     enqueue(world, getCreateInvocationMirror(), compiler.globalDependencies);
-    world.registerInvocation(context, compiler.noSuchMethodSelector);
+    world.registerInvocation(compiler.noSuchMethodSelector);
   }
 
   void enableIsolateSupport(Enqueuer enqueuer) {
@@ -1937,11 +1934,13 @@
     if (foundClosure) {
       reflectableMembers.add(closureClass);
     }
-    // It would be nice to have a better means to select
     Set<Element> closurizedMembers = compiler.resolverWorld.closurizedMembers;
     if (closurizedMembers.any(reflectableMembers.contains)) {
       reflectableMembers.add(boundClosureClass);
     }
+    // Add typedefs.
+    reflectableMembers
+        .addAll(compiler.world.allTypedefs.where(referencedFromMirrorSystem));
     // Register all symbols of reflectable elements
     for (Element element in reflectableMembers) {
       symbolsUsed.add(element.name);
@@ -2170,7 +2169,7 @@
     // when reflection is used.  However, as long as we disable tree-shaking
     // eagerly it doesn't matter.
     if (type.isTypedef) {
-      backend.typedefTypeLiterals.add(type.element);
+      backend.compiler.world.allTypedefs.add(type.element);
     }
     backend.customElementsAnalysis.registerTypeLiteral(type, registry);
   }
diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/js_backend.dart b/sdk/lib/_internal/compiler/implementation/js_backend/js_backend.dart
index 0f8ae3d..30836e4 100644
--- a/sdk/lib/_internal/compiler/implementation/js_backend/js_backend.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_backend/js_backend.dart
@@ -25,7 +25,6 @@
 import '../util/characters.dart';
 import '../util/util.dart';
 
-import '../compilation_info.dart';
 import '../dump_info.dart';
 
 part 'backend.dart';
diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/native_emitter.dart b/sdk/lib/_internal/compiler/implementation/js_backend/native_emitter.dart
index 5db0019..18eb57f 100644
--- a/sdk/lib/_internal/compiler/implementation/js_backend/native_emitter.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_backend/native_emitter.dart
@@ -244,7 +244,7 @@
         emitter.classEmitter.emitClassBuilderWithReflectionData(
             backend.namer.getNameOfClass(classElement),
             classElement, builders[classElement],
-            emitter.getElementDecriptor(classElement));
+            emitter.getElementDescriptor(classElement));
         emitter.needsDefineClass = true;
       }
     }
diff --git a/sdk/lib/_internal/compiler/implementation/js_emitter/class_emitter.dart b/sdk/lib/_internal/compiler/implementation/js_emitter/class_emitter.dart
index e3856a9..277917e 100644
--- a/sdk/lib/_internal/compiler/implementation/js_emitter/class_emitter.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_emitter/class_emitter.dart
@@ -80,32 +80,10 @@
     //        [ constructorName, fields,
     //            fields.map(
     //                (name) => js('this.# = #', [name, name]))]));
-    task.precompiledFunction.add(
-        new jsAst.FunctionDeclaration(
-            new jsAst.VariableDeclaration(constructorName),
-            js('function(#) { #; }',
-                [fields,
-                 fields.map((name) => js('this.# = #', [name, name]))])));
-    // TODO(floitsch): do we actually need the name field?
-    // TODO(floitsch): these should all go through the namer.
-
-    task.precompiledFunction.add(
-        js.statement(r'''{
-          #.builtin$cls = #;
-          if (!"name" in #)
-              #.name = #;
-          $desc=$collectedClasses.#;
-          if ($desc instanceof Array) $desc = $desc[1];
-          #.prototype = $desc;
-        }''',
-            [   constructorName, js.string(constructorName),
-                constructorName,
-                constructorName, js.string(constructorName),
-                constructorName,
-                constructorName
-             ]));
-
-    task.precompiledConstructorNames.add(js('#', constructorName));
+    jsAst.Expression constructorAst = js('function(#) { #; }',
+        [fields,
+         fields.map((name) => js('this.# = #', [name, name]))]);
+    task.emitPrecompiledConstructor(constructorName, constructorAst);
   }
 
   /// Returns `true` if fields added.
diff --git a/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart b/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart
index b1dbdc3..800b63e 100644
--- a/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart
@@ -29,8 +29,11 @@
   final Namer namer;
   ConstantEmitter constantEmitter;
   NativeEmitter nativeEmitter;
+
+  // The full code that is written to each hunk part-file.
   Map<OutputUnit, CodeBuffer> outputBuffers = new Map<OutputUnit, CodeBuffer>();
   final CodeBuffer deferredConstants = new CodeBuffer();
+
   /** Shorter access to [isolatePropertiesName]. Both here in the code, as
       well as in the generated code. */
   String isolateProperties;
@@ -55,6 +58,8 @@
   // TODO(ngeoffray): remove this field.
   Set<ClassElement> instantiatedClasses;
 
+  List<TypedefElement> typedefsNeededForReflection;
+
   JavaScriptBackend get backend => compiler.backend;
   TypeVariableHandler get typeVariableHandler => backend.typeVariableHandler;
 
@@ -63,9 +68,12 @@
   String get n => compiler.enableMinification ? "" : "\n";
   String get N => compiler.enableMinification ? "\n" : ";\n";
 
+  CodeBuffer getBuffer(OutputUnit outputUnit) {
+    return outputBuffers.putIfAbsent(outputUnit, () => new CodeBuffer());
+  }
+
   CodeBuffer get mainBuffer {
-    return outputBuffers.putIfAbsent(compiler.deferredLoadTask.mainOutputUnit,
-        () => new CodeBuffer());
+    return getBuffer(compiler.deferredLoadTask.mainOutputUnit);
   }
 
   /**
@@ -752,6 +760,8 @@
       ClassElement cls = element;
       if (cls.isUnnamedMixinApplication) return null;
       return cls.name;
+    } else if (element.isTypedef) {
+      return element.name;
     }
     throw compiler.internalError(element,
         'Do not know how to reflect on this $element.');
@@ -861,7 +871,7 @@
     for (Element element in Elements.sortedByPosition(elements)) {
       ClassBuilder builder = new ClassBuilder(element, namer);
       containerBuilder.addMember(element, builder);
-      getElementDecriptor(element).properties.addAll(builder.properties);
+      getElementDescriptor(element).properties.addAll(builder.properties);
     }
   }
 
@@ -1149,10 +1159,15 @@
     }
   }
 
-  /**
-   * Compute all the classes that must be emitted.
-   */
-  void computeNeededClasses() {
+  /// Compute all the classes and typedefs that must be emitted.
+  void computeNeededDeclarations() {
+    // Compute needed typedefs.
+    typedefsNeededForReflection = Elements.sortedByPosition(
+        compiler.world.allTypedefs
+            .where(backend.isAccessibleByReflection)
+            .toList());
+
+    // Compute needed classes.
     instantiatedClasses =
         compiler.codegenWorld.instantiatedClasses.where(computeClassFilter())
             .toSet();
@@ -1303,7 +1318,9 @@
     mainBuffer.add(N);
   }
 
-  void writeLibraryDescriptors(LibraryElement library) {
+  void writeLibraryDescriptors(
+      LibraryElement library,
+      Map<OutputUnit, CodeBuffer> libraryDescriptorBuffers) {
     var uri = "";
     if (!compiler.enableMinification || backend.mustRetainUris) {
       uri = library.canonicalUri;
@@ -1311,30 +1328,28 @@
         uri = relativize(compiler.outputUri, library.canonicalUri, false);
       }
     }
+    Map<OutputUnit, ClassBuilder> descriptors = elementDescriptors[library];
     String libraryName =
         (!compiler.enableMinification || backend.mustRetainLibraryNames) ?
         library.getLibraryName() :
         "";
-    Map<OutputUnit, ClassBuilder> descriptors =
-        elementDescriptors[library];
 
     for (OutputUnit outputUnit in compiler.deferredLoadTask.allOutputUnits) {
-      ClassBuilder descriptor =
-          descriptors.putIfAbsent(outputUnit,
-                                  () => new ClassBuilder(library, namer));
-      if (descriptor.properties.isEmpty) continue;
-      bool isDeferred =
-          outputUnit != compiler.deferredLoadTask.mainOutputUnit;
+      if (!descriptors.containsKey(outputUnit)) continue;
+ 
+      ClassBuilder descriptor = descriptors[outputUnit];
+
       jsAst.Fun metadata = metadataEmitter.buildMetadataFunction(library);
 
-      jsAst.ObjectInitializer initializers =
-          descriptor.toObjectInitializer();
-      CodeBuffer outputBuffer =
-          outputBuffers.putIfAbsent(outputUnit, () => new CodeBuffer());
-      int sizeBefore = outputBuffer.length;
+      jsAst.ObjectInitializer initializers = descriptor.toObjectInitializer();
+
+      CodeBuffer libraryDescriptorBuffer =
+          libraryDescriptorBuffers.putIfAbsent(outputUnit,
+              () => new CodeBuffer());
+
       compiler.dumpInfoTask.registerElementAst(library, metadata);
       compiler.dumpInfoTask.registerElementAst(library, initializers);
-      outputBuffers[outputUnit]
+      libraryDescriptorBuffer
           ..write('["$libraryName",$_')
           ..write('"${uri}",$_')
           ..write(metadata == null ? "" : jsAst.prettyPrint(metadata,
@@ -1348,11 +1363,34 @@
                                     monitor: compiler.dumpInfoTask))
           ..write(library == compiler.mainApp ? ',${n}1' : "")
           ..write('],$n');
-      int sizeAfter = outputBuffer.length;
     }
   }
 
-  String assembleProgram() {
+  void emitPrecompiledConstructor(String constructorName,
+                                  jsAst.Expression constructorAst) {
+    precompiledFunction.add(
+        new jsAst.FunctionDeclaration(
+            new jsAst.VariableDeclaration(constructorName), constructorAst));
+    precompiledFunction.add(
+   js.statement(r'''{
+          #.builtin$cls = #;
+          if (!"name" in #)
+              #.name = #;
+          $desc=$collectedClasses.#;
+          if ($desc instanceof Array) $desc = $desc[1];
+          #.prototype = $desc;
+        }''',
+        [   constructorName, js.string(constructorName),
+            constructorName,
+            constructorName, js.string(constructorName),
+            constructorName,
+            constructorName
+         ]));
+
+    precompiledConstructorNames.add(js('#', constructorName));
+  }
+
+  void assembleProgram() {
     measure(() {
       invalidateCaches();
 
@@ -1360,7 +1398,9 @@
       // 'is$' method.
       typeTestEmitter.computeRequiredTypeChecks();
 
-      computeNeededClasses();
+      computeNeededDeclarations();
+
+      OutputUnit mainOutputUnit = compiler.deferredLoadTask.mainOutputUnit;
 
       mainBuffer.add(buildGeneratedBy());
       addComment(HOOKS_API_USAGE, mainBuffer);
@@ -1395,7 +1435,8 @@
       // Only output the classesCollector if we actually have any classes.
       if (!(nativeClasses.isEmpty &&
             compiler.codegenWorld.staticFunctionsNeedingGetter.isEmpty &&
-          outputClassLists.values.every((classList) => classList.isEmpty))) {
+          outputClassLists.values.every((classList) => classList.isEmpty) &&
+          typedefsNeededForReflection.isEmpty)) {
         // Shorten the code by using "$$" as temporary.
         classesCollector = r"$$";
         mainBuffer.add('var $classesCollector$_=$_{}$N$n');
@@ -1419,7 +1460,7 @@
       // Might create methodClosures.
       for (List<ClassElement> outputClassList in outputClassLists.values) {
         for (ClassElement element in outputClassList) {
-          generateClass(element, getElementDecriptor(element));
+          generateClass(element, getElementDescriptor(element));
         }
       }
 
@@ -1431,6 +1472,10 @@
       // the classesCollector variable.
       classesCollector = 'classesCollector should not be used from now on';
 
+      // For each output unit, the library descriptors written to it.
+      Map<OutputUnit, CodeBuffer> libraryDescriptorBuffers =
+          new Map<OutputUnit, CodeBuffer>();
+
       // TODO(sigurdm): Need to check this for each outputUnit.
       if (!elementDescriptors.isEmpty) {
         var oldClassesCollector = classesCollector;
@@ -1439,6 +1484,8 @@
           mainBuffer.write(';');
         }
 
+        // TODO(karlklose): document what kinds of fields this loop adds to the
+        // library class builder.
         for (Element element in elementDescriptors.keys) {
           // TODO(ahe): Should iterate over all libraries.  Otherwise, we will
           // not see libraries that only have fields.
@@ -1447,17 +1494,42 @@
             ClassBuilder builder = new ClassBuilder(library, namer);
             if (classEmitter.emitFields(
                     library, builder, null, emitStatics: true)) {
-              OutputUnit mainUnit = compiler.deferredLoadTask.mainOutputUnit;
               jsAst.ObjectInitializer initializer =
                 builder.toObjectInitializer();
               compiler.dumpInfoTask.registerElementAst(builder.element,
                                                        initializer);
-              getElementDescriptorForOutputUnit(library, mainUnit)
+              getElementDescriptorForOutputUnit(library, mainOutputUnit)
                   .properties.addAll(initializer.properties);
             }
           }
         }
 
+        // Emit all required typedef declarations into the main output unit.
+        // TODO(karlklose): unify required classes and typedefs to declarations
+        // and have builders for each kind.
+        for (TypedefElement typedef in typedefsNeededForReflection) {
+          OutputUnit mainUnit = compiler.deferredLoadTask.mainOutputUnit;
+          LibraryElement library = typedef.library;
+          // TODO(karlklose): add a TypedefBuilder and move this code there.
+          DartType type = typedef.alias;
+          int typeIndex = metadataEmitter.reifyType(type);
+          String typeReference =
+              encoding.encodeTypedefFieldDescriptor(typeIndex);
+          jsAst.Property descriptor = new jsAst.Property(
+              js.string(namer.classDescriptorProperty),
+              js.string(typeReference));
+          jsAst.Node declaration = new jsAst.ObjectInitializer([descriptor]);
+          String mangledName = namer.getNameX(typedef);
+          String reflectionName = getReflectionName(typedef, mangledName);
+          getElementDescriptorForOutputUnit(library, mainUnit)
+              ..addProperty(mangledName, declaration)
+              ..addProperty("+$reflectionName", js.string(''));
+          // Also emit a trivial constructor for CSP mode.
+          String constructorName = mangledName;
+          jsAst.Expression constructorAst = js('function() {}');
+          emitPrecompiledConstructor(constructorName, constructorAst);
+        }
+
         if (!mangledFieldNames.isEmpty) {
           var keys = mangledFieldNames.keys.toList();
           keys.sort();
@@ -1494,14 +1566,6 @@
             mainBuffer.write(';');
           }
         }
-        mainBuffer
-            ..write('(')
-            ..write(
-                jsAst.prettyPrint(
-                    getReflectionDataParser(classesCollector, backend),
-                    compiler))
-            ..write(')')
-            ..write('([$n');
 
         List<Element> sortedElements =
             Elements.sortedByPosition(elementDescriptors.keys);
@@ -1518,21 +1582,30 @@
               compiler.reportInfo(
                   element, MessageKind.GENERIC, {'text': 'Pending statics.'}));
         }
+
         for (LibraryElement library in sortedElements.where((element) =>
             element.isLibrary)) {
-          writeLibraryDescriptors(library);
+          writeLibraryDescriptors(library, libraryDescriptorBuffers);
           elementDescriptors[library] = const {};
         }
         if (pendingStatics != null && !pendingStatics.isEmpty) {
           compiler.internalError(pendingStatics.first,
               'Pending statics (see above).');
         }
-        mainBuffer.write('])$N');
+        mainBuffer
+            ..write('(')
+            ..write(
+                jsAst.prettyPrint(
+                    getReflectionDataParser(classesCollector, backend),
+                    compiler))
+            ..write(')')
+            ..write('([$n')
+            ..add(libraryDescriptorBuffers[mainOutputUnit])
+            ..write('])$N');
 
         emitFinishClassesInvocationIfNecessary(mainBuffer);
         classesCollector = oldClassesCollector;
       }
-      OutputUnit mainOutputUnit = compiler.deferredLoadTask.mainOutputUnit;
       typeTestEmitter.emitRuntimeTypeSupport(mainBuffer, mainOutputUnit);
       interceptorEmitter.emitGetInterceptorMethods(mainBuffer);
       interceptorEmitter.emitOneShotInterceptors(mainBuffer);
@@ -1653,14 +1726,17 @@
         sourceMapTags =
             generateSourceMapTag(compiler.sourceMapUri, compiler.outputUri);
       }
+      mainBuffer.add(sourceMapTags);
+      assembledCode = mainBuffer.getText();
       compiler.outputProvider('', 'js')
           ..add(assembledCode)
-          ..add(sourceMapTags)
           ..close();
       compiler.assembledCode = assembledCode;
 
       if (!compiler.useContentSecurityPolicy) {
-        mainBuffer.write("""
+        CodeBuffer cspBuffer = new CodeBuffer();
+        cspBuffer.add(mainBuffer);
+        cspBuffer.write("""
 {
   var message =
       'Deprecation: Automatic generation of output for Content Security\\n' +
@@ -1675,23 +1751,22 @@
   }
 }\n""");
 
-        mainBuffer.write(
+        cspBuffer.write(
             jsAst.prettyPrint(
                 precompiledFunctionAst, compiler,
                 allowVariableMinification: false).getText());
 
         compiler.outputProvider('', 'precompiled.js')
-            ..add(mainBuffer.getText())
+            ..add(cspBuffer.getText())
             ..close();
       }
-      emitDeferredCode();
+      emitDeferredCode(libraryDescriptorBuffers);
 
       if (backend.requiresPreamble &&
           !backend.htmlLibraryIsLoaded) {
         compiler.reportHint(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE);
       }
     });
-    return compiler.assembledCode;
   }
 
   String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
@@ -1714,7 +1789,7 @@
         () => new ClassBuilder(element, namer));
   }
 
-  ClassBuilder getElementDecriptor(Element element) {
+  ClassBuilder getElementDescriptor(Element element) {
     Element owner = element.library;
     if (!element.isTopLevel && !element.isNative) {
       // For static (not top level) elements, record their code in a buffer
@@ -1734,55 +1809,61 @@
         compiler.deferredLoadTask.outputUnitForElement(element));
   }
 
-  void emitDeferredCode() {
+  void emitDeferredCode(Map<OutputUnit, CodeBuffer> libraryDescriptorBuffers) {
     for (OutputUnit outputUnit in compiler.deferredLoadTask.allOutputUnits) {
       if (outputUnit == compiler.deferredLoadTask.mainOutputUnit) continue;
-      CodeBuffer outputBuffer = outputBuffers.putIfAbsent(outputUnit,
-          () => new CodeBuffer());
+
+      CodeBuffer libraryDescriptorBuffer = libraryDescriptorBuffers[outputUnit];
+
+      CodeBuffer outputBuffer = new CodeBuffer();
 
       var oldClassesCollector = classesCollector;
       classesCollector = r"$$";
 
-      var buffer = new CodeBuffer()
-        ..write(buildGeneratedBy())
-        ..write('var old${namer.currentIsolate}$_='
-                '$_${namer.currentIsolate}$N'
-                // TODO(ahe): This defines a lot of properties on the
-                // Isolate.prototype object.  We know this will turn it into a
-                // slow object in V8, so instead we should do something similar
-                // to Isolate.$finishIsolateConstructor.
-                '${namer.currentIsolate}$_='
-                '$_${namer.isolateName}.prototype$N$n'
-                // The classesCollector object ($$).
-                '$classesCollector$_=$_{};$n')
-        ..write('(')
-        ..write(
-            jsAst.prettyPrint(
-                getReflectionDataParser(classesCollector, backend),
-                compiler, monitor: compiler.dumpInfoTask))
-        ..write(')')
-        ..write('([$n')
-        ..addBuffer(outputBuffer)
-        ..write('])$N');
+      outputBuffer
+        ..write(buildGeneratedBy());
+      if (libraryDescriptorBuffer != null) {
+        outputBuffer
+          ..write('var old${namer.currentIsolate}$_='
+                  '$_${namer.currentIsolate}$N'
+      // TODO(ahe): This defines a lot of properties on the
+      // Isolate.prototype object.  We know this will turn it into a
+      // slow object in V8, so instead we should do something similar
+      // to Isolate.$finishIsolateConstructor.
+                  '${namer.currentIsolate}$_='
+                  '$_${namer.isolateName}.prototype$N$n'
+                  // The classesCollector object ($$).
+                  '$classesCollector$_=$_{};$n')
+          ..write('(')
+          ..write(
+              jsAst.prettyPrint(
+                  getReflectionDataParser(classesCollector, backend),
+                  compiler, monitor: compiler.dumpInfoTask))
+          ..write(')')
+          ..write('([$n')
+          ..addBuffer(libraryDescriptorBuffer)
+          ..write('])$N');
 
-      if (outputClassLists.containsKey(outputUnit)) {
-        buffer.write(
-            '$finishClassesName($classesCollector,$_${namer.currentIsolate},'
-            '$_$isolatePropertiesName)$N');
-      }
+        if (outputClassLists.containsKey(outputUnit)) {
+          outputBuffer.write(
+              '$finishClassesName($classesCollector,$_${namer.currentIsolate},'
+              '$_$isolatePropertiesName)$N');
+        }
 
-      buffer.write(
+        outputBuffer.write(
           // Reset the classesCollector ($$).
           '$classesCollector$_=${_}null$N$n'
           '${namer.currentIsolate}$_=${_}old${namer.currentIsolate}$N');
+      }
 
       classesCollector = oldClassesCollector;
 
-      typeTestEmitter.emitRuntimeTypeSupport(buffer, outputUnit);
+      typeTestEmitter.emitRuntimeTypeSupport(outputBuffer, outputUnit);
 
-      emitCompileTimeConstants(buffer, outputUnit);
+      emitCompileTimeConstants(outputBuffer, outputUnit);
 
-      String code = buffer.getText();
+      String code = outputBuffer.getText();
+      outputBuffers[outputUnit] = outputBuffer;
       compiler.outputProvider(outputUnit.partFileName(compiler), 'part.js')
         ..add(code)
         ..close();
diff --git a/sdk/lib/_internal/compiler/implementation/js_emitter/js_emitter.dart b/sdk/lib/_internal/compiler/implementation/js_emitter/js_emitter.dart
index 8fceeea..7199112 100644
--- a/sdk/lib/_internal/compiler/implementation/js_emitter/js_emitter.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_emitter/js_emitter.dart
@@ -69,6 +69,8 @@
 import '../deferred_load.dart' show
     OutputUnit;
 
+import '../runtime_data.dart' as encoding;
+
 part 'class_builder.dart';
 part 'class_emitter.dart';
 part 'code_emitter_helper.dart';
diff --git a/sdk/lib/_internal/compiler/implementation/js_emitter/metadata_emitter.dart b/sdk/lib/_internal/compiler/implementation/js_emitter/metadata_emitter.dart
index 89fdfa6..92b5f55 100644
--- a/sdk/lib/_internal/compiler/implementation/js_emitter/metadata_emitter.dart
+++ b/sdk/lib/_internal/compiler/implementation/js_emitter/metadata_emitter.dart
@@ -90,20 +90,8 @@
   }
 
   void emitMetadata(CodeBuffer buffer) {
-    var literals = backend.typedefTypeLiterals.toList();
-    Elements.sortedByPosition(literals);
-    var properties = [];
-    for (TypedefElement literal in literals) {
-      var key = namer.getNameX(literal);
-      var value = js.number(reifyType(literal.rawType));
-      properties.add(new jsAst.Property(js.string(key), value));
-    }
-    var map = new jsAst.ObjectInitializer(properties);
-    buffer.write(
-        jsAst.prettyPrint(
-            js.statement('init.functionAliases = #', map), compiler));
-    buffer.write('${N}init.metadata$_=$_[');
-    for (var metadata in globalMetadata) {
+    buffer.write('init.metadata$_=$_[');
+    for (String metadata in globalMetadata) {
       if (metadata is String) {
         if (metadata != 'null') {
           buffer.write(metadata);
diff --git a/sdk/lib/_internal/compiler/implementation/resolution/members.dart b/sdk/lib/_internal/compiler/implementation/resolution/members.dart
index 1f608ad..42e2cc0 100644
--- a/sdk/lib/_internal/compiler/implementation/resolution/members.dart
+++ b/sdk/lib/_internal/compiler/implementation/resolution/members.dart
@@ -17,13 +17,18 @@
   Setlet<Element> get otherDependencies;
 
   Element operator[](Node node);
-  Selector getSelector(Send send);
+
+  // TODO(johnniwinther): Investigate whether [Node] could be a [Send].
+  Selector getSelector(Node node);
   Selector getGetterSelectorInComplexSendSet(SendSet node);
   Selector getOperatorSelectorInComplexSendSet(SendSet node);
   DartType getType(Node node);
   void setSelector(Node node, Selector selector);
   void setGetterSelectorInComplexSendSet(SendSet node, Selector selector);
   void setOperatorSelectorInComplexSendSet(SendSet node, Selector selector);
+
+  /// Returns the for-in loop variable for [node].
+  Element getForInVariable(ForIn node);
   Selector getIteratorSelector(ForIn node);
   Selector getMoveNextSelector(ForIn node);
   Selector getCurrentSelector(ForIn node);
@@ -34,6 +39,13 @@
   Constant getConstant(Node node);
   bool isAssert(Send send);
 
+  /// Returns the [FunctionElement] defined by [node].
+  FunctionElement getFunctionDefinition(FunctionExpression node);
+
+  /// Returns target constructor for the redirecting factory body [node].
+  ConstructorElement getRedirectingTargetConstructor(
+      RedirectingFactoryBody node);
+
   /**
    * Returns [:true:] if [node] is a type literal.
    *
@@ -196,6 +208,10 @@
     return selectors[node.inToken];
   }
 
+  Element getForInVariable(ForIn node) {
+    return this[node];
+  }
+
   void setConstant(Node node, Constant constant) {
     constants[node] = constant;
   }
@@ -285,6 +301,15 @@
     return asserts.contains(node);
   }
 
+  FunctionElement getFunctionDefinition(FunctionExpression node) {
+    return this[node];
+  }
+
+  ConstructorElement getRedirectingTargetConstructor(
+      RedirectingFactoryBody node) {
+    return this[node];
+  }
+
   void defineTarget(Node node, JumpTarget target) {
     if (definedTargets == null) {
       // TODO(johnniwinther): Use [Maplet] when available.
@@ -598,7 +623,7 @@
 
         ResolverVisitor visitor = visitorFor(element);
         ResolutionRegistry registry = visitor.registry;
-        registry.useElement(tree, element);
+        registry.defineFunction(tree, element);
         visitor.setupFunction(tree, element);
 
         if (isConstructor && !element.isForwardingConstructor) {
@@ -657,7 +682,7 @@
         useEnclosingScope: useEnclosingScope);
   }
 
-  TreeElements resolveField(VariableElementX element) {
+  TreeElements resolveField(FieldElementX element) {
     VariableDefinitions tree = element.parseNode(compiler);
     if(element.modifiers.isStatic && element.isTopLevel) {
       error(element.modifiers.getStatic(),
@@ -672,7 +697,6 @@
     } else {
       element.variables.type = const DynamicType();
     }
-    registry.useElement(tree, element);
 
     Expression initializer = element.initializer;
     Modifiers modifiers = element.modifiers;
@@ -776,9 +800,8 @@
       assert(invariant(node, treeElements != null,
           message: 'No TreeElements cached for $factory.'));
       FunctionExpression functionNode = factory.parseNode(compiler);
-      Return redirectionNode = functionNode.body;
-      InterfaceType factoryType =
-          treeElements.getType(redirectionNode.expression);
+      RedirectingFactoryBody redirectionNode = functionNode.body;
+      InterfaceType factoryType = treeElements.getType(redirectionNode);
 
       targetType = targetType.substByContext(factoryType);
       factory.effectiveTarget = target;
@@ -1076,8 +1099,11 @@
           }
         }
         if (member.isField) {
-          if (!member.modifiers.isStatic &&
-              !member.modifiers.isFinal) {
+          if (member.modifiers.isConst && !member.modifiers.isStatic) {
+            compiler.reportError(
+                member, MessageKind.ILLEGAL_CONST_FIELD_MODIFIER);
+          }
+          if (!member.modifiers.isStatic && !member.modifiers.isFinal) {
             nonFinalInstanceFields.add(member);
           }
         }
@@ -1278,6 +1304,7 @@
 
   TreeElements resolveTypedef(TypedefElementX element) {
     if (element.isResolved) return element.treeElements;
+    compiler.world.allTypedefs.add(element);
     return _resolveTypeDeclaration(element, () {
       ResolutionRegistry registry = new ResolutionRegistry(compiler, element);
       return compiler.withCurrentElement(element, () {
@@ -1863,7 +1890,7 @@
       AmbiguousElement ambiguous = element;
       type = reportFailureAndCreateType(
           ambiguous.messageKind, ambiguous.messageArguments);
-      ambiguous.diagnose(registry.currentElement, compiler);
+      ambiguous.diagnose(registry.mapping.currentElement, compiler);
     } else if (element.isErroneous) {
       ErroneousElement erroneousElement = element;
       type = reportFailureAndCreateType(
@@ -2018,20 +2045,21 @@
       : typeResolver = new TypeResolver(compiler),
         super(compiler);
 
-  Element defineElement(Node node, Element element,
-                        {bool doAddToScope: true}) {
-    invariant(node, element != null);
-    registry.defineElement(node, element);
-    if (doAddToScope) {
-      Element existing = scope.add(element);
-      if (existing != element) {
-        reportDuplicateDefinition(node, element, existing);
-      }
+  /// Add [element] to the current scope and check for duplicate definitions.
+  void addToScope(Element element) {
+    Element existing = scope.add(element);
+    if (existing != element) {
+      reportDuplicateDefinition(element.name, element, existing);
     }
-    return element;
   }
 
-  void reportDuplicateDefinition(/*Node|String*/ name,
+  /// Register [node] as the definition of [element].
+  void defineLocalVariable(Node node, LocalVariableElement element) {
+    invariant(node, element != null);
+    registry.defineElement(node, element);
+  }
+
+  void reportDuplicateDefinition(String name,
                                  Spannable definition,
                                  Spannable existing) {
     compiler.reportError(definition,
@@ -2057,6 +2085,7 @@
   bool inInstanceContext;
   bool inCheckContext;
   bool inCatchBlock;
+
   Scope scope;
   ClassElement currentClass;
   ExpressionStatement currentExpressionStatement;
@@ -2314,7 +2343,9 @@
       if (element.isInitializingFormal) {
         registry.useElement(parameterNode, element);
       } else {
-        defineElement(parameterNode, element);
+        LocalParameterElement parameterElement = element;
+        defineLocalVariable(parameterNode, parameterElement);
+        addToScope(parameterElement);
       }
       parameterNodes = parameterNodes.tail;
     });
@@ -2392,14 +2423,26 @@
 
   visitFunctionDeclaration(FunctionDeclaration node) {
     assert(node.function.name != null);
-    visit(node.function);
-    FunctionElement functionElement = registry.getDefinition(node.function);
-    // TODO(floitsch): this might lead to two errors complaining about
-    // shadowing.
-    defineElement(node, functionElement);
+    visitFunctionExpression(node.function, inFunctionDeclaration: true);
   }
 
-  visitFunctionExpression(FunctionExpression node) {
+
+  /// Process a local function declaration or an anonymous function expression.
+  ///
+  /// [inFunctionDeclaration] is `true` when the current node is the immediate
+  /// child of a function declaration.
+  ///
+  /// This is used to distinguish local function declarations from anonymous
+  /// function expressions.
+  visitFunctionExpression(FunctionExpression node,
+                          {bool inFunctionDeclaration: false}) {
+    bool doAddToScope = inFunctionDeclaration;
+    if (!inFunctionDeclaration && node.name != null) {
+      compiler.reportError(
+          node.name,
+          MessageKind.NAMED_FUNCTION_EXPRESSION,
+          {'name': node.name});
+    }
     visit(node.returnType);
     String name;
     if (node.name == null) {
@@ -2413,9 +2456,12 @@
     function.functionSignatureCache =
         SignatureResolver.analyze(compiler, node.parameters, node.returnType,
             function, registry, createRealParameters: true);
+    registry.defineFunction(node, function);
+    if (doAddToScope) {
+      addToScope(function);
+    }
     Scope oldScope = scope; // The scope is modified by [setupFunction].
     setupFunction(node, function);
-    defineElement(node, function, doAddToScope: node.name != null);
 
     Element previousEnclosingElement = enclosingElement;
     enclosingElement = function;
@@ -2441,11 +2487,6 @@
 
   ResolutionResult resolveSend(Send node) {
     Selector selector = resolveSelector(node, null);
-    if (selector != null) {
-      compiler.enqueuer.resolution.compilationInfo.registerCallSite(
-          registry.mapping, node);
-    }
-
     if (node.isSuperCall) registry.registerSuperUse(node);
 
     if (node.receiver == null) {
@@ -3013,23 +3054,19 @@
   }
 
   visitReturn(Return node) {
-    if (node.isRedirectingFactoryBody) {
-      handleRedirectingFactoryBody(node);
-    } else {
-      Node expression = node.expression;
-      if (expression != null &&
-          enclosingElement.isGenerativeConstructor) {
-        // It is a compile-time error if a return statement of the form
-        // `return e;` appears in a generative constructor.  (Dart Language
-        // Specification 13.12.)
-        compiler.reportError(expression,
-                             MessageKind.CANNOT_RETURN_FROM_CONSTRUCTOR);
-      }
-      visit(node.expression);
+    Node expression = node.expression;
+    if (expression != null &&
+        enclosingElement.isGenerativeConstructor) {
+      // It is a compile-time error if a return statement of the form
+      // `return e;` appears in a generative constructor.  (Dart Language
+      // Specification 13.12.)
+      compiler.reportError(expression,
+                           MessageKind.CANNOT_RETURN_FROM_CONSTRUCTOR);
     }
+    visit(node.expression);
   }
 
-  void handleRedirectingFactoryBody(Return node) {
+  visitRedirectingFactoryBody(RedirectingFactoryBody node) {
     final isSymbolConstructor = enclosingElement == compiler.symbolConstructor;
     if (!enclosingElement.isFactoryConstructor) {
       compiler.reportError(
@@ -3042,7 +3079,7 @@
     ConstructorElement redirectionTarget = resolveRedirectingFactory(
         node, inConstContext: isConstConstructor);
     constructor.immediateRedirectionTarget = redirectionTarget;
-    registry.useElement(node.expression, redirectionTarget);
+    registry.setRedirectingTargetConstructor(node, redirectionTarget);
     if (Elements.isUnresolved(redirectionTarget)) {
       registry.registerThrowNoSuchMethod();
       return;
@@ -3060,7 +3097,7 @@
     // Check that the target constructor is type compatible with the
     // redirecting constructor.
     ClassElement targetClass = redirectionTarget.enclosingClass;
-    InterfaceType type = registry.getType(node.expression);
+    InterfaceType type = registry.getType(node);
     FunctionType targetType = redirectionTarget.computeType(compiler)
         .subst(type.typeArguments, targetClass.typeVariables);
     FunctionType constructorType = constructor.computeType(compiler);
@@ -3301,7 +3338,7 @@
     return node.accept(new ConstructorResolver(compiler, this));
   }
 
-  ConstructorElement resolveRedirectingFactory(Return node,
+  ConstructorElement resolveRedirectingFactory(RedirectingFactoryBody node,
                                                {bool inConstContext: false}) {
     return node.accept(new ConstructorResolver(compiler, this,
                                                inConstContext: inConstContext));
@@ -3499,7 +3536,7 @@
     }
     if (loopVariable != null) {
       // loopVariable may be null if it could not be resolved.
-      registry.defineElement(declaration, loopVariable);
+      registry.setForInVariable(node, loopVariable);
     }
     visitLoopBodyIn(node, node.body, blockScope);
   }
@@ -3935,9 +3972,7 @@
     element.functionSignature = signature;
 
     scope = new MethodScope(scope, element);
-    signature.forEachParameter((FormalElement element) {
-      defineElement(element.node, element);
-    });
+    signature.forEachParameter(addToScope);
 
     element.alias = signature.type;
 
@@ -4584,10 +4619,11 @@
   visitNodeList(NodeList node) {
     for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
       Identifier name = visit(link.head);
-      VariableElement element = new LocalVariableElementX(
+      LocalVariableElement element = new LocalVariableElementX(
           name.source, resolver.enclosingElement,
           variables, name.token);
-      resolver.defineElement(link.head, element);
+      resolver.defineLocalVariable(link.head, element);
+      resolver.addToScope(element);
       if (definitions.modifiers.isConst) {
         compiler.enqueuer.resolution.addDeferredAction(element, () {
           compiler.resolver.constantCompiler.compileConstant(element);
@@ -4767,10 +4803,10 @@
   }
 
   /// Assumed to be called by [resolveRedirectingFactory].
-  Element visitReturn(Return node) {
-    Node expression = node.expression;
-    return finishConstructorReference(visit(expression),
-                                      expression, expression);
+  Element visitRedirectingFactoryBody(RedirectingFactoryBody node) {
+    Node constructorReference = node.constructorReference;
+    return finishConstructorReference(visit(constructorReference),
+        constructorReference, node);
   }
 }
 
diff --git a/sdk/lib/_internal/compiler/implementation/resolution/registry.dart b/sdk/lib/_internal/compiler/implementation/resolution/registry.dart
index 2dc4962..fb13cf4 100644
--- a/sdk/lib/_internal/compiler/implementation/resolution/registry.dart
+++ b/sdk/lib/_internal/compiler/implementation/resolution/registry.dart
@@ -19,8 +19,6 @@
 
   bool get isForResolution => true;
 
-  Element get currentElement => mapping.currentElement;
-
   ResolutionEnqueuer get world => compiler.enqueuer.resolution;
 
   World get universe => compiler.world;
@@ -31,13 +29,18 @@
   //  Node-to-Element mapping functionality.
   //////////////////////////////////////////////////////////////////////////////
 
+  /// Register [node] as the declaration of [element].
+  void defineFunction(FunctionExpression node, FunctionElement element) {
+    mapping[node] = element;
+  }
+
   /// Register [node] as a reference to [element].
   Element useElement(Node node, Element element) {
     if (element == null) return null;
     return mapping[node] = element;
   }
 
-  /// Register [node] as a declaration of [element].
+  /// Register [node] as the declaration of [element].
   void defineElement(Node node, Element element) {
     mapping[node] = element;
   }
@@ -47,6 +50,17 @@
     return mapping[node];
   }
 
+  /// Sets the loop variable of the for-in [node] to be [element].
+  void setForInVariable(ForIn node, Element element) {
+    mapping[node] = element;
+  }
+
+  /// Sets the target constructor [node] to be [element].
+  void setRedirectingTargetConstructor(RedirectingFactoryBody node,
+                                       ConstructorElement element) {
+    useElement(node, element);
+  }
+
   //////////////////////////////////////////////////////////////////////////////
   //  Node-to-Selector mapping functionality.
   //////////////////////////////////////////////////////////////////////////////
@@ -84,7 +98,6 @@
   DartType useType(Node annotation, DartType type) {
     if (type != null) {
       mapping.setType(annotation, type);
-      useElement(annotation, type.element);
     }
     return type;
   }
@@ -223,7 +236,7 @@
   }
 
   void registerDynamicInvocation(Selector selector) {
-    world.registerDynamicInvocation(currentElement, selector);
+    world.registerDynamicInvocation(selector);
   }
 
   void registerSuperNoSuchMethod() {
@@ -255,11 +268,11 @@
   }
 
   void registerDynamicGetter(Selector selector) {
-    world.registerDynamicGetter(currentElement, selector);
+    world.registerDynamicGetter(selector);
   }
 
   void registerDynamicSetter(Selector selector) {
-    world.registerDynamicSetter(currentElement, selector);
+    world.registerDynamicSetter(selector);
   }
 
   void registerConstSymbol(String name) {
diff --git a/sdk/lib/_internal/compiler/implementation/resolution/secret_tree_element.dart b/sdk/lib/_internal/compiler/implementation/resolution/secret_tree_element.dart
index 665938e..16216df 100644
--- a/sdk/lib/_internal/compiler/implementation/resolution/secret_tree_element.dart
+++ b/sdk/lib/_internal/compiler/implementation/resolution/secret_tree_element.dart
@@ -18,12 +18,35 @@
  */
 library secret_tree_element;
 
-/**
- * The superclass of all AST nodes.
- */
+import '../dart2jslib.dart' show invariant, Spannable;
+
+/// Interface for associating
 abstract class TreeElementMixin {
+  Object get _element;
+  void set _element(Object value);
+}
+
+/// Null implementation of [TreeElementMixin] which does not allow association
+/// of elements.
+///
+/// This class is the superclass of all AST nodes.
+abstract class NullTreeElementMixin implements TreeElementMixin, Spannable {
+
   // Deliberately using [Object] here to thwart code completion.
   // You're not really supposed to access this field anyways.
+  Object get _element => null;
+  set _element(_) {
+    assert(invariant(this, false,
+        message: "Elements cannot be associated with ${runtimeType}."));
+  }
+}
+
+/// Actual implementation of [TreeElementMixin] which stores the associated
+/// element in the private field [_element].
+///
+/// This class is mixed into the node classes that are actually associated with
+/// elements.
+abstract class StoredTreeElementMixin implements TreeElementMixin {
   Object _element;
 }
 
@@ -33,9 +56,7 @@
  *
  * Using [Object] as return type to thwart code completion.
  */
-Object getTreeElement(TreeElementMixin node) {
-  return node._element;
-}
+Object getTreeElement(TreeElementMixin node) => node._element;
 
 /**
  * Do not call this method directly.  Instead, use an instance of
diff --git a/sdk/lib/_internal/compiler/implementation/runtime_data.dart b/sdk/lib/_internal/compiler/implementation/runtime_data.dart
new file mode 100644
index 0000000..545aa72
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/runtime_data.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Contains encoding, decoding and detection functionality for the
+/// representation of program data at runtime.
+///
+/// This library is shared between the compiler and the runtime system.
+library dart2js.runtime_data;
+
+
+String encodeTypedefFieldDescriptor(int typeIndex) {
+  return ":$typeIndex;";
+}
+
+bool isTypedefDescriptor(String descriptor) {
+  return descriptor.startsWith(':');
+}
+
+int getTypeFromTypedef(String descriptor) {
+  return int.parse(descriptor.substring(1, descriptor.length - 1));
+}
\ No newline at end of file
diff --git a/sdk/lib/_internal/compiler/implementation/scanner/listener.dart b/sdk/lib/_internal/compiler/implementation/scanner/listener.dart
index d884211..22596d4 100644
--- a/sdk/lib/_internal/compiler/implementation/scanner/listener.dart
+++ b/sdk/lib/_internal/compiler/implementation/scanner/listener.dart
@@ -1571,7 +1571,7 @@
 
   void endRedirectingFactoryBody(Token beginToken,
                                  Token endToken) {
-    pushNode(new Return(beginToken, endToken, popNode()));
+    pushNode(new RedirectingFactoryBody(beginToken, endToken, popNode()));
   }
 
   void endReturnStatement(bool hasExpression,
diff --git a/sdk/lib/_internal/compiler/implementation/ssa/builder.dart b/sdk/lib/_internal/compiler/implementation/ssa/builder.dart
index 5ab75da..b4048bd 100644
--- a/sdk/lib/_internal/compiler/implementation/ssa/builder.dart
+++ b/sdk/lib/_internal/compiler/implementation/ssa/builder.dart
@@ -1430,7 +1430,7 @@
     ast.FunctionExpression function = functionElement.node;
     assert(function != null);
     assert(!function.modifiers.isExternal);
-    assert(elements[function] != null);
+    assert(elements.getFunctionDefinition(function) != null);
     openFunction(functionElement, function);
     String name = functionElement.name;
     // If [functionElement] is `operator==` we explicitely add a null check at
@@ -2895,7 +2895,8 @@
   visitFunctionDeclaration(ast.FunctionDeclaration node) {
     assert(isReachable);
     visit(node.function);
-    LocalFunctionElement localFunction = elements[node];
+    LocalFunctionElement localFunction =
+        elements.getFunctionDefinition(node.function);
     localsHandler.updateLocal(localFunction, pop());
   }
 
@@ -4884,48 +4885,51 @@
     closeAndGotoExit(new HThrow(exception, isRethrow: true));
   }
 
+  visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) {
+    ConstructorElement targetConstructor =
+        elements.getRedirectingTargetConstructor(node).implementation;
+    ConstructorElement redirectingConstructor = sourceElement.implementation;
+    List<HInstruction> inputs = <HInstruction>[];
+    FunctionSignature targetSignature = targetConstructor.functionSignature;
+    FunctionSignature redirectingSignature =
+        redirectingConstructor.functionSignature;
+    redirectingSignature.forEachRequiredParameter((ParameterElement element) {
+      inputs.add(localsHandler.readLocal(element));
+    });
+    List<Element> targetOptionals =
+        targetSignature.orderedOptionalParameters;
+    List<Element> redirectingOptionals =
+        redirectingSignature.orderedOptionalParameters;
+    int i = 0;
+    for (; i < redirectingOptionals.length; i++) {
+      ParameterElement parameter = redirectingOptionals[i];
+      inputs.add(localsHandler.readLocal(parameter));
+    }
+    for (; i < targetOptionals.length; i++) {
+      inputs.add(handleConstantForOptionalParameter(targetOptionals[i]));
+    }
+    ClassElement targetClass = targetConstructor.enclosingClass;
+    if (backend.classNeedsRti(targetClass)) {
+      ClassElement cls = redirectingConstructor.enclosingClass;
+      InterfaceType targetType =
+          redirectingConstructor.computeEffectiveTargetType(cls.thisType);
+      targetType = localsHandler.substInContext(targetType);
+      targetType.typeArguments.forEach((DartType argument) {
+        inputs.add(analyzeTypeArgument(argument));
+      });
+    }
+    pushInvokeStatic(node, targetConstructor, inputs);
+    HInstruction value = pop();
+    emitReturn(value, node);
+  }
+
   visitReturn(ast.Return node) {
     if (identical(node.beginToken.stringValue, 'native')) {
       native.handleSsaNative(this, node.expression);
       return;
     }
     HInstruction value;
-    if (node.isRedirectingFactoryBody) {
-      FunctionElement targetConstructor =
-          elements[node.expression].implementation;
-      ConstructorElement redirectingConstructor = sourceElement.implementation;
-      List<HInstruction> inputs = <HInstruction>[];
-      FunctionSignature targetSignature = targetConstructor.functionSignature;
-      FunctionSignature redirectingSignature =
-          redirectingConstructor.functionSignature;
-      redirectingSignature.forEachRequiredParameter((ParameterElement element) {
-        inputs.add(localsHandler.readLocal(element));
-      });
-      List<Element> targetOptionals =
-          targetSignature.orderedOptionalParameters;
-      List<Element> redirectingOptionals =
-          redirectingSignature.orderedOptionalParameters;
-      int i = 0;
-      for (; i < redirectingOptionals.length; i++) {
-        ParameterElement parameter = redirectingOptionals[i];
-        inputs.add(localsHandler.readLocal(parameter));
-      }
-      for (; i < targetOptionals.length; i++) {
-        inputs.add(handleConstantForOptionalParameter(targetOptionals[i]));
-      }
-      ClassElement targetClass = targetConstructor.enclosingClass;
-      if (backend.classNeedsRti(targetClass)) {
-        ClassElement cls = redirectingConstructor.enclosingClass;
-        InterfaceType targetType =
-            redirectingConstructor.computeEffectiveTargetType(cls.thisType);
-        targetType = localsHandler.substInContext(targetType);
-        targetType.typeArguments.forEach((DartType argument) {
-          inputs.add(analyzeTypeArgument(argument));
-        });
-      }
-      pushInvokeStatic(node, targetConstructor, inputs);
-      value = pop();
-    } else if (node.expression == null) {
+    if (node.expression == null) {
       value = graph.addConstantNull(compiler);
     } else {
       visit(node.expression);
@@ -5114,7 +5118,7 @@
       pushInvokeDynamic(node, call, [iterator]);
 
       ast.Node identifier = node.declaredIdentifier;
-      Element variable = elements[identifier];
+      Element variable = elements.getForInVariable(node);
       Selector selector = elements.getSelector(identifier);
 
       HInstruction value = pop();
@@ -6028,6 +6032,11 @@
     tooDifficult = true;
   }
 
+  void visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) {
+    if (!registerNode()) return;
+    tooDifficult = true;
+  }
+
   void visitRethrow(ast.Rethrow node) {
     if (!registerNode()) return;
     tooDifficult = true;
@@ -6036,8 +6045,7 @@
   void visitReturn(ast.Return node) {
     if (!registerNode()) return;
     if (seenReturn
-        || identical(node.beginToken.stringValue, 'native')
-        || node.isRedirectingFactoryBody) {
+        || identical(node.beginToken.stringValue, 'native')) {
       tooDifficult = true;
       return;
     }
diff --git a/sdk/lib/_internal/compiler/implementation/tree/nodes.dart b/sdk/lib/_internal/compiler/implementation/tree/nodes.dart
index 6b94a26..c00d7b19 100644
--- a/sdk/lib/_internal/compiler/implementation/tree/nodes.dart
+++ b/sdk/lib/_internal/compiler/implementation/tree/nodes.dart
@@ -65,6 +65,9 @@
   R visitPartOf(PartOf node) => visitNode(node);
   R visitPostfix(Postfix node) => visitNodeList(node);
   R visitPrefix(Prefix node) => visitNodeList(node);
+  R visitRedirectingFactoryBody(RedirectingFactoryBody node) {
+    return visitStatement(node);
+  }
   R visitRethrow(Rethrow node) => visitStatement(node);
   R visitReturn(Return node) => visitStatement(node);
   R visitSend(Send node) => visitExpression(node);
@@ -110,7 +113,7 @@
  * token stream. These references are stored in fields ending with
  * "Token".
  */
-abstract class Node extends TreeElementMixin implements Spannable {
+abstract class Node extends NullTreeElementMixin implements Spannable {
   final int hashCode;
   static int _HASH_COUNTER = 0;
 
@@ -185,6 +188,7 @@
   ParenthesizedExpression asParenthesizedExpression() => null;
   Part asPart() => null;
   PartOf asPartOf() => null;
+  RedirectingFactoryBody asRedirectingFactoryBody() => null;
   Rethrow asRethrow() => null;
   Return asReturn() => null;
   Send asSend() => null;
@@ -338,7 +342,7 @@
  * property access, assignment, operators, and method calls with this
  * one node.
  */
-class Send extends Expression {
+class Send extends Expression with StoredTreeElementMixin {
   final Node receiver;
   final Node selector;
   final NodeList argumentsNode;
@@ -701,7 +705,7 @@
   Token getEndToken() => function.getEndToken();
 }
 
-class FunctionExpression extends Expression {
+class FunctionExpression extends Expression with StoredTreeElementMixin {
   final Node name;
 
   /**
@@ -728,8 +732,7 @@
   accept(Visitor visitor) => visitor.visitFunctionExpression(this);
 
   bool get isRedirectingFactory {
-    return body != null && body.asReturn() != null &&
-        body.asReturn().isRedirectingFactoryBody;
+    return body != null && body.asRedirectingFactoryBody() != null;
   }
 
   visitChildren(Visitor visitor) {
@@ -975,7 +978,7 @@
   }
 }
 
-class Identifier extends Expression {
+class Identifier extends Expression with StoredTreeElementMixin {
   final Token token;
 
   String get source => token.value;
@@ -1022,8 +1025,6 @@
 
   bool get hasExpression => expression != null;
 
-  bool get isRedirectingFactoryBody => beginToken.stringValue == '=';
-
   accept(Visitor visitor) => visitor.visitReturn(this);
 
   visitChildren(Visitor visitor) {
@@ -1038,6 +1039,27 @@
   }
 }
 
+class RedirectingFactoryBody extends Statement with StoredTreeElementMixin {
+  final Node constructorReference;
+  final Token beginToken;
+  final Token endToken;
+
+  RedirectingFactoryBody(this.beginToken, this.endToken,
+                         this.constructorReference);
+
+  RedirectingFactoryBody asRedirectingFactoryBody() => this;
+
+  accept(Visitor visitor) => visitor.visitRedirectingFactoryBody(this);
+
+  visitChildren(Visitor visitor) {
+    constructorReference.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => endToken;
+}
+
 class ExpressionStatement extends Statement {
   final Expression expression;
   final Token endToken;
@@ -1665,7 +1687,7 @@
   accept(Visitor visitor) => visitor.visitContinueStatement(this);
 }
 
-class ForIn extends Loop {
+class ForIn extends Loop with StoredTreeElementMixin {
   final Node declaredIdentifier;
   final Expression expression;
 
diff --git a/sdk/lib/_internal/compiler/implementation/tree/prettyprint.dart b/sdk/lib/_internal/compiler/implementation/tree/prettyprint.dart
index d995f44..7a998fb 100644
--- a/sdk/lib/_internal/compiler/implementation/tree/prettyprint.dart
+++ b/sdk/lib/_internal/compiler/implementation/tree/prettyprint.dart
@@ -303,6 +303,12 @@
     visitNodeWithChildren(node, "ParenthesizedExpression");
   }
 
+  visitRedirectingFactoryBody(RedirectingFactoryBody node) {
+    openNode(node, "RedirectingFactoryBody");
+    visitChildNode(node.constructorReference, "constructorReference");
+    closeNode();
+  }
+
   visitRethrow(Rethrow node) {
     visitNodeWithChildren(node, "Rethrow");
   }
diff --git a/sdk/lib/_internal/compiler/implementation/tree/tree.dart b/sdk/lib/_internal/compiler/implementation/tree/tree.dart
index cc1ceb0..b302f31 100644
--- a/sdk/lib/_internal/compiler/implementation/tree/tree.dart
+++ b/sdk/lib/_internal/compiler/implementation/tree/tree.dart
@@ -10,7 +10,8 @@
 import '../util/util.dart';
 import '../util/characters.dart';
 
-import '../resolution/secret_tree_element.dart' show TreeElementMixin;
+import '../resolution/secret_tree_element.dart'
+    show StoredTreeElementMixin, NullTreeElementMixin;
 
 import '../elements/elements.dart' show MetadataAnnotation;
 
diff --git a/sdk/lib/_internal/compiler/implementation/tree/unparser.dart b/sdk/lib/_internal/compiler/implementation/tree/unparser.dart
index fceda5b..a09aec5 100644
--- a/sdk/lib/_internal/compiler/implementation/tree/unparser.dart
+++ b/sdk/lib/_internal/compiler/implementation/tree/unparser.dart
@@ -366,14 +366,19 @@
     visitIdentifier(node);
   }
 
+  visitRedirectingFactoryBody(RedirectingFactoryBody node) {
+    space();
+    write(node.beginToken.value);
+    space();
+    visit(node.constructorReference);
+    write(node.endToken.value);
+  }
+
   visitRethrow(Rethrow node) {
     write('rethrow;');
   }
 
   visitReturn(Return node) {
-    if (node.isRedirectingFactoryBody) {
-      write(' ');
-    }
     write(node.beginToken.value);
     if (node.hasExpression && node.beginToken.stringValue != '=>') {
       write(' ');
diff --git a/sdk/lib/_internal/compiler/implementation/tree_validator.dart b/sdk/lib/_internal/compiler/implementation/tree_validator.dart
index 399ad60..e3dfcc9 100644
--- a/sdk/lib/_internal/compiler/implementation/tree_validator.dart
+++ b/sdk/lib/_internal/compiler/implementation/tree_validator.dart
@@ -58,7 +58,7 @@
   }
 
   visitReturn(Return node) {
-    if (!node.isRedirectingFactoryBody && node.hasExpression) {
+    if (node.hasExpression) {
       // We allow non-expression expressions in Return nodes, but only when
       // using them to hold redirecting factory constructors.
       expect(node, node.expression.asExpression() != null);
diff --git a/sdk/lib/_internal/compiler/implementation/typechecker.dart b/sdk/lib/_internal/compiler/implementation/typechecker.dart
index f0391fe..522e6c2 100644
--- a/sdk/lib/_internal/compiler/implementation/typechecker.dart
+++ b/sdk/lib/_internal/compiler/implementation/typechecker.dart
@@ -546,40 +546,41 @@
   }
 
   DartType visitDoWhile(DoWhile node) {
-    StatementType bodyType = analyze(node.body);
+    analyze(node.body);
     checkCondition(node.condition);
-    return bodyType.join(StatementType.NOT_RETURNING);
+    return const StatementType();
   }
 
   DartType visitExpressionStatement(ExpressionStatement node) {
     Expression expression = node.expression;
     analyze(expression);
-    return (expression.asThrow() != null)
-        ? StatementType.RETURNING
-        : StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   /** Dart Programming Language Specification: 11.5.1 For Loop */
   DartType visitFor(For node) {
-    analyzeWithDefault(node.initializer, StatementType.NOT_RETURNING);
+    if (node.initializer != null) {
+      analyze(node.initializer);
+    }
     if (node.condition != null) {
       checkCondition(node.condition);
     }
-    analyzeWithDefault(node.update, StatementType.NOT_RETURNING);
-    StatementType bodyType = analyze(node.body);
-    return bodyType.join(StatementType.NOT_RETURNING);
+    if (node.update != null) {
+      analyze(node.update);
+    }
+    return analyze(node.body);
   }
 
   DartType visitFunctionDeclaration(FunctionDeclaration node) {
     analyze(node.function);
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   DartType visitFunctionExpression(FunctionExpression node) {
     DartType type;
     DartType returnType;
     DartType previousType;
-    final FunctionElement element = elements[node];
+    final FunctionElement element = elements.getFunctionDefinition(node);
     assert(invariant(node, element != null,
                      message: 'FunctionExpression with no element'));
     if (Elements.isUnresolved(element)) return const DynamicType();
@@ -605,17 +606,7 @@
     }
     DartType previous = expectedReturnType;
     expectedReturnType = returnType;
-    StatementType bodyType = analyze(node.body);
-    if (!returnType.isVoid && !returnType.treatAsDynamic
-        && bodyType != StatementType.RETURNING) {
-      MessageKind kind;
-      if (bodyType == StatementType.MAYBE_RETURNING) {
-        kind = MessageKind.MAYBE_MISSING_RETURN;
-      } else {
-        kind = MessageKind.MISSING_RETURN;
-      }
-      reportTypeWarning(node.name, kind);
-    }
+    analyze(node.body);
     expectedReturnType = previous;
     return type;
   }
@@ -642,12 +633,11 @@
     Statement thenPart = node.thenPart;
 
     checkCondition(node.condition);
-
-    StatementType thenType = analyzeInPromotedContext(condition, thenPart);
-
-    StatementType elseType = node.hasElsePart ? analyze(node.elsePart)
-                                              : StatementType.NOT_RETURNING;
-    return thenType.join(elseType);
+    analyzeInPromotedContext(condition, thenPart);
+    if (node.elsePart != null) {
+      analyze(node.elsePart);
+    }
+    return const StatementType();
   }
 
   void checkPrivateAccess(Node node, Element element, String name) {
@@ -1537,40 +1527,26 @@
   }
 
   DartType visitNodeList(NodeList node) {
-    DartType type = StatementType.NOT_RETURNING;
-    bool reportedDeadCode = false;
     for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
-      DartType nextType =
-          analyze(link.head, inInitializer: analyzingInitializer);
-      if (type == StatementType.RETURNING) {
-        if (!reportedDeadCode) {
-          reportTypeWarning(link.head, MessageKind.UNREACHABLE_CODE);
-          reportedDeadCode = true;
-        }
-      } else if (type == StatementType.MAYBE_RETURNING){
-        if (nextType == StatementType.RETURNING) {
-          type = nextType;
-        }
-      } else {
-        type = nextType;
-      }
+      analyze(link.head, inInitializer: analyzingInitializer);
     }
-    return type;
+    return const StatementType();
+  }
+
+  DartType visitRedirectingFactoryBody(RedirectingFactoryBody node) {
+    // TODO(lrn): Typecheck the body. It must refer to the constructor
+    // of a subtype.
+    return const StatementType();
   }
 
   DartType visitRethrow(Rethrow node) {
-    return StatementType.RETURNING;
+    return const StatementType();
   }
 
   /** Dart Programming Language Specification: 11.10 Return */
   DartType visitReturn(Return node) {
     if (identical(node.beginToken.stringValue, 'native')) {
-      return StatementType.RETURNING;
-    }
-    if (node.isRedirectingFactoryBody) {
-      // TODO(lrn): Typecheck the body. It must refer to the constructor
-      // of a subtype.
-      return StatementType.RETURNING;
+      return const StatementType();
     }
 
     final expression = node.expression;
@@ -1600,7 +1576,7 @@
       reportTypeWarning(node, MessageKind.RETURN_NOTHING,
                         {'returnType': expectedReturnType});
     }
-    return StatementType.RETURNING;
+    return const StatementType();
   }
 
   DartType visitThrow(Throw node) {
@@ -1630,23 +1606,14 @@
         checkAssignable(initialization.assignmentOperator, initializer, type);
       }
     }
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   DartType visitWhile(While node) {
     checkCondition(node.condition);
-    StatementType bodyType = analyze(node.body);
+    analyze(node.body);
     Expression cond = node.condition.asParenthesizedExpression().expression;
-    if (cond.asLiteralBool() != null && cond.asLiteralBool().value == true) {
-      // If the condition is a constant boolean expression denoting true,
-      // control-flow always enters the loop body.
-      // TODO(karlklose): this should be StatementType.RETURNING unless there
-      // is a break in the loop body that has the loop or a label outside the
-      // loop as a target.
-      return bodyType;
-    } else {
-      return bodyType.join(StatementType.NOT_RETURNING);
-    }
+    return const StatementType();
   }
 
   DartType visitParenthesizedExpression(ParenthesizedExpression node) {
@@ -1681,21 +1648,21 @@
   }
 
   visitEmptyStatement(EmptyStatement node) {
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   visitBreakStatement(BreakStatement node) {
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   visitContinueStatement(ContinueStatement node) {
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   visitForIn(ForIn node) {
     analyze(node.expression);
-    StatementType bodyType = analyze(node.body);
-    return bodyType.join(StatementType.NOT_RETURNING);
+    analyze(node.body);
+    return const StatementType();
   }
 
   visitLabeledStatement(LabeledStatement node) {
@@ -1747,7 +1714,7 @@
       analyze(switchCase);
     }
 
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   visitSwitchCase(SwitchCase node) {
@@ -1763,7 +1730,7 @@
       analyze(catchBlock);
     }
     analyzeWithDefault(node.finallyBlock, null);
-    return StatementType.NOT_RETURNING;
+    return const StatementType();
   }
 
   visitCatchBlock(CatchBlock node) {
diff --git a/sdk/lib/_internal/compiler/implementation/use_unused_api.dart b/sdk/lib/_internal/compiler/implementation/use_unused_api.dart
index 41a6a6f..202bb23 100644
--- a/sdk/lib/_internal/compiler/implementation/use_unused_api.dart
+++ b/sdk/lib/_internal/compiler/implementation/use_unused_api.dart
@@ -107,6 +107,7 @@
     ..asPart()
     ..asPartOf()
     ..asRethrow()
+    ..asReturn()
     ..asStatement()
     ..asStringInterpolation()
     ..asStringInterpolationPart()
diff --git a/sdk/lib/_internal/compiler/implementation/warnings.dart b/sdk/lib/_internal/compiler/implementation/warnings.dart
index 02c2ebf..6fc0277 100644
--- a/sdk/lib/_internal/compiler/implementation/warnings.dart
+++ b/sdk/lib/_internal/compiler/implementation/warnings.dart
@@ -146,15 +146,6 @@
   static const MessageKind THIS_IS_THE_METHOD = const MessageKind(
       "This is the method declaration.");
 
-  static const MessageKind UNREACHABLE_CODE = const MessageKind(
-      "Unreachable code.");
-
-  static const MessageKind MISSING_RETURN = const MessageKind(
-      "Missing return.");
-
-  static const MessageKind MAYBE_MISSING_RETURN = const MessageKind(
-      "Not all paths lead to a return or throw statement.");
-
   static const MessageKind CANNOT_RESOLVE = const MessageKind(
       "Cannot resolve '#{name}'.");
 
@@ -991,6 +982,16 @@
   static const MessageKind ILLEGAL_FINAL_METHOD_MODIFIER = const MessageKind(
       "Cannot have final modifier on method.");
 
+  static const MessageKind ILLEGAL_CONST_FIELD_MODIFIER = const MessageKind(
+      "Cannot have const modifier on non-static field.",
+      howToFix: "Try adding a static modifier, or removing the const modifier.",
+      examples: const ["""
+class C {
+  const int a = 1;
+}
+
+main() => new C();"""]);
+
   static const MessageKind ILLEGAL_CONSTRUCTOR_MODIFIERS = const MessageKind(
       "Illegal constructor modifiers: '#{modifiers}'.");
 
@@ -1175,7 +1176,8 @@
 
   static const MessageKind DEFERRED_LIBRARY_DART_2_DART =
       const MessageKind(
-          "Deferred loading is not supported by the dart backend yet.");
+          "Deferred loading is not supported by the dart backend yet."
+          "The output will not be split.");
 
   static const MessageKind DEFERRED_LIBRARY_WITHOUT_PREFIX =
       const MessageKind(
@@ -1477,6 +1479,11 @@
           howToFix: "Try using a different name.",
           examples: const ["do() {} main() {}"]);
 
+  static const MessageKind  NAMED_FUNCTION_EXPRESSION =
+      const MessageKind("Function expression '#{name}' cannot be named.",
+          howToFix: "Try removing the name.",
+          examples: const ["main() { var f = func() {}; }"]);
+
   static const MessageKind UNUSED_METHOD = const MessageKind(
       "The method '#{name}' is never called.",
       howToFix: "Consider deleting it.",
diff --git a/sdk/lib/_internal/compiler/implementation/world.dart b/sdk/lib/_internal/compiler/implementation/world.dart
index b2e9c9c..9ecb9ea 100644
--- a/sdk/lib/_internal/compiler/implementation/world.dart
+++ b/sdk/lib/_internal/compiler/implementation/world.dart
@@ -10,6 +10,8 @@
   final Set<Element> functionsCalledInLoop = new Set<Element>();
   final Map<Element, SideEffects> sideEffects = new Map<Element, SideEffects>();
 
+  final Set<TypedefElement> allTypedefs = new Set<TypedefElement>();
+
   final Map<ClassElement, Set<MixinApplicationElement>> mixinUses =
       new Map<ClassElement, Set<MixinApplicationElement>>();
 
diff --git a/sdk/lib/_internal/lib/js_mirrors.dart b/sdk/lib/_internal/lib/js_mirrors.dart
index 912af14..c322846 100644
--- a/sdk/lib/_internal/lib/js_mirrors.dart
+++ b/sdk/lib/_internal/lib/js_mirrors.dart
@@ -4,7 +4,7 @@
 
 library dart._js_mirrors;
 
-import 'dart:async';
+import '../compiler/implementation/runtime_data.dart' as encoding;
 
 import 'dart:collection' show
     UnmodifiableListView,
@@ -334,10 +334,12 @@
       var cls = reflectClassByMangledName(className);
       if (cls is ClassMirror) {
         cls = cls.originalDeclaration;
-        if (cls is JsClassMirror) {
-          result[cls.simpleName] = cls;
-          cls._owner = this;
-        }
+      }
+      if (cls is JsClassMirror) {
+        result[cls.simpleName] = cls;
+        cls._owner = this;
+      } else  if (cls is JsTypedefMirror) {
+        result[cls.simpleName] = cls;
       }
     }
     return _cachedClasses =
@@ -572,12 +574,6 @@
   }
   var constructor = JS('var', 'init.allClasses[#]', mangledName);
   if (constructor == null) {
-    int index = JS('int|Null', 'init.functionAliases[#]', mangledName);
-    if (index != null) {
-      mirror = new JsTypedefMirror(symbol, mangledName, getMetadata(index));
-      JsCache.update(classMirrors, mangledName, mirror);
-      return mirror;
-    }
     // Probably an intercepted class.
     // TODO(ahe): How to handle intercepted classes?
     throw new UnsupportedError('Cannot find class for: ${n(symbol)}');
@@ -602,23 +598,28 @@
     }
   }
 
-  var superclassName = fields.split(';')[0];
-  var mixins = superclassName.split('+');
-  if (mixins.length > 1 && mangledGlobalNames[mangledName] == null) {
-    mirror = reflectMixinApplication(mixins, mangledName);
+  if (encoding.isTypedefDescriptor(fields)) {
+    int index = encoding.getTypeFromTypedef(fields);
+    mirror = new JsTypedefMirror(symbol, mangledName, getMetadata(index));
   } else {
-    ClassMirror classMirror = new JsClassMirror(
-        symbol, mangledName, constructor, fields, fieldsMetadata);
-    List typeVariables =
-        JS('JSExtendableArray|Null', '#.prototype["<>"]', constructor);
-    if (typeVariables == null || typeVariables.length == 0) {
-      mirror = classMirror;
+    var superclassName = fields.split(';')[0];
+    var mixins = superclassName.split('+');
+    if (mixins.length > 1 && mangledGlobalNames[mangledName] == null) {
+      mirror = reflectMixinApplication(mixins, mangledName);
     } else {
-      String typeArguments = 'dynamic';
-      for (int i = 1; i < typeVariables.length; i++) {
-        typeArguments += ',dynamic';
+      ClassMirror classMirror = new JsClassMirror(
+          symbol, mangledName, constructor, fields, fieldsMetadata);
+      List typeVariables =
+          JS('JSExtendableArray|Null', '#.prototype["<>"]', constructor);
+      if (typeVariables == null || typeVariables.length == 0) {
+        mirror = classMirror;
+      } else {
+        String typeArguments = 'dynamic';
+        for (int i = 1; i < typeVariables.length; i++) {
+          typeArguments += ',dynamic';
+        }
+        mirror = new JsTypeBoundClassMirror(classMirror, typeArguments);
       }
-      mirror = new JsTypeBoundClassMirror(classMirror, typeArguments);
     }
   }
 
diff --git a/sdk/lib/_internal/pub/lib/src/barback/transformer_isolate.dart b/sdk/lib/_internal/pub/lib/src/barback/transformer_isolate.dart
index 9a769e4..3b665fc 100644
--- a/sdk/lib/_internal/pub/lib/src/barback/transformer_isolate.dart
+++ b/sdk/lib/_internal/pub/lib/src/barback/transformer_isolate.dart
@@ -83,8 +83,9 @@
         // TODO(nweiz): don't parse this as a string once issues 12617 and 12689
         // are fixed.
         var firstErrorLine = error.message.split('\n')[1];
-        var missingTransformer = idsToUrls.keys.firstWhere((id) =>
-                firstErrorLine.startsWith("Failure getting ${idsToUrls[id]}:"),
+        var missingTransformer = idsToUrls.keys.firstWhere(
+            (id) => firstErrorLine.startsWith(
+                "Uncaught Error: Failure getting ${idsToUrls[id]}:"),
             orElse: () => throw error);
         var packageUri = idToPackageUri(idsToAssetIds[missingTransformer]);
 
diff --git a/sdk/lib/_internal/pub/lib/src/command/global_activate.dart b/sdk/lib/_internal/pub/lib/src/command/global_activate.dart
index 6fc25e3..98ba40e 100644
--- a/sdk/lib/_internal/pub/lib/src/command/global_activate.dart
+++ b/sdk/lib/_internal/pub/lib/src/command/global_activate.dart
@@ -13,34 +13,56 @@
 /// Handles the `global activate` pub command.
 class GlobalActivateCommand extends PubCommand {
   String get description => "Make a package's executables globally available.";
-  String get usage => "pub global activate <package> [version]";
+  String get usage => "pub global activate <package...>";
   bool get takesArguments => true;
 
+  GlobalActivateCommand() {
+    commandParser.addOption("source",
+        abbr: "s",
+        help: "The source used to find the package.",
+        allowed: ["hosted", "path"],
+        defaultsTo: "hosted");
+  }
+
   Future onRun() {
-    // Make sure there is a package.
-    if (commandOptions.rest.isEmpty) {
-      usageError("No package to activate given.");
+    var args = commandOptions.rest;
+
+    readArg([String error]) {
+      if (args.isEmpty) usageError(error);
+      var arg = args.first;
+      args = args.skip(1);
+      return arg;
     }
 
-    // Don't allow extra arguments.
-    if (commandOptions.rest.length > 2) {
-      var unexpected = commandOptions.rest.skip(2).map((arg) => '"$arg"');
+    validateNoExtraArgs() {
+      if (args.isEmpty) return;
+      var unexpected = args.map((arg) => '"$arg"');
       var arguments = pluralize("argument", unexpected.length);
       usageError("Unexpected $arguments ${toSentence(unexpected)}.");
     }
 
-    var package = commandOptions.rest.first;
+    var package = readArg("No package to activate given.");
 
-    // Parse the version constraint, if there is one.
-    var constraint = VersionConstraint.any;
-    if (commandOptions.rest.length == 2) {
-      try {
-        constraint = new VersionConstraint.parse(commandOptions.rest[1]);
-      } on FormatException catch (error) {
-        usageError(error.message);
-      }
+    switch (commandOptions["source"]) {
+      case "hosted":
+        // Parse the version constraint, if there is one.
+        var constraint = VersionConstraint.any;
+        if (args.isNotEmpty) {
+          try {
+            constraint = new VersionConstraint.parse(readArg());
+          } on FormatException catch (error) {
+            usageError(error.message);
+          }
+        }
+
+        validateNoExtraArgs();
+        return globals.activateHosted(package, constraint);
+
+      case "path":
+        validateNoExtraArgs();
+        return globals.activatePath(package);
     }
 
-    return globals.activate(package, constraint);
+    throw "unreachable";
   }
 }
diff --git a/sdk/lib/_internal/pub/lib/src/command/global_deactivate.dart b/sdk/lib/_internal/pub/lib/src/command/global_deactivate.dart
index 585d9d1..2148347 100644
--- a/sdk/lib/_internal/pub/lib/src/command/global_deactivate.dart
+++ b/sdk/lib/_internal/pub/lib/src/command/global_deactivate.dart
@@ -7,6 +7,7 @@
 import 'dart:async';
 
 import '../command.dart';
+import '../log.dart' as log;
 import '../utils.dart';
 
 /// Handles the `global deactivate` pub command.
@@ -28,7 +29,10 @@
       usageError("Unexpected $arguments ${toSentence(unexpected)}.");
     }
 
-    globals.deactivate(commandOptions.rest.first);
+    if (!globals.deactivate(commandOptions.rest.first, logDeactivate: true)) {
+      dataError("No active package ${log.bold(commandOptions.rest.first)}.");
+    }
+
     return null;
   }
 }
diff --git a/sdk/lib/_internal/pub/lib/src/command/lish.dart b/sdk/lib/_internal/pub/lib/src/command/lish.dart
index a58ae3c..8263505 100644
--- a/sdk/lib/_internal/pub/lib/src/command/lish.dart
+++ b/sdk/lib/_internal/pub/lib/src/command/lish.dart
@@ -26,7 +26,20 @@
   List<String> get aliases => const ["lish", "lush"];
 
   /// The URL of the server to which to upload the package.
-  Uri get server => Uri.parse(commandOptions['server']);
+  Uri get server {
+    // An explicit argument takes precedence.
+    if (commandOptions.wasParsed('server')) {
+      return Uri.parse(commandOptions['server']);
+    }
+
+    // Otherwise, use the one specified in the pubspec.
+    if (entrypoint.root.pubspec.publishTo != null) {
+      return Uri.parse(entrypoint.root.pubspec.publishTo);
+    }
+
+    // Otherwise, use the default.
+    return Uri.parse(HostedSource.defaultUrl);
+  }
 
   /// Whether the publish is just a preview.
   bool get dryRun => commandOptions['dry-run'];
@@ -98,6 +111,12 @@
       usageError('Cannot use both --force and --dry-run.');
     }
 
+    if (entrypoint.root.pubspec.isPrivate) {
+      dataError('A private package cannot be published.\n'
+          'You can enable this by changing the "publishTo" field in your '
+              'pubspec.');
+    }
+
     var files = entrypoint.root.listFiles();
     log.fine('Archiving and publishing ${entrypoint.root}.');
 
diff --git a/sdk/lib/_internal/pub/lib/src/entrypoint.dart b/sdk/lib/_internal/pub/lib/src/entrypoint.dart
index 6ce137d..9d2f815 100644
--- a/sdk/lib/_internal/pub/lib/src/entrypoint.dart
+++ b/sdk/lib/_internal/pub/lib/src/entrypoint.dart
@@ -210,7 +210,7 @@
 
   /// Gets dependencies if the lockfile is out of date with respect to the
   /// pubspec.
-  Future _ensureLockFileIsUpToDate() {
+  Future ensureLockFileIsUpToDate() {
     return syncFuture(() {
       // If we don't have a current lock file, we definitely need to install.
       if (!_isLockFileUpToDate(lockFile)) {
@@ -249,7 +249,7 @@
   /// Before loading, makes sure the lockfile and dependencies are installed
   /// and up to date.
   Future<PackageGraph> loadPackageGraph() {
-    return _ensureLockFileIsUpToDate().then((_) {
+    return ensureLockFileIsUpToDate().then((_) {
       return Future.wait(lockFile.packages.values.map((id) {
         var source = cache.sources[id.source];
         return source.getDirectory(id)
diff --git a/sdk/lib/_internal/pub/lib/src/global_packages.dart b/sdk/lib/_internal/pub/lib/src/global_packages.dart
index b9bb548..c47e979 100644
--- a/sdk/lib/_internal/pub/lib/src/global_packages.dart
+++ b/sdk/lib/_internal/pub/lib/src/global_packages.dart
@@ -16,7 +16,9 @@
 import 'package.dart';
 import 'system_cache.dart';
 import 'solver/version_solver.dart';
+import 'source.dart';
 import 'source/cached.dart';
+import 'source/path.dart';
 import 'utils.dart';
 import 'version.dart';
 
@@ -29,6 +31,21 @@
 /// Only one version of a given package name can be globally activated at a
 /// time. Activating a different version of a package will deactivate the
 /// previous one.
+///
+/// This handles packages from uncached and cached sources a little differently.
+/// For a cached source, the package is physically in the user's pub cache and
+/// we don't want to mess with it by putting a lockfile in there. Instead, when
+/// we activate the package, we create a full lockfile and put it in the
+/// "global_packages" directory. It's named "<package>.lock". Unlike a normal
+/// lockfile, it also contains an entry for the root package itself, so that we
+/// know the version and description that was activated.
+///
+/// Uncached packages (i.e. "path" packages) are somewhere else on the user's
+/// local file system and can have a lockfile directly in place. (And, in fact,
+/// we want to ensure we honor the user's lockfile there.) To activate it, we
+/// just need to know where that package directory is. For that, we create a
+/// lockfile that *only* contains the root package's [PackageId] -- basically
+/// just the path to the directory where the real lockfile lives.
 class GlobalPackages {
   /// The [SystemCache] containing the global packages.
   final SystemCache cache;
@@ -36,10 +53,6 @@
   /// The directory where the lockfiles for activated packages are stored.
   String get _directory => p.join(cache.rootDir, "global_packages");
 
-  /// The source that global packages can be activated from.
-  // TODO(rnystrom): Allow activating packages from other sources.
-  CachedSource get _source => cache.sources["hosted"] as CachedSource;
-
   /// Creates a new global package registry backed by the given directory on
   /// the user's file system.
   ///
@@ -49,109 +62,207 @@
 
   /// Finds the latest version of the hosted package with [name] that matches
   /// [constraint] and makes it the active global version.
-  Future activate(String name, VersionConstraint constraint) {
+  Future activateHosted(String name, VersionConstraint constraint) {
     // See if we already have it activated.
-    var lockFile;
+    var lockFile = _describeActive(name);
     var currentVersion;
-    try {
-      lockFile = new LockFile.load(_getLockFilePath(name), cache.sources);
-      currentVersion = lockFile.packages[name].version;
+    if (lockFile != null) {
+      var id = lockFile.packages[name];
+
+      // Try to preserve the current version if we've already activated the
+      // hosted package.
+      if (id.source == "hosted") currentVersion = id.version;
 
       // Pull the root package out of the lock file so the solver doesn't see
       // it.
       lockFile.packages.remove(name);
-
-      log.message("Package ${log.bold(name)} is already active at "
-          "version ${log.bold(currentVersion)}.");
-    } on IOException catch (error) {
-      // If we couldn't read the lock file, it's not activated.
+    } else {
       lockFile = new LockFile.empty();
     }
 
-    var package;
-    var id;
     return _selectVersion(name, currentVersion, constraint).then((version) {
       // Make sure it's in the cache.
-      id = new PackageId(name, _source.name, version, name);
-      return _source.downloadToSystemCache(id);
-    }).then((p) {
-      package = p;
-      // Resolve it and download its dependencies.
-      return resolveVersions(SolveType.GET, cache.sources, package,
-          lockFile: lockFile);
+      var id = new PackageId(name, "hosted", version, name);
+      return _installInCache(id, lockFile);
+    });
+  }
+
+  /// Makes the local package at [path] globally active.
+  Future activatePath(String path) {
+    var entrypoint = new Entrypoint(path, cache);
+
+    // Get the package's dependencies.
+    return entrypoint.ensureLockFileIsUpToDate().then((_) {
+      var name = entrypoint.root.name;
+
+      // Call this just to log what the current active package is, if any.
+      _describeActive(name);
+
+      // Write a lockfile that points to the local package.
+      var fullPath = canonicalize(entrypoint.root.dir);
+      var id = new PackageId(name, "path", entrypoint.root.version,
+          PathSource.describePath(fullPath));
+      _writeLockFile(id, new LockFile.empty());
+    });
+  }
+
+  /// Installs the package [id] with [lockFile] into the system cache along
+  /// with its dependencies.
+  Future _installInCache(PackageId id, LockFile lockFile) {
+    var source = cache.sources[id.source];
+
+    // Put the main package in the cache.
+    return source.downloadToSystemCache(id).then((package) {
+      // If we didn't know the version for the ID (which is true for Git
+      // packages), look it up now that we have it.
+      if (id.version == Version.none) {
+        id = id.atVersion(package.version);
+      }
+
+      return source.resolveId(id).then((id_) {
+        id = id_;
+
+        // Resolve it and download its dependencies.
+        return resolveVersions(SolveType.GET, cache.sources, package,
+            lockFile: lockFile);
+      });
     }).then((result) {
       if (!result.succeeded) throw result.error;
       result.showReport(SolveType.GET);
 
       // Make sure all of the dependencies are locally installed.
-      return Future.wait(result.packages.map((id) {
-        var source = cache.sources[id.source];
-        if (source is! CachedSource) return new Future.value();
-        return source.downloadToSystemCache(id)
-            .then((_) => source.resolveId(id));
-      }));
+      return Future.wait(result.packages.map(_cacheDependency));
     }).then((ids) {
-      var lockFile = new LockFile(ids);
-
-      // Add the root package itself to the lockfile.
-      lockFile.packages[name] = id;
-
-      ensureDir(_directory);
-      writeTextFile(_getLockFilePath(name),
-          lockFile.serialize(cache.rootDir, cache.sources));
-
-      log.message("Activated ${log.bold(package.name)} ${package.version}.");
-      // TODO(rnystrom): Look in "bin" and display list of binaries that
-      // user can run.
+      _writeLockFile(id, new LockFile(ids));
     });
   }
 
-  /// Deactivates a previously-activated package named [name] or fails with
-  /// an error if [name] is not an active package.
-  void deactivate(String name) {
-    // See if we already have it activated.
-    try {
-      var lockFilePath = p.join(_directory, "$name.lock");
-      var lockFile = new LockFile.load(lockFilePath, cache.sources);
-      var version = lockFile.packages[name].version;
+  /// Downloads [id] into the system cache if it's a cached package.
+  ///
+  /// Returns the resolved [PackageId] for [id].
+  Future<PackageId> _cacheDependency(PackageId id) {
+    var source = cache.sources[id.source];
 
-      deleteEntry(lockFilePath);
-      log.message("Deactivated package ${log.bold(name)} $version.");
+    return syncFuture(() {
+      if (id.isRoot) return null;
+      if (source is! CachedSource) return null;
+
+      return source.downloadToSystemCache(id);
+    }).then((_) => source.resolveId(id));
+  }
+
+  /// Finishes activating package [id] by saving [lockFile] in the cache.
+  void _writeLockFile(PackageId id, LockFile lockFile) {
+    // Add the root package to the lockfile.
+    lockFile.packages[id.name] = id;
+
+    ensureDir(_directory);
+    writeTextFile(_getLockFilePath(id.name),
+        lockFile.serialize(cache.rootDir, cache.sources));
+
+    if (id.source == "path") {
+      var path = PathSource.pathFromDescription(id.description);
+      log.message('Activated ${log.bold(id.name)} ${id.version} at path '
+          '"$path".');
+    } else {
+      log.message("Activated ${log.bold(id.name)} ${id.version}.");
+    }
+
+    // TODO(rnystrom): Look in "bin" and display list of binaries that
+    // user can run.
+  }
+
+  /// Gets the lock file for the currently active package with [name].
+  ///
+  /// Displays a message to the user about the current package, if any. Returns
+  /// the [LockFile] for the active package or `null` otherwise.
+  LockFile _describeActive(String package) {
+    try {
+      var lockFile = new LockFile.load(_getLockFilePath(package),
+          cache.sources);
+      var id = lockFile.packages[package];
+
+      if (id.source == "path") {
+        var path = PathSource.pathFromDescription(id.description);
+        log.message('Package ${log.bold(package)} is currently active at '
+            'path "$path".');
+      } else {
+        log.message("Package ${log.bold(package)} is currently active at "
+            "version ${log.bold(id.version)}.");
+      }
+
+      return lockFile;
     } on IOException catch (error) {
-      dataError("No active package ${log.bold(name)}.");
+      // If we couldn't read the lock file, it's not activated.
+      return null;
     }
   }
 
-  /// Finds the active packge with [name].
+  /// Deactivates a previously-activated package named [name].
+  ///
+  /// If [logDeletion] is true, displays to the user when a package is
+  /// deactivated. Otherwise, deactivates silently.
+  ///
+  /// Returns `false` if no package with [name] was currently active.
+  bool deactivate(String name, {bool logDeactivate: false}) {
+    var lockFilePath = _getLockFilePath(name);
+    if (!fileExists(lockFilePath)) return false;
+
+    var lockFile = new LockFile.load(lockFilePath, cache.sources);
+    var id = lockFile.packages[name];
+
+    deleteEntry(lockFilePath);
+
+    if (logDeactivate) {
+      if (id.source == "path") {
+        var path = PathSource.pathFromDescription(id.description);
+        log.message('Deactivated package ${log.bold(name)} at path "$path".');
+      } else {
+        log.message("Deactivated package ${log.bold(name)} ${id.version}.");
+      }
+    }
+
+    return true;
+  }
+
+  /// Finds the active package with [name].
   ///
   /// Returns an [Entrypoint] loaded with the active package if found.
   Future<Entrypoint> find(String name) {
-    var lockFile;
-    var version;
     return syncFuture(() {
+      var lockFile;
       try {
         lockFile = new LockFile.load(_getLockFilePath(name), cache.sources);
-        version = lockFile.packages[name].version;
       } on IOException catch (error) {
         // If we couldn't read the lock file, it's not activated.
         dataError("No active package ${log.bold(name)}.");
       }
-    }).then((_) {
+
       // Load the package from the cache.
-      var id = new PackageId(name, _source.name, version, name);
-      return _source.getDirectory(id);
-    }).then((dir) {
-      return new Package.load(name, dir, cache.sources);
-    }).then((package) {
-      // Pull the root package out of the lock file so the solver doesn't see
-      // it.
+      var id = lockFile.packages[name];
       lockFile.packages.remove(name);
 
-      return new Entrypoint.inMemory(package, lockFile, cache);
+      var source = cache.sources[id.source];
+      if (source is CachedSource) {
+        // For cached sources, the package itself is in the cache and the
+        // lockfile is the one we just loaded.
+        return cache.sources[id.source].getDirectory(id)
+            .then((dir) => new Package.load(name, dir, cache.sources))
+            .then((package) {
+          return new Entrypoint.inMemory(package, lockFile, cache);
+        });
+      }
+
+      // For uncached sources (i.e. path), the ID just points to the real
+      // directory for the package.
+      assert(id.source == "path");
+      return new Entrypoint(PathSource.pathFromDescription(id.description),
+          cache);
     });
   }
 
-  /// Picks the best version of [package] to activate that meets [constraint].
+  /// Picks the best hosted version of [package] to activate that meets
+  /// [constraint].
   ///
   /// If [version] is not `null`, this tries to maintain that version if
   /// possible.
@@ -163,7 +274,8 @@
     }
 
     // Otherwise, select the best version the matches the constraint.
-    return _source.getVersions(package, package).then((versions) {
+    var source = cache.sources["hosted"];
+    return source.getVersions(package, package).then((versions) {
       versions = versions.where(constraint.allows).toList();
 
       if (versions.isEmpty) {
@@ -178,6 +290,7 @@
     });
   }
 
-  /// Gets the path to the lock file for an activated package with [name].
+  /// Gets the path to the lock file for an activated cached package with
+  /// [name].
   String _getLockFilePath(name) => p.join(_directory, name + ".lock");
 }
diff --git a/sdk/lib/_internal/pub/lib/src/pubspec.dart b/sdk/lib/_internal/pub/lib/src/pubspec.dart
index 883a77c..e9ea596 100644
--- a/sdk/lib/_internal/pub/lib/src/pubspec.dart
+++ b/sdk/lib/_internal/pub/lib/src/pubspec.dart
@@ -209,6 +209,41 @@
   }
   PubspecEnvironment _environment;
 
+  /// The URL of the server that the package should default to being published
+  /// to, "none" if the package should not be published, or `null` if it should
+  /// be published to the default server.
+  ///
+  /// If this does return a URL string, it will be a valid parseable URL.
+  String get publishTo {
+    if (_parsedPublishTo) return _publishTo;
+
+    var publishTo = fields['publishTo'];
+    if (publishTo != null) {
+      var span = fields.nodes['publishTo'].span;
+
+      if (publishTo is! String) {
+        _error('"publishTo" field must be a string.', span);
+      }
+
+      // It must be "none" or a valid URL.
+      if (publishTo != "none") {
+        _wrapFormatException('"publishTo" field', span,
+            () => Uri.parse(publishTo));
+      }
+    }
+
+    _parsedPublishTo = true;
+    _publishTo = publishTo;
+    return _publishTo;
+  }
+  bool _parsedPublishTo = false;
+  String _publishTo;
+
+  /// Whether the package is private and cannot be published.
+  ///
+  /// This is specified in the pubspec by setting "publishTo" to "none".
+  bool get isPrivate => publishTo == "none";
+
   /// Whether or not the pubspec has no contents.
   bool get isEmpty =>
     name == null && version == Version.none && dependencies.isEmpty;
@@ -304,6 +339,7 @@
     _getError(() => this.devDependencies);
     _getError(() => this.transformers);
     _getError(() => this.environment);
+    _getError(() => this.publishTo);
     return errors;
   }
 
diff --git a/sdk/lib/_internal/pub/lib/src/source/path.dart b/sdk/lib/_internal/pub/lib/src/source/path.dart
index 7e43dfb..fc13cc7 100644
--- a/sdk/lib/_internal/pub/lib/src/source/path.dart
+++ b/sdk/lib/_internal/pub/lib/src/source/path.dart
@@ -6,7 +6,7 @@
 
 import 'dart:async';
 
-import 'package:path/path.dart' as path;
+import 'package:path/path.dart' as p;
 
 import '../exceptions.dart';
 import '../io.dart';
@@ -17,6 +17,21 @@
 
 /// A package [Source] that gets packages from a given local file path.
 class PathSource extends Source {
+  /// Returns a valid description for a reference to a package at [path].
+  static describePath(String path) {
+    return {
+      "path": path,
+      "relative": p.isRelative(path)
+    };
+  }
+
+  /// Given a valid path reference description, returns the file path it
+  /// describes.
+  ///
+  /// This returned path may be relative or absolute and it is up to the caller
+  /// to know how to interpret a relative path.
+  static String pathFromDescription(description) => description["path"];
+
   final name = 'path';
 
   Future<Pubspec> doDescribe(PackageId id) {
@@ -79,14 +94,14 @@
 
     // Resolve the path relative to the containing file path, and remember
     // whether the original path was relative or absolute.
-    bool isRelative = path.isRelative(description);
-    if (path.isRelative(description)) {
+    var isRelative = p.isRelative(description);
+    if (p.isRelative(description)) {
       // Can't handle relative paths coming from pubspecs that are not on the
       // local file system.
       assert(containingPath != null);
 
-      description = path.normalize(
-          path.join(path.dirname(containingPath), description));
+      description = p.normalize(
+          p.join(p.dirname(containingPath), description));
     }
 
     return {
@@ -102,7 +117,7 @@
   dynamic serializeDescription(String containingPath, description) {
     if (description["relative"]) {
       return {
-        "path": path.relative(description['path'], from: containingPath),
+        "path": p.relative(description['path'], from: containingPath),
         "relative": true
       };
     }
@@ -113,7 +128,7 @@
   String formatDescription(String containingPath, description) {
     var sourcePath = description["path"];
     if (description["relative"]) {
-      sourcePath = path.relative(description['path'], from: containingPath);
+      sourcePath = p.relative(description['path'], from: containingPath);
     }
 
     return sourcePath;
diff --git a/sdk/lib/_internal/pub/test/global/activate/activate_hosted_after_path_test.dart b/sdk/lib/_internal/pub/test/global/activate/activate_hosted_after_path_test.dart
new file mode 100644
index 0000000..6dec06b
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/activate/activate_hosted_after_path_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+
+import '../../../lib/src/io.dart';
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('activating a hosted package deactivates the path one', () {
+    servePackages([
+      packageMap("foo", "2.0.0")
+    ], contents: [
+      d.dir("bin", [
+        d.file("foo.dart", "main(args) => print('hosted');")
+      ])
+    ]);
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('path');")
+      ])
+    ]).create();
+
+    schedulePub(args: ["global", "activate", "-spath", "../foo"]);
+
+    var path = canonicalize(p.join(sandboxDir, "foo"));
+    schedulePub(args: ["global", "activate", "foo"], output: """
+        Package foo is currently active at path "$path".
+        Downloading foo 2.0.0...
+        Resolving dependencies...
+        Activated foo 2.0.0.""");
+
+    // Should now run the hosted one.
+    var pub = pubRun(global: true, args: ["foo"]);
+    pub.stdout.expect("hosted");
+    pub.shouldExit();
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/activate/activate_path_after_hosted_test.dart b/sdk/lib/_internal/pub/test/global/activate/activate_path_after_hosted_test.dart
new file mode 100644
index 0000000..fa74487
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/activate/activate_path_after_hosted_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+
+import '../../../lib/src/io.dart';
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('activating a hosted package deactivates the path one', () {
+    servePackages([
+      packageMap("foo", "1.0.0")
+    ], contents: [
+      d.dir("bin", [
+        d.file("foo.dart", "main(args) => print('hosted');")
+      ])
+    ]);
+
+    d.dir("foo", [
+      d.libPubspec("foo", "2.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('path');")
+      ])
+    ]).create();
+
+    schedulePub(args: ["global", "activate", "foo"]);
+
+    var path = canonicalize(p.join(sandboxDir, "foo"));
+    schedulePub(args: ["global", "activate", "-spath", "../foo"], output: """
+        Package foo is currently active at version 1.0.0.
+        Activated foo 2.0.0 at path "$path".""");
+
+    // Should now run the path one.
+    var pub = pubRun(global: true, args: ["foo"]);
+    pub.stdout.expect("path");
+    pub.shouldExit();
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/activate/bad_version_test.dart b/sdk/lib/_internal/pub/test/global/activate/bad_version_test.dart
index 1bf2eea..245fe8b 100644
--- a/sdk/lib/_internal/pub/test/global/activate/bad_version_test.dart
+++ b/sdk/lib/_internal/pub/test/global/activate/bad_version_test.dart
@@ -12,8 +12,10 @@
         error: """
             Could not parse version "1.0". Unknown text at "1.0".
 
-            Usage: pub global activate <package> [version]
-            -h, --help    Print usage information for this command.
+            Usage: pub global activate <package...>
+            -h, --help      Print usage information for this command.
+            -s, --source    The source used to find the package.
+                            [hosted (default), path]
 
             Run "pub help" to see global options.
             """,
diff --git a/sdk/lib/_internal/pub/test/global/activate/constraint_with_path_test.dart b/sdk/lib/_internal/pub/test/global/activate/constraint_with_path_test.dart
new file mode 100644
index 0000000..68f2ac3
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/activate/constraint_with_path_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../../lib/src/exit_codes.dart' as exit_codes;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('fails if a version is passed with the path source', () {
+    schedulePub(args: ["global", "activate", "-spath", "foo", "1.2.3"],
+        error: """
+            Unexpected argument "1.2.3".
+
+            Usage: pub global activate <package...>
+            -h, --help      Print usage information for this command.
+            -s, --source    The source used to find the package.
+                            [hosted (default), path]
+
+            Run "pub help" to see global options.
+            """,
+        exitCode: exit_codes.USAGE);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/activate/different_version_test.dart b/sdk/lib/_internal/pub/test/global/activate/different_version_test.dart
index 9091a55..57fa511 100644
--- a/sdk/lib/_internal/pub/test/global/activate/different_version_test.dart
+++ b/sdk/lib/_internal/pub/test/global/activate/different_version_test.dart
@@ -18,7 +18,7 @@
 
     // Activating it again with a different constraint changes the version.
     schedulePub(args: ["global", "activate", "foo", ">1.0.0"], output: """
-Package foo is already active at version 1.0.0.
+Package foo is currently active at version 1.0.0.
 Downloading foo 2.0.0...
 Resolving dependencies...
 Activated foo 2.0.0.""");
diff --git a/sdk/lib/_internal/pub/test/global/activate/installs_dependencies_for_path_test.dart b/sdk/lib/_internal/pub/test/global/activate/installs_dependencies_for_path_test.dart
new file mode 100644
index 0000000..3480de1
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/activate/installs_dependencies_for_path_test.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+import 'package:scheduled_test/scheduled_stream.dart';
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('activating a path package installs dependencies', () {
+    servePackages([
+      packageMap("bar", "1.0.0", {"baz": "any"}),
+      packageMap("baz", "2.0.0")
+    ]);
+
+    d.dir("foo", [
+      d.libPubspec("foo", "0.0.0", deps: {
+        "bar": "any"
+      }),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('ok');")
+      ])
+    ]).create();
+
+    var pub = startPub(args: ["global", "activate", "-spath", "../foo"]);
+    pub.stdout.expect(consumeThrough("Resolving dependencies..."));
+    pub.stdout.expect(consumeThrough("Downloading bar 1.0.0..."));
+    pub.stdout.expect(consumeThrough("Downloading baz 2.0.0..."));
+    pub.stdout.expect(consumeThrough(
+        startsWith("Activated foo 0.0.0 at path")));
+    pub.shouldExit();
+
+    // Puts the lockfile in the linked package itself.
+    d.dir("foo", [
+      d.matcherFile("pubspec.lock", allOf([
+        contains("bar"), contains("1.0.0"),
+        contains("baz"), contains("2.0.0")
+      ]))
+    ]).validate();
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/activate/missing_package_arg_test.dart b/sdk/lib/_internal/pub/test/global/activate/missing_package_arg_test.dart
index 2eeba0e..54384b4 100644
--- a/sdk/lib/_internal/pub/test/global/activate/missing_package_arg_test.dart
+++ b/sdk/lib/_internal/pub/test/global/activate/missing_package_arg_test.dart
@@ -12,11 +12,12 @@
         error: """
             No package to activate given.
 
-            Usage: pub global activate <package> [version]
-            -h, --help    Print usage information for this command.
+            Usage: pub global activate <package...>
+            -h, --help      Print usage information for this command.
+            -s, --source    The source used to find the package.
+                            [hosted (default), path]
 
-            Run "pub help" to see global options.
-            """,
+            Run "pub help" to see global options.""",
         exitCode: exit_codes.USAGE);
   });
 }
diff --git a/sdk/lib/_internal/pub/test/global/activate/path_package_test.dart b/sdk/lib/_internal/pub/test/global/activate/path_package_test.dart
new file mode 100644
index 0000000..8f5a2d3
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/activate/path_package_test.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+
+import '../../../lib/src/io.dart';
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('activates a package at a local path', () {
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('ok');")
+      ])
+    ]).create();
+
+    var path = canonicalize(p.join(sandboxDir, "foo"));
+    schedulePub(args: ["global", "activate", "--source", "path", "../foo"],
+        output: 'Activated foo 1.0.0 at path "$path".');
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/activate/same_version_test.dart b/sdk/lib/_internal/pub/test/global/activate/same_version_test.dart
index f13a70a..3d02098 100644
--- a/sdk/lib/_internal/pub/test/global/activate/same_version_test.dart
+++ b/sdk/lib/_internal/pub/test/global/activate/same_version_test.dart
@@ -18,7 +18,7 @@
 
     // Activating it again re-resolves but maintains the version.
     schedulePub(args: ["global", "activate", "foo", ">1.0.0"], output: """
-Package foo is already active at version 1.2.3.
+Package foo is currently active at version 1.2.3.
 Resolving dependencies...
 Activated foo 1.2.3.""");
   });
diff --git a/sdk/lib/_internal/pub/test/global/activate/unexpected_arguments_test.dart b/sdk/lib/_internal/pub/test/global/activate/unexpected_arguments_test.dart
index 0e08982..82a94de 100644
--- a/sdk/lib/_internal/pub/test/global/activate/unexpected_arguments_test.dart
+++ b/sdk/lib/_internal/pub/test/global/activate/unexpected_arguments_test.dart
@@ -12,11 +12,12 @@
         error: """
             Unexpected arguments "bar" and "baz".
 
-            Usage: pub global activate <package> [version]
-            -h, --help    Print usage information for this command.
+            Usage: pub global activate <package...>
+            -h, --help      Print usage information for this command.
+            -s, --source    The source used to find the package.
+                            [hosted (default), path]
 
-            Run "pub help" to see global options.
-            """,
+            Run "pub help" to see global options.""",
         exitCode: exit_codes.USAGE);
   });
 }
diff --git a/sdk/lib/_internal/pub/test/global/deactivate/deactivate_package_test.dart b/sdk/lib/_internal/pub/test/global/deactivate/hosted_package_test.dart
similarity index 89%
rename from sdk/lib/_internal/pub/test/global/deactivate/deactivate_package_test.dart
rename to sdk/lib/_internal/pub/test/global/deactivate/hosted_package_test.dart
index 661cced..5625ab8 100644
--- a/sdk/lib/_internal/pub/test/global/deactivate/deactivate_package_test.dart
+++ b/sdk/lib/_internal/pub/test/global/deactivate/hosted_package_test.dart
@@ -6,7 +6,7 @@
 
 main() {
   initConfig();
-  integration('deactivates an active package', () {
+  integration('deactivates an active hosted package', () {
     servePackages([
       packageMap("foo", "1.0.0")
     ]);
diff --git a/sdk/lib/_internal/pub/test/global/deactivate/path_package_test.dart b/sdk/lib/_internal/pub/test/global/deactivate/path_package_test.dart
new file mode 100644
index 0000000..90047f6
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/deactivate/path_package_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+
+import '../../../lib/src/io.dart';
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('deactivates an active path package', () {
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('ok');")
+      ])
+    ]).create();
+
+    schedulePub(args: ["global", "activate", "--source", "path", "../foo"]);
+
+    var path = canonicalize(p.join(sandboxDir, "foo"));
+    schedulePub(args: ["global", "deactivate", "foo"],
+        output: 'Deactivated package foo at path "$path".');
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/run/missing_path_package_test.dart b/sdk/lib/_internal/pub/test/global/run/missing_path_package_test.dart
new file mode 100644
index 0000000..a8be8c8
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/run/missing_path_package_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:path/path.dart' as p;
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../../lib/src/io.dart';
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('errors if the local package does not exist', () {
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('ok');")
+      ])
+    ]).create();
+
+    schedulePub(args: ["global", "activate", "--source", "path", "../foo"]);
+
+    schedule(() => deleteEntry(p.join(sandboxDir, "foo")));
+
+    var pub = pubRun(global: true, args: ["foo"]);
+    var path = canonicalize(p.join(sandboxDir, "foo"));
+    pub.stderr.expect('Could not find a file named "pubspec.yaml" in "$path".');
+    pub.shouldExit(1);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/run/reflects_changes_to_local_package_test.dart b/sdk/lib/_internal/pub/test/global/run/reflects_changes_to_local_package_test.dart
new file mode 100644
index 0000000..9fc5b4e
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/run/reflects_changes_to_local_package_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('changes in a path package are immediately reflected',
+      () {
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('ok');")
+      ])
+    ]).create();
+
+    schedulePub(args: ["global", "activate", "--source", "path", "../foo"]);
+
+    d.file("foo/bin/foo.dart", "main() => print('changed');").create();
+
+    var pub = pubRun(global: true, args: ["foo"]);
+    pub.stdout.expect("changed");
+    pub.shouldExit();
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/global/run/runs_path_script_test.dart b/sdk/lib/_internal/pub/test/global/run/runs_path_script_test.dart
new file mode 100644
index 0000000..b8d9b2c
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/global/run/runs_path_script_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('runs a script in a path package', () {
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("bin", [
+        d.file("foo.dart", "main() => print('ok');")
+      ])
+    ]).create();
+
+    schedulePub(args: ["global", "activate", "--source", "path", "../foo"]);
+
+    var pub = pubRun(global: true, args: ["foo"]);
+    pub.stdout.expect("ok");
+    pub.shouldExit();
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/does_not_publish_if_private_test.dart b/sdk/lib/_internal/pub/test/lish/does_not_publish_if_private_test.dart
new file mode 100644
index 0000000..69e843e
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/does_not_publish_if_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('does not publish if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/does_not_publish_if_private_with_server_arg_test.dart b/sdk/lib/_internal/pub/test/lish/does_not_publish_if_private_with_server_arg_test.dart
new file mode 100644
index 0000000..f5dbcc7
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/does_not_publish_if_private_with_server_arg_test.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('does not publish if the package is private even if a server '
+      'argument is provided', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--server", "http://example.com"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/force_does_not_publish_if_private_test.dart b/sdk/lib/_internal/pub/test/lish/force_does_not_publish_if_private_test.dart
new file mode 100644
index 0000000..b92ea72
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/force_does_not_publish_if_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('force does not publish if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--force"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/force_publishes_if_there_are_warnings_test.dart b/sdk/lib/_internal/pub/test/lish/force_publishes_if_there_are_warnings_test.dart
index c6e19ca..092855d 100644
--- a/sdk/lib/_internal/pub/test/lish/force_publishes_if_there_are_warnings_test.dart
+++ b/sdk/lib/_internal/pub/test/lish/force_publishes_if_there_are_warnings_test.dart
@@ -20,7 +20,7 @@
 
   integration('--force publishes if there are warnings', () {
     var pkg = packageMap("test_pkg", "1.0.0");
-    pkg["author"] = "Nathan Weizenbaum";
+    pkg["author"] = "Natalie Weizenbaum";
     d.dir(appPath, [d.pubspec(pkg)]).create();
 
     var server = new ScheduledServer();
@@ -39,7 +39,7 @@
     pub.shouldExit(exit_codes.SUCCESS);
     pub.stderr.expect(consumeThrough('Suggestions:'));
     pub.stderr.expect(emitsLines(
-        '* Author "Nathan Weizenbaum" in pubspec.yaml should have an email '
+        '* Author "Natalie Weizenbaum" in pubspec.yaml should have an email '
             'address\n'
         '  (e.g. "name <email>").'));
     pub.stdout.expect(consumeThrough('Package test_pkg 1.0.0 uploaded!'));
diff --git a/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_continues_test.dart b/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_continues_test.dart
index 13af22b..89d84e7 100644
--- a/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_continues_test.dart
+++ b/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_continues_test.dart
@@ -20,7 +20,7 @@
 
   integration('package validation has a warning and continues', () {
     var pkg = packageMap("test_pkg", "1.0.0");
-    pkg["author"] = "Nathan Weizenbaum";
+    pkg["author"] = "Natalie Weizenbaum";
     d.dir(appPath, [d.pubspec(pkg)]).create();
 
     var server = new ScheduledServer();
diff --git a/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart b/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
index 65ed4a6..314c528e9 100644
--- a/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
+++ b/sdk/lib/_internal/pub/test/lish/package_validation_has_a_warning_and_is_canceled_test.dart
@@ -16,7 +16,7 @@
 
   integration('package validation has a warning and is canceled', () {
     var pkg = packageMap("test_pkg", "1.0.0");
-    pkg["author"] = "Nathan Weizenbaum";
+    pkg["author"] = "Natalie Weizenbaum";
     d.dir(appPath, [d.pubspec(pkg)]).create();
 
     var server = new ScheduledServer();
diff --git a/sdk/lib/_internal/pub/test/lish/preview_errors_if_private_test.dart b/sdk/lib/_internal/pub/test/lish/preview_errors_if_private_test.dart
new file mode 100644
index 0000000..cc538a7c
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/preview_errors_if_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('preview shows an error if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--dry-run"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_a_warning_test.dart b/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_a_warning_test.dart
index c7925f4..cbc94fb 100644
--- a/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_a_warning_test.dart
+++ b/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_a_warning_test.dart
@@ -16,7 +16,7 @@
 
   integration('preview package validation has a warning', () {
     var pkg = packageMap("test_pkg", "1.0.0");
-    pkg["author"] = "Nathan Weizenbaum";
+    pkg["author"] = "Natalie Weizenbaum";
     d.dir(appPath, [d.pubspec(pkg)]).create();
 
     var server = new ScheduledServer();
@@ -25,7 +25,7 @@
     pub.shouldExit(exit_codes.SUCCESS);
     pub.stderr.expect(consumeThrough('Suggestions:'));
     pub.stderr.expect(emitsLines(
-        '* Author "Nathan Weizenbaum" in pubspec.yaml should have an email '
+        '* Author "Natalie Weizenbaum" in pubspec.yaml should have an email '
             'address\n'
         '  (e.g. "name <email>").\n'
         '\n'
diff --git a/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_no_warnings_test.dart b/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_no_warnings_test.dart
index 37dac6b..66f092e 100644
--- a/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_no_warnings_test.dart
+++ b/sdk/lib/_internal/pub/test/lish/preview_package_validation_has_no_warnings_test.dart
@@ -16,7 +16,7 @@
 
   integration('preview package validation has no warnings', () {
     var pkg = packageMap("test_pkg", "1.0.0");
-    pkg["author"] = "Nathan Weizenbaum <nweiz@google.com>";
+    pkg["author"] = "Natalie Weizenbaum <nweiz@google.com>";
     d.dir(appPath, [d.pubspec(pkg)]).create();
 
     var server = new ScheduledServer();
diff --git a/sdk/lib/_internal/pub/test/lish/server_arg_does_not_override_private_test.dart b/sdk/lib/_internal/pub/test/lish/server_arg_does_not_override_private_test.dart
new file mode 100644
index 0000000..d3a5667
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/server_arg_does_not_override_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('an explicit --server argument does not override privacy', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--server", "http://arg.com"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/server_arg_overrides_publish_to_url_test.dart b/sdk/lib/_internal/pub/test/lish/server_arg_overrides_publish_to_url_test.dart
new file mode 100644
index 0000000..5c04d66
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/server_arg_overrides_publish_to_url_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('an explicit --server argument overrides a "publishTo" url', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "http://pubspec.com";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--dry-run", "--server", "http://arg.com"],
+        output: contains("http://arg.com"));
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/lish/uses_publish_to_url_test.dart b/sdk/lib/_internal/pub/test/lish/uses_publish_to_url_test.dart
new file mode 100644
index 0000000..ae011fe
--- /dev/null
+++ b/sdk/lib/_internal/pub/test/lish/uses_publish_to_url_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('preview shows an error if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "http://example.com";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--dry-run"],
+        output: contains("Publishing test_pkg 1.0.0 to http://example.com"));
+  });
+}
diff --git a/sdk/lib/_internal/pub/test/pubspec_test.dart b/sdk/lib/_internal/pub/test/pubspec_test.dart
index 1d38c6c..41e6d7c 100644
--- a/sdk/lib/_internal/pub/test/pubspec_test.dart
+++ b/sdk/lib/_internal/pub/test/pubspec_test.dart
@@ -413,5 +413,36 @@
             (pubspec) => pubspec.environment);
       });
     });
+
+    group("publishTo", () {
+      test("defaults to null if omitted", () {
+        var pubspec = new Pubspec.parse('', sources);
+        expect(pubspec.publishTo, isNull);
+      });
+
+      test("throws if not a string", () {
+        expectPubspecException('publishTo: 123',
+            (pubspec) => pubspec.publishTo);
+      });
+
+      test("allows a URL", () {
+        var pubspec = new Pubspec.parse('''
+publishTo: http://example.com
+''', sources);
+        expect(pubspec.publishTo, equals("http://example.com"));
+      });
+
+      test("allows none", () {
+        var pubspec = new Pubspec.parse('''
+publishTo: none
+''', sources);
+        expect(pubspec.publishTo, equals("none"));
+      });
+
+      test("throws on other strings", () {
+        expectPubspecException('publishTo: http://bad.url:not-port',
+            (pubspec) => pubspec.publishTo);
+      });
+    });
   });
 }
diff --git a/sdk/lib/_internal/pub/test/test_pub.dart b/sdk/lib/_internal/pub/test/test_pub.dart
index 5b499e3..54e30de 100644
--- a/sdk/lib/_internal/pub/test/test_pub.dart
+++ b/sdk/lib/_internal/pub/test/test_pub.dart
@@ -876,7 +876,7 @@
   var package = {
     "name": name,
     "version": version,
-    "author": "Nathan Weizenbaum <nweiz@google.com>",
+    "author": "Natalie Weizenbaum <nweiz@google.com>",
     "homepage": "http://pub.dartlang.org",
     "description": "A package, I guess."
   };
diff --git a/sdk/lib/_internal/pub/test/transformer/fails_to_load_a_transform_with_an_import_error_test.dart b/sdk/lib/_internal/pub/test/transformer/fails_to_load_a_transform_with_an_import_error_test.dart
index 643be22..10e9c91 100644
--- a/sdk/lib/_internal/pub/test/transformer/fails_to_load_a_transform_with_an_import_error_test.dart
+++ b/sdk/lib/_internal/pub/test/transformer/fails_to_load_a_transform_with_an_import_error_test.dart
@@ -30,7 +30,7 @@
       createLockFile('myapp', pkg: ['barback']);
       var pub = startPubServe();
       pub.stderr.expect("'Unhandled exception:");
-      pub.stderr.expect(startsWith("Failure getting "));
+      pub.stderr.expect(startsWith("Uncaught Error: Failure getting "));
       pub.shouldExit(1);
     });
   });
diff --git a/sdk/lib/_internal/pub/test/validator/pubspec_field_test.dart b/sdk/lib/_internal/pub/test/validator/pubspec_field_test.dart
index 9c951a8..4f0f33e 100644
--- a/sdk/lib/_internal/pub/test/validator/pubspec_field_test.dart
+++ b/sdk/lib/_internal/pub/test/validator/pubspec_field_test.dart
@@ -115,7 +115,7 @@
 
     integration('has a single author without an email', () {
       var pkg = packageMap("test_pkg", "1.0.0");
-      pkg["author"] = "Nathan Weizenbaum";
+      pkg["author"] = "Natalie Weizenbaum";
       d.dir(appPath, [d.pubspec(pkg)]).create();
 
       expectValidationWarning(pubspecField);
@@ -126,7 +126,7 @@
       pkg.remove("author");
       pkg["authors"] = [
         "Bob Nystrom <rnystrom@google.com>",
-        "Nathan Weizenbaum",
+        "Natalie Weizenbaum",
         "John Messerly <jmesserly@google.com>"
       ];
       d.dir(appPath, [d.pubspec(pkg)]).create();
diff --git a/sdk/lib/async/schedule_microtask.dart b/sdk/lib/async/schedule_microtask.dart
index 53a0c81..7e1c82d 100644
--- a/sdk/lib/async/schedule_microtask.dart
+++ b/sdk/lib/async/schedule_microtask.dart
@@ -12,39 +12,91 @@
   _AsyncCallbackEntry(this.callback);
 }
 
+/** Head of single linked list of pending callbacks. */
 _AsyncCallbackEntry _nextCallback;
+/** Tail of single linked list of pending callbacks. */
 _AsyncCallbackEntry _lastCallback;
+/**
+ * Tail of priority callbacks added by the currently executing callback.
+ *
+ * Priority callbacks are put at the beginning of the
+ * callback queue, so that if one callback schedules more than one
+ * priority callback, they are still enqueued in scheduling order.
+ */
+_AsyncCallbackEntry _lastPriorityCallback;
+/**
+ * Whether we are currently inside the callback loop.
+ *
+ * If we are inside the loop, we never need to schedule the loop,
+ * even if adding a first element.
+ */
+bool _isInCallbackLoop = false;
 
 void _asyncRunCallbackLoop() {
-  _AsyncCallbackEntry entry = _nextCallback;
-  // As long as we are iterating over the registered callbacks we don't
-  // set the [_lastCallback] entry.
-  while (entry != null) {
+  while (_nextCallback != null) {
+    _lastPriorityCallback = null;
+    _AsyncCallbackEntry entry = _nextCallback;
+    _nextCallback = entry.next;
+    if (_nextCallback == null) _lastCallback = null;
     entry.callback();
-    entry = _nextCallback = entry.next;
   }
-  // Any new callback must register a callback function now.
-  _lastCallback = null;
 }
 
 void _asyncRunCallback() {
+  _isInCallbackLoop = true;
   try {
     _asyncRunCallbackLoop();
-  } catch (e, s) {
-    _AsyncRun._scheduleImmediate(_asyncRunCallback);
-    _nextCallback = _nextCallback.next;
-    rethrow;
+  } finally {
+    _lastPriorityCallback = null;
+    _isInCallbackLoop = false;
+    if (_nextCallback != null) _AsyncRun._scheduleImmediate(_asyncRunCallback);
   }
 }
 
+/**
+ * Schedules a callback to be called as a microtask.
+ *
+ * The microtask is called after all other currently scheduled
+ * microtasks, but as part of the current system event.
+ */
 void _scheduleAsyncCallback(callback) {
   // Optimizing a group of Timer.run callbacks to be executed in the
   // same Timer callback.
-  if (_lastCallback == null) {
+  if (_nextCallback == null) {
     _nextCallback = _lastCallback = new _AsyncCallbackEntry(callback);
-    _AsyncRun._scheduleImmediate(_asyncRunCallback);
+    if (!_isInCallbackLoop) {
+      _AsyncRun._scheduleImmediate(_asyncRunCallback);
+    }
   } else {
-    _lastCallback = _lastCallback.next = new _AsyncCallbackEntry(callback);
+    _AsyncCallbackEntry newEntry = new _AsyncCallbackEntry(callback);
+    _lastCallback.next = newEntry;
+    _lastCallback = newEntry;
+  }
+}
+
+/**
+ * Schedules a callback to be called before all other currently scheduled ones.
+ *
+ * This callback takes priority over existing scheduled callbacks.
+ * It is only used internally to give higher priority to error reporting.
+ */
+void _schedulePriorityAsyncCallback(callback) {
+  _AsyncCallbackEntry entry = new _AsyncCallbackEntry(callback);
+  if (_nextCallback == null) {
+    _nextCallback = _lastCallback = _lastPriorityCallback = entry;
+    if (!_isInCallbackLoop) {
+      _AsyncRun._scheduleImmediate(_asyncRunCallback);
+    }
+  } else if (_lastPriorityCallback == null) {
+    entry.next = _nextCallback;
+    _nextCallback = _lastPriorityCallback = entry;
+  } else {
+    entry.next = _lastPriorityCallback.next;
+    _lastPriorityCallback.next = entry;
+    _lastPriorityCallback = entry;
+    if (entry.next == null) {
+      _lastCallback = entry;
+    }
   }
 }
 
diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart
index 97a1089..541a66f 100644
--- a/sdk/lib/async/zone.dart
+++ b/sdk/lib/async/zone.dart
@@ -816,14 +816,8 @@
 
 void _rootHandleUncaughtError(
     Zone self, ZoneDelegate parent, Zone zone, error, StackTrace stackTrace) {
-  _rootScheduleMicrotask(null, null, _ROOT_ZONE, () {
-    print("Uncaught Error: ${error}");
-    var trace = stackTrace;
-    if (trace == null && error is Error) trace = error.stackTrace;
-    if (trace != null) {
-      print("Stack Trace: \n$trace\n");
-    }
-    throw error;
+  _schedulePriorityAsyncCallback(() {
+    throw new _UncaughtAsyncError(error, stackTrace);
   });
 }
 
diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
index 1e26d01f..4b601a7 100644
--- a/sdk/lib/core/uri.dart
+++ b/sdk/lib/core/uri.dart
@@ -371,8 +371,9 @@
     }
 
     assert(state == NOT_IN_PATH);
-    bool ensureLeadingSlash = (host != null || scheme == "file");
-    path = _makePath(uri, pathStart, index, null, ensureLeadingSlash);
+    bool isFile = (scheme == "file");
+    bool ensureLeadingSlash = host != null;
+    path = _makePath(uri, pathStart, index, null, ensureLeadingSlash, isFile);
 
     if (char == _QUESTION) {
       int numberSignIndex = uri.indexOf('#', index + 1);
@@ -480,7 +481,7 @@
                Iterable<String> pathSegments,
                String query,
                Map<String, String> queryParameters,
-               fragment}) {
+               String fragment}) {
     scheme = _makeScheme(scheme, _stringOrNullLength(scheme));
     userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
     host = _makeHost(host, 0, _stringOrNullLength(host), false);
@@ -494,10 +495,9 @@
         (userInfo.isNotEmpty || port != null || isFile)) {
       host = "";
     }
-    bool ensureLeadingSlash = (host != null || isFile);
+    bool ensureLeadingSlash = host != null;
     path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
-                     ensureLeadingSlash);
-
+                     ensureLeadingSlash, isFile);
     return new Uri._internal(scheme, userInfo, host, port,
                              path, query, fragment);
   }
@@ -819,6 +819,116 @@
   }
 
   /**
+   * Returns a new `Uri` based on this one, but with some parts replaced.
+   *
+   * This method takes the same parameters as the [new Uri] constructor,
+   * and they have the same meaning.
+   *
+   * At most one of [path] and [pathSegments] must be provided.
+   * Likewise, at most one of [query] and [queryParameters] must be provided.
+   *
+   * Each part that is not provided will default to the corresponding
+   * value from this `Uri` instead.
+   *
+   * This method is different from [Uri.resolve] which overrides in a
+   * hierarchial manner,
+   * and can instead replace each part of a `Uri` individually.
+   *
+   * Example:
+   *
+   *     Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
+   *     Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
+   *     print(uri2);  // prints "A://b@c:4/D/E/E/?f#G"
+   *
+   * This method acts similarly to using the `new Uri` constructor with
+   * some of the arguments taken from this `Uri` . Example:
+   *
+   *     Uri uri3 = new Uri(
+   *         scheme: "A",
+   *         userInfo: uri1.userInfo,
+   *         host: uri1.host,
+   *         port: uri1.port,
+   *         path: "D/E/E",
+   *         query: uri1.query,
+   *         fragment: "G");
+   *     print(uri3);  // prints "A://b@c:4/D/E/E/?f#G"
+   *     print(uri2 == uri3);  // prints true.
+   *
+   * Using this method can be seen as a shorthand for the `Uri` constructor
+   * call above, but may also be slightly faster because the parts taken
+   * from this `Uri` need not be checked for validity again.
+   */
+  Uri replace({String scheme,
+               String userInfo,
+               String host,
+               int port,
+               String path,
+               Iterable<String> pathSegments,
+               String query,
+               Map<String, String> queryParameters,
+               String fragment}) {
+    // Set to true if the scheme has (potentially) changed.
+    // In that case, the default port may also have changed and we need
+    // to check even the existing port.
+    bool schemeChanged = false;
+    if (scheme != null) {
+      scheme = _makeScheme(scheme, scheme.length);
+      schemeChanged = true;
+    } else {
+      scheme = this.scheme;
+    }
+    bool isFile = (scheme == "file");
+    if (userInfo != null) {
+      userInfo = _makeUserInfo(userInfo, 0, userInfo.length);
+    } else {
+      userInfo = this.userInfo;
+    }
+    if (port != null) {
+      port = _makePort(port, scheme);
+    } else {
+      port = this.port;
+      if (schemeChanged) {
+        // The default port might have changed.
+        port = _makePort(port, scheme);
+      }
+    }
+    if (host != null) {
+      host = _makeHost(host, 0, host.length, false);
+    } else if (this.hasAuthority) {
+      host = this.host;
+    } else if (userInfo.isNotEmpty || port != null || isFile) {
+      host = "";
+    }
+
+    bool ensureLeadingSlash = (host != null);
+    if (path != null || pathSegments != null) {
+      path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
+                       ensureLeadingSlash, isFile);
+    } else {
+      path = this.path;
+      if ((isFile || (ensureLeadingSlash && !path.isEmpty)) &&
+          !path.startsWith('/')) {
+        path = "/$path";
+      }
+    }
+
+    if (query != null || queryParameters != null) {
+      query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
+    } else if (this.hasQuery) {
+      query = this.query;
+    }
+
+    if (fragment != null) {
+      fragment = _makeFragment(fragment, 0, fragment.length);
+    } else if (this.hasFragment) {
+      fragment = this.fragment;
+    }
+
+    return new Uri._internal(
+        scheme, userInfo, host, port, path, query, fragment);
+  }
+
+  /**
    * Returns the URI path split into its segments. Each of the
    * segments in the returned list have been decoded. If the path is
    * empty the empty list will be returned. A leading slash `/` does
@@ -1018,8 +1128,9 @@
 
   static String _makePath(String path, int start, int end,
                           Iterable<String> pathSegments,
-                          bool ensureLeadingSlash) {
-    if (path == null && pathSegments == null) return "";
+                          bool ensureLeadingSlash,
+                          bool isFile) {
+    if (path == null && pathSegments == null) return isFile ? "/" : "";
     if (path != null && pathSegments != null) {
       throw new ArgumentError('Both path and pathSegments specified');
     }
@@ -1029,7 +1140,10 @@
     } else {
       result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
     }
-    if (ensureLeadingSlash && result.isNotEmpty && !result.startsWith("/")) {
+    if (result.isEmpty) {
+      if (isFile) return "/";
+    } else if ((isFile || ensureLeadingSlash) &&
+               result.codeUnitAt(0) != _SLASH) {
       return "/$result";
     }
     return result;
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index fd51eca..01a239a 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -1934,9 +1934,11 @@
   @DomName('CanvasRenderingContext2D.lineDashOffset')
   // TODO(14316): Firefox has this functionality with mozDashOffset, but it
   // needs to be polyfilled.
-  void set lineDashOffset(num value) => JS('void',
-      'typeof #.lineDashOffset != "undefined" ? #.lineDashOffset = # : '
-      '#.webkitLineDashOffset = #', this, this, value, this, value);
+  void set lineDashOffset(num value) {
+    JS('void',
+       'typeof #.lineDashOffset != "undefined" ? #.lineDashOffset = # : '
+       '#.webkitLineDashOffset = #', this, this, value, this, value);
+  }
 
   @SupportedBrowser(SupportedBrowser.CHROME)
   @SupportedBrowser(SupportedBrowser.SAFARI)
@@ -10309,7 +10311,9 @@
 
   @DomName('Element.scrollLeft')
   @DocsEditable()
-  void set scrollLeft(int value) => JS("void", "#.scrollLeft = #", this, value.round());
+  void set scrollLeft(int value) {
+    JS("void", "#.scrollLeft = #", this, value.round());
+  }
 
   @DomName('Element.scrollTop')
   @DocsEditable()
@@ -10317,7 +10321,9 @@
 
   @DomName('Element.scrollTop')
   @DocsEditable()
-  void set scrollTop(int value) => JS("void", "#.scrollTop = #", this, value.round());
+  void set scrollTop(int value) {
+    JS("void", "#.scrollTop = #", this, value.round());
+  }
 
   @DomName('Element.scrollWidth')
   @DocsEditable()
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index 6f14303..79a166f 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -13020,6 +13020,10 @@
   @DocsEditable()
   int get readyState => _blink.BlinkFileReader.$readyState_Getter(this);
 
+  @DomName('FileReader.result')
+  @DocsEditable()
+  Object get _result => _blink.BlinkFileReader.$result_Getter(this);
+
   @DomName('FileReader.abort')
   @DocsEditable()
   void abort() => _blink.BlinkFileReader.$abort_Callback(this);
diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart
index b64eb75..717bb59 100644
--- a/sdk/lib/io/socket.dart
+++ b/sdk/lib/io/socket.dart
@@ -450,8 +450,8 @@
    *
    * [host] can either be a [String] or an [InternetAddress]. If [host] is a
    * [String], [connect] will perform a [InternetAddress.lookup] and try
-   * all returned [InternetAddress]es, in turn, until connected. Unless a
-   * connection was established, the error from the first attempt is
+   * all returned [InternetAddress]es, until connected. Unless a
+   * connection was established, the error from the first failing connection is
    * returned.
    */
   external static Future<RawSocket> connect(host, int port);
@@ -543,8 +543,8 @@
    *
    * [host] can either be a [String] or an [InternetAddress]. If [host] is a
    * [String], [connect] will perform a [InternetAddress.lookup] and try
-   * all returned [InternetAddress]es, in turn, until connected. Unless a
-   * connection was established, the error from the first attempt is
+   * all returned [InternetAddress]es, until connected. Unless a
+   * connection was established, the error from the first failing connection is
    * returned.
    */
   external static Future<Socket> connect(host, int port);
diff --git a/site/try/index.html b/site/try/index.html
index d07a01f..c882f65 100644
--- a/site/try/index.html
+++ b/site/try/index.html
@@ -190,7 +190,6 @@
 
 <!-- Enable Google Analytics -->
 <script type="text/javascript">
-/*
   var _gaq = _gaq || [];
   _gaq.push(['_setAccount', 'UA-26406144-2']);
   _gaq.push(['_trackPageview']);
@@ -200,7 +199,6 @@
     ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
     var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
   })();
-*/
 </script>
 </head>
 <body>
diff --git a/tests/co19/co19-dart2dart.status b/tests/co19/co19-dart2dart.status
index 2335037..d4a089e 100644
--- a/tests/co19/co19-dart2dart.status
+++ b/tests/co19/co19-dart2dart.status
@@ -27,8 +27,6 @@
 LibTest/core/Symbol/Symbol_A01_t03: RuntimeError # co19-roll r607: Please triage this failure
 LibTest/core/Symbol/Symbol_A01_t05: RuntimeError # co19-roll r607: Please triage this failure
 
-LibTest/async/DeferredLibrary/DeferredLibrary_A01_t01: CompileTimeError # dart issue 17523
-
 Language/07_Classes/9_Superclasses/1_Inheritance_and_Overriding_A01_t03: Fail # TODO(dart2dart-team): Please triage this failure.
 
 Language/03_Overview/2_Privacy_A01_t19: Fail # Calling unresolved class constructor.
diff --git a/tests/compiler/dart2js/analyze_api_test.dart b/tests/compiler/dart2js/analyze_api_test.dart
index 3ecf592..de48a2e 100644
--- a/tests/compiler/dart2js/analyze_api_test.dart
+++ b/tests/compiler/dart2js/analyze_api_test.dart
@@ -20,7 +20,6 @@
 // TODO(johnniwinther): Support canonical URIs as keys and message kinds as
 // values.
 const Map<String, List<String>> WHITE_LIST = const {
-  "js_mirrors.dart" : const ["Unreachable code."] // issue 18936
 };
 
 void main() {
diff --git a/tests/compiler/dart2js/analyze_unused_dart2js_test.dart b/tests/compiler/dart2js/analyze_unused_dart2js_test.dart
index 699beff..34c1483 100644
--- a/tests/compiler/dart2js/analyze_unused_dart2js_test.dart
+++ b/tests/compiler/dart2js/analyze_unused_dart2js_test.dart
@@ -14,16 +14,26 @@
 // Do not remove WHITE_LIST even if it's empty.  The error message for
 // unused members refers to WHITE_LIST by name.
 const Map<String, List<String>> WHITE_LIST = const {
-  // TODO(johnniwinther): Explicitly check that we use no helpers, both methods
-  // and classes, are used in production code.*/
   // Helper methods for debugging should never be called from production code:
   "implementation/helpers/": const [" is never "],
 
+  // Node.asLiteralBool is never used.
+  "implementation/tree/nodes.dart": const [
+      "The method 'asLiteralBool' is never called"],
+
   // Some things in dart_printer are not yet used
-  "implementation/dart_backend/backend_ast_nodes.dart" : const [" is never "],
+  "implementation/dart_backend/backend_ast_nodes.dart": const [" is never "],
+
+  // dart2js uses only the encoding functions, the decoding functions are used
+  // from the generated code.
+  "implementation/runtime_data.dart": const [" is never "],
 
   // Setlet implements the Set interface: Issue 18959.
   "implementation/util/setlet.dart": const [" is never "],
+
+  // MethodElement
+  // TODO(20377): Why is MethodElement unused?
+  "implementation/elements/elements.dart": const [" is never "]
 };
 
 void main() {
diff --git a/tests/compiler/dart2js/closure_codegen_test.dart b/tests/compiler/dart2js/closure_codegen_test.dart
index d9d05d6..ed1ef86 100644
--- a/tests/compiler/dart2js/closure_codegen_test.dart
+++ b/tests/compiler/dart2js/closure_codegen_test.dart
@@ -33,7 +33,7 @@
 class A {
   var x;
   foo(_) { // make sure only g has no arguments
-    var f = function g() { return 499;  };
+    var f = () { return 499;  };
     return 499 + x + f();
   }
 }
diff --git a/tests/compiler/dart2js/dictionary_types_test.dart b/tests/compiler/dart2js/dictionary_types_test.dart
index b95942a..feaa693 100644
--- a/tests/compiler/dart2js/dictionary_types_test.dart
+++ b/tests/compiler/dart2js/dictionary_types_test.dart
@@ -95,7 +95,7 @@
 
   main () {
     dict['goo'] = 42;
-    var closure = dictfun() => dict;
+    var closure = () => dict;
     notInt = closure()['boo'];
     alsoNotInt = dict['goo'];
     print("\$notInt and \$alsoNotInt.");
@@ -115,7 +115,7 @@
     Expect.isTrue(getType('aString').containsOnlyString(compiler));
     Expect.equals(getType('doubleOrNull'), types.doubleType.nullable());
   }));
-  asyncTest(() => 
+  asyncTest(() =>
     compileAndTest("ValueType.dart", (types, getType, compiler) {
       Expect.equals(getType('knownDouble'), types.doubleType);
       Expect.equals(getType('intOrNull'), types.uint31Type.nullable());
diff --git a/tests/compiler/dart2js/resolver_test.dart b/tests/compiler/dart2js/resolver_test.dart
index 76ff4bb..399c21e 100644
--- a/tests/compiler/dart2js/resolver_test.dart
+++ b/tests/compiler/dart2js/resolver_test.dart
@@ -415,7 +415,7 @@
 
     MethodScope scope = visitor.scope;
     Expect.equals(0, scope.elements.length);
-    Expect.equals(10, map(visitor).length);
+    Expect.equals(9, map(visitor).length);
 
     VariableDefinitions initializer = tree.initializer;
     Node iNode = initializer.definitions.nodes.head;
@@ -427,46 +427,41 @@
     List<Node> nodes = map(visitor).keys.toList();
     List<Element> elements = map(visitor).values.toList();
 
-
-    // for (int i = 0; i < 10; i = i + 1) { i = 5; };
-    //      ^^^
-    Expect.isTrue(nodes[0] is TypeAnnotation);
-
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //          ^^^^^
-    checkSendSet(iElement, nodes[1], elements[1]);
+    checkSendSet(iElement, nodes[0], elements[0]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                 ^
-    checkIdentifier(iElement, nodes[2], elements[2]);
+    checkIdentifier(iElement, nodes[1], elements[1]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                 ^
-    checkSend(iElement, nodes[3], elements[3]);
+    checkSend(iElement, nodes[2], elements[2]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                         ^
+    checkIdentifier(iElement, nodes[3], elements[3]);
+
+    // for (int i = 0; i < 10; i = i + 1) { i = 5; };
+    //                             ^
     checkIdentifier(iElement, nodes[4], elements[4]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                             ^
-    checkIdentifier(iElement, nodes[5], elements[5]);
-
-    // for (int i = 0; i < 10; i = i + 1) { i = 5; };
-    //                             ^
-    checkSend(iElement, nodes[6], elements[6]);
+    checkSend(iElement, nodes[5], elements[5]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                         ^^^^^^^^^
-    checkSendSet(iElement, nodes[7], elements[7]);
+    checkSendSet(iElement, nodes[6], elements[6]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                                      ^
-    checkIdentifier(iElement, nodes[8], elements[8]);
+    checkIdentifier(iElement, nodes[7], elements[7]);
 
     // for (int i = 0; i < 10; i = i + 1) { i = 5; };
     //                                      ^^^^^
-    checkSendSet(iElement, nodes[9], elements[9]);
+    checkSendSet(iElement, nodes[8], elements[8]);
   });
 }
 
@@ -493,7 +488,7 @@
     // Test that we get a warning when Foo is not defined.
     Map mapping = compiler.resolveStatement(statement).map;
 
-    Expect.equals(2, mapping.length); // Both Foo and bar have an element.
+    Expect.equals(1, mapping.length); // Only [bar] has an element.
     Expect.equals(1, compiler.warnings.length);
 
     Node warningNode = compiler.warnings[0].node;
@@ -509,7 +504,7 @@
     // Test that there is no warning after defining Foo.
     compiler.parseScript("class Foo {}");
     mapping = compiler.resolveStatement(statement).map;
-    Expect.equals(2, mapping.length);
+    Expect.equals(1, mapping.length);
     Expect.equals(0, compiler.warnings.length);
 
     // Test that 'var' does not create a warning.
@@ -534,7 +529,7 @@
       compiler.parseScript("class Foo extends Bar {}");
       compiler.parseScript("class Bar {}");
       Map mapping = compiler.resolveStatement("Foo bar;").map;
-      Expect.equals(2, mapping.length);
+      Expect.equals(1, mapping.length);
 
       ClassElement fooElement = compiler.mainApp.find('Foo');
       ClassElement barElement = compiler.mainApp.find('Bar');
@@ -614,7 +609,7 @@
   return MockCompiler.create((MockCompiler compiler) {
     ResolverVisitor visitor = compiler.resolverVisitor();
     Map mapping = compiler.resolveStatement("int f() {}").map;
-    Expect.equals(3, mapping.length);
+    Expect.equals(1, mapping.length);
     Element element;
     Node node;
     mapping.forEach((Node n, Element e) {
diff --git a/tests/compiler/dart2js/type_checker_test.dart b/tests/compiler/dart2js/type_checker_test.dart
index 7f634e4..0db2516c 100644
--- a/tests/compiler/dart2js/type_checker_test.dart
+++ b/tests/compiler/dart2js/type_checker_test.dart
@@ -36,7 +36,6 @@
                 testMethodInvocations,
                 testMethodInvocationsInClass,
                 testGetterSetterInvocation,
-                testControlFlow,
                 // testNewExpression,
                 testConditionalExpression,
                 testIfStatement,
@@ -657,48 +656,6 @@
   });
 }
 
-/** Tests analysis of returns (not required by the specification). */
-Future testControlFlow(MockCompiler compiler) {
-  Future check(String code, [expectedWarnings]) {
-    return analyzeTopLevel(code, expectedWarnings);
-  }
-
-  return Future.wait([
-    check("void foo() { if (true) { return; } }"),
-    check("foo() { if (true) { return; } }"),
-    check("int foo() { if (true) { return 1; } }",
-          MessageKind.MAYBE_MISSING_RETURN),
-    check("""void bar() {
-               if (true) {
-                 if (true) { return; } else { return; }
-               } else { return; }
-             }"""),
-    check("void baz() { return; int i = 1; }",
-          MessageKind.UNREACHABLE_CODE),
-    check("""void qux() {
-               if (true) {
-                 return;
-               } else if (true) {
-                 if (true) {
-                   return;
-                 }
-                 throw 'hest';
-               }
-               throw 'fisk';
-             }"""),
-    check("int hest() {}", MessageKind.MISSING_RETURN),
-    check("""int fisk() {
-               if (true) {
-                 if (true) { return 1; } else {}
-               } else { return 1; }
-             }""", MessageKind.MAYBE_MISSING_RETURN),
-    check("int foo() { while(true) { return 1; } }"),
-    check("int foo() { while(true) { return 1; } return 2; }",
-          MessageKind.UNREACHABLE_CODE),
-  ]);
-}
-
-
 void testFunctionCall(MockCompiler compiler) {
   compiler.parseScript(CLASS_WITH_METHODS);
 
diff --git a/tests/compiler/dart2js/unparser_test.dart b/tests/compiler/dart2js/unparser_test.dart
index a14f676..8130806 100644
--- a/tests/compiler/dart2js/unparser_test.dart
+++ b/tests/compiler/dart2js/unparser_test.dart
@@ -216,71 +216,71 @@
 }
 
 testRedirectingFactoryConstructors() {
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = Bar;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = Bar<T>;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = Bar<List<T>,T>;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = Bar<T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = Bar<List<T>,T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = prefix.Bar;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = prefix.Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = prefix.Bar<T>;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = prefix.Bar<List<T>,T>;");
-  testUnparseMemberAndAsMemberOfFoo("factory Foo() = prefix.Bar<T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=Bar;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=Bar<T>;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=Bar<List<T>,T>;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=Bar<T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=Bar<List<T>,T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=prefix.Bar;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=prefix.Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=prefix.Bar<T>;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=prefix.Bar<List<T>,T>;");
+  testUnparseMemberAndAsMemberOfFoo("factory Foo()=prefix.Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "factory Foo() = prefix.Bar<List<T>,T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = Bar;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = Bar<T>;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = Bar<List<T>,T>;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = Bar<T>.baz;");
+      "factory Foo()=prefix.Bar<List<T>,T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=Bar;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=Bar<T>;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=Bar<List<T>,T>;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "const factory Foo() = Bar<List<T>,T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = prefix.Bar;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = prefix.Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = prefix.Bar<T>;");
+      "const factory Foo()=Bar<List<T>,T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=prefix.Bar;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=prefix.Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=prefix.Bar<T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "const factory Foo() = prefix.Bar<List<T>,T>;");
-  testUnparseMemberAndAsMemberOfFoo("const factory Foo() = prefix.Bar<T>.baz;");
+      "const factory Foo()=prefix.Bar<List<T>,T>;");
+  testUnparseMemberAndAsMemberOfFoo("const factory Foo()=prefix.Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "const factory Foo() = prefix.Bar<List<T>,T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = Bar;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = Bar<T>;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = Bar<List<T>,T>;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = Bar<T>.baz;");
+      "const factory Foo()=prefix.Bar<List<T>,T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=Bar;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=Bar<T>;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=Bar<List<T>,T>;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external factory Foo() = Bar<List<T>,T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = prefix.Bar;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = prefix.Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("external factory Foo() = prefix.Bar<T>;");
+      "external factory Foo()=Bar<List<T>,T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=prefix.Bar;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=prefix.Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("external factory Foo()=prefix.Bar<T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external factory Foo() = prefix.Bar<List<T>,T>;");
+      "external factory Foo()=prefix.Bar<List<T>,T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external factory Foo() = prefix.Bar<T>.baz;");
+      "external factory Foo()=prefix.Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external factory Foo() = prefix.Bar<List<T>,T>.baz;");
-  testUnparseMemberAndAsMemberOfFoo("external const factory Foo() = Bar;");
-  testUnparseMemberAndAsMemberOfFoo("external const factory Foo() = Bar.baz;");
-  testUnparseMemberAndAsMemberOfFoo("external const factory Foo() = Bar<T>;");
+      "external factory Foo()=prefix.Bar<List<T>,T>.baz;");
+  testUnparseMemberAndAsMemberOfFoo("external const factory Foo()=Bar;");
+  testUnparseMemberAndAsMemberOfFoo("external const factory Foo()=Bar.baz;");
+  testUnparseMemberAndAsMemberOfFoo("external const factory Foo()=Bar<T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = Bar<List<T>,T>;");
+      "external const factory Foo()=Bar<List<T>,T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = Bar<T>.baz;");
+      "external const factory Foo()=Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = Bar<List<T>,T>.baz;");
+      "external const factory Foo()=Bar<List<T>,T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = prefix.Bar;");
+      "external const factory Foo()=prefix.Bar;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = prefix.Bar.baz;");
+      "external const factory Foo()=prefix.Bar.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = prefix.Bar<T>;");
+      "external const factory Foo()=prefix.Bar<T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = prefix.Bar<List<T>,T>;");
+      "external const factory Foo()=prefix.Bar<List<T>,T>;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = prefix.Bar<T>.baz;");
+      "external const factory Foo()=prefix.Bar<T>.baz;");
   testUnparseMemberAndAsMemberOfFoo(
-      "external const factory Foo() = prefix.Bar<List<T>,T>.baz;");
+      "external const factory Foo()=prefix.Bar<List<T>,T>.baz;");
 }
 
 testClassDeclarations() {
diff --git a/tests/compiler/dart2js_extra/closure2_test.dart b/tests/compiler/dart2js_extra/closure2_test.dart
index 942f324..fd8e726 100644
--- a/tests/compiler/dart2js_extra/closure2_test.dart
+++ b/tests/compiler/dart2js_extra/closure2_test.dart
@@ -8,7 +8,7 @@
   // A closure will be implemented as a class. Make sure everything is set up
   // correctly when no other class is generated. In particular we need
   // the Dart-Object class to be generated.
-  var f = fun() => 42;
+  var f = () => 42;
   Expect.equals(42, f());
 }
 
diff --git a/tests/compiler/dart2js_extra/closure3_test.dart b/tests/compiler/dart2js_extra/closure3_test.dart
index cab801f..daea300 100644
--- a/tests/compiler/dart2js_extra/closure3_test.dart
+++ b/tests/compiler/dart2js_extra/closure3_test.dart
@@ -5,7 +5,7 @@
 import "package:expect/expect.dart";
 
 main() {
-  var f = fun({a: 3, b: 8}) {
+  var f = ({a: 3, b: 8}) {
     return a + b;
   };
   Expect.equals(11, f());
diff --git a/tests/compiler/dart2js_extra/closure4_test.dart b/tests/compiler/dart2js_extra/closure4_test.dart
index 6ef0c8c..08e00bd 100644
--- a/tests/compiler/dart2js_extra/closure4_test.dart
+++ b/tests/compiler/dart2js_extra/closure4_test.dart
@@ -5,29 +5,32 @@
 import "package:expect/expect.dart";
 
 closure0() {
-  var f = fib(i) {
+  var fib;
+  fib = (i) {
     if (i < 2) return i;
     return fib(i - 1) + fib(i - 2);
   };
-  Expect.equals(5, f(5));
+  Expect.equals(5, fib(5));
 }
 
 closure1() {
   var decr1 = 0;
   var decr2 = 0;
-  var f = fib(i) {
+  var fib;
+  fib = (i) {
     if (i < 2) return i;
     return fib(i - decr1) + fib(i - decr2);
   };
   decr1++;
   decr2 += 2;
-  Expect.equals(5, f(5));
+  Expect.equals(5, fib(5));
 }
 
 closure2() {
-  var f = fun(doReturnClosure) {
+  var f;
+  f = (doReturnClosure) {
     if (doReturnClosure) {
-      return inner() => fun(false);
+      return () => f(false);
     } else {
       return 499;
     }
diff --git a/tests/compiler/dart2js_extra/closure_capture2_test.dart b/tests/compiler/dart2js_extra/closure_capture2_test.dart
index 6c13143..b49bf98 100644
--- a/tests/compiler/dart2js_extra/closure_capture2_test.dart
+++ b/tests/compiler/dart2js_extra/closure_capture2_test.dart
@@ -11,14 +11,12 @@
   var g;
   {
     var x = 499;
-    // TODO(floitsch): remove name from functions.
-    f = fun() { return x; };
+    f = () { return x; };
     x++;
   }
   {
     var x = 42;
-    // TODO(floitsch): remove name from functions.
-    g = fun() { return x; };
+    g = () { return x; };
     x++;
   }
   Expect.equals(500, f());
@@ -29,8 +27,7 @@
   // f captures variable $0 which once could yield to troubles with HForeign if
   // we did not mangle correctly.
   var $1 = 499;
-  // TODO(floitsch): remove name from functions.
-  var f = fun() { return $1; };
+  var f = () { return $1; };
   $1++;
   Expect.equals(500, f());
 }
diff --git a/tests/compiler/dart2js_extra/closure_capture3_test.dart b/tests/compiler/dart2js_extra/closure_capture3_test.dart
index 6044a2a..5a54b13 100644
--- a/tests/compiler/dart2js_extra/closure_capture3_test.dart
+++ b/tests/compiler/dart2js_extra/closure_capture3_test.dart
@@ -9,13 +9,13 @@
   Closure(val) : this.x = val;
 
   foo() {
-    return fun() {
+    return () {
       return x;
     };
   }
 
   bar() {
-    return fun() {
+    return () {
       return toto();
     };
   }
@@ -25,8 +25,8 @@
   }
 
   nestedClosure() {
-    var f = g() { return x; };
-    return fun() { return f() + 2; };
+    var f = () { return x; };
+    return () { return f() + 2; };
   }
 }
 
diff --git a/tests/compiler/dart2js_extra/closure_capture4_test.dart b/tests/compiler/dart2js_extra/closure_capture4_test.dart
index e19a37b..aa52d74 100644
--- a/tests/compiler/dart2js_extra/closure_capture4_test.dart
+++ b/tests/compiler/dart2js_extra/closure_capture4_test.dart
@@ -8,7 +8,7 @@
   var input = [1, 2, 3];
   var fs = [];
   for (var x in input) {
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
   }
   Expect.equals(3, fs.length);
   Expect.equals(1, fs[0]());
@@ -20,7 +20,7 @@
   var input = [1, 2, 3];
   var fs = [];
   for (var x in input) {
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
     x++;
   }
   Expect.equals(3, fs.length);
@@ -34,7 +34,7 @@
   var fs = [];
   for (var i = 0; i < input.length; i++) {
     var j = i;
-    fs.add(fun() { return input[j]; });
+    fs.add(() { return input[j]; });
   }
   Expect.equals(3, fs.length);
   Expect.equals(1, fs[0]());
@@ -47,7 +47,7 @@
   var fs = [];
   for (var i = 0; i < input.length; i++) {
     var x = input[i];
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
     x++;
   }
   Expect.equals(3, fs.length);
@@ -62,7 +62,7 @@
   var x;
   for (var i = 0; i < input.length; i++) {
     x = input[i];
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
     x++;
   }
   Expect.equals(3, fs.length);
@@ -77,7 +77,7 @@
   var i = 0;
   do {
     var x = input[i];
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
   } while (++i < input.length);
   Expect.equals(3, fs.length);
   Expect.equals(1, fs[0]());
diff --git a/tests/compiler/dart2js_extra/closure_capture5_test.dart b/tests/compiler/dart2js_extra/closure_capture5_test.dart
index 8689cdc..37df6df 100644
--- a/tests/compiler/dart2js_extra/closure_capture5_test.dart
+++ b/tests/compiler/dart2js_extra/closure_capture5_test.dart
@@ -7,7 +7,7 @@
 closure0() {
   var fs = [];
   for (var x = 1; x <= 3; x++) {
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
   }
   Expect.equals(3, fs.length);
   Expect.equals(1, fs[0]());
@@ -18,7 +18,7 @@
 closure1() {
   var fs = [];
   for (var x = 0; x < 6; x++) {
-    fs.add(fun() { return x; });
+    fs.add(() { return x; });
     x++;
   }
   Expect.equals(3, fs.length);
@@ -31,7 +31,7 @@
   var input = [1, 2, 3];
   var fs = [];
   for (var i = 0; i < input.length; i++) {
-    fs.add(fun() { return input[i]; });
+    fs.add(() { return input[i]; });
   }
   Expect.equals(3, fs.length);
   Expect.equals(1, fs[0]());
@@ -43,8 +43,8 @@
   var fs = [];
   for (var i = 0;
        i < 3;
-       (f() {
-         fs.add(g() => i);
+       (() {
+         fs.add(() => i);
          i++;
        })()) {
     i++;
@@ -57,8 +57,8 @@
 closure4() {
   var g;
   for (var i = 0;
-       (f() {
-         g = fun() => i;
+       (() {
+         g = () => i;
          return false;
        })();
        i++){
diff --git a/tests/compiler/dart2js_extra/closure_capture_test.dart b/tests/compiler/dart2js_extra/closure_capture_test.dart
index f76b024..c55317b 100644
--- a/tests/compiler/dart2js_extra/closure_capture_test.dart
+++ b/tests/compiler/dart2js_extra/closure_capture_test.dart
@@ -6,15 +6,14 @@
 
 closure0() {
   var x = 499;
-  // TODO(floitsch): remove name from functions.
-  var f = fun() { return x; };
+  var f = () { return x; };
   Expect.equals(499, f());
 }
 
 class A {
   closure1() {
     var x = 499;
-    var f = fun() { return x; };
+    var f = () { return x; };
     Expect.equals(499, f());
   }
 }
@@ -25,12 +24,12 @@
 
 closure2() {
   var x = 499;
-  Expect.equals(499, applyFun(fun() { return x; }));
+  Expect.equals(499, applyFun(() { return x; }));
 }
 
 closure3() {
   var y = 400;
-  var f = fun(x) { return y + x; };
+  var f = (x) { return y + x; };
   Expect.equals(499, f(99));
 }
 
@@ -40,13 +39,12 @@
 
 closure4() {
   var z = 9;
-  Expect.equals(499, applyFun2(fun(x, y) { return x + y + z; }));
+  Expect.equals(499, applyFun2((x, y) { return x + y + z; }));
 }
 
 closure5() {
   var x = 498;
-  // TODO(floitsch): remove name from functions.
-  var f = fun() { return x; };
+  var f = () { return x; };
   x++;
   Expect.equals(499, f());
 }
@@ -54,7 +52,7 @@
 class A2 {
   closure6() {
     var x = 498;
-    var f = fun() { return x; };
+    var f = () { return x; };
     x++;
     Expect.equals(499, f());
   }
@@ -62,21 +60,21 @@
 
 closure7() {
   var x = 498;
-  var f = fun() { return x; };
+  var f = () { return x; };
   x++;
   Expect.equals(499, applyFun(f));
 }
 
 closure8() {
   var y = 399;
-  var f = fun(x) { return y + x; };
+  var f = (x) { return y + x; };
   y++;
   Expect.equals(499, f(99));
 }
 
 closure9() {
   var z = 9;
-  Expect.equals(499, applyFun2(fun(x, y) { return x + y + z; }));
+  Expect.equals(499, applyFun2((x, y) { return x + y + z; }));
 }
 
 main() {
diff --git a/tests/compiler/dart2js_extra/closure_test.dart b/tests/compiler/dart2js_extra/closure_test.dart
index fad942f..b0bdeec 100644
--- a/tests/compiler/dart2js_extra/closure_test.dart
+++ b/tests/compiler/dart2js_extra/closure_test.dart
@@ -5,14 +5,13 @@
 import "package:expect/expect.dart";
 
 closure0() {
-  // TODO(floitsch): remove name from functions.
-  var f = fun() { return 499; };
+  var f = () { return 499; };
   Expect.equals(499, f());
 }
 
 class A {
   closure1() {
-    var f = fun() { return 499; };
+    var f = () { return 499; };
     Expect.equals(499, f());
   }
 }
@@ -22,11 +21,11 @@
 }
 
 closure2() {
-  Expect.equals(499, applyFun(fun() { return 499; }));
+  Expect.equals(499, applyFun(() { return 499; }));
 }
 
 closure3() {
-  var f = fun(x) { return 400 + x; };
+  var f = (x) { return 400 + x; };
   Expect.equals(499, f(99));
 }
 
@@ -35,7 +34,7 @@
 }
 
 closure4() {
-  Expect.equals(499, applyFun2(fun(x, y) { return x + y; }));
+  Expect.equals(499, applyFun2((x, y) { return x + y; }));
 }
 
 main() {
diff --git a/tests/corelib/uri_test.dart b/tests/corelib/uri_test.dart
index ef99595..c2c8286 100644
--- a/tests/corelib/uri_test.dart
+++ b/tests/corelib/uri_test.dart
@@ -365,9 +365,71 @@
                         query: "", fragment: "").toString());
 }
 
+void testReplace() {
+  var uris = [
+    Uri.parse(""),
+    Uri.parse("a://@:/?#"),
+    Uri.parse("a://b@c:4/e/f?g#h"),
+    Uri.parse("$SCHEMECHAR://$USERINFOCHAR@$REGNAMECHAR:$DIGIT/$PCHAR/$PCHAR"
+              "?$QUERYCHAR#$QUERYCHAR"),
+  ];
+  for (var uri1 in uris) {
+    for (var uri2 in uris) {
+      if (identical(uri1, uri2)) continue;
+      var scheme = uri1.scheme;
+      var userInfo = uri1.hasAuthority ? uri1.userInfo : "";
+      var host = uri1.hasAuthority ? uri1.host : null;
+      var port = uri1.hasAuthority ? uri1.port : 0;
+      var path = uri1.path;
+      var query = uri1.hasQuery ? uri1.query : null;
+      var fragment = uri1.hasFragment ? uri1.fragment : null;
+
+      var tmp1 = uri1;
+      test() {
+        var tmp2 = new Uri(scheme: scheme, userInfo: userInfo, host: host,
+                           port: port, path: path,
+                           query: query == "" ? null : query,
+                           queryParameters: query == "" ? {} : null,
+                           fragment: fragment);
+        Expect.equals(tmp1, tmp2);
+      }
+
+      test();
+
+      scheme = uri2.scheme;
+      tmp1 = tmp1.replace(scheme: scheme);
+      test();
+
+      if (uri2.hasAuthority) {
+        userInfo = uri2.userInfo;
+        host = uri2.host;
+        port = uri2.port;
+        tmp1 = tmp1.replace(userInfo: userInfo, host: host, port: port);
+        test();
+      }
+
+      path = uri2.path;
+      tmp1 = tmp1.replace(path: path);
+      test();
+
+      if (uri2.hasQuery) {
+        query = uri2.query;
+        tmp1 = tmp1.replace(query: query);
+        test();
+      }
+
+      if (uri2.hasFragment) {
+        fragment = uri2.fragment;
+        tmp1 = tmp1.replace(fragment: fragment);
+        test();
+      }
+    }
+  }
+}
+
 main() {
   testUri("http:", true);
-  testUri("file://", true);
+  testUri("file:///", true);
   testUri("file", false);
   testUri("http://user@example.com:8080/fisk?query=89&hest=silas", true);
   testUri("http://user@example.com:8080/fisk?query=89&hest=silas#fragment",
@@ -390,7 +452,7 @@
                           path: "/a/b/c/",
                           query: null,
                           fragment: null).toString());
-  Expect.stringEquals("file://", Uri.parse("file:").toString());
+  Expect.stringEquals("file:///", Uri.parse("file:").toString());
 
   testResolvePath("/a/g", "/a/b/c/./../../g");
   testResolvePath("/a/g", "/a/b/c/./../../g");
@@ -529,6 +591,7 @@
   testValidCharacters();
   testInvalidUrls();
   testNormalization();
+  testReplace();
 }
 
 String dump(Uri uri) {
diff --git a/tests/html/html.status b/tests/html/html.status
index 55d6dfb..12563bf 100644
--- a/tests/html/html.status
+++ b/tests/html/html.status
@@ -51,13 +51,22 @@
 indexeddb_1_test/functional: RuntimeError # Issue 19127. Actually a timeout, but do not skip.
 mouse_event_test: Skip # Times out. Issue 19127
 request_animation_frame_test: Skip # Times out, and also passes while taking 4.00 minutes. Issue 19127.
-transition_event_test/functional: RuntimeError # Issue 19127
 xhr_test/xhr: RuntimeError # Issue 19127
 
 [ $compiler == none && $runtime == drt && $system == windows ]
 worker_test/functional: Pass, Crash # Issue 9929.
 touchevent_test/supported: Pass, Fail # Issue 17061
 
+[ $compiler == none && $runtime == drt && $system == windows && $checked ]
+fileapi_test/getDirectory: RuntimeError # Issue 20366
+fileapi_test/getFile: RuntimeError # Issue 20366
+fileapi_test/directoryReader: RuntimeError # Issue 20366
+fileapi_test/entry: RuntimeError # Issue 20366
+fileapi_test/fileEntry: RuntimeError # Issue 20366
+indexeddb_2_test: RuntimeError # Issue 20366
+indexeddb_5_test: RuntimeError # Issue 20366
+xhr_test/xhr_requestBlob: RuntimeError # Issue 20366
+
 [ $compiler == dart2js && $runtime == chromeOnAndroid ]
 crypto_test/functional: Pass, Slow # TODO(dart2js-team): Please triage this failure.
 input_element_test/supported_datetime-local: Pass, Slow # TODO(dart2js-team): Please triage this failure.
diff --git a/tests/language/const_instance_field_test.dart b/tests/language/const_instance_field_test.dart
new file mode 100644
index 0000000..5724c17
--- /dev/null
+++ b/tests/language/const_instance_field_test.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Test that const instance fields are compile-time errors.
+
+class C {
+  const field = 0; /// 01: compile-time error
+}
+
+void main() {
+  new C();
+}
\ No newline at end of file
diff --git a/tests/language/language.status b/tests/language/language.status
index 13465ba..e15eba5 100644
--- a/tests/language/language.status
+++ b/tests/language/language.status
@@ -143,6 +143,6 @@
 [ $compiler == none && $runtime == vm && $arch == mips && $mode == debug ]
 stack_overflow_test: Skip # Crashes. Issue 17440.
 stack_overflow_stacktrace_test: Skip # Crashes. Issue 17440.
+large_class_declaration_test: Skip # Times out. Issue 20352
 
-[ $compiler == none ]
-const_constructor_nonconst_field_test/01: Fail # Issue 18779, 18780
+
diff --git a/tests/language/language_analyzer.status b/tests/language/language_analyzer.status
index 2128e12..2beeedd 100644
--- a/tests/language/language_analyzer.status
+++ b/tests/language/language_analyzer.status
@@ -165,6 +165,9 @@
 
 regress_19413_test/01: MissingStaticWarning # Issue 19424
 
+# test issue 20074
+regress_20074_test: CompileTimeError # Issue 20074
+
 # The following tests are currently assumed to be failing because the test is wrong.
 #
 application_negative_test: CompileTimeError # Test Issue 14528
diff --git a/tests/language/language_analyzer2.status b/tests/language/language_analyzer2.status
index 722fd39c..d8d25d5 100644
--- a/tests/language/language_analyzer2.status
+++ b/tests/language/language_analyzer2.status
@@ -165,6 +165,9 @@
 
 regress_19413_test/01: MissingStaticWarning # Issue 19424
 
+# test issue 20074
+regress_20074_test: CompileTimeError # Issue 20074
+
 # The following tests are currently assumed to be failing because the test is wrong.
 #
 application_negative_test: CompileTimeError # Test Issue 14528
diff --git a/tests/language/regress_20074_test.dart b/tests/language/regress_20074_test.dart
new file mode 100644
index 0000000..9b11db0
--- /dev/null
+++ b/tests/language/regress_20074_test.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Regression test for issue 20074. Check that a parameter is not declared
+// in the same scope as its function declaration.
+
+doit() {
+  error(error) {
+    print(error);
+  }
+  error('foobar');
+}
+
+main() {
+  doit();
+}
\ No newline at end of file
diff --git a/tests/lib/js/null_test.dart b/tests/lib/js/null_test.dart
index 82bc9df..16970856 100644
--- a/tests/lib/js/null_test.dart
+++ b/tests/lib/js/null_test.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
diff --git a/tests/lib/js/null_test.html b/tests/lib/js/null_test.html
index 8d23cfa..65b66b7 100644
--- a/tests/lib/js/null_test.html
+++ b/tests/lib/js/null_test.html
@@ -13,10 +13,7 @@
 </head>
 <body>
   <h1> Running null_test </h1>
-  <script>
-    function isUndefined(o) { return o === undefined; }
-    function isNull(o) { return o === null; }
-  </script>
+  <script src="/root_dart/tests/lib/js/null_test.js"></script>
   <script type="text/javascript"
       src="/root_dart/tools/testing/dart/test_controller.js"></script>
   %TEST_SCRIPTS%
diff --git a/tests/lib/js/null_test.js b/tests/lib/js/null_test.js
new file mode 100644
index 0000000..35e1241
--- /dev/null
+++ b/tests/lib/js/null_test.js
@@ -0,0 +1,7 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This code is not inlined in the test to support testing in CSP mode.
+function isUndefined(o) { return o === undefined; }
+function isNull(o) { return o === null; }
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index e16c839..25ae832 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -19,7 +19,6 @@
 async/deferred/deferred_in_isolate_test: RuntimeError # Issue 16898
 mirrors/deferred_mirrors_metadata_test: Fail # Issue 16898
 mirrors/deferred_mirrors_metatarget_test: Fail # Issue 16898
-js/null_test: Fail # Issue 20310
 
 [ $compiler == dart2js ]
 async/schedule_microtask6_test: RuntimeError # global error handling is not supported. http://dartbug.com/5958
@@ -107,7 +106,6 @@
 mirrors/type_variable_is_static_test: RuntimeError # Issue 6335
 mirrors/type_variable_owner_test/01: RuntimeError # Issue 12785
 mirrors/variable_is_const_test/none: RuntimeError # Issue 14671
-mirrors/variable_is_const_test/01: MissingCompileTimeError # Issue 5519
 mirrors/raw_type_test/01: RuntimeError # http://dartbug.com/6490
 mirrors/mirrors_reader_test: Slow, RuntimeError # Issue 16589
 
@@ -204,7 +202,6 @@
 convert/json_lib_test: Fail # Issue 10961
 
 [ $compiler == dart2js && $minified ]
-mirrors/typedef_test/01: Fail # http://dartbug.com/6490
 mirrors/mirrors_used_get_name_test: RuntimeError
 mirrors/mirrors_used_get_name2_test: RuntimeError
 
@@ -253,6 +250,8 @@
 
 async/timer_not_available_test: SkipByDesign # only meant to test when there is no way to implement timer (currently only in d8)
 
+mirrors/typedef_declaration_test/01: Fail # dartbug.com/16048. Remove multitest marker when it passes.
+
 [ $compiler == none && ( $runtime == drt || $runtime == dartium || $runtime == ContentShellOnAndroid) ]
 async/schedule_microtask6_test: Fail # Issue 10910
 async/timer_test: Fail, Pass # Issue 15487
diff --git a/tests/lib/mirrors/relation_subclass_test.dart b/tests/lib/mirrors/relation_subclass_test.dart
index 2c2c525..2e8bbc2 100644
--- a/tests/lib/mirrors/relation_subclass_test.dart
+++ b/tests/lib/mirrors/relation_subclass_test.dart
@@ -57,13 +57,12 @@
   Expect.isFalse(Obj.isSubclassOf(Func));
 
   // Function typedef.
-  // TODO(16939): retrieve via declaration when dart2js supports it.
-  var NumPred = reflectType(NumberPredicate);
-  var IntPred = reflectType(IntegerPredicate);
-  var DubPred = reflectType(DoublePredicate);
-  var NumGen = reflectType(NumberGenerator);
-  var IntGen = reflectType(IntegerGenerator);
-  var DubGen = reflectType(DoubleGenerator);
+  var NumPred = thisLibrary.declarations[#NumberPredicate];
+  var IntPred = thisLibrary.declarations[#IntegerPredicate];
+  var DubPred = thisLibrary.declarations[#DoublePredicate];
+  var NumGen = thisLibrary.declarations[#NumberGenerator];
+  var IntGen = thisLibrary.declarations[#IntegerGenerator];
+  var DubGen = thisLibrary.declarations[#DoubleGenerator];
 
   isArgumentOrTypeError(e) => e is ArgumentError || e is TypeError;
   Expect.throws(() => Func.isSubclassOf(NumPred), isArgumentOrTypeError);
diff --git a/tests/lib/mirrors/typedef_declaration_test.dart b/tests/lib/mirrors/typedef_declaration_test.dart
new file mode 100644
index 0000000..f039c1b
--- /dev/null
+++ b/tests/lib/mirrors/typedef_declaration_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library test;
+
+import 'package:expect/expect.dart';
+
+@MirrorsUsed(targets: "Foo")
+import 'dart:mirrors';
+
+typedef int Foo(String x);
+typedef int Bar();
+
+main() {
+  LibraryMirror thisLibrary = currentMirrorSystem().findLibrary(#test);
+
+  Mirror fooMirror = thisLibrary.declarations[#Foo];
+
+  Expect.isTrue(fooMirror != null, 'Foo not found.');
+  Expect.isTrue(thisLibrary.declarations[#Foo] is TypedefMirror,
+                'TypedefMirror expected, found $fooMirror');
+
+  // The following code does not currenty work on the VM, because it does not
+  // support MirrorsUsed (see dartbug.com/16048).
+  Mirror barMirror = thisLibrary.declarations[#Bar];              /// 01: ok
+  Expect.isTrue(barMirror == null,                                /// 01: continued
+                'Bar should not be emitted due to MirrorsUsed.'); /// 01: continued
+}
diff --git a/tests/standalone/standalone.status b/tests/standalone/standalone.status
index b7aeba1..7191b73 100644
--- a/tests/standalone/standalone.status
+++ b/tests/standalone/standalone.status
@@ -123,6 +123,11 @@
 io/signals_test: Skip # Starts 10 dart subprocesses, uses too much memory
 io/file_read_special_device_test: Fail # dartbug.com/17440
 
+[ $arch == mips && $mode == debug ]
+io/web_socket_test: Skip # Times out. Issue 20352
+io/test_runner_test: Skip # Flakily times out in a subtest. Issue 201351
+full_coverage_test: Skip # Times out. Issue 20352
+
 [ $compiler == none && ($runtime == dartium || $runtime == ContentShellOnAndroid) && $unchecked ]
 assert_test: Fail # Issue 13719: Please triage this failure.
 
diff --git a/tools/VERSION b/tools/VERSION
index c565255..a0c155d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 1
 MINOR 6
 PATCH 0
-PRERELEASE 7
+PRERELEASE 8
 PRERELEASE_PATCH 0
diff --git a/tools/bots/cross-vm.py b/tools/bots/cross-vm.py
index 9c81275..8792fe7 100644
--- a/tools/bots/cross-vm.py
+++ b/tools/bots/cross-vm.py
@@ -69,7 +69,8 @@
   test_py = os.path.join('tools', 'test.py')
   test_args = [sys.executable, test_py, '--progress=line', '--report',
                '--time', '--compiler=none', '--runtime=vm', '--write-debug-log',
-               '--write-test-outcome-log', '--mode=' + mode, '--arch=' + arch]
+               '--write-test-outcome-log', '--mode=' + mode, '--arch=' + arch,
+               '--exclude-suite=pkg']
 
   revision = int(os.environ['BUILDBOT_GOT_REVISION'])
   tarball = tarball_name(arch, mode, revision)
diff --git a/tools/bots/functional_testing.py b/tools/bots/functional_testing.py
index 327281f..9b06ba5 100644
--- a/tools/bots/functional_testing.py
+++ b/tools/bots/functional_testing.py
@@ -9,8 +9,10 @@
 """
 
 import os
+import os.path
 import re
 import shutil
+import subprocess
 import sys
 
 import bot
@@ -23,6 +25,9 @@
 
 HOST_OS = utils.GuessOS()
 
+def IsWindows():
+  return HOST_OS == 'win32'
+
 def SrcConfig(name, is_buildbot):
   """Returns info for the current buildbot based on the name of the builder.
 
@@ -48,6 +53,19 @@
   bot.RunProcess(args)
 
 def FTSlave(config):
+
+  # Run SWTBot tests
+  if len(sys.argv) > 0:
+    scriptdir = os.path.dirname(sys.argv[0])
+    builddir = os.path.join(scriptdir, '..', '..', 'editor', 'build')
+    testScript = os.path.join(builddir, 'testswteditor.py')
+    cmd = [sys.executable, testScript]
+    try:
+      subprocess.call(cmd, shell=IsWindows())
+    except:
+      pass
+
+  # Prepare to run EggPlant tests
   with bot.BuildStep('Fetching editor'):
     revision = int(os.environ['BUILDBOT_GOT_REVISION'])
     bot_name, _ = bot.GetBotName()
@@ -84,6 +102,7 @@
       os.makedirs(builddir)
       script_locations = os.path.join(bot_utils.DART_DIR, 'editor', 'ft')
       Run(['/home/chrome-bot/func-test/bot-run', builddir, script_locations])
+      #TODO Copy builddir to shared storage somewhere.
 
 def FTSteps(config):
   if config.builder_tag == 'master':
diff --git a/tools/dom/idl/dart/dart.idl b/tools/dom/idl/dart/dart.idl
index 3afce21..cfb832b 100644
--- a/tools/dom/idl/dart/dart.idl
+++ b/tools/dom/idl/dart/dart.idl
@@ -33,11 +33,6 @@
 };
 
 [Supplemental]
-interface FileReader {
-  [Suppressed] readonly attribute Object result;
-};
-
-[Supplemental]
 interface Node {
   [Custom] Node cloneNode([Default=Undefined] optional boolean deep);
   [Suppressed] readonly attribute Element nextElementSibling;
diff --git a/tools/dom/scripts/htmlrenamer.py b/tools/dom/scripts/htmlrenamer.py
index b42afe2..df47c2a 100644
--- a/tools/dom/scripts/htmlrenamer.py
+++ b/tools/dom/scripts/htmlrenamer.py
@@ -253,6 +253,7 @@
   'Element.scrollHeight',
 
   'Event.initEvent',
+  'FileReader.result',
   'Geolocation.clearWatch',
   'Geolocation.getCurrentPosition',
   'Geolocation.watchPosition',
diff --git a/tools/dom/templates/html/impl/impl_CanvasRenderingContext2D.darttemplate b/tools/dom/templates/html/impl/impl_CanvasRenderingContext2D.darttemplate
index 1068fdc..4e92d9a 100644
--- a/tools/dom/templates/html/impl/impl_CanvasRenderingContext2D.darttemplate
+++ b/tools/dom/templates/html/impl/impl_CanvasRenderingContext2D.darttemplate
@@ -270,9 +270,11 @@
   @DomName('CanvasRenderingContext2D.lineDashOffset')
   // TODO(14316): Firefox has this functionality with mozDashOffset, but it
   // needs to be polyfilled.
-  void set lineDashOffset(num value) => JS('void',
-      'typeof #.lineDashOffset != "undefined" ? #.lineDashOffset = # : '
-      '#.webkitLineDashOffset = #', this, this, value, this, value);
+  void set lineDashOffset(num value) {
+    JS('void',
+       'typeof #.lineDashOffset != "undefined" ? #.lineDashOffset = # : '
+       '#.webkitLineDashOffset = #', this, this, value, this, value);
+  }
 $else
   // TODO(amouravski): Add Dartium native methods for drawImage once we figure
   // out how to not break native bindings.
diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate
index f1cf507..210ef42 100644
--- a/tools/dom/templates/html/impl/impl_Element.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
@@ -1368,7 +1368,9 @@
 
   @DomName('Element.scrollLeft')
   @DocsEditable()
-  void set scrollLeft(int value) => JS("void", "#.scrollLeft = #", this, value.round());
+  void set scrollLeft(int value) {
+    JS("void", "#.scrollLeft = #", this, value.round());
+  }
 
   @DomName('Element.scrollTop')
   @DocsEditable()
@@ -1376,7 +1378,9 @@
 
   @DomName('Element.scrollTop')
   @DocsEditable()
-  void set scrollTop(int value) => JS("void", "#.scrollTop = #", this, value.round());
+  void set scrollTop(int value) {
+    JS("void", "#.scrollTop = #", this, value.round());
+  }
 
   @DomName('Element.scrollWidth')
   @DocsEditable()
diff --git a/tools/testing/dart/browser_controller.dart b/tools/testing/dart/browser_controller.dart
index 1f43845..e0784cf 100644
--- a/tools/testing/dart/browser_controller.dart
+++ b/tools/testing/dart/browser_controller.dart
@@ -745,6 +745,7 @@
   BrowserTest lastTest;
   bool timeout = false;
   Timer nextTestTimeout;
+  Stopwatch timeSinceRestart = new Stopwatch();
 
   BrowserTestingStatus(Browser this.browser);
 }
@@ -802,6 +803,7 @@
 class BrowserTestRunner {
   static const int MAX_NEXT_TEST_TIMEOUTS = 10;
   static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60);
+  static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60);
 
   final Map globalConfiguration;
   final bool checkedMode; // needed for dartium
@@ -864,6 +866,7 @@
               var status = new BrowserTestingStatus(browser);
               browserStatus[browser.id] = status;
               status.nextTestTimeout = createNextTestTimer(status);
+              status.timeSinceRestart.start();
             }
             return success;
           });
@@ -995,6 +998,11 @@
   void handleTimeout(BrowserTestingStatus status) {
     // We simply kill the browser and starts up a new one!
     // We could be smarter here, but it does not seems like it is worth it.
+    if (status.timeout) {
+      DebugLogger.error(
+          "Got test timeout for an already restarting browser");
+      return;
+    }
     status.timeout = true;
     timedOut.add(status.currentTest.url);
     var id = status.browser.id;
@@ -1048,6 +1056,7 @@
     var status = new BrowserTestingStatus(browser);
     browserStatus[new_id] = status;
     status.nextTestTimeout = createNextTestTimer(status);
+    status.timeSinceRestart.start();
     browser.start(testingServer.getDriverUrl(new_id)).then((success) {
       // We may have started terminating in the mean time.
       if (underTermination) {
@@ -1083,6 +1092,24 @@
     // We are currently terminating this browser, don't start a new test.
     if (status.timeout) return null;
 
+    // Restart content_shell and dartium on Android if they have been
+    // running for longer than RESTART_BROWSER_INTERVAL. The tests have
+    // had flaky timeouts, and this may help.
+    if ((browserName == 'ContentShellOnAndroid' ||
+         browserName == 'DartiumOnAndroid' ) &&
+        status.timeSinceRestart.elapsed > RESTART_BROWSER_INTERVAL) {
+      var id = status.browser.id;
+      // Reset stopwatch so we don't trigger again before restarting.
+      status.timeout = true;
+      status.browser.close().then((_) {
+        // We don't want to start a new browser if we are terminating.
+        if (underTermination) return;
+        restartBrowser(id);
+      });
+      // Don't send a test to the browser we are restarting.
+      return null;
+    }
+
     BrowserTest test = testQueue.removeLast();
     if (status.currentTest == null) {
       status.currentTest = test;
@@ -1123,12 +1150,14 @@
   void handleNextTestTimeout(status) {
     DebugLogger.warning(
         "Browser timed out before getting next test. Restarting");
+    if (status.timeout) return;
     numBrowserGetTestTimeouts++;
     if (numBrowserGetTestTimeouts >= MAX_NEXT_TEST_TIMEOUTS) {
       DebugLogger.error(
           "Too many browser timeouts before getting next test. Terminating");
       terminate().then((_) => exit(1));
     } else {
+      status.timeout = true;
       status.browser.close().then((_) => restartBrowser(status.browser.id));
     }
   }
diff --git a/tools/testing/dart/compiler_configuration.dart b/tools/testing/dart/compiler_configuration.dart
index 58a4c54..3407c67 100644
--- a/tools/testing/dart/compiler_configuration.dart
+++ b/tools/testing/dart/compiler_configuration.dart
@@ -73,7 +73,9 @@
       case 'dart2js':
         return new Dart2jsCompilerConfiguration(
             isDebug: isDebug, isChecked: isChecked,
-            isHostChecked: isHostChecked, useSdk: useSdk, isCsp: isCsp);
+            isHostChecked: isHostChecked, useSdk: useSdk, isCsp: isCsp,
+            extraDart2jsOptions:
+                TestUtils.getExtraOptions(configuration, 'dart2js_options'));
       case 'dart2dart':
         return new Dart2dartCompilerConfiguration(
             isDebug: isDebug, isChecked: isChecked,
@@ -222,13 +224,15 @@
 /// Configuration for dart2js compiler.
 class Dart2jsCompilerConfiguration extends Dart2xCompilerConfiguration {
   final bool isCsp;
+  final List<String> extraDart2jsOptions;
 
   Dart2jsCompilerConfiguration({
       bool isDebug,
       bool isChecked,
       bool isHostChecked,
       bool useSdk,
-      bool this.isCsp})
+      bool this.isCsp,
+      this.extraDart2jsOptions})
       : super(
           'dart2js',
           isDebug: isDebug, isChecked: isChecked,
@@ -254,7 +258,7 @@
                 '$tempDir/out.js',
                 buildDir,
                 CommandBuilder.instance,
-                arguments,
+                []..addAll(arguments)..addAll(extraDart2jsOptions),
                 environmentOverrides)],
         '$tempDir/out.js',
         'application/javascript');
diff --git a/tools/testing/dart/test_options.dart b/tools/testing/dart/test_options.dart
index b41edfa..15cb9bc 100644
--- a/tools/testing/dart/test_options.dart
+++ b/tools/testing/dart/test_options.dart
@@ -420,6 +420,12 @@
               [],
               null),
           new _TestOptionSpecification(
+              'dart2js_options',
+              'Extra options for dart2js compilation step',
+              ['--dart2js-options'],
+              [],
+              null),
+          new _TestOptionSpecification(
               'exclude_suite',
               'Exclude suites from default selector, only works when no'
               ' selector has been specified on the command line',
diff --git a/tools/testing/dart/test_suite.dart b/tools/testing/dart/test_suite.dart
index a1ac5ab..57fbd57 100644
--- a/tools/testing/dart/test_suite.dart
+++ b/tools/testing/dart/test_suite.dart
@@ -2106,16 +2106,22 @@
   }
 
   /**
+   * Gets extra options under [key] passed to the testing script.
+   */
+  static List<String> getExtraOptions(Map configuration, String key) {
+    if (configuration[key] == null) return <String>[];
+    return configuration[key]
+        .split(" ")
+        .map((s) => s.trim())
+        .where((s) => s.isNotEmpty)
+        .toList();
+  }
+
+  /**
    * Gets extra vm options passed to the testing script.
    */
-  static List<String> getExtraVmOptions(Map configuration) {
-    var extraVmOptions = [];
-    if (configuration['vm_options'] != null) {
-      extraVmOptions = configuration['vm_options'].split(" ");
-      extraVmOptions.removeWhere((s) => s.trim() == "");
-    }
-    return extraVmOptions;
-  }
+  static List<String> getExtraVmOptions(Map configuration) =>
+      getExtraOptions(configuration, 'vm_options');
 
   static String getShortName(String path) {
     final PATH_REPLACEMENTS = const {