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<<a href="#type_ServerService">ServerService</a>>
+ }
+}</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<<a href="#type_ServerService">ServerService</a>> )</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<<a href="#type_AnalysisError">AnalysisError</a>>
+ }
+}</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<<a href="#type_AnalysisError">AnalysisError</a>> )</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<<a href="#type_HoverInformation">HoverInformation</a>>
+ }
+}</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<<a href="#type_HoverInformation">HoverInformation</a>> )</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<<a href="#type_FilePath">FilePath</a>>
+ "<b>excluded</b>": List<<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>
+}</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<<a href="#type_FilePath">FilePath</a>> )</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<<a href="#type_FilePath">FilePath</a>> )</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<<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>
+}</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<<a href="#type_FilePath">FilePath</a>> )</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<<a href="#type_AnalysisService">AnalysisService</a>, List<<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>
+}</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<<a href="#type_AnalysisService">AnalysisService</a>, List<<a href="#type_FilePath">FilePath</a>>> )</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<<a href="#type_FilePath">FilePath</a>, <a href="#type_ContentChange">ContentChange</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 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<<a href="#type_FilePath">FilePath</a>, <a href="#type_ContentChange">ContentChange</a>> )</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<<a href="#type_AnalysisError">AnalysisError</a>>
+ }
+}</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<<a href="#type_AnalysisError">AnalysisError</a>> )</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<<a href="#type_FilePath">FilePath</a>>
+ }
+}</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<<a href="#type_FilePath">FilePath</a>> )</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<<a href="#type_FoldingRegion">FoldingRegion</a>>
+ }
+}</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<<a href="#type_FoldingRegion">FoldingRegion</a>> )</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<<a href="#type_HighlightRegion">HighlightRegion</a>>
+ }
+}</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<<a href="#type_HighlightRegion">HighlightRegion</a>> )</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<<a href="#type_NavigationRegion">NavigationRegion</a>>
+ }
+}</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<<a href="#type_NavigationRegion">NavigationRegion</a>> )</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<<a href="#type_Occurrences">Occurrences</a>>
+ }
+}</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<<a href="#type_Occurrences">Occurrences</a>> )</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<<a href="#type_Override">Override</a>>
+ }
+}</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<<a href="#type_Override">Override</a>> )</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<<a href="#type_CompletionSuggestion">CompletionSuggestion</a>>
+ "<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<<a href="#type_CompletionSuggestion">CompletionSuggestion</a>> )</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<<a href="#type_TypeHierarchyItem">TypeHierarchyItem</a>>
+ }
+}</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<<a href="#type_TypeHierarchyItem">TypeHierarchyItem</a>> )</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<<a href="#type_SearchResult">SearchResult</a>>
+ "<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<<a href="#type_SearchResult">SearchResult</a>> )</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<<a href="#type_SourceChange">SourceChange</a>>
+ }
+}</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<<a href="#type_SourceChange">SourceChange</a>> )</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<<a href="#type_RefactoringKind">RefactoringKind</a>>
+ }
+}</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<<a href="#type_RefactoringKind">RefactoringKind</a>> )</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<<a href="#type_ErrorFixes">ErrorFixes</a>>
+ }
+}</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<<a href="#type_ErrorFixes">ErrorFixes</a>> )</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<<a href="#type_RefactoringProblem">RefactoringProblem</a>>
+ "<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<<a href="#type_RefactoringProblem">RefactoringProblem</a>> )</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<<a href="#type_DebugService">DebugService</a>>
+ }
+}</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<<a href="#type_DebugService">DebugService</a>> )</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<<a href="#type_ExecutableFile">ExecutableFile</a>>
+ "<b>dartToHtml</b>": Map<<a href="#type_FilePath">FilePath</a>, List<<a href="#type_FilePath">FilePath</a>>>
+ "<b>htmlToDart</b>": Map<<a href="#type_FilePath">FilePath</a>, List<<a href="#type_FilePath">FilePath</a>>>
+ }
+}</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<<a href="#type_ExecutableFile">ExecutableFile</a>> )</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<<a href="#type_FilePath">FilePath</a>, List<<a href="#type_FilePath">FilePath</a>>> )</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<<a href="#type_FilePath">FilePath</a>, List<<a href="#type_FilePath">FilePath</a>>> )</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<String> )</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<String> )</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<<a href="#type_SourceChange">SourceChange</a>> )</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<<a href="#type_Position">Position</a>> )</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<<a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a>> )</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<<a href="#type_Element">Element</a>> )</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<int> )</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<<a href="#type_Outline">Outline</a>> )</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<<a href="#type_OverriddenMember">OverriddenMember</a>> )</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<<a href="#type_Element">Element</a>> )</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<<a href="#type_SourceFileEdit">SourceFileEdit</a>> )</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<<a href="#type_LinkedEditGroup">LinkedEditGroup</a>> )</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<<a href="#type_SourceEdit">SourceEdit</a>> )</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<int> )</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<int> )</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<int> )</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<String> )</i></b></dt><dd>
+
+ <p>
+ The proposed names for the local variable.
+ </p>
+ </dd><dt class="field"><b><i>offsets ( List<int> )</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<int> )</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<String> )</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<<a href="#type_Parameter">Parameter</a>> )</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<int> )</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<int> )</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<<a href="#type_Parameter">Parameter</a>> )</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 '<';
+ case '>':
+ return '>';
+ case '&':
+ return '&';
+ }
+ }
+ 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 {