| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // TODO(jmesserly): this file needs to be refactored, it's a port from | 5 // TODO(jmesserly): this file needs to be refactored, it's a port from |
| 6 // package:dev_compiler's tests | 6 // package:dev_compiler's tests |
| 7 library analyzer.test.src.task.strong.strong_test_helper; | 7 library analyzer.test.src.task.strong.strong_test_helper; |
| 8 | 8 |
| 9 import 'package:analyzer/dart/ast/ast.dart'; | 9 import 'package:analyzer/dart/ast/ast.dart'; |
| 10 import 'package:analyzer/dart/ast/visitor.dart'; | 10 import 'package:analyzer/dart/ast/visitor.dart'; |
| 11 import 'package:analyzer/dart/element/element.dart'; | 11 import 'package:analyzer/dart/element/element.dart'; |
| 12 import 'package:analyzer/file_system/file_system.dart'; | 12 import 'package:analyzer/file_system/file_system.dart'; |
| 13 import 'package:analyzer/file_system/memory_file_system.dart'; | 13 import 'package:analyzer/file_system/memory_file_system.dart'; |
| 14 import 'package:analyzer/src/context/context.dart' show SdkAnalysisContext; | 14 import 'package:analyzer/src/context/context.dart' show SdkAnalysisContext; |
| 15 import 'package:analyzer/src/generated/engine.dart'; | 15 import 'package:analyzer/src/generated/engine.dart'; |
| 16 import 'package:analyzer/src/generated/error.dart'; | 16 import 'package:analyzer/src/generated/error.dart'; |
| 17 import 'package:analyzer/src/generated/sdk.dart'; | 17 import 'package:analyzer/src/generated/sdk.dart'; |
| 18 import 'package:analyzer/src/generated/source.dart'; | 18 import 'package:analyzer/src/generated/source.dart'; |
| 19 import 'package:analyzer/src/generated/type_system.dart'; | 19 import 'package:analyzer/src/generated/type_system.dart'; |
| 20 import 'package:analyzer/src/task/strong/checker.dart'; | 20 import 'package:analyzer/src/task/strong/checker.dart'; |
| 21 import 'package:logging/logging.dart'; | 21 import 'package:logging/logging.dart'; |
| 22 import 'package:source_span/source_span.dart'; | 22 import 'package:source_span/source_span.dart'; |
| 23 import 'package:unittest/unittest.dart'; | 23 import 'package:unittest/unittest.dart'; |
| 24 | 24 |
| 25 const String GREEN_COLOR = '\u001b[32m'; | |
| 26 | 25 |
| 27 const String NO_COLOR = '\u001b[0m'; | 26 MemoryResourceProvider files; |
| 27 bool _checkCalled; |
| 28 | 28 |
| 29 const String _CYAN_COLOR = '\u001b[36m'; | 29 initStrongModeTests() { |
| 30 setUp(() { |
| 31 AnalysisEngine.instance.processRequiredPlugins(); |
| 32 files = new MemoryResourceProvider(); |
| 33 _checkCalled = false; |
| 34 }); |
| 30 | 35 |
| 31 const String _MAGENTA_COLOR = '\u001b[35m'; | 36 tearDown(() { |
| 37 // This is a sanity check, in case only addFile is called. |
| 38 expect(_checkCalled, true, reason: 'must call check() method in test case'); |
| 39 files = null; |
| 40 }); |
| 41 } |
| 32 | 42 |
| 33 const String _RED_COLOR = '\u001b[31m'; | 43 /// Adds a file using [addFile] and calls [check]. |
| 44 void checkFile(String content) { |
| 45 addFile(content); |
| 46 check(); |
| 47 } |
| 48 |
| 49 /// Adds [files] using [addFiles] and calls [check]. |
| 50 void checkFiles(Map<String, String> files) { |
| 51 addFiles(files); |
| 52 check(); |
| 53 } |
| 54 |
| 55 /// Adds a file to check. The file should contain: |
| 56 /// |
| 57 /// * all expected failures are listed in the source code using comments |
| 58 /// immediately in front of the AST node that should contain the error. |
| 59 /// |
| 60 /// * errors are formatted as a token `level:Type`, where `level` is the |
| 61 /// logging level were the error would be reported at, and `Type` is the |
| 62 /// concrete subclass of [StaticInfo] that denotes the error. |
| 63 /// |
| 64 /// For example to check that an assignment produces a type error, you can |
| 65 /// create a file like: |
| 66 /// |
| 67 /// addFile(''' |
| 68 /// String x = /*severe:STATIC_TYPE_ERROR*/3; |
| 69 /// '''); |
| 70 /// check(); |
| 71 /// |
| 72 /// For a single file, you may also use [checkFile]. |
| 73 void addFile(String content, {String name: '/main.dart'}) { |
| 74 name = name.replaceFirst('^package:', '/packages/'); |
| 75 files.newFile(name, content); |
| 76 } |
| 77 |
| 78 /// Run the checker on a program, staring from '/main.dart', and verifies that |
| 79 /// errors/warnings/hints match the expected value. |
| 80 /// |
| 81 /// See [addFile] for more information about how to encode expectations in |
| 82 /// the file text. |
| 83 void check() { |
| 84 _checkCalled = true; |
| 85 |
| 86 expect(files.getFile('/main.dart').exists, true, |
| 87 reason: '`/main.dart` is missing'); |
| 88 |
| 89 var uriResolver = new TestUriResolver(files); |
| 90 // Enable task model strong mode |
| 91 var context = AnalysisEngine.instance.createAnalysisContext(); |
| 92 context.analysisOptions.strongMode = true; |
| 93 context.analysisOptions.strongModeHints = true; |
| 94 context.sourceFactory = new SourceFactory([ |
| 95 new MockDartSdk(_mockSdkSources, reportMissing: true).resolver, |
| 96 uriResolver |
| 97 ]); |
| 98 |
| 99 // Run the checker on /main.dart. |
| 100 Source mainSource = uriResolver.resolveAbsolute(new Uri.file('/main.dart')); |
| 101 var initialLibrary = |
| 102 context.resolveCompilationUnit2(mainSource, mainSource); |
| 103 |
| 104 var collector = new _ErrorCollector(); |
| 105 var checker = new CodeChecker( |
| 106 context.typeProvider, new StrongTypeSystemImpl(), collector, |
| 107 hints: true); |
| 108 |
| 109 // Extract expectations from the comments in the test files, and |
| 110 // check that all errors we emit are included in the expected map. |
| 111 var allLibraries = reachableLibraries(initialLibrary.element.library); |
| 112 for (var lib in allLibraries) { |
| 113 for (var unit in lib.units) { |
| 114 var errors = <AnalysisError>[]; |
| 115 collector.errors = errors; |
| 116 |
| 117 var source = unit.source; |
| 118 if (source.uri.scheme == 'dart') continue; |
| 119 |
| 120 var librarySource = context.getLibrariesContaining(source).single; |
| 121 var resolved = context.resolveCompilationUnit2(source, librarySource); |
| 122 var analyzerErrors = context |
| 123 .getErrors(source) |
| 124 .errors |
| 125 .where((error) => |
| 126 error.errorCode.name.startsWith('STRONG_MODE_INFERRED_TYPE')) |
| 127 .toList(); |
| 128 errors.addAll(analyzerErrors); |
| 129 checker.visitCompilationUnit(resolved); |
| 130 |
| 131 new _ExpectedErrorVisitor(errors).validate(resolved); |
| 132 } |
| 133 } |
| 134 } |
| 34 | 135 |
| 35 /// Sample mock SDK sources. | 136 /// Sample mock SDK sources. |
| 36 final Map<String, String> mockSdkSources = { | 137 final Map<String, String> _mockSdkSources = { |
| 37 // The list of types below is derived from: | 138 // The list of types below is derived from: |
| 38 // * types we use via our smoke queries, including HtmlElement and | 139 // * types we use via our smoke queries, including HtmlElement and |
| 39 // types from `_typeHandlers` (deserialize.dart) | 140 // types from `_typeHandlers` (deserialize.dart) |
| 40 // * types that are used internally by the resolver (see | 141 // * types that are used internally by the resolver (see |
| 41 // _initializeFrom in resolver.dart). | 142 // _initializeFrom in resolver.dart). |
| 42 'dart:core': ''' | 143 'dart:core': ''' |
| 43 library dart.core; | 144 library dart.core; |
| 44 | 145 |
| 45 void print(Object o) {} | 146 void print(Object o) {} |
| 46 | 147 |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 | 222 |
| 122 'dart:_foreign_helper': ''' | 223 'dart:_foreign_helper': ''' |
| 123 library dart._foreign_helper; | 224 library dart._foreign_helper; |
| 124 | 225 |
| 125 JS(String typeDescription, String codeTemplate, | 226 JS(String typeDescription, String codeTemplate, |
| 126 [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]) | 227 [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]) |
| 127 {} | 228 {} |
| 128 ''' | 229 ''' |
| 129 }; | 230 }; |
| 130 | 231 |
| 131 /// Returns an ANSII color escape sequence corresponding to [levelName]. Colors | |
| 132 /// are defined for: severe, error, warning, or info. Returns null if the level | |
| 133 /// name is not recognized. | |
| 134 String colorOf(String levelName) { | |
| 135 levelName = levelName.toLowerCase(); | |
| 136 if (levelName == 'shout' || levelName == 'severe' || levelName == 'error') { | |
| 137 return _RED_COLOR; | |
| 138 } | |
| 139 if (levelName == 'warning') return _MAGENTA_COLOR; | |
| 140 if (levelName == 'info') return _CYAN_COLOR; | |
| 141 return null; | |
| 142 } | |
| 143 | |
| 144 SourceSpanWithContext createSpanHelper( | 232 SourceSpanWithContext createSpanHelper( |
| 145 LineInfo lineInfo, int start, int end, Source source, String content) { | 233 LineInfo lineInfo, int start, int end, Source source, String content) { |
| 146 var startLoc = locationForOffset(lineInfo, source.uri, start); | 234 var startLoc = locationForOffset(lineInfo, source.uri, start); |
| 147 var endLoc = locationForOffset(lineInfo, source.uri, end); | 235 var endLoc = locationForOffset(lineInfo, source.uri, end); |
| 148 | 236 |
| 149 var lineStart = startLoc.offset - startLoc.column; | 237 var lineStart = startLoc.offset - startLoc.column; |
| 150 // Find the end of the line. This is not exposed directly on LineInfo, but | 238 // Find the end of the line. This is not exposed directly on LineInfo, but |
| 151 // we can find it pretty easily. | 239 // we can find it pretty easily. |
| 152 // TODO(jmesserly): for now we do the simple linear scan. Ideally we can get | 240 // TODO(jmesserly): for now we do the simple linear scan. Ideally we can get |
| 153 // some help from the LineInfo API. | 241 // some help from the LineInfo API. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 188 if (seen.contains(lib)) return; | 276 if (seen.contains(lib)) return; |
| 189 seen.add(lib); | 277 seen.add(lib); |
| 190 results.add(lib); | 278 results.add(lib); |
| 191 lib.importedLibraries.forEach(find); | 279 lib.importedLibraries.forEach(find); |
| 192 lib.exportedLibraries.forEach(find); | 280 lib.exportedLibraries.forEach(find); |
| 193 } | 281 } |
| 194 find(start); | 282 find(start); |
| 195 return results; | 283 return results; |
| 196 } | 284 } |
| 197 | 285 |
| 198 /// Run the checker on a program with files contents as indicated in | |
| 199 /// [testFiles]. | |
| 200 /// | |
| 201 /// This function makes several assumptions to make it easier to describe error | |
| 202 /// expectations: | |
| 203 /// | |
| 204 /// * a file named `/main.dart` exists in [testFiles]. | |
| 205 /// * all expected failures are listed in the source code using comments | |
| 206 /// immediately in front of the AST node that should contain the error. | |
| 207 /// * errors are formatted as a token `level:Type`, where `level` is the | |
| 208 /// logging level were the error would be reported at, and `Type` is the | |
| 209 /// concrete subclass of [StaticInfo] that denotes the error. | |
| 210 /// | |
| 211 /// For example, to check that an assignment produces a warning about a boxing | |
| 212 /// conversion, you can describe the test as follows: | |
| 213 /// | |
| 214 /// testChecker({ | |
| 215 /// '/main.dart': ''' | |
| 216 /// testMethod() { | |
| 217 /// dynamic x = /*warning:Box*/3; | |
| 218 /// } | |
| 219 /// ''' | |
| 220 /// }); | |
| 221 /// | |
| 222 void testChecker(String name, Map<String, String> testFiles) { | |
| 223 test(name, () { | |
| 224 AnalysisEngine.instance.processRequiredPlugins(); | |
| 225 expect(testFiles.containsKey('/main.dart'), isTrue, | |
| 226 reason: '`/main.dart` is missing in testFiles'); | |
| 227 | |
| 228 var provider = new MemoryResourceProvider(); | |
| 229 testFiles.forEach((key, value) { | |
| 230 var scheme = 'package:'; | |
| 231 if (key.startsWith(scheme)) { | |
| 232 key = '/packages/${key.substring(scheme.length)}'; | |
| 233 } | |
| 234 provider.newFile(key, value); | |
| 235 }); | |
| 236 var uriResolver = new TestUriResolver(provider); | |
| 237 // Enable task model strong mode | |
| 238 var context = AnalysisEngine.instance.createAnalysisContext(); | |
| 239 context.analysisOptions.strongMode = true; | |
| 240 context.analysisOptions.strongModeHints = true; | |
| 241 | |
| 242 context.sourceFactory = new SourceFactory([ | |
| 243 new MockDartSdk(mockSdkSources, reportMissing: true).resolver, | |
| 244 uriResolver | |
| 245 ]); | |
| 246 | |
| 247 // Run the checker on /main.dart. | |
| 248 Source mainSource = uriResolver.resolveAbsolute(new Uri.file('/main.dart')); | |
| 249 var initialLibrary = | |
| 250 context.resolveCompilationUnit2(mainSource, mainSource); | |
| 251 | |
| 252 var collector = new _ErrorCollector(); | |
| 253 var checker = new CodeChecker( | |
| 254 context.typeProvider, new StrongTypeSystemImpl(), collector, | |
| 255 hints: true); | |
| 256 | |
| 257 // Extract expectations from the comments in the test files, and | |
| 258 // check that all errors we emit are included in the expected map. | |
| 259 var allLibraries = reachableLibraries(initialLibrary.element.library); | |
| 260 for (var lib in allLibraries) { | |
| 261 for (var unit in lib.units) { | |
| 262 var errors = <AnalysisError>[]; | |
| 263 collector.errors = errors; | |
| 264 | |
| 265 var source = unit.source; | |
| 266 if (source.uri.scheme == 'dart') continue; | |
| 267 | |
| 268 var librarySource = context.getLibrariesContaining(source).single; | |
| 269 var resolved = context.resolveCompilationUnit2(source, librarySource); | |
| 270 var analyzerErrors = context | |
| 271 .getErrors(source) | |
| 272 .errors | |
| 273 .where((error) => | |
| 274 error.errorCode.name.startsWith('STRONG_MODE_INFERRED_TYPE')) | |
| 275 .toList(); | |
| 276 errors.addAll(analyzerErrors); | |
| 277 checker.visitCompilationUnit(resolved); | |
| 278 | |
| 279 new _ExpectedErrorVisitor(errors).validate(resolved); | |
| 280 } | |
| 281 } | |
| 282 }); | |
| 283 } | |
| 284 | |
| 285 /// Dart SDK which contains a mock implementation of the SDK libraries. May be | 286 /// Dart SDK which contains a mock implementation of the SDK libraries. May be |
| 286 /// used to speed up execution when most of the core libraries is not needed. | 287 /// used to speed up execution when most of the core libraries is not needed. |
| 287 class MockDartSdk implements DartSdk { | 288 class MockDartSdk implements DartSdk { |
| 288 final Map<Uri, _MockSdkSource> _sources = {}; | 289 final Map<Uri, _MockSdkSource> _sources = {}; |
| 289 final bool reportMissing; | 290 final bool reportMissing; |
| 290 final Map<String, SdkLibrary> _libs = {}; | 291 final Map<String, SdkLibrary> _libs = {}; |
| 291 final String sdkVersion = '0'; | 292 final String sdkVersion = '0'; |
| 292 final AnalysisContext context = new SdkAnalysisContext(); | 293 final AnalysisContext context = new SdkAnalysisContext(); |
| 293 DartUriResolver _resolver; | 294 DartUriResolver _resolver; |
| 294 MockDartSdk(Map<String, String> sources, {this.reportMissing}) { | 295 MockDartSdk(Map<String, String> sources, {this.reportMissing}) { |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 478 reason: 'expected different error type at:\n\n$actualMsg'); | 479 reason: 'expected different error type at:\n\n$actualMsg'); |
| 479 | 480 |
| 480 // We found it. Stop the search. | 481 // We found it. Stop the search. |
| 481 _actualErrors.remove(actual); | 482 _actualErrors.remove(actual); |
| 482 return; | 483 return; |
| 483 } | 484 } |
| 484 } | 485 } |
| 485 | 486 |
| 486 var span = _createSpan(node.offset, node.length); | 487 var span = _createSpan(node.offset, node.length); |
| 487 var levelName = expected.level.name.toLowerCase(); | 488 var levelName = expected.level.name.toLowerCase(); |
| 488 var msg = span.message(expected.typeName, color: colorOf(levelName)); | 489 var msg = span.message(expected.typeName, color: _colorOf(levelName)); |
| 489 fail('expected error was not reported at:\n\n$levelName: $msg'); | 490 fail('expected error was not reported at:\n\n$levelName: $msg'); |
| 490 } | 491 } |
| 491 | 492 |
| 492 String _formatActualError(AnalysisError actual) { | 493 String _formatActualError(AnalysisError actual) { |
| 493 var span = _createSpan(actual.offset, actual.length); | 494 var span = _createSpan(actual.offset, actual.length); |
| 494 var levelName = _actualErrorLevel(actual).name.toLowerCase(); | 495 var levelName = _actualErrorLevel(actual).name.toLowerCase(); |
| 495 var msg = span.message(actual.message, color: colorOf(levelName)); | 496 var msg = span.message(actual.message, color: _colorOf(levelName)); |
| 496 return '$levelName: [${errorCodeName(actual.errorCode)}] $msg'; | 497 return '$levelName: [${errorCodeName(actual.errorCode)}] $msg'; |
| 497 } | 498 } |
| 499 |
| 500 /// Returns an ANSII color escape sequence corresponding to [levelName]. |
| 501 /// |
| 502 /// Colors are defined for: severe, error, warning, or info. |
| 503 /// Returns null if the level name is not recognized. |
| 504 String _colorOf(String levelName) { |
| 505 const String CYAN_COLOR = '\u001b[36m'; |
| 506 const String MAGENTA_COLOR = '\u001b[35m'; |
| 507 const String RED_COLOR = '\u001b[31m'; |
| 508 |
| 509 levelName = levelName.toLowerCase(); |
| 510 if (levelName == 'shout' || levelName == 'severe' || levelName == 'error') { |
| 511 return RED_COLOR; |
| 512 } |
| 513 if (levelName == 'warning') return MAGENTA_COLOR; |
| 514 if (levelName == 'info') return CYAN_COLOR; |
| 515 return null; |
| 516 } |
| 498 } | 517 } |
| 499 | 518 |
| 500 class _MockSdkSource implements Source { | 519 class _MockSdkSource implements Source { |
| 501 /// Absolute URI which this source can be imported from. | 520 /// Absolute URI which this source can be imported from. |
| 502 final Uri uri; | 521 final Uri uri; |
| 503 final String _contents; | 522 final String _contents; |
| 504 | 523 |
| 505 final int modificationStamp = 1; | 524 final int modificationStamp = 1; |
| 506 | 525 |
| 507 _MockSdkSource(this.uri, this._contents); | 526 _MockSdkSource(this.uri, this._contents); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 524 UriKind get uriKind => UriKind.DART_URI; | 543 UriKind get uriKind => UriKind.DART_URI; |
| 525 | 544 |
| 526 bool exists() => true; | 545 bool exists() => true; |
| 527 | 546 |
| 528 Source resolveRelative(Uri relativeUri) => | 547 Source resolveRelative(Uri relativeUri) => |
| 529 throw new UnsupportedError('not expecting relative urls in dart: mocks'); | 548 throw new UnsupportedError('not expecting relative urls in dart: mocks'); |
| 530 | 549 |
| 531 Uri resolveRelativeUri(Uri relativeUri) => | 550 Uri resolveRelativeUri(Uri relativeUri) => |
| 532 throw new UnsupportedError('not expecting relative urls in dart: mocks'); | 551 throw new UnsupportedError('not expecting relative urls in dart: mocks'); |
| 533 } | 552 } |
| OLD | NEW |