OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 import 'dart:io'; | 5 import 'dart:io'; |
6 import 'dart:convert'; | 6 import 'dart:convert'; |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'package:path/path.dart' as path; | 9 import 'package:path/path.dart' as path; |
10 import 'package:expect/expect.dart'; | 10 import 'package:expect/expect.dart'; |
11 import 'package:source_maps/source_maps.dart'; | 11 import 'package:source_maps/source_maps.dart'; |
12 import 'package:compiler/src/apiimpl.dart'; | 12 import 'package:compiler/src/apiimpl.dart'; |
13 import 'package:compiler/src/elements/elements.dart' | 13 import 'package:compiler/src/elements/elements.dart' |
14 show AstElement, | 14 show |
15 ClassElement, | 15 AstElement, |
16 CompilationUnitElement, | 16 ClassElement, |
17 Element, | 17 CompilationUnitElement, |
18 FunctionElement, | 18 Element, |
19 LibraryElement, | 19 FunctionElement, |
20 MemberElement; | 20 LibraryElement, |
| 21 MemberElement; |
21 import 'package:compiler/src/io/source_file.dart' show SourceFile; | 22 import 'package:compiler/src/io/source_file.dart' show SourceFile; |
22 import 'package:compiler/src/io/source_information.dart' | 23 import 'package:compiler/src/io/source_information.dart' |
23 show computeElementNameForSourceMaps; | 24 show computeElementNameForSourceMaps; |
24 | 25 |
25 validateSourceMap(Uri targetUri, | 26 validateSourceMap(Uri targetUri, |
26 {Uri mainUri, | 27 {Uri mainUri, Position mainPosition, CompilerImpl compiler}) { |
27 Position mainPosition, | |
28 CompilerImpl compiler}) { | |
29 Uri mapUri = getMapUri(targetUri); | 28 Uri mapUri = getMapUri(targetUri); |
30 List<String> targetLines = new File.fromUri(targetUri).readAsLinesSync(); | 29 List<String> targetLines = new File.fromUri(targetUri).readAsLinesSync(); |
31 SingleMapping sourceMap = getSourceMap(mapUri); | 30 SingleMapping sourceMap = getSourceMap(mapUri); |
32 checkFileReferences(targetUri, mapUri, sourceMap); | 31 checkFileReferences(targetUri, mapUri, sourceMap); |
33 checkIndexReferences(targetLines, mapUri, sourceMap); | 32 checkIndexReferences(targetLines, mapUri, sourceMap); |
34 checkRedundancy(sourceMap); | 33 checkRedundancy(sourceMap); |
35 if (compiler != null) { | 34 if (compiler != null) { |
36 checkNames(targetUri, mapUri, sourceMap, compiler); | 35 checkNames(targetUri, mapUri, sourceMap, compiler); |
37 } | 36 } |
38 if (mainUri != null && mainPosition != null) { | 37 if (mainUri != null && mainPosition != null) { |
39 checkMainPosition(targetUri, targetLines ,sourceMap, mainUri, mainPosition); | 38 checkMainPosition(targetUri, targetLines, sourceMap, mainUri, mainPosition); |
40 } | 39 } |
41 } | 40 } |
42 | 41 |
43 checkIndexReferences(List<String> targetLines, | 42 checkIndexReferences( |
44 Uri mapUri, | 43 List<String> targetLines, Uri mapUri, SingleMapping sourceMap) { |
45 SingleMapping sourceMap) { | |
46 int urlsLength = sourceMap.urls.length; | 44 int urlsLength = sourceMap.urls.length; |
47 List<List<String>> sources = new List(urlsLength); | 45 List<List<String>> sources = new List(urlsLength); |
48 print('Reading sources'); | 46 print('Reading sources'); |
49 for (int i = 0; i < urlsLength; i++) { | 47 for (int i = 0; i < urlsLength; i++) { |
50 sources[i] = new File.fromUri(mapUri.resolve(sourceMap.urls[i])). | 48 sources[i] = new File.fromUri(mapUri.resolve(sourceMap.urls[i])) |
51 readAsStringSync().split('\n'); | 49 .readAsStringSync() |
| 50 .split('\n'); |
52 } | 51 } |
53 | 52 |
54 sourceMap.lines.forEach((TargetLineEntry line) { | 53 sourceMap.lines.forEach((TargetLineEntry line) { |
55 Expect.isTrue(line.line >= 0); | 54 Expect.isTrue(line.line >= 0); |
56 Expect.isTrue(line.line < targetLines.length); | 55 Expect.isTrue(line.line < targetLines.length); |
57 for (TargetEntry entry in line.entries) { | 56 for (TargetEntry entry in line.entries) { |
58 int urlIndex = entry.sourceUrlId; | 57 int urlIndex = entry.sourceUrlId; |
59 | 58 |
60 // TODO(zarah): Entry columns sometimes point one or more characters too | 59 // TODO(zarah): Entry columns sometimes point one or more characters too |
61 // far. Incomment this check when this is fixed. | 60 // far. Incomment this check when this is fixed. |
62 // | 61 // |
63 // Expect.isTrue(entry.column < target[line.line].length); | 62 // Expect.isTrue(entry.column < target[line.line].length); |
64 Expect.isTrue(entry.column >= 0); | 63 Expect.isTrue(entry.column >= 0); |
65 Expect.isTrue(urlIndex == null || | 64 Expect |
66 (urlIndex >= 0 && urlIndex < urlsLength)); | 65 .isTrue(urlIndex == null || (urlIndex >= 0 && urlIndex < urlsLength)); |
67 Expect.isTrue(entry.sourceLine == null || | 66 Expect.isTrue(entry.sourceLine == null || |
68 (entry.sourceLine >= 0 && | 67 (entry.sourceLine >= 0 && |
69 entry.sourceLine < sources[urlIndex].length)); | 68 entry.sourceLine < sources[urlIndex].length)); |
70 Expect.isTrue(entry.sourceColumn == null || | 69 Expect.isTrue(entry.sourceColumn == null || |
71 (entry.sourceColumn >= 0 && | 70 (entry.sourceColumn >= 0 && |
72 entry.sourceColumn < sources[urlIndex][entry.sourceLine].length)); | 71 entry.sourceColumn < sources[urlIndex][entry.sourceLine].length)); |
73 Expect.isTrue(entry.sourceNameId == null || | 72 Expect.isTrue(entry.sourceNameId == null || |
74 (entry.sourceNameId >= 0 && | 73 (entry.sourceNameId >= 0 && |
75 entry.sourceNameId < sourceMap.names.length)); | 74 entry.sourceNameId < sourceMap.names.length)); |
76 } | 75 } |
77 }); | 76 }); |
78 } | 77 } |
79 | 78 |
80 checkFileReferences(Uri targetUri, Uri mapUri, SingleMapping sourceMap) { | 79 checkFileReferences(Uri targetUri, Uri mapUri, SingleMapping sourceMap) { |
81 Expect.equals(targetUri, mapUri.resolve(sourceMap.targetUrl)); | 80 Expect.equals(targetUri, mapUri.resolve(sourceMap.targetUrl)); |
82 print('Checking sources'); | 81 print('Checking sources'); |
83 sourceMap.urls.forEach((String url) { | 82 sourceMap.urls.forEach((String url) { |
84 Expect.isTrue(new File.fromUri(mapUri.resolve(url)).existsSync()); | 83 Expect.isTrue(new File.fromUri(mapUri.resolve(url)).existsSync()); |
85 }); | 84 }); |
86 } | 85 } |
87 | 86 |
88 checkRedundancy(SingleMapping sourceMap) { | 87 checkRedundancy(SingleMapping sourceMap) { |
89 sourceMap.lines.forEach((TargetLineEntry line) { | 88 sourceMap.lines.forEach((TargetLineEntry line) { |
90 TargetEntry previous = null; | 89 TargetEntry previous = null; |
91 for (TargetEntry next in line.entries) { | 90 for (TargetEntry next in line.entries) { |
92 if (previous != null) { | 91 if (previous != null) { |
93 Expect.isFalse(sameSourcePoint(previous, next), | 92 Expect.isFalse( |
| 93 sameSourcePoint(previous, next), |
94 '$previous and $next are consecutive entries on line $line in the ' | 94 '$previous and $next are consecutive entries on line $line in the ' |
95 'source map but point to same source locations'); | 95 'source map but point to same source locations'); |
96 } | 96 } |
97 previous = next; | 97 previous = next; |
98 } | 98 } |
99 }); | 99 }); |
100 } | 100 } |
101 | 101 |
102 checkNames(Uri targetUri, Uri mapUri, | 102 checkNames( |
103 SingleMapping sourceMap, CompilerImpl compiler) { | 103 Uri targetUri, Uri mapUri, SingleMapping sourceMap, CompilerImpl compiler) { |
104 Map<Uri, CompilationUnitElement> compilationUnitMap = {}; | 104 Map<Uri, CompilationUnitElement> compilationUnitMap = {}; |
105 | 105 |
106 void mapCompilationUnits(LibraryElement library) { | 106 void mapCompilationUnits(LibraryElement library) { |
107 library.compilationUnits.forEach((CompilationUnitElement compilationUnit) { | 107 library.compilationUnits.forEach((CompilationUnitElement compilationUnit) { |
108 compilationUnitMap[compilationUnit.script.readableUri] = compilationUnit; | 108 compilationUnitMap[compilationUnit.script.readableUri] = compilationUnit; |
109 }); | 109 }); |
110 } | 110 } |
111 | 111 |
112 compiler.libraryLoader.libraries.forEach((LibraryElement library) { | 112 compiler.libraryLoader.libraries.forEach((LibraryElement library) { |
113 mapCompilationUnits(library); | 113 mapCompilationUnits(library); |
114 if (library.patch != null) { | 114 if (library.patch != null) { |
115 mapCompilationUnits(library.patch); | 115 mapCompilationUnits(library.patch); |
116 } | 116 } |
117 }); | 117 }); |
118 | 118 |
119 sourceMap.lines.forEach((TargetLineEntry line) { | 119 sourceMap.lines.forEach((TargetLineEntry line) { |
120 for (TargetEntry entry in line.entries) { | 120 for (TargetEntry entry in line.entries) { |
121 if (entry.sourceNameId != null) { | 121 if (entry.sourceNameId != null) { |
122 Uri uri = mapUri.resolve(sourceMap.urls[entry.sourceUrlId]); | 122 Uri uri = mapUri.resolve(sourceMap.urls[entry.sourceUrlId]); |
123 Position targetPosition = | 123 Position targetPosition = new Position(line.line, entry.column); |
124 new Position(line.line, entry.column); | |
125 Position sourcePosition = | 124 Position sourcePosition = |
126 new Position(entry.sourceLine, entry.sourceColumn); | 125 new Position(entry.sourceLine, entry.sourceColumn); |
127 String name = sourceMap.names[entry.sourceNameId]; | 126 String name = sourceMap.names[entry.sourceNameId]; |
128 | 127 |
129 CompilationUnitElement compilationUnit = compilationUnitMap[uri]; | 128 CompilationUnitElement compilationUnit = compilationUnitMap[uri]; |
130 Expect.isNotNull(compilationUnit, | 129 Expect.isNotNull( |
131 "No compilation unit found for $uri."); | 130 compilationUnit, "No compilation unit found for $uri."); |
132 | 131 |
133 SourceFile sourceFile = compilationUnit.script.file; | 132 SourceFile sourceFile = compilationUnit.script.file; |
134 | 133 |
135 Position positionFromOffset(int offset) { | 134 Position positionFromOffset(int offset) { |
136 int line = sourceFile.getLine(offset); | 135 int line = sourceFile.getLine(offset); |
137 int column = sourceFile.getColumn(line, offset); | 136 int column = sourceFile.getColumn(line, offset); |
138 return new Position(line, column); | 137 return new Position(line, column); |
139 } | 138 } |
140 | 139 |
141 Interval intervalFromElement(AstElement element) { | 140 Interval intervalFromElement(AstElement element) { |
142 if (!element.hasNode) return null; | 141 if (!element.hasNode) return null; |
143 | 142 |
144 var begin = element.node.getBeginToken().charOffset; | 143 var begin = element.node.getBeginToken().charOffset; |
145 var end = element.node.getEndToken(); | 144 var end = element.node.getEndToken(); |
146 end = end.charOffset + end.charCount; | 145 end = end.charOffset + end.charCount; |
147 return new Interval(positionFromOffset(begin), | 146 return new Interval( |
148 positionFromOffset(end)); | 147 positionFromOffset(begin), positionFromOffset(end)); |
149 } | 148 } |
150 | 149 |
151 AstElement findInnermost(AstElement element) { | 150 AstElement findInnermost(AstElement element) { |
152 bool isInsideElement(FunctionElement closure) { | 151 bool isInsideElement(FunctionElement closure) { |
153 Element enclosing = closure; | 152 Element enclosing = closure; |
154 while (enclosing != null) { | 153 while (enclosing != null) { |
155 if (enclosing == element) return true; | 154 if (enclosing == element) return true; |
156 enclosing = enclosing.enclosingElement; | 155 enclosing = enclosing.enclosingElement; |
157 } | 156 } |
158 return false; | 157 return false; |
(...skipping 11 matching lines...) Expand all Loading... |
170 } | 169 } |
171 }); | 170 }); |
172 } | 171 } |
173 return element; | 172 return element; |
174 } | 173 } |
175 | 174 |
176 void match(AstElement element) { | 175 void match(AstElement element) { |
177 Interval interval = intervalFromElement(element); | 176 Interval interval = intervalFromElement(element); |
178 if (interval != null && interval.contains(sourcePosition)) { | 177 if (interval != null && interval.contains(sourcePosition)) { |
179 AstElement innerElement = findInnermost(element); | 178 AstElement innerElement = findInnermost(element); |
180 String expectedName = | 179 String expectedName = computeElementNameForSourceMaps(innerElement); |
181 computeElementNameForSourceMaps(innerElement); | |
182 if (name != expectedName) { | 180 if (name != expectedName) { |
183 // For the code | 181 // For the code |
184 // (){}(); | 182 // (){}(); |
185 // ^ | 183 // ^ |
186 // the indicated position is within the scope of the local | 184 // the indicated position is within the scope of the local |
187 // function but it is also the position for the invocation of it. | 185 // function but it is also the position for the invocation of it. |
188 // Allow name to be either from the local or from its calling | 186 // Allow name to be either from the local or from its calling |
189 // context. | 187 // context. |
190 if (innerElement.isLocal && innerElement.isFunction) { | 188 if (innerElement.isLocal && innerElement.isFunction) { |
191 var enclosingElement = innerElement.enclosingElement; | 189 var enclosingElement = innerElement.enclosingElement; |
192 String expectedName2 = | 190 String expectedName2 = |
193 computeElementNameForSourceMaps(enclosingElement); | 191 computeElementNameForSourceMaps(enclosingElement); |
194 Expect.isTrue(name == expectedName2, | 192 Expect.isTrue( |
| 193 name == expectedName2, |
195 "Unexpected name '${name}', " | 194 "Unexpected name '${name}', " |
196 "expected '${expectedName}' for $innerElement " | 195 "expected '${expectedName}' for $innerElement " |
197 "or '${expectedName2}' for $enclosingElement."); | 196 "or '${expectedName2}' for $enclosingElement."); |
198 } else { | 197 } else { |
199 Expect.equals(expectedName, name, | 198 Expect.equals( |
| 199 expectedName, |
| 200 name, |
200 "Unexpected name '${name}', " | 201 "Unexpected name '${name}', " |
201 "expected '${expectedName}' or for $innerElement."); | 202 "expected '${expectedName}' or for $innerElement."); |
202 } | 203 } |
203 } | 204 } |
204 } | 205 } |
205 } | 206 } |
206 | 207 |
207 compilationUnit.forEachLocalMember((AstElement element) { | 208 compilationUnit.forEachLocalMember((AstElement element) { |
208 if (element.isClass) { | 209 if (element.isClass) { |
209 ClassElement classElement = element; | 210 ClassElement classElement = element; |
210 classElement.forEachLocalMember(match); | 211 classElement.forEachLocalMember(match); |
211 } else { | 212 } else { |
212 match(element); | 213 match(element); |
213 } | 214 } |
214 }); | 215 }); |
215 } | 216 } |
216 } | 217 } |
217 }); | 218 }); |
218 } | 219 } |
219 | 220 |
220 RegExp mainSignaturePrefix = new RegExp(r'main: \[?function\('); | 221 RegExp mainSignaturePrefix = new RegExp(r'main: \[?function\('); |
221 | 222 |
222 // Check that the line pointing to by [mainPosition] in [mainUri] contains | 223 // Check that the line pointing to by [mainPosition] in [mainUri] contains |
223 // the main function signature. | 224 // the main function signature. |
224 checkMainPosition(Uri targetUri, | 225 checkMainPosition(Uri targetUri, List<String> targetLines, |
225 List<String> targetLines, | 226 SingleMapping sourceMap, Uri mainUri, Position mainPosition) { |
226 SingleMapping sourceMap, | |
227 Uri mainUri, | |
228 Position mainPosition) { | |
229 bool mainPositionFound = false; | 227 bool mainPositionFound = false; |
230 sourceMap.lines.forEach((TargetLineEntry lineEntry) { | 228 sourceMap.lines.forEach((TargetLineEntry lineEntry) { |
231 lineEntry.entries.forEach((TargetEntry entry) { | 229 lineEntry.entries.forEach((TargetEntry entry) { |
232 if (entry.sourceLine == null || entry.sourceUrlId == null) return; | 230 if (entry.sourceLine == null || entry.sourceUrlId == null) return; |
233 Uri sourceUri = targetUri.resolve(sourceMap.urls[entry.sourceUrlId]); | 231 Uri sourceUri = targetUri.resolve(sourceMap.urls[entry.sourceUrlId]); |
234 if (sourceUri != mainUri) return; | 232 if (sourceUri != mainUri) return; |
235 if (entry.sourceLine + 1 == mainPosition.line && | 233 if (entry.sourceLine + 1 == mainPosition.line && |
236 entry.sourceColumn + 1 == mainPosition.column) { | 234 entry.sourceColumn + 1 == mainPosition.column) { |
237 Expect.isNotNull(entry.sourceNameId, | 235 Expect.isNotNull(entry.sourceNameId, "Main position has no name."); |
238 "Main position has no name."); | |
239 String name = sourceMap.names[entry.sourceNameId]; | 236 String name = sourceMap.names[entry.sourceNameId]; |
240 Expect.equals('main', name, | 237 Expect.equals( |
241 "Main position name is not '$name', not 'main'."); | 238 'main', name, "Main position name is not '$name', not 'main'."); |
242 String line = targetLines[lineEntry.line]; | 239 String line = targetLines[lineEntry.line]; |
243 Expect.isTrue(line.contains(mainSignaturePrefix), | 240 Expect.isTrue( |
| 241 line.contains(mainSignaturePrefix), |
244 "Line mapped to main position " | 242 "Line mapped to main position " |
245 "([${lineEntry.line + 1},${entry.column + 1}]) " | 243 "([${lineEntry.line + 1},${entry.column + 1}]) " |
246 "expected to contain '${mainSignaturePrefix.pattern}':\n$line\n"); | 244 "expected to contain '${mainSignaturePrefix.pattern}':\n$line\n"); |
247 mainPositionFound = true; | 245 mainPositionFound = true; |
248 } | 246 } |
249 }); | 247 }); |
250 }); | 248 }); |
251 Expect.isTrue(mainPositionFound, | 249 Expect.isTrue( |
252 'No main position $mainPosition found in $mainUri'); | 250 mainPositionFound, 'No main position $mainPosition found in $mainUri'); |
253 } | 251 } |
254 | 252 |
255 | |
256 sameSourcePoint(TargetEntry entry, TargetEntry otherEntry) { | 253 sameSourcePoint(TargetEntry entry, TargetEntry otherEntry) { |
257 return | 254 return (entry.sourceUrlId == otherEntry.sourceUrlId) && |
258 (entry.sourceUrlId == otherEntry.sourceUrlId) && | |
259 (entry.sourceLine == otherEntry.sourceLine) && | 255 (entry.sourceLine == otherEntry.sourceLine) && |
260 (entry.sourceColumn == otherEntry.sourceColumn) && | 256 (entry.sourceColumn == otherEntry.sourceColumn) && |
261 (entry.sourceNameId == otherEntry.sourceNameId); | 257 (entry.sourceNameId == otherEntry.sourceNameId); |
262 } | 258 } |
263 | 259 |
264 Uri getMapUri(Uri targetUri) { | 260 Uri getMapUri(Uri targetUri) { |
265 print('Accessing $targetUri'); | 261 print('Accessing $targetUri'); |
266 File targetFile = new File.fromUri(targetUri); | 262 File targetFile = new File.fromUri(targetUri); |
267 Expect.isTrue(targetFile.existsSync(), "File '$targetUri' doesn't exist."); | 263 Expect.isTrue(targetFile.existsSync(), "File '$targetUri' doesn't exist."); |
268 List<String> target = targetFile.readAsStringSync().split('\n'); | 264 List<String> target = targetFile.readAsStringSync().split('\n'); |
269 String mapReference = target[target.length - 2]; // #sourceMappingURL=<url> | 265 String mapReference = target[target.length - 2]; // #sourceMappingURL=<url> |
270 Expect.isTrue(mapReference.startsWith('//# sourceMappingURL=')); | 266 Expect.isTrue(mapReference.startsWith('//# sourceMappingURL=')); |
271 String mapName = mapReference.substring(mapReference.indexOf('=') + 1); | 267 String mapName = mapReference.substring(mapReference.indexOf('=') + 1); |
272 return targetUri.resolve(mapName); | 268 return targetUri.resolve(mapName); |
273 } | 269 } |
274 | 270 |
275 SingleMapping getSourceMap(Uri mapUri) { | 271 SingleMapping getSourceMap(Uri mapUri) { |
276 print('Accessing $mapUri'); | 272 print('Accessing $mapUri'); |
277 File mapFile = new File.fromUri(mapUri); | 273 File mapFile = new File.fromUri(mapUri); |
278 Expect.isTrue(mapFile.existsSync()); | 274 Expect.isTrue(mapFile.existsSync()); |
279 return new SingleMapping.fromJson( | 275 return new SingleMapping.fromJson(JSON.decode(mapFile.readAsStringSync())); |
280 JSON.decode(mapFile.readAsStringSync())); | |
281 } | 276 } |
282 | 277 |
283 copyDirectory(Directory sourceDir, Directory destinationDir) { | 278 copyDirectory(Directory sourceDir, Directory destinationDir) { |
284 sourceDir.listSync().forEach((FileSystemEntity element) { | 279 sourceDir.listSync().forEach((FileSystemEntity element) { |
285 String newPath = path.join(destinationDir.path, | 280 String newPath = |
286 path.basename(element.path)); | 281 path.join(destinationDir.path, path.basename(element.path)); |
287 if (element is File) { | 282 if (element is File) { |
288 element.copySync(newPath); | 283 element.copySync(newPath); |
289 } else if (element is Directory) { | 284 } else if (element is Directory) { |
290 Directory newDestinationDir = new Directory(newPath); | 285 Directory newDestinationDir = new Directory(newPath); |
291 newDestinationDir.createSync(); | 286 newDestinationDir.createSync(); |
292 copyDirectory(element, newDestinationDir); | 287 copyDirectory(element, newDestinationDir); |
293 } | 288 } |
294 }); | 289 }); |
295 } | 290 } |
296 | 291 |
297 Future<Directory> createTempDir() { | 292 Future<Directory> createTempDir() { |
298 return Directory.systemTemp | 293 return Directory.systemTemp |
299 .createTemp('sourceMap_test-') | 294 .createTemp('sourceMap_test-') |
300 .then((Directory dir) { | 295 .then((Directory dir) { |
301 return dir; | 296 return dir; |
302 }); | 297 }); |
303 } | 298 } |
304 | 299 |
305 class Position { | 300 class Position { |
306 final int line; | 301 final int line; |
307 final int column; | 302 final int column; |
308 | 303 |
309 const Position(this.line, this.column); | 304 const Position(this.line, this.column); |
310 | 305 |
311 bool operator <=(Position other) { | 306 bool operator <=(Position other) { |
312 return line < other.line || | 307 return line < other.line || line == other.line && column <= other.column; |
313 line == other.line && column <= other.column; | |
314 } | 308 } |
315 | 309 |
316 String toString() => '[${line + 1},${column + 1}]'; | 310 String toString() => '[${line + 1},${column + 1}]'; |
317 } | 311 } |
318 | 312 |
319 class Interval { | 313 class Interval { |
320 final Position begin; | 314 final Position begin; |
321 final Position end; | 315 final Position end; |
322 | 316 |
323 Interval(this.begin, this.end); | 317 Interval(this.begin, this.end); |
324 | 318 |
325 bool contains(Position other) { | 319 bool contains(Position other) { |
326 return begin <= other && other <= end; | 320 return begin <= other && other <= end; |
327 } | 321 } |
328 | 322 |
329 String toString() => '$begin-$end'; | 323 String toString() => '$begin-$end'; |
330 } | 324 } |
OLD | NEW |