Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 import 'dart:async'; | |
|
ahe
2017/04/19 11:38:29
Missing copyright.
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 2 import 'dart:convert'; | |
| 3 import 'dart:io'; | |
| 4 | |
| 5 import 'package:front_end/src/base/instrumentation.dart'; | |
| 6 import 'package:front_end/src/fasta/scanner.dart'; | |
| 7 import 'package:front_end/src/fasta/scanner/io.dart'; | |
| 8 | |
| 9 /// Implementation of [Instrumentation] which checks property/value pairs | |
| 10 /// against expectations encoded in source files using "/*@...*/" comments. | |
| 11 class ValidatingInstrumentation implements Instrumentation { | |
| 12 static final _ESCAPE_SEQUENCE = new RegExp(r'\\(.)'); | |
| 13 | |
| 14 /// Map from category names to the property names they are short for. | |
|
ahe
2017/04/19 11:38:29
Here and several other places below: consider usin
Paul Berry
2017/04/19 13:20:09
Done.
| |
| 15 static const _CATEGORIES = const { | |
| 16 'inference': const [ | |
| 17 'topType', | |
| 18 'typeArg', | |
| 19 'promotedType', | |
| 20 'type', | |
| 21 'returnType' | |
| 22 ], | |
| 23 }; | |
| 24 | |
| 25 /// Map from file URI to the as-yet unsatisfied expectations from that file, | |
| 26 /// organized by file offset. | |
| 27 final _expectations = <Uri, Map<int, List<_Expectation>>>{}; | |
|
ahe
2017/04/19 11:38:29
Consider renaming to unsatisfiedExpecations.
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 28 | |
| 29 /// Information about "testedFeatures" annotations, organized by file URI and | |
| 30 /// file offset. The inner map is guaranteed to be in ascending order of | |
| 31 /// file offset. | |
| 32 final _testedFeaturesState = <Uri, Map<int, Set<String>>>{}; | |
| 33 | |
| 34 /// String descriptions of the expectation mismatches found so far. | |
| 35 final _problems = <String>[]; | |
| 36 | |
| 37 /// Fixes that would need to be performed on source files in order for all | |
| 38 /// expectations to be met, organized by file URI. The inner map is not | |
| 39 /// guaranteed to be in ascending order of file offset. | |
| 40 final _fixes = <Uri, List<_Fix>>{}; | |
| 41 | |
| 42 /// Indicates whether any expectation mismatches were found. | |
| 43 /// | |
| 44 /// Should be called after [finish]. | |
| 45 bool get hasProblems => _problems.isNotEmpty; | |
| 46 | |
| 47 /// Gets a description of all expectation mismatches that were found, in a | |
| 48 /// form suitable for printing to the console. | |
| 49 /// | |
| 50 /// Should be called after [finish]. | |
| 51 get problemsAsString => _problems.join('\n'); | |
| 52 | |
| 53 /// Checks whether the property/value pairs passed to [record] match the | |
| 54 /// expectations loaded by [loadExpectations]. | |
| 55 void finish() { | |
| 56 _expectations.forEach((uri, expectationsForUri) { | |
| 57 expectationsForUri.forEach((offset, expectationsAtOffset) { | |
| 58 for (var expectation in expectationsAtOffset) { | |
| 59 _problem( | |
| 60 uri, | |
| 61 offset, | |
| 62 'expected ${expectation.property}=${expectation.value}, ' | |
| 63 'got nothing', | |
| 64 new _Fix( | |
| 65 expectation.commentOffset, expectation.commentLength, '')); | |
| 66 } | |
| 67 }); | |
| 68 }); | |
| 69 } | |
| 70 | |
| 71 /// Updates the source file at [uri] based on the actual property/value | |
| 72 /// pairs that were observed. | |
| 73 Future<Null> fixSource(Uri uri) async { | |
| 74 var fixes = _fixes[uri]; | |
| 75 if (fixes == null) return; | |
| 76 var bytes = (await readBytesFromFile(uri)).toList(); | |
| 77 // Remove the trailing \0 that's added by readBytesFromFile. | |
|
ahe
2017/04/19 11:38:29
We probably need to add boolean argument to readBy
ahe
2017/04/19 13:10:59
I've done that in CL 2827543006.
Paul Berry
2017/04/19 13:20:09
Thanks! I'll wait until you land that and then cl
Paul Berry
2017/04/19 13:53:55
Done.
| |
| 78 bytes.removeLast(); | |
| 79 fixes.sort((a, b) => b.offset.compareTo(a.offset)); | |
|
ahe
2017/04/19 11:38:29
I assume you sort them in reverse order to avoid h
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 80 for (var fix in fixes) { | |
| 81 bytes.replaceRange( | |
| 82 fix.offset, fix.offset + fix.length, UTF8.encode(fix.replacement)); | |
| 83 } | |
| 84 await new File.fromUri(uri).writeAsBytes(bytes); | |
| 85 } | |
| 86 | |
| 87 /// Loads expectations from the source file located at [uri]. | |
| 88 /// | |
| 89 /// Should be called before [finish]. | |
| 90 Future<Null> loadExpectations(Uri uri) async { | |
| 91 var bytes = await readBytesFromFile(uri); | |
| 92 var expectations = _expectations.putIfAbsent(uri, () => {}); | |
| 93 var testedFeaturesState = _testedFeaturesState.putIfAbsent(uri, () => {}); | |
| 94 ScannerResult result = scan(bytes, includeComments: true); | |
| 95 for (Token token = result.tokens; !token.isEof; token = token.next) { | |
| 96 for (Token commentToken = token.precedingCommentTokens; | |
| 97 commentToken != null; | |
| 98 commentToken = commentToken.next) { | |
|
ahe
2017/04/19 11:38:29
Perhaps we should add a forEachComment to ScannerR
Paul Berry
2017/04/19 13:20:10
Fair enough. I will do that as a follow-up CL.
Paul Berry
2017/04/19 15:31:20
I looked into this and it's uglier than I expected
| |
| 99 String lexeme = commentToken.lexeme; | |
| 100 if (lexeme.startsWith('/*@') && lexeme.endsWith('*/')) { | |
| 101 var expectation = lexeme.substring(3, lexeme.length - 2); | |
| 102 var equals = expectation.indexOf('='); | |
| 103 String property; | |
| 104 String value; | |
| 105 if (equals == -1) { | |
| 106 property = expectation; | |
| 107 value = ''; | |
| 108 } else { | |
| 109 property = expectation.substring(0, equals); | |
| 110 value = expectation | |
| 111 .substring(equals + 1) | |
| 112 .replaceAllMapped(_ESCAPE_SEQUENCE, (m) => m.group(1)); | |
| 113 } | |
|
ahe
2017/04/19 11:38:29
I suggest adding:
property = property.trim();
val
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 114 if (property == 'testedFeatures') { | |
| 115 Set<String> state = new Set<String>(); | |
| 116 for (String category in value.split(',')) { | |
|
ahe
2017/04/19 11:38:29
And here:
category = category.trim();
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 117 // If an unrecognized category name is found, it is assumed to be | |
| 118 // just a property name. | |
| 119 state.addAll(_CATEGORIES[category] ?? [category]); | |
| 120 } | |
| 121 testedFeaturesState[commentToken.offset] = state; | |
| 122 } else { | |
| 123 var offset = token.charOffset; | |
| 124 var expectationsAtOffset = | |
| 125 expectations.putIfAbsent(offset, () => []); | |
| 126 expectationsAtOffset.add(new _Expectation( | |
| 127 property, value, commentToken.offset, commentToken.length)); | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 } | |
| 132 } | |
| 133 | |
| 134 @override | |
| 135 void record( | |
| 136 String property, Uri uri, int offset, InstrumentationValue value) { | |
| 137 var expectationsForUri = _expectations[uri]; | |
| 138 if (expectationsForUri == null) return; | |
| 139 var expectationsAtOffset = expectationsForUri[offset]; | |
| 140 if (expectationsAtOffset != null) { | |
| 141 for (int i = 0; i < expectationsAtOffset.length; i++) { | |
| 142 var expectation = expectationsAtOffset[i]; | |
| 143 if (expectation.property == property) { | |
| 144 if (!value.matches(expectation.value)) { | |
| 145 _problemWithStack( | |
| 146 uri, | |
| 147 offset, | |
| 148 'expected $property=${expectation.value}, got ' | |
| 149 '$property=${value.canonicalize()}', | |
| 150 new _Fix(expectation.commentOffset, expectation.commentLength, | |
| 151 _makeExpectationComment(property, value))); | |
| 152 } | |
| 153 expectationsAtOffset.removeAt(i); | |
| 154 return; | |
| 155 } | |
| 156 } | |
| 157 } | |
| 158 // Unexpected property/value pair. See if we should report. | |
| 159 if (_shouldCheck(property, uri, offset)) { | |
| 160 _problemWithStack( | |
| 161 uri, | |
| 162 offset, | |
| 163 'expected nothing, got $property=${value.canonicalize()}', | |
| 164 new _Fix(offset, 0, _makeExpectationComment(property, value))); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 String _escape(String s) { | |
| 169 return s.replaceAll(r'\', r'\\').replaceAll('*/', r'*\/'); | |
| 170 } | |
| 171 | |
| 172 String _formatProblem( | |
| 173 Uri uri, int offset, String desc, StackTrace stackTrace) { | |
| 174 return '$uri:$offset: $desc${stackTrace == null ? '' : '\n$stackTrace'}'; | |
|
ahe
2017/04/19 11:38:29
Perhaps you can use format from ../messages.dart h
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 175 } | |
| 176 | |
| 177 String _makeExpectationComment(String property, InstrumentationValue value) { | |
| 178 return '/*@$property=${_escape(value.canonicalize())}*/'; | |
| 179 } | |
| 180 | |
| 181 void _problem(Uri uri, int offset, String desc, _Fix fix) { | |
| 182 _problems.add(_formatProblem(uri, offset, desc, null)); | |
| 183 _fixes.putIfAbsent(uri, () => []).add(fix); | |
| 184 } | |
| 185 | |
| 186 void _problemWithStack(Uri uri, int offset, String desc, _Fix fix) { | |
| 187 try { | |
| 188 throw null; | |
|
ahe
2017/04/19 11:38:29
We now have StackTrace.current.
Paul Berry
2017/04/19 13:20:10
Done.
| |
| 189 } catch (_, stackTrace) { | |
| 190 _problems.add(_formatProblem(uri, offset, desc, stackTrace)); | |
| 191 _fixes.putIfAbsent(uri, () => []).add(fix); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 bool _shouldCheck(String property, Uri uri, int offset) { | |
| 196 var state = false; | |
| 197 var testedFeaturesStateForUri = _testedFeaturesState[uri]; | |
| 198 if (testedFeaturesStateForUri == null) return false; | |
| 199 for (int i in testedFeaturesStateForUri.keys) { | |
| 200 if (i > offset) break; | |
| 201 var testedFeaturesStateAtOffset = testedFeaturesStateForUri[i]; | |
| 202 state = testedFeaturesStateAtOffset.contains(property); | |
| 203 } | |
| 204 return state; | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 class _Expectation { | |
| 209 final String property; | |
| 210 final String value; | |
| 211 final int commentOffset; | |
| 212 final int commentLength; | |
| 213 | |
| 214 _Expectation( | |
| 215 this.property, this.value, this.commentOffset, this.commentLength); | |
| 216 } | |
| 217 | |
| 218 class _Fix { | |
| 219 final int offset; | |
| 220 final int length; | |
| 221 final String replacement; | |
| 222 | |
| 223 _Fix(this.offset, this.length, this.replacement); | |
| 224 } | |
| OLD | NEW |