├── test ├── data │ ├── pubspec.yaml │ └── sample.dart ├── dart2_fix_test.dart ├── changes_test.dart └── integration_test.dart ├── analysis_options.yaml ├── .gitignore ├── .travis.yml ├── pubspec.yaml ├── tool └── travis.sh ├── bin └── dart2_fix.dart ├── CHANGELOG.md ├── LICENSE ├── lib └── src │ ├── model.dart │ ├── deprecation_analyzer.dart │ ├── deprecation_analysis_server.dart │ ├── dart_fix.dart │ └── changes.dart ├── CONTRIBUTING.md └── README.md /test/data/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sample 2 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - test/data/** 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .packages 2 | .dart_tool/ 3 | .idea/ 4 | .pub/ 5 | build/ 6 | pubspec.lock 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | script: 3 | - ./tool/travis.sh 4 | dart: 5 | - dev 6 | 7 | branches: 8 | only: 9 | - master 10 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart2_fix 2 | author: Dart Team 3 | description: A tool to migrate API usage to Dart 2. 4 | homepage: https://github.com/dart-lang/dart2_fix 5 | version: 1.0.6 6 | 7 | environment: 8 | sdk: '>=2.0.0-dev.17.0 <3.0.0' 9 | 10 | dependencies: 11 | analysis_server_lib: ^0.1.4 12 | analyzer: ^0.32.0 13 | args: ^1.4.0 14 | cli_util: ^0.1.2 15 | intl: ^0.15.0 16 | path: ^1.5.0 17 | 18 | dev_dependencies: 19 | test: ^1.0.0 20 | 21 | executables: 22 | dart2_fix: null 23 | -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 4 | # for details. All rights reserved. Use of this source code is governed by a 5 | # BSD-style license that can be found in the LICENSE file. 6 | 7 | # Fast fail the script on failures. 8 | set -e 9 | 10 | # Provision packages. 11 | pub get 12 | 13 | # Ensure the code analyzes cleanly. 14 | dartanalyzer --fatal-warnings . 15 | 16 | # Run the unit tests. 17 | pub run test 18 | 19 | # Ensure that we can analyze ourself. 20 | dart bin/dart2_fix.dart 21 | -------------------------------------------------------------------------------- /bin/dart2_fix.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'dart:async'; 7 | 8 | import 'package:dart2_fix/src/dart_fix.dart'; 9 | import 'package:dart2_fix/src/model.dart'; 10 | 11 | Future main(List args) async { 12 | ExitResult result = await dartFix(args); 13 | if (!result.isOk && result.errorMessage != null) { 14 | stderr.writeln(result.errorMessage); 15 | } 16 | exit(result.result); 17 | } 18 | -------------------------------------------------------------------------------- /test/dart2_fix_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:dart2_fix/src/dart_fix.dart'; 6 | import 'package:dart2_fix/src/model.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | group('dart2_fix', () { 11 | test('no args exits ok', () async { 12 | ExitResult code = await dartFix([]); 13 | expect(code.isOk, true); 14 | }); 15 | 16 | test('unexpected args fail', () async { 17 | ExitResult code = await dartFix(['foo']); 18 | expect(code.isOk, false); 19 | expect(code.isOk, isNotNull); 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.6 2 | - Upgraded dependencies for Dart 2. 3 | 4 | ## 1.0.5 5 | - Support more constants related to HTTP. 6 | 7 | ## 1.0.4 8 | - Add support for removing `@proxy` annotations. 9 | - Convert `JSON.encode` references to `jsonEncode` (and similarly for jsonDecode, 10 | base64Encode, and base64Decode). 11 | 12 | ## 1.0.3 13 | - Add support for deprecated `dart:io` constants. 14 | 15 | ## 1.0.2 16 | - Add a `dart2_fix` pub run entry-point. 17 | - Updates to the latest version of packages. 18 | - Added an `--check-package=` option. 19 | 20 | ## 1.0.1 21 | - Made the tool work under Dart 2 runtime semantics. 22 | - Updated when we print timing info for the tool. 23 | - Added an SDK lower bound of `2.0.0-dev.36.0`. 24 | - Added a `--verbose` flag. 25 | 26 | ## 1.0.0+2 27 | - Fix an issue converting `DateTime.AUGUST`. 28 | 29 | ## 1.0.0+1 30 | - Fix an issue converting `double.INFINITY`. 31 | 32 | ## 1.0.0 33 | - Initial version. 34 | -------------------------------------------------------------------------------- /test/data/sample.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | 5 | void main() { 6 | print(PI); // dart:math 7 | print(JSON); // dart:convert 8 | print(Duration.ZERO); // dart:core 9 | print(double.INFINITY); // dart:core 10 | print(DateTime.AUGUST); // dart:core 11 | print(SYSTEM_ENCODING); // dart:io 12 | print(ZLIB); // dart:io 13 | print(GZIP); // dart:io 14 | print(FileSystemEntityType.DIRECTORY); // dart:io 15 | print(ProcessSignal.SIGINT); // dart:io 16 | print(StdioType.TERMINAL); // dart:io 17 | print(HttpStatus.CONTINUE); // dart:_http 18 | print(HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR); // dart:_http 19 | print(HttpHeaders.ACCEPT); // dart:_http 20 | print(HttpHeaders.REQUEST_HEADERS); // dart:_http 21 | print(ContentType.JSON); // dart:_http 22 | print(HttpClient.DEFAULT_HTTPS_PORT); // dart:_http 23 | print(WebSocketStatus.NORMAL_CLOSURE); // dart:_http 24 | print(WebSocket.CLOSED); // dart:_http 25 | } 26 | -------------------------------------------------------------------------------- /test/changes_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:dart2_fix/src/changes.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | group('TextReplaceChange', () { 10 | test('applyChange', () { 11 | String from = 'abc def ghi'; 12 | String to = '123 def ghi'; 13 | TextReplaceChange change = 14 | new TextReplaceChange(null, from.indexOf('abc'), 'abc', '123'); 15 | expect(change.applyTo(from), to); 16 | }); 17 | test('applyChange', () { 18 | String from = 'abc def ghi'; 19 | String to = 'abc 123 ghi'; 20 | TextReplaceChange change = 21 | new TextReplaceChange(null, from.indexOf('def'), 'def', '123'); 22 | expect(change.applyTo(from), to); 23 | }); 24 | test('applyChange', () { 25 | String from = 'abc def ghi'; 26 | String to = 'abc def 123'; 27 | TextReplaceChange change = 28 | new TextReplaceChange(null, from.indexOf('ghi'), 'ghi', '123'); 29 | expect(change.applyTo(from), to); 30 | }); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /lib/src/model.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:path/path.dart' as p; 6 | 7 | /// An analysis issue - either an error, or a deprecation issue. 8 | class Issue { 9 | /// The message of the issue. 10 | final String message; 11 | 12 | /// The severity of the issue - used to display to the user. 13 | final String severity; 14 | 15 | // The file path of the issue. 16 | final String path; 17 | 18 | /// The source line the issue is on. 19 | final int line; 20 | 21 | /// The offset of the issue from the start of the file. 22 | final int offset; 23 | 24 | /// The length of the issue in chars. 25 | final int length; 26 | 27 | /// The full contents of the file. 28 | final String contents; 29 | 30 | Issue(this.message, this.severity, this.path, this.line, this.offset, 31 | this.length, this.contents); 32 | 33 | String get matchingSource => contents.substring(offset, offset + length); 34 | 35 | String get shortPath => p.relative(path); 36 | 37 | String get shortMessage => message.endsWith('.') 38 | ? message.substring(0, message.length - 1) 39 | : message; 40 | 41 | String toString() => '$severity: $message'; 42 | } 43 | 44 | /// Return the results of a call to DeprecationLocator.locateIssues(). 45 | /// 46 | /// This is a tuple so that we can return both analysis errors and deprecation 47 | /// issues. 48 | class DeprecationResults { 49 | final List errors; 50 | final List deprecations; 51 | 52 | DeprecationResults(this.errors, this.deprecations); 53 | } 54 | 55 | /// The exit status of the application. 56 | class ExitResult { 57 | static final ExitResult ok = new ExitResult(0); 58 | 59 | final int result; 60 | final String errorMessage; 61 | 62 | ExitResult(this.result, [this.errorMessage]); 63 | 64 | bool get isOk => result == 0; 65 | 66 | String toString() => result.toString(); 67 | } 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | 6 | Before we can use your code, you must sign the 7 | [Google Individual Contributor License Agreement][CLA] (CLA), which you can do 8 | online. The CLA is necessary mainly because you own the copyright to your 9 | changes, even after your contribution becomes part of our codebase, so we need 10 | your permission to use and distribute your code. We also need to be sure of 11 | various other things—for instance that you'll tell us if you know that your code 12 | infringes on other people's patents. You don't have to sign the CLA until after 13 | you've submitted your code for review and a member has approved it, but you must 14 | do it before we can put your code into our codebase. 15 | 16 | Before you start working on a larger contribution, you should get in touch with 17 | us first through the issue tracker with your idea so that we can help out and 18 | possibly guide you. Coordinating up front makes it much easier to avoid 19 | frustration later on. 20 | 21 | [CLA]: https://cla.developers.google.com/about/google-individual 22 | 23 | ### Code reviews 24 | 25 | All submissions, including submissions by project members, require review. We 26 | recommend [forking the repository][fork], making changes in your fork, and 27 | [sending us a pull request][pr] so we can review the changes and merge them into 28 | this repository. 29 | 30 | [fork]: https://help.github.com/articles/about-forks/ 31 | [pr]: https://help.github.com/articles/creating-a-pull-request/ 32 | 33 | Functional changes will require tests to be added or changed. The tests live in 34 | the `test/` directory, and are run with `pub run test`. If you need to create 35 | new tests, use the existing tests as a guideline for what they should look like. 36 | 37 | Before you send your pull request, make sure all the tests pass! 38 | 39 | ### File headers 40 | 41 | All files in the project must start with the following header. 42 | 43 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 44 | // for details. All rights reserved. Use of this source code is governed by a 45 | // BSD-style license that can be found in the LICENSE file. 46 | 47 | ### The small print 48 | 49 | Contributions made by corporations are covered by a different agreement than the 50 | one above, the 51 | [Software Grant and Corporate Contributor License Agreement][CCLA]. 52 | 53 | [CCLA]: https://developers.google.com/open-source/cla/corporate 54 | -------------------------------------------------------------------------------- /lib/src/deprecation_analyzer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:analyzer/dart/analysis/analysis_context.dart'; 8 | import 'package:analyzer/dart/analysis/context_locator.dart'; 9 | import 'package:analyzer/dart/analysis/results.dart'; 10 | import 'package:analyzer/dart/analysis/session.dart'; 11 | import 'package:analyzer/error/error.dart'; 12 | import 'package:analyzer/file_system/file_system.dart'; 13 | import 'package:analyzer/file_system/physical_file_system.dart'; 14 | import 'package:analyzer/src/dart/error/hint_codes.dart'; 15 | import 'package:analyzer/src/generated/source.dart'; 16 | import 'package:dart2_fix/src/model.dart'; 17 | 18 | // TODO(devoncarew): This API doesn't seem to use the driver cache. 19 | 20 | class DeprecationLocator { 21 | /** 22 | * The resource provider used to access the file system. 23 | */ 24 | final ResourceProvider resourceProvider; 25 | 26 | DeprecationLocator(this.resourceProvider); 27 | 28 | DeprecationLocator.defaults() 29 | : resourceProvider = PhysicalResourceProvider.INSTANCE; 30 | 31 | Future locateIssues(List paths) async { 32 | ContextLocator locator = 33 | new ContextLocator(resourceProvider: resourceProvider); 34 | List contexts = 35 | locator.locateContexts(includedPaths: paths); 36 | 37 | DeprecationResults results = new DeprecationResults([], []); 38 | 39 | for (AnalysisContext context in contexts) { 40 | AnalysisSession session = context.currentSession; 41 | // TODO(devoncarew): analyzedFiles() is returning non-Dart files. Either 42 | // it shouldn't, or session.getResolvedAst() should not try and parse 43 | // non-Dart files. 44 | for (String path in context.contextRoot.analyzedFiles()) { 45 | if (!path.endsWith('.dart')) { 46 | continue; 47 | } 48 | await _locateInFile(results, await session.getResolvedAst(path)); 49 | } 50 | } 51 | 52 | return results; 53 | } 54 | 55 | void _locateInFile(DeprecationResults results, ResolveResult result) { 56 | Iterable deprecations = result.errors 57 | .where((error) => error.errorCode == HintCode.DEPRECATED_MEMBER_USE); 58 | results.deprecations.addAll(deprecations 59 | .map((e) => _errorToIssue(result.lineInfo, e, result.content))); 60 | 61 | // TODO(devoncarew): We want ErrorSeverity.ERROR here, but I believe that 62 | // strong mode error severity remapping is not happening. 63 | Iterable errors = result.errors.where((error) => 64 | error.errorCode.errorSeverity.ordinal >= ErrorSeverity.WARNING.ordinal); 65 | results.errors.addAll( 66 | errors.map((e) => _errorToIssue(result.lineInfo, e, result.content))); 67 | } 68 | } 69 | 70 | Issue _errorToIssue(LineInfo lineInfo, AnalysisError error, String contents) { 71 | int line = lineInfo.getLocation(error.offset).lineNumber; 72 | 73 | return new Issue(error.message, error.errorCode.errorSeverity.displayName, 74 | error.source.fullName, line, error.offset, error.length, contents); 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/dart-lang/dart2_fix.svg?branch=master)](https://travis-ci.org/dart-lang/dart2_fix) 2 | 3 | A tool to migrate API usage to Dart 2. 4 | 5 | **Note: This tool needs to be run on a version of the Dart SDK that contains the deprecated annotations (a pre- `2.0.0-dev.68.0` SDK).** 6 | 7 | ### What does it do? 8 | 9 | `dart2_fix` is a command line utility that can automatically migrate some Dart 1 API usages in your 10 | source code to Dart 2 ones. Currently, it focuses on updating deprecated constant names; for example: 11 | - update `dart:convert`'s `UTF8` to `utf8` 12 | - update `dart:core`'s `Duration.ZERO` to `Duration.zero` 13 | - update `dart:math`'s `PI` to `pi` 14 | 15 | For more information about preparing your code for Dart 2, please see the 16 | [Dart 2 migration guide](http://www.dartlang.org/dart-2). 17 | 18 | ### How do I use it? 19 | 20 | To install, run `pub global activate dart2_fix`. Then, from your project directory, run: 21 | 22 | `pub global run dart2_fix` 23 | 24 | When run without any arguments, it will check your project, but will not make changes; it'll 25 | indicate what would be changed if asked to make modifications. For example: 26 | 27 | ``` 28 | test/test/runner/load_suite_test.dart 29 | line 56 • Duration.ZERO => Duration.zero 30 | line 60 • Duration.ZERO => Duration.zero 31 | line 86 • Duration.ZERO => Duration.zero 32 | 33 | test/tool/host.dart 34 | line 169 • JSON => json 35 | line 173 • JSON => json 36 | 37 | Found 5 fixes in 2.3s. 38 | 39 | To apply these fixes, run again using the --apply argument. 40 | ``` 41 | 42 | To actually modify your project source code, run with the `--apply` argument (`pub global run dart2_fix --apply`): 43 | 44 | ``` 45 | Updating test... 46 | 47 | test/test/runner/load_suite_test.dart 48 | 3 fixes applied for Duration.ZERO => Duration.zero 49 | 50 | test/tool/host.dart 51 | 2 fixes applied for JSON => json 52 | 53 | Applied 5 fixes in 1.9s. 54 | ``` 55 | 56 | ### What about Flutter code? 57 | 58 | To run this tool on Flutter code, use: 59 | 60 | ``` 61 | flutter packages pub global activate dart2_fix 62 | ``` 63 | 64 | then - to check your code - run: 65 | 66 | ``` 67 | flutter packages pub global run dart2_fix 68 | ``` 69 | 70 | and to apply fixes, run: 71 | 72 | ``` 73 | flutter packages pub global run dart2_fix --apply 74 | ``` 75 | 76 | ### Will this make all of my code Dart 2 compliant? 77 | 78 | No. Currently this only fixes the renaming of various deprecated constant 79 | names. Some of the less uniform constant renamings are not handled by this 80 | tool. For example `Endianness.BIG_ENDIAN` has been renamed to `Endian.big` but 81 | this will not be caught. After running this tool, remaining issues can be found 82 | by running the dart analyzer (or flutter analyze) and fixing any deprecation 83 | warnings. 84 | 85 | ### I'm getting new static (or runtime errors) after running dart2_fix, what went wrong? 86 | 87 | This tool can't catch conflicts between the new constant names and any fields or 88 | local variables that you might have in scope. If you get new analysis warnings 89 | or runtime failures after running this tool, check to see whether one of the 90 | changes made has caused a naming conflict with something else in scope. The 91 | most common cause of this is having a local variable named `json` in a scope 92 | where `JSON.decode` gets renamed to `json.decode`. To help with fixing these 93 | kinds of conflicts, the following top level members have been added to 94 | `dart:convert`: `jsonDecode`, `jsonEncode`, `base64Decode`, `base64Encode`, and 95 | `base64UrlEncode`. These top level members are equivalent to `json.decode`, 96 | `json.decode`, etc, and can be used to avoid naming conflicts where required. 97 | 98 | 99 | ### Features and bugs 100 | 101 | Please file feature requests and bugs at the [issue tracker][tracker]. 102 | 103 | [tracker]: https://github.com/dart-lang/dart2_fix/issues 104 | -------------------------------------------------------------------------------- /lib/src/deprecation_analysis_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:analysis_server_lib/analysis_server_lib.dart'; 9 | import 'package:cli_util/cli_logging.dart'; 10 | import 'package:dart2_fix/src/model.dart'; 11 | import 'package:path/path.dart' as path; 12 | 13 | class DeprecationLocator { 14 | final Logger logger; 15 | 16 | DeprecationLocator(this.logger); 17 | 18 | Future locateIssues(List directories) async { 19 | AnalysisServer client = await AnalysisServer.create(onRead: (String str) { 20 | logger.trace('<== ${_trimMax(str)}'); 21 | }, onWrite: (String str) { 22 | logger.trace('==> ${_trimMax(str)}'); 23 | }); 24 | 25 | Completer completer = new Completer(); 26 | client.processCompleter.future.then((int code) { 27 | if (!completer.isCompleted) { 28 | completer.completeError('analysis exited early (exit code $code)'); 29 | } 30 | }); 31 | 32 | await client.server.onConnected.first.timeout(new Duration(seconds: 10)); 33 | 34 | bool hadServerError = false; 35 | 36 | // handle errors 37 | client.server.onError.listen((ServerError error) { 38 | StackTrace trace = error.stackTrace == null 39 | ? null 40 | : new StackTrace.fromString(error.stackTrace); 41 | 42 | logger.stderr('${error}'); 43 | logger.stderr('${trace.toString().trim()}'); 44 | 45 | hadServerError = true; 46 | }); 47 | 48 | client.server.setSubscriptions(['STATUS']); 49 | client.server.onStatus.listen((ServerStatus status) { 50 | if (status.analysis == null) return; 51 | 52 | if (!status.analysis.isAnalyzing) { 53 | // notify finished 54 | if (!completer.isCompleted) { 55 | completer.complete(true); 56 | } 57 | client.dispose(); 58 | } 59 | }); 60 | 61 | Map> errorMap = new Map(); 62 | client.analysis.onErrors.listen((AnalysisErrors e) { 63 | errorMap[e.file] = e.errors; 64 | }); 65 | 66 | client.analysis.setAnalysisRoots( 67 | directories.map((dir) => path.canonicalize(dir)).toList(), []); 68 | 69 | // wait for finish 70 | await completer.future; 71 | 72 | List sources = errorMap.keys.toList(); 73 | List errors = sources 74 | .map((String key) => errorMap[key]) 75 | .fold([], (List a, List b) { 76 | a.addAll(b); 77 | return a; 78 | }) 79 | .toList() 80 | .cast(); 81 | 82 | DeprecationResults results = new DeprecationResults([], []); 83 | 84 | SourceLoader sourceLoader = new SourceLoader(); 85 | 86 | for (AnalysisError error in errors) { 87 | if (error.severity == 'ERROR') { 88 | results.errors.add(_errorToIssue(error, sourceLoader)); 89 | } else if (error.code == 'deprecated_member_use') { 90 | results.deprecations.add(_errorToIssue(error, sourceLoader)); 91 | } 92 | } 93 | 94 | return results; 95 | } 96 | 97 | Issue _errorToIssue(AnalysisError error, SourceLoader sourceLoader) { 98 | return new Issue( 99 | error.message, 100 | error.severity, 101 | error.location.file, 102 | error.location.startLine, 103 | error.location.offset, 104 | error.location.length, 105 | sourceLoader.loadSource(error.location.file)); 106 | } 107 | } 108 | 109 | class SourceLoader { 110 | Map cache = {}; 111 | 112 | SourceLoader(); 113 | 114 | String loadSource(String filePath) { 115 | return cache.putIfAbsent( 116 | filePath, () => new File(filePath).readAsStringSync()); 117 | } 118 | } 119 | 120 | String _trimMax(String str) { 121 | final int max = 200; 122 | 123 | if (str.length > max) { 124 | return str.substring(0, 200) + '...'; 125 | } else { 126 | return str; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /test/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cli_util/cli_logging.dart'; 4 | import 'package:dart2_fix/src/dart_fix.dart'; 5 | import 'package:dart2_fix/src/model.dart'; 6 | import 'package:path/path.dart' as path; 7 | import 'package:test/test.dart'; 8 | 9 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 10 | // for details. All rights reserved. Use of this source code is governed by a 11 | // BSD-style license that can be found in the LICENSE file. 12 | 13 | void main() { 14 | group('DartFix', () { 15 | Project project; 16 | BufferLogger logger; 17 | 18 | setUp(() { 19 | logger = new BufferLogger(); 20 | }); 21 | 22 | tearDown(() { 23 | project?.delete(); 24 | }); 25 | 26 | test('check', () async { 27 | ExitResult result = await dartFixInternal( 28 | logger, [new Directory('test/data')], 29 | performDryRun: true); 30 | expect(result.result, 0); 31 | 32 | String out = logger.stdOutput.toString(); 33 | expect(out, contains('line 6 - PI => pi')); 34 | expect(out, contains('line 7 - JSON => json')); 35 | expect(out, contains('line 8 - Duration.ZERO => Duration.zero')); 36 | expect(out, contains('line 9 - double.INFINITY => double.infinity')); 37 | expect(out, contains('line 10 - DateTime.AUGUST => DateTime.august')); 38 | 39 | expect(out, contains('line 11 - SYSTEM_ENCODING => systemEncoding')); 40 | expect(out, contains('line 12 - ZLIB => zlib')); 41 | expect(out, contains('line 13 - GZIP => gzip')); 42 | expect( 43 | out, 44 | contains( 45 | 'line 14 - FileSystemEntityType.DIRECTORY => FileSystemEntityType.directory'), 46 | ); 47 | expect( 48 | out, 49 | contains('line 15 - ProcessSignal.SIGINT => ProcessSignal.sigint'), 50 | ); 51 | expect( 52 | out, 53 | contains('line 16 - StdioType.TERMINAL => StdioType.terminal'), 54 | ); 55 | 56 | expect( 57 | out, 58 | contains( 59 | 'line 17 - HttpStatus.CONTINUE => HttpStatus.continue_') 60 | ); 61 | expect( 62 | out, 63 | contains( 64 | 'line 18 - HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR => HttpStatus.networkConnectTimeoutError') 65 | ); 66 | expect( 67 | out, 68 | contains( 69 | 'line 19 - HttpHeaders.ACCEPT => HttpHeaders.accept') 70 | ); 71 | expect( 72 | out, 73 | contains( 74 | 'line 20 - HttpHeaders.REQUEST_HEADERS => HttpHeaders.requestHeaders') 75 | ); 76 | expect( 77 | out, 78 | contains( 79 | 'line 21 - ContentType.JSON => ContentType.json') 80 | ); 81 | expect( 82 | out, 83 | contains( 84 | 'line 22 - HttpClient.DEFAULT_HTTPS_PORT => HttpClient.defaultHttpsPort') 85 | ); 86 | expect( 87 | out, 88 | contains( 89 | 'line 23 - WebSocketStatus.NORMAL_CLOSURE => WebSocketStatus.normalClosure') 90 | ); 91 | expect( 92 | out, 93 | contains( 94 | 'line 24 - WebSocket.CLOSED => WebSocket.closed') 95 | ); 96 | 97 | expect(out, contains('Found 19 fixes')); 98 | }); 99 | 100 | test('JSON.encode', () async { 101 | project = Project.createProject(''' 102 | import 'dart:convert'; 103 | 104 | void main() { 105 | String str = JSON.encode({}); 106 | dynamic data = JSON.decode(str); 107 | print(data); 108 | 109 | String encoded = BASE64.encode([1, 2, 3]); 110 | List decoded = BASE64.decode(encoded); 111 | print(decoded); 112 | } 113 | '''); 114 | 115 | ExitResult result = await dartFixInternal(logger, [project.projectDir], 116 | performDryRun: false); 117 | expect(result.result, 0); 118 | 119 | expect(logger.stdOutput.toString(), contains('Applied 4 fixes')); 120 | 121 | expect(project.getMainSource(), ''' 122 | import 'dart:convert'; 123 | 124 | void main() { 125 | String str = jsonEncode({}); 126 | dynamic data = jsonDecode(str); 127 | print(data); 128 | 129 | String encoded = base64Encode([1, 2, 3]); 130 | List decoded = base64Decode(encoded); 131 | print(decoded); 132 | } 133 | '''); 134 | }); 135 | 136 | test('proxy', () async { 137 | project = Project.createProject(''' 138 | @proxy 139 | String foo = 'foo'; 140 | void main() { print(foo); } 141 | '''); 142 | 143 | ExitResult result = await dartFixInternal(logger, [project.projectDir], 144 | performDryRun: false); 145 | expect(result.result, 0); 146 | 147 | expect(logger.stdOutput.toString(), contains('Applied 1 fix')); 148 | 149 | expect(project.getMainSource(), ''' 150 | 151 | String foo = 'foo'; 152 | void main() { print(foo); } 153 | '''); 154 | }); 155 | }); 156 | } 157 | 158 | class BufferLogger implements Logger { 159 | StringBuffer stdOutput = new StringBuffer(); 160 | StringBuffer stdError = new StringBuffer(); 161 | 162 | BufferLogger() {} 163 | 164 | final Ansi ansi = new TestAnsi(); 165 | 166 | void flush() {} 167 | 168 | bool get isVerbose => false; 169 | 170 | Progress progress(String message) { 171 | return new _SimpleProgress(this, message); 172 | } 173 | 174 | @override 175 | void stderr(String message) { 176 | stdError.writeln(message); 177 | } 178 | 179 | @override 180 | void stdout(String message) { 181 | stdOutput.writeln(message); 182 | } 183 | 184 | @override 185 | void trace(String message) {} 186 | } 187 | 188 | class _SimpleProgress implements Progress { 189 | final Logger logger; 190 | final String message; 191 | Stopwatch _stopwatch; 192 | 193 | _SimpleProgress(this.logger, this.message) { 194 | _stopwatch = new Stopwatch()..start(); 195 | logger.stdout('$message...'); 196 | } 197 | 198 | Duration get elapsed => _stopwatch.elapsed; 199 | 200 | void cancel() {} 201 | 202 | void finish({String message, bool showTiming}) {} 203 | } 204 | 205 | class TestAnsi extends Ansi { 206 | TestAnsi() : super(false); 207 | 208 | String get bullet => '-'; 209 | } 210 | 211 | class Project { 212 | Directory projectDir; 213 | 214 | static Project createProject(String mainSource) { 215 | Directory dir = Directory.systemTemp.createTempSync('dart2fix'); 216 | 217 | File mainFile = new File(path.join(dir.path, 'lib', 'main.dart')); 218 | mainFile.parent.createSync(recursive: true); 219 | mainFile.writeAsStringSync(mainSource); 220 | 221 | File packagesFile = new File(path.join(dir.path, '.packages')); 222 | packagesFile.writeAsStringSync(''); 223 | 224 | return new Project(dir); 225 | } 226 | 227 | Project(this.projectDir); 228 | 229 | String getMainSource() { 230 | return new File(path.join(projectDir.path, 'lib', 'main.dart')) 231 | .readAsStringSync(); 232 | } 233 | 234 | void delete() { 235 | projectDir.deleteSync(recursive: true); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /lib/src/dart_fix.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | import 'package:args/args.dart'; 10 | import 'package:cli_util/cli_logging.dart'; 11 | import 'package:dart2_fix/src/changes.dart'; 12 | import 'package:dart2_fix/src/deprecation_analysis_server.dart'; 13 | import 'package:dart2_fix/src/model.dart'; 14 | import 'package:intl/intl.dart'; 15 | import 'package:path/path.dart' as p; 16 | 17 | final NumberFormat _nf = new NumberFormat('0.0'); 18 | 19 | Future dartFix(List args) async { 20 | ArgParser argParser = new ArgParser(); 21 | argParser.addFlag('help', 22 | negatable: false, abbr: 'h', help: 'Display usage help.'); 23 | argParser.addFlag('apply', 24 | abbr: 'w', 25 | negatable: false, 26 | help: "Apply the refactoring changes to the project's source files."); 27 | argParser.addOption('check-package', 28 | help: 29 | 'Retrieve the source for the given hosted package and run checks on ' 30 | 'it\n(this command is not typically run by users).', 31 | valueHelp: 'package name'); 32 | argParser.addFlag('verbose', 33 | negatable: false, help: 'Print verbose logging information.'); 34 | argParser.addFlag('color', 35 | help: 'Use ansi colors when printing messages.', 36 | defaultsTo: Ansi.terminalSupportsAnsi); 37 | 38 | ArgResults results; 39 | 40 | try { 41 | results = argParser.parse(args); 42 | 43 | if (results['help']) { 44 | _printUsage(argParser); 45 | return ExitResult.ok; 46 | } 47 | } on FormatException catch (e) { 48 | stderr.writeln(e.message); 49 | print(''); 50 | _printUsage(argParser); 51 | return new ExitResult(1); 52 | } 53 | 54 | List dirs = results.rest.isEmpty 55 | ? [Directory.current] 56 | : results.rest.map((s) => new Directory(s)).toList(); 57 | 58 | for (Directory dir in dirs) { 59 | if (!dir.existsSync()) { 60 | return new ExitResult( 61 | 1, "'${dir.path}' does not exist or is not a directory."); 62 | } 63 | } 64 | 65 | final bool verbose = results['verbose']; 66 | Ansi ansi = new Ansi(results.wasParsed('color') 67 | ? results['color'] 68 | : Ansi.terminalSupportsAnsi); 69 | Logger logger = verbose 70 | ? new Logger.verbose(ansi: ansi) 71 | : new Logger.standard(ansi: ansi); 72 | 73 | final bool performDryRun = !results['apply']; 74 | final String packageName = results['check-package']; 75 | 76 | if (packageName != null) { 77 | return await checkPackage(logger, packageName, performDryRun); 78 | } else { 79 | return await dartFixInternal(logger, dirs, performDryRun: performDryRun); 80 | } 81 | } 82 | 83 | // public for testing 84 | Future dartFixInternal( 85 | Logger logger, 86 | List dirs, { 87 | bool performDryRun: true, 88 | }) async { 89 | final Ansi ansi = logger.ansi; 90 | 91 | Progress progress; 92 | if (performDryRun) { 93 | progress = 94 | logger.progress('Checking ${dirs.map((d) => d.path).join(', ')}'); 95 | } else { 96 | progress = 97 | logger.progress('Updating ${dirs.map((d) => d.path).join(', ')}'); 98 | } 99 | 100 | Stopwatch stopwatch = new Stopwatch()..start(); 101 | DeprecationLocator locator = new DeprecationLocator(logger); 102 | final DeprecationResults issues = 103 | await locator.locateIssues(dirs.map((d) => d.path).toList()); 104 | 105 | progress.finish(); 106 | 107 | logger.stdout(''); 108 | 109 | if (issues.errors.isNotEmpty) { 110 | for (Issue issue in issues.errors) { 111 | logger.stdout('${issue.severity.toLowerCase()} ${ansi.bullet} ${issue 112 | .shortPath}:${issue 113 | .line} ${ansi 114 | .bullet} ${ansi.emphasized(issue.shortMessage)}'); 115 | } 116 | 117 | logger.stdout(''); 118 | 119 | String suffix = performDryRun ? '' : '; no changes applied'; 120 | 121 | double seconds = stopwatch.elapsedMilliseconds / 1000.0; 122 | return new ExitResult( 123 | 1, 124 | 'Found ${issues.errors.length} analysis ${_pluralize( 125 | issues.errors.length, 'error', 'errors')}$suffix (in ${_nf.format( 126 | seconds)}s).'); 127 | } 128 | 129 | ChangeManager changeManager = ChangeManager.create(); 130 | Map> changes = {}; 131 | 132 | int count = 0; 133 | 134 | for (Issue issue in issues.deprecations) { 135 | final Change change = changeManager.getChangeFor(issue); 136 | if (change != null) { 137 | count++; 138 | changes.putIfAbsent(issue.path, () => []); 139 | changes[issue.path].add(change); 140 | } 141 | } 142 | 143 | if (performDryRun) { 144 | List paths = changes.keys.toList(); 145 | paths.sort(); 146 | 147 | for (String path in paths) { 148 | logger.stdout(p.relative(path)); 149 | 150 | List fileChanges = changes[path]; 151 | fileChanges.sort(); 152 | 153 | for (Change change in fileChanges) { 154 | logger.stdout(' line ${change.line} ${ansi.bullet} ${ansi.emphasized( 155 | change.describe)}'); 156 | } 157 | 158 | logger.stdout(''); 159 | } 160 | 161 | double seconds = stopwatch.elapsedMilliseconds / 1000.0; 162 | logger.stdout('Found ${count} fixes in ${_nf.format(seconds)}s.'); 163 | if (count > 0) { 164 | logger.stdout(''); 165 | logger.stdout( 166 | 'To apply these fixes, run again using the --apply argument.'); 167 | } 168 | } else { 169 | List paths = changes.keys.toList(); 170 | paths.sort(); 171 | 172 | for (String path in paths) { 173 | logger.stdout(p.relative(path)); 174 | 175 | List fileChanges = changes[path]; 176 | fileChanges.sort(); 177 | 178 | File file = new File(path); 179 | String contents = file.readAsStringSync(); 180 | 181 | Map fixCounts = {}; 182 | 183 | for (Change change in fileChanges.reversed) { 184 | contents = change.applyTo(contents); 185 | String description = change.describe; 186 | fixCounts.putIfAbsent(description, () => 0); 187 | fixCounts[description] = fixCounts[description] + 1; 188 | } 189 | 190 | file.writeAsStringSync(contents); 191 | 192 | for (String desc in fixCounts.keys) { 193 | logger.stdout(' ${fixCounts[desc]} ${_pluralize( 194 | fixCounts[desc], 'fix', 'fixes')} applied for ${ansi.emphasized( 195 | desc)}'); 196 | } 197 | 198 | logger.stdout(''); 199 | } 200 | 201 | double seconds = stopwatch.elapsedMilliseconds / 1000.0; 202 | logger 203 | .stdout('Applied ${count} ${_pluralize(count, 'fix', 'fixes')} in ${_nf 204 | .format(seconds)}s.'); 205 | } 206 | 207 | return ExitResult.ok; 208 | } 209 | 210 | Future checkPackage( 211 | Logger logger, String packageName, bool performDryRun) async { 212 | String jsonData = 213 | await _uriDownload('https://pub.dartlang.org/api/packages/$packageName/'); 214 | Map json = jsonDecode(jsonData); 215 | Map pubspec = json['latest']['pubspec']; 216 | String homepage = pubspec['homepage']; 217 | 218 | if (homepage == null) { 219 | return new ExitResult(1, 'No homepage definied for package $packageName.'); 220 | } 221 | 222 | Uri homepageUri = Uri.parse(homepage); 223 | if (homepageUri.host != 'github.com') { 224 | return new ExitResult( 225 | 1, "Homepage is '$homepage', but we require it to be a github repo."); 226 | } 227 | 228 | if (homepageUri.path.substring(1).split('/').length > 2) { 229 | return new ExitResult( 230 | 1, 231 | 'Unsupported homepage reference: $homepage.\n' 232 | 'This tool only supports a homepage reference that points to the root of a repo.'); 233 | } 234 | 235 | Directory dir = new Directory(packageName); 236 | dir.createSync(); 237 | 238 | Progress progress; 239 | 240 | if (performDryRun) { 241 | progress = logger.progress('Cloning $homepage into $packageName'); 242 | try { 243 | await gitClone(homepageUri, dir); 244 | } finally { 245 | progress.finish(); 246 | } 247 | 248 | progress = logger.progress('Running pub get'); 249 | try { 250 | await pubGet(dir); 251 | } finally { 252 | progress.finish(); 253 | } 254 | } 255 | 256 | return await dartFixInternal(logger, [dir], performDryRun: performDryRun); 257 | } 258 | 259 | Future gitClone(Uri uri, Directory dir) async { 260 | Process process = await Process.start('git', ['clone', uri.toString(), '.'], 261 | workingDirectory: dir.path); 262 | return process.exitCode; 263 | } 264 | 265 | Future pubGet(Directory dir) async { 266 | String pubPath = p.join(p.dirname(Platform.resolvedExecutable), 'pub'); 267 | Process process = 268 | await Process.start(pubPath, ['get'], workingDirectory: dir.path); 269 | return process.exitCode; 270 | } 271 | 272 | String _pluralize(int count, String singular, String plural) => 273 | count == 1 ? singular : plural; 274 | 275 | void _printUsage(ArgParser argParser) { 276 | print('usage: dart2_fix [options...] '); 277 | print(''); 278 | print(argParser.usage); 279 | } 280 | 281 | Future _uriDownload(String uri) async { 282 | HttpClient client = new HttpClient(); 283 | HttpClientRequest request = await client.getUrl(Uri.parse(uri)); 284 | HttpClientResponse response = await request.close(); 285 | 286 | Completer completer = new Completer(); 287 | StringBuffer contents = new StringBuffer(); 288 | response.transform(utf8.decoder).listen((String data) { 289 | contents.write(data); 290 | }, onDone: () => completer.complete(contents.toString())); 291 | return completer.future; 292 | } 293 | -------------------------------------------------------------------------------- /lib/src/changes.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:dart2_fix/src/model.dart'; 6 | 7 | /// Dart 2.0 deprecations. 8 | /// 9 | /// In the future, we should drive this via improved deprecation annotations. 10 | final Map _textReplacements = { 11 | // dart:async 12 | 'Zone.ROOT': 'Zone.root', 13 | 14 | // dart:core 15 | 'DateTime.MONDAY': 'DateTime.monday', 16 | 'DateTime.TUESDAY': 'DateTime.tuesday', 17 | 'DateTime.WEDNESDAY': 'DateTime.wednesday', 18 | 'DateTime.THURSDAY': 'DateTime.thursday', 19 | 'DateTime.FRIDAY': 'DateTime.friday', 20 | 'DateTime.SATURDAY': 'DateTime.saturday', 21 | 'DateTime.SUNDAY': 'DateTime.sunday', 22 | 'DateTime.DAYS_PER_WEEK': 'DateTime.daysPerWeek', 23 | 'DateTime.JANUARY': 'DateTime.january', 24 | 'DateTime.FEBRUARY': 'DateTime.february', 25 | 'DateTime.MARCH': 'DateTime.march', 26 | 'DateTime.APRIL': 'DateTime.april', 27 | 'DateTime.MAY': 'DateTime.may', 28 | 'DateTime.JUNE': 'DateTime.june', 29 | 'DateTime.JULY': 'DateTime.july', 30 | 'DateTime.AUGUST': 'DateTime.august', 31 | 'DateTime.SEPTEMBER': 'DateTime.september', 32 | 'DateTime.OCTOBER': 'DateTime.october', 33 | 'DateTime.NOVEMBER': 'DateTime.november', 34 | 'DateTime.DECEMBER': 'DateTime.december', 35 | 'DateTime.MONTHS_PER_YEAR': 'DateTime.monthsPerYear', 36 | 37 | 'double.NAN': 'double.nan', 38 | 'double.INFINITY': 'double.infinity', 39 | 'double.NEGATIVE_INFINITY': 'double.negativeInfinity', 40 | 'double.MIN_POSITIVE': 'double.minPositive', 41 | 'double.MAX_FINITE': 'double.maxFinite', 42 | 43 | 'Duration.MICROSECONDS_PER_MILLISECOND': 44 | 'Duration.microsecondsPerMillisecond', 45 | 'Duration.MILLISECONDS_PER_SECOND': 'Duration.millisecondsPerSecond', 46 | 'Duration.SECONDS_PER_MINUTE': 'Duration.secondsPerMinute', 47 | 'Duration.MINUTES_PER_HOUR': 'Duration.minutesPerHour', 48 | 49 | 'Duration.HOURS_PER_DAY': 'Duration.hoursPerDay', 50 | 'Duration.MICROSECONDS_PER_SECOND': 'Duration.microsecondsPerSecond', 51 | 'Duration.MICROSECONDS_PER_MINUTE': 'Duration.microsecondsPerMinute', 52 | 'Duration.MICROSECONDS_PER_HOUR': 'Duration.microsecondsPerHour', 53 | 54 | 'Duration.MICROSECONDS_PER_DAY': 'Duration.millisecondsPerMinute', 55 | 'Duration.MILLISECONDS_PER_MINUTE': 'Duration.millisecondsPerMinute', 56 | 'Duration.MILLISECONDS_PER_HOUR': 'Duration.millisecondsPerHour', 57 | 'Duration.MILLISECONDS_PER_DAY': 'Duration.millisecondsPerDay', 58 | 59 | 'Duration.SECONDS_PER_HOUR': 'Duration.secondsPerHour', 60 | 'Duration.SECONDS_PER_DAY': 'Duration.secondsPerDay', 61 | 'Duration.MINUTES_PER_DAY': 'Duration.minutesPerDay', 62 | 'Duration.ZERO': 'Duration.zero', 63 | 64 | // dart:isolate 65 | 'Isolate.IMMEDIATE': 'Isolate.immediate', 66 | 'Isolate.BEFORE_NEXT_EVENT': 'Isolate.beforeNextEvent', 67 | 68 | // dart:typed_data 69 | 'Endianness.BIG_ENDIAN': 'Endian.big', 70 | 'Endianness.LITTLE_ENDIAN': 'Endian.little', 71 | 'Endianness.HOST_ENDIAN': 'Endian.host', 72 | 'Int8List.BYTES_PER_ELEMENT': 'Int8List.bytesPerElement', 73 | // and, other (long tail) dart:typed_data renames omitted 74 | 75 | // Note, for the dart:io changes, we omit checking for top-level READ, WRITE, 76 | // APPEND, WRITE_ONLY, and WRITE_ONLY_APPEND as those may have false positives. 77 | 78 | // dart:io/data_transformer.dart 79 | 'ZLIB': 'zlib', 80 | 'GZIP': 'gzip', 81 | 'ZLibOption.MIN_WINDOW_BITS': 'ZLibOption.minWindowBits', 82 | 'ZLibOption.MAX_WINDOW_BITS': 'ZLibOption.maxWindowBits', 83 | 'ZLibOption.DEFAULT_WINDOW_BITS': 'ZLibOption.defaultWindowBits', 84 | 'ZLibOption.MIN_LEVEL': 'ZLibOption.minLevel', 85 | 'ZLibOption.MAX_LEVEL': 'ZLibOption.maxLevel', 86 | 'ZLibOption.DEFAULT_LEVEL': 'ZLibOption.defaultLevel', 87 | 'ZLibOption.MIN_MEM_LEVEL': 'ZLibOption.minMemLevel', 88 | 'ZLibOption.MAX_MEM_LEVEL': 'ZLibOption.maxMemLevel', 89 | 'ZLibOption.DEFAULT_MEM_LEVEL': 'ZLibOption.defaultMemLevel', 90 | 'ZLibOption.STRATEGY_FILTERED': 'ZLibOption.strategyFiltered', 91 | 'ZLibOption.STRATEGY_HUFFMAN_ONLY': 'ZLibOption.strategyHuffmanOnly', 92 | 'ZLibOption.STRATEGY_RLE': 'ZLibOption.strategyRle', 93 | 'ZLibOption.STRATEGY_FIXED': 'ZLibOption.strategyFixed', 94 | 'ZLibOption.STRATEGY_DEFAULT': 'ZLibOption.strategyDefault', 95 | 96 | // dart:io/file.dart 97 | 'FileMode.READ': 'FileMode.read', 98 | 'FileMode.WRITE': 'FileMode.write', 99 | 'FileMode.APPEND': 'FileMode.append', 100 | 'FileMode.WRITE_ONLY': 'FileMode.writeOnly', 101 | 'FileMode.WRITE_ONLY_APPEND': 'FileMode.writeOnlyAppend', 102 | 'FileLock.SHARED': 'FileLock.shared', 103 | 'FileLock.EXCLUSIVE': 'FileLock.exclusive', 104 | 'FileLock.BLOCKING_SHARED': 'FileLock.blockingShared', 105 | 'FileLock.BLOCKING_EXCLUSIVE': 'FileLock.blockingExclusive', 106 | 107 | // dart:io/file_system_entity.dart 108 | 'FileSystemEntityType.FILE': 'FileSystemEntityType.file', 109 | 'FileSystemEntityType.DIRECTORY': 'FileSystemEntityType.directory', 110 | 'FileSystemEntityType.LINK': 'FileSystemEntityType.link', 111 | 'FileSystemEntityType.NOT_FOUND': 'FileSystemEntityType.notFound', 112 | 'FileSystemEvent.CREATE': 'FileSystemEvent.create', 113 | 'FileSystemEvent.MODIFY': 'FileSystemEvent.modify', 114 | 'FileSystemEvent.DELETE': 'FileSystemEvent.delete', 115 | 'FileSystemEvent.MOVE': 'FileSystemEvent.move', 116 | 'FileSystemEvent.ALL': 'FileSystemEvent.all', 117 | 118 | // dart:io/process.dart 119 | 'ProcessStartMode.NORMAL': 'ProcessStartMode.normal', 120 | 'ProcessStartMode.INHERIT_STDIO': 'ProcessStartMode.inheritStdio', 121 | 'ProcessStartMode.DETACHED': 'ProcessStartMode.detached', 122 | 'ProcessStartMode.DETACHED_WITH_STDIO': 'ProcessStartMode.detachedWithStdio', 123 | 'ProcessSignal.SIGHUP': 'ProcessSignal.sighup', 124 | 'ProcessSignal.SIGINT': 'ProcessSignal.sigint', 125 | 'ProcessSignal.SIGQUIT': 'ProcessSignal.sigquit', 126 | 'ProcessSignal.SIGILL': 'ProcessSignal.sigill', 127 | 'ProcessSignal.SIGTRAP': 'ProcessSignal.sigtrap', 128 | 'ProcessSignal.SIGABRT': 'ProcessSignal.sigabrt', 129 | 'ProcessSignal.SIGBUS': 'ProcessSignal.sigbus', 130 | 'ProcessSignal.SIGFPE': 'ProcessSignal.sigfpe', 131 | 'ProcessSignal.SIGKILL': 'ProcessSignal.sigkill', 132 | 'ProcessSignal.SIGUSR1': 'ProcessSignal.sigusr1', 133 | 'ProcessSignal.SIGSEGV': 'ProcessSignal.sigsegv', 134 | 'ProcessSignal.SIGUSR2': 'ProcessSignal.sigusr2', 135 | 'ProcessSignal.SIGPIPE': 'ProcessSignal.sigpipe', 136 | 'ProcessSignal.SIGALRM': 'ProcessSignal.sigalrm', 137 | 'ProcessSignal.SIGTERM': 'ProcessSignal.sigterm', 138 | 'ProcessSignal.SIGCHLD': 'ProcessSignal.sigchld', 139 | 'ProcessSignal.SIGCONT': 'ProcessSignal.sigcont', 140 | 'ProcessSignal.SIGSTOP': 'ProcessSignal.sigstop', 141 | 'ProcessSignal.SIGTSTP': 'ProcessSignal.sigtstp', 142 | 'ProcessSignal.SIGTTIN': 'ProcessSignal.sigttin', 143 | 'ProcessSignal.SIGTTOU': 'ProcessSignal.sigttou', 144 | 'ProcessSignal.SIGURG': 'ProcessSignal.sigurg', 145 | 'ProcessSignal.SIGXCPU': 'ProcessSignal.sigxcpu', 146 | 'ProcessSignal.SIGXFSZ': 'ProcessSignal.sigxfsz', 147 | 'ProcessSignal.SIGVTALRM': 'ProcessSignal.sigvtalrm', 148 | 'ProcessSignal.SIGPROF': 'ProcessSignal.sigprof', 149 | 'ProcessSignal.SIGWINCH': 'ProcessSignal.sigwinch', 150 | 'ProcessSignal.SIGPOLL': 'ProcessSignal.sigpoll', 151 | 'ProcessSignal.SIGSYS': 'ProcessSignal.sigsys', 152 | 153 | // dart:io/socket.dart 154 | 'InternetAddressType.TERMINAL': 'InternetAddressType.terminal', 155 | 'InternetAddressType.IP_V4': 'InternetAddressType.IPv4', 156 | 'InternetAddressType.IP_V6': 'InternetAddressType.IPv6', 157 | 'InternetAddressType.ANY': 'InternetAddressType.any', 158 | 'InternetAddress.LOOPBACK_IP_V4': 'InternetAddress.loopbackIPv4', 159 | 'InternetAddress.LOOPBACK_IP_V6': 'InternetAddress.loopbackIPv6', 160 | 'InternetAddress.ANY_IP_V4': 'InternetAddress.anyIPv4', 161 | 'InternetAddress.ANY_IP_V6': 'InternetAddress.anyIPv6', 162 | 'SocketDirection.RECEIVE': 'SocketDirection.receive', 163 | 'SocketDirection.SEND': 'SocketDirection.send', 164 | 'SocketDirection.BOTH': 'SocketDirection.both', 165 | 'SocketOption.TCP_NODELAY': 'SocketOption.tcpNoDelay', 166 | 'RawSocketEvent.READ': 'RawSocketEvent.read', 167 | 'RawSocketEvent.WRITE': 'RawSocketEvent.write', 168 | 'RawSocketEvent.READ_CLOSED': 'RawSocketEvent.readClosed', 169 | 'RawSocketEvent.CLOSED': 'RawSocketEvent.closed', 170 | 171 | // dart:io/stdio.dart 172 | 'StdioType.TERMINAL': 'StdioType.terminal', 173 | 'StdioType.PIPE': 'StdioType.pipe', 174 | 'StdioType.FILE': 'StdioType.file', 175 | 'StdioType.OTHER': 'StdioType.other', 176 | 177 | // dart:io/string_transformer.dart 178 | 'SYSTEM_ENCODING': 'systemEncoding', 179 | 180 | // dart:_http/http.dart 181 | 'HttpStatus.CONTINUE': 'HttpStatus.continue_', 182 | 'HttpStatus.SWITCHING_PROTOCOLS': 'HttpStatus.switchingProtocols', 183 | 'HttpStatus.OK': 'HttpStatus.ok', 184 | 'HttpStatus.CREATED': 'HttpStatus.created', 185 | 'HttpStatus.ACCEPTED': 'HttpStatus.accepted', 186 | 'HttpStatus.NON_AUTHORITATIVE_INFORMATION': 187 | 'HttpStatus.nonAuthoritativeInformation', 188 | 'HttpStatus.NO_CONTENT': 'HttpStatus.noContent', 189 | 'HttpStatus.RESET_CONTENT': 'HttpStatus.resetContent', 190 | 'HttpStatus.PARTIAL_CONTENT': 'HttpStatus.partialContent', 191 | 'HttpStatus.MULTIPLE_CHOICES': 'HttpStatus.multipleChoices', 192 | 'HttpStatus.MOVED_PERMANENTLY': 'HttpStatus.movedPermanently', 193 | 'HttpStatus.FOUND': 'HttpStatus.found', 194 | 'HttpStatus.MOVED_TEMPORARILY': 'HttpStatus.movedTemporarily', 195 | 'HttpStatus.SEE_OTHER': 'HttpStatus.seeOther', 196 | 'HttpStatus.NOT_MODIFIED': 'HttpStatus.notModified', 197 | 'HttpStatus.USE_PROXY': 'HttpStatus.useProxy', 198 | 'HttpStatus.TEMPORARY_REDIRECT': 'HttpStatus.temporaryRedirect', 199 | 'HttpStatus.BAD_REQUEST': 'HttpStatus.badRequest', 200 | 'HttpStatus.UNAUTHORIZED': 'HttpStatus.unauthorized', 201 | 'HttpStatus.PAYMENT_REQUIRED': 'HttpStatus.paymentRequired', 202 | 'HttpStatus.FORBIDDEN': 'HttpStatus.forbidden', 203 | 'HttpStatus.NOT_FOUND': 'HttpStatus.notFound', 204 | 'HttpStatus.METHOD_NOT_ALLOWED': 'HttpStatus.methodNotAllowed', 205 | 'HttpStatus.NOT_ACCEPTABLE': 'HttpStatus.notAcceptable', 206 | 'HttpStatus.PROXY_AUTHENTICATION_REQUIRED': 207 | 'HttpStatus.proxyAuthenticationRequired', 208 | 'HttpStatus.REQUEST_TIMEOUT': 'HttpStatus.requestTimeout', 209 | 'HttpStatus.CONFLICT': 'HttpStatus.conflict', 210 | 'HttpStatus.GONE': 'HttpStatus.gone', 211 | 'HttpStatus.LENGTH_REQUIRED': 'HttpStatus.lengthRequired', 212 | 'HttpStatus.PRECONDITION_FAILED': 'HttpStatus.preconditionFailed', 213 | 'HttpStatus.REQUEST_ENTITY_TOO_LARGE': 'HttpStatus.requestEntityTooLarge', 214 | 'HttpStatus.REQUEST_URI_TOO_LONG': 'HttpStatus.requestUriTooLong', 215 | 'HttpStatus.UNSUPPORTED_MEDIA_TYPE': 'HttpStatus.unsupportedMediaType', 216 | 'HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE': 217 | 'HttpStatus.requestedRangeNotSatisfiable', 218 | 'HttpStatus.EXPECTATION_FAILED': 'HttpStatus.expectationFailed', 219 | 'HttpStatus.UPGRADE_REQUIRED': 'HttpStatus.upgradeRequired', 220 | 'HttpStatus.INTERNAL_SERVER_ERROR': 'HttpStatus.internalServerError', 221 | 'HttpStatus.NOT_IMPLEMENTED': 'HttpStatus.notImplemented', 222 | 'HttpStatus.BAD_GATEWAY': 'HttpStatus.badGateway', 223 | 'HttpStatus.SERVICE_UNAVAILABLE': 'HttpStatus.serviceUnavailable', 224 | 'HttpStatus.GATEWAY_TIMEOUT': 'HttpStatus.gatewayTimeout', 225 | 'HttpStatus.HTTP_VERSION_NOT_SUPPORTED': 'HttpStatus.httpVersionNotSupported', 226 | 'HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR': 227 | 'HttpStatus.networkConnectTimeoutError', 228 | 229 | 'HttpHeaders.ACCEPT': 'HttpHeaders.acceptHeader', 230 | 'HttpHeaders.ACCEPT_CHARSET': 'HttpHeaders.acceptCharsetHeader', 231 | 'HttpHeaders.ACCEPT_ENCODING': 'HttpHeaders.acceptEncodingHeader', 232 | 'HttpHeaders.ACCEPT_LANGUAGE': 'HttpHeaders.acceptLanguageHeader', 233 | 'HttpHeaders.ACCEPT_RANGES': 'HttpHeaders.acceptRangesHeader', 234 | 'HttpHeaders.AGE': 'HttpHeaders.ageHeader', 235 | 'HttpHeaders.ALLOW': 'HttpHeaders.allowHeader', 236 | 'HttpHeaders.AUTHORIZATION': 'HttpHeaders.authorizationHeader', 237 | 'HttpHeaders.CACHE_CONTROL': 'HttpHeaders.cacheControlHeader', 238 | 'HttpHeaders.CONNECTION': 'HttpHeaders.connectionHeader', 239 | 'HttpHeaders.CONTENT_ENCODING': 'HttpHeaders.contentEncodingHeader', 240 | 'HttpHeaders.CONTENT_LANGUAGE': 'HttpHeaders.contentLanguageHeader', 241 | 'HttpHeaders.CONTENT_LENGTH': 'HttpHeaders.contentLengthHeader', 242 | 'HttpHeaders.CONTENT_LOCATION': 'HttpHeaders.contentLocationHeader', 243 | 'HttpHeaders.CONTENT_MD5': 'HttpHeaders.contentMD5Header', 244 | 'HttpHeaders.CONTENT_RANGE': 'HttpHeaders.contentRangeHeader', 245 | 'HttpHeaders.CONTENT_TYPE': 'HttpHeaders.contentTypeHeader', 246 | 'HttpHeaders.DATE': 'HttpHeaders.dateHeader', 247 | 'HttpHeaders.ETAG': 'HttpHeaders.etagHeader', 248 | 'HttpHeaders.EXPECT': 'HttpHeaders.expectHeader', 249 | 'HttpHeaders.EXPIRES': 'HttpHeaders.expiresHeader', 250 | 'HttpHeaders.FROM': 'HttpHeaders.fromHeader', 251 | 'HttpHeaders.HOST': 'HttpHeaders.hostHeader', 252 | 'HttpHeaders.IF_MATCH': 'HttpHeaders.ifMatchHeader', 253 | 'HttpHeaders.IF_MODIFIED_SINCE': 'HttpHeaders.ifModifiedSinceHeader', 254 | 'HttpHeaders.IF_NONE_MATCH': 'HttpHeaders.ifNoneMatchHeader', 255 | 'HttpHeaders.IF_RANGE': 'HttpHeaders.ifRangeHeader', 256 | 'HttpHeaders.IF_UNMODIFIED_SINCE': 'HttpHeaders.ifUnmodifiedSinceHeader', 257 | 'HttpHeaders.LAST_MODIFIED': 'HttpHeaders.lastModifiedHeader', 258 | 'HttpHeaders.LOCATION': 'HttpHeaders.locationHeader', 259 | 'HttpHeaders.MAX_FORWARDS': 'HttpHeaders.maxForwardsHeader', 260 | 'HttpHeaders.PRAGMA': 'HttpHeaders.pragmaHeader', 261 | 'HttpHeaders.PROXY_AUTHENTICATE': 'HttpHeaders.proxyAuthenticateHeader', 262 | 'HttpHeaders.PROXY_AUTHORIZATION': 'HttpHeaders.proxyAuthorizationHeader', 263 | 'HttpHeaders.RANGE': 'HttpHeaders.rangeHeader', 264 | 'HttpHeaders.REFERER': 'HttpHeaders.refererHeader', 265 | 'HttpHeaders.RETRY_AFTER': 'HttpHeaders.retryAfterHeader', 266 | 'HttpHeaders.SERVER': 'HttpHeaders.serverHeader', 267 | 'HttpHeaders.TE': 'HttpHeaders.teHeader', 268 | 'HttpHeaders.TRAILER': 'HttpHeaders.trailerHeader', 269 | 'HttpHeaders.TRANSFER_ENCODING': 'HttpHeaders.transferEncodingHeader', 270 | 'HttpHeaders.UPGRADE': 'HttpHeaders.upgradeHeader', 271 | 'HttpHeaders.USER_AGENT': 'HttpHeaders.userAgentHeader', 272 | 'HttpHeaders.VARY': 'HttpHeaders.varyHeader', 273 | 'HttpHeaders.VIA': 'HttpHeaders.viaHeader', 274 | 'HttpHeaders.WARNING': 'HttpHeaders.warningHeader', 275 | 'HttpHeaders.WWW_AUTHENTICATE': 'HttpHeaders.wwwAuthenticateHeader', 276 | 277 | 'HttpHeaders.COOKIE': 'HttpHeaders.cookieHeader', 278 | 'HttpHeaders.SET_COOKIE': 'HttpHeaders.setCookieHeader', 279 | 280 | 'HttpHeaders.GENERAL_HEADERS': 'HttpHeaders.generalHeaders', 281 | 'HttpHeaders.ENTITY_HEADERS': 'HttpHeaders.entityHeaders', 282 | 'HttpHeaders.RESPONSE_HEADERS': 'HttpHeaders.responseHeaders', 283 | 'HttpHeaders.REQUEST_HEADERS': 'HttpHeaders.requestHeaders', 284 | 285 | 'ContentType.TEXT': 'ContentType.text', 286 | 'ContentType.HTML': 'ContentType.html', 287 | 'ContentType.JSON': 'ContentType.json', 288 | 'ContentType.BINARY': 'ContentType.binary', 289 | 290 | 'HttpClient.DEFAULT_HTTP_PORT': 'HttpClient.defaultHttpPort', 291 | 'HttpClient.DEFAULT_HTTPS_PORT': 'HttpClient.defaultHttpsPort', 292 | 293 | // dart:_http/websocket.dart 294 | 'WebSocketStatus.NORMAL_CLOSURE': 'WebSocketStatus.normalClosure', 295 | 'WebSocketStatus.GOING_AWAY': 'WebSocketStatus.goingAway', 296 | 'WebSocketStatus.PROTOCOL_ERROR': 'WebSocketStatus.protocolError', 297 | 'WebSocketStatus.UNSUPPORTED_DATA': 'WebSocketStatus.unsupportedData', 298 | 'WebSocketStatus.RESERVED_1004': 'WebSocketStatus.reserved1004', 299 | 'WebSocketStatus.NO_STATUS_RECEIVED': 'WebSocketStatus.noStatusReceived', 300 | 'WebSocketStatus.ABNORMAL_CLOSURE': 'WebSocketStatus.abnormalClosure', 301 | 'WebSocketStatus.INVALID_FRAME_PAYLOAD_DATA': 302 | 'WebSocketStatus.invalidFramePayloadData', 303 | 'WebSocketStatus.POLICY_VIOLATION': 'WebSocketStatus.policyViolation', 304 | 'WebSocketStatus.MESSAGE_TOO_BIG': 'WebSocketStatus.messageTooBig', 305 | 'WebSocketStatus.MISSING_MANDATORY_EXTENSION': 306 | 'WebSocketStatus.missingMandatoryExtension', 307 | 'WebSocketStatus.INTERNAL_SERVER_ERROR': 308 | 'WebSocketStatus.internalServerError', 309 | 'WebSocketStatus.RESERVED_1015': 'WebSocketStatus.reserved1015', 310 | 311 | 'CompressionOptions.DEFAULT': 'CompressionOptions.compressionDefault', 312 | 'CompressionOptions.OFF': 'CompressionOptions.compressionOff', 313 | 314 | 'WebSocket.CONNECTING': 'WebSocket.connecting', 315 | 'WebSocket.OPEN': 'WebSocket.open', 316 | 'WebSocket.CLOSING': 'WebSocket.closing', 317 | 'WebSocket.CLOSED': 'WebSocket.closed', 318 | 319 | // Unqualified/top-level declarations checked after qualified ones. 320 | 321 | // dart:math 322 | 'E': 'e', 323 | 'LN10': 'ln10', 324 | 'LN2': 'ln2', 325 | 'LOG2E': 'log2e', 326 | 'LOG10E': 'log10e', 327 | 'PI': 'pi', 328 | 'SQRT1_2': 'sqrt1_2', 329 | 'SQRT2': 'sqrt2', 330 | 331 | // dart:convert 332 | '{JSON}.encode': 'jsonEncode', 333 | '{JSON}.decode': 'jsonDecode', 334 | '{BASE64}.encode': 'base64Encode', 335 | '{BASE64}.decode': 'base64Decode', 336 | 337 | 'ASCII': 'ascii', 338 | 'BASE64': 'base64', 339 | 'BASE64URL': 'base64Url', 340 | 'HTML_ESCAPE': 'htmlEscape', 341 | 'UNKOWN': 'unknown', 342 | 'ATTRIBUTE': 'attribute', 343 | 'SQ_ATTRIBUTE': 'sqAttribute', 344 | 'ELEMENT': 'element', 345 | 'JSON': 'json', 346 | 'LATIN1': 'latin1', 347 | 'UNICODE_REPLACEMENT_CHARACTER_RUNE': 'unicodeReplacementCharacterRune', 348 | 'UNICODE_BOM_CHARACTER_RUNE': 'unicodeBomCharacterRune', 349 | 'UTF8': 'utf8', 350 | 351 | // @proxy annotation 352 | '@{proxy}': '', 353 | }; 354 | 355 | /// A class that can look up the correct change to perform for a given issue. 356 | class ChangeManager { 357 | static ChangeManager create() { 358 | return new ChangeManager._(_textReplacements); 359 | } 360 | 361 | final Map textReplacements; 362 | 363 | Set keys; 364 | List changeBuilders; 365 | 366 | ChangeManager._(this.textReplacements) { 367 | keys = new Set(); 368 | changeBuilders = []; 369 | 370 | for (String pattern in textReplacements.keys) { 371 | final ChangeBuilder builder = 372 | new ChangeBuilder(pattern, textReplacements[pattern]); 373 | keys.add(builder.deprecated); 374 | changeBuilders.add(builder); 375 | } 376 | } 377 | 378 | /// Given an analysis issue, look up the correct change. Returns `null` if 379 | /// there is no matching change. 380 | Change getChangeFor(Issue issue) { 381 | if (!keys.contains(issue.matchingSource)) { 382 | return null; 383 | } 384 | 385 | for (ChangeBuilder builder in changeBuilders) { 386 | if (builder.matches(issue)) { 387 | return builder.createChange(issue); 388 | } 389 | } 390 | 391 | return null; 392 | } 393 | } 394 | 395 | class ChangeBuilder { 396 | String deprecated; 397 | int offset; 398 | String match; 399 | String replacement; 400 | 401 | ChangeBuilder(String pattern, String replace) { 402 | this.replacement = replace; 403 | 404 | if (pattern.contains('{')) { 405 | deprecated = 406 | pattern.substring(pattern.indexOf('{') + 1, pattern.indexOf('}')); 407 | match = pattern.replaceAll('{', '').replaceAll('}', ''); 408 | offset = pattern.indexOf('{'); 409 | } else if (pattern.contains('.')) { 410 | deprecated = pattern.substring(pattern.indexOf('.') + 1); 411 | match = pattern; 412 | offset = pattern.length - deprecated.length; 413 | } else { 414 | deprecated = pattern; 415 | offset = 0; 416 | match = pattern; 417 | } 418 | } 419 | 420 | bool matches(Issue issue) { 421 | if (issue.matchingSource != deprecated) { 422 | return false; 423 | } 424 | 425 | final int x = issue.offset - offset; 426 | if (x < 0 || (x + match.length) > issue.contents.length) { 427 | return false; 428 | } 429 | 430 | if (issue.contents.substring(x, x + match.length) == match) { 431 | return true; 432 | } 433 | 434 | return false; 435 | } 436 | 437 | Change createChange(Issue issue) { 438 | return new TextReplaceChange( 439 | issue, issue.offset - offset, match, replacement); 440 | } 441 | } 442 | 443 | /// A description of a source change - the type of the change, the location it 444 | /// acts on, and how to perform the change. 445 | abstract class Change implements Comparable { 446 | /// A user facing description of the change. 447 | String get describe; 448 | 449 | /// The offset of the change from the start of the file; used for ordering 450 | /// purposes. 451 | int get offset; 452 | 453 | /// The source line of the change - used to display to the user. 454 | int get line; 455 | 456 | /// Perform the change operation on the given source and return the changed 457 | /// results. 458 | String applyTo(String contents); 459 | 460 | int compareTo(Change other) => offset - other.offset; 461 | } 462 | 463 | /// An implementation of a Change that is backed by an analysis issue. 464 | class TextReplaceChange extends Change { 465 | final Issue issue; 466 | final int offset; 467 | final String original; 468 | final String replacement; 469 | 470 | TextReplaceChange(this.issue, this.offset, this.original, this.replacement); 471 | 472 | String get describe => 473 | '$original => ${replacement.isEmpty ? "''" : replacement}'; 474 | 475 | int get line => issue.line; 476 | 477 | String applyTo(String contents) { 478 | return contents.substring(0, offset) + 479 | replacement + 480 | contents.substring(offset + original.length); 481 | } 482 | } 483 | --------------------------------------------------------------------------------