|  | // Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'dart:io'; | 
|  | import 'dart:convert'; | 
|  | import 'dart:async'; | 
|  |  | 
|  | import 'package:path/path.dart' as path; | 
|  | import 'package:expect/expect.dart'; | 
|  | import 'package:source_maps/source_maps.dart'; | 
|  | import 'package:compiler/src/apiimpl.dart'; | 
|  |  | 
|  | validateSourceMap(Uri targetUri, | 
|  | {Uri mainUri, Position mainPosition, CompilerImpl compiler}) { | 
|  | Uri mapUri = getMapUri(targetUri); | 
|  | List<String> targetLines = new File.fromUri(targetUri).readAsLinesSync(); | 
|  | SingleMapping sourceMap = getSourceMap(mapUri); | 
|  | checkFileReferences(targetUri, mapUri, sourceMap); | 
|  | checkIndexReferences(targetLines, mapUri, sourceMap); | 
|  | checkRedundancy(sourceMap); | 
|  | if (compiler != null) { | 
|  | checkNames(targetUri, mapUri, sourceMap, compiler); | 
|  | } | 
|  | if (mainUri != null && mainPosition != null) { | 
|  | checkMainPosition(targetUri, targetLines, sourceMap, mainUri, mainPosition); | 
|  | } | 
|  | } | 
|  |  | 
|  | checkIndexReferences( | 
|  | List<String> targetLines, Uri mapUri, SingleMapping sourceMap) { | 
|  | int urlsLength = sourceMap.urls.length; | 
|  | List<List<String>> sources = new List(urlsLength); | 
|  | print('Reading sources'); | 
|  | for (int i = 0; i < urlsLength; i++) { | 
|  | sources[i] = new File.fromUri(mapUri.resolve(sourceMap.urls[i])) | 
|  | .readAsStringSync() | 
|  | .split('\n'); | 
|  | } | 
|  |  | 
|  | sourceMap.lines.forEach((TargetLineEntry line) { | 
|  | Expect.isTrue(line.line >= 0); | 
|  | Expect.isTrue(line.line < targetLines.length); | 
|  | for (TargetEntry entry in line.entries) { | 
|  | int urlIndex = entry.sourceUrlId; | 
|  |  | 
|  | // TODO(zarah): Entry columns sometimes point one or more characters too | 
|  | // far. Incomment this check when this is fixed. | 
|  | // | 
|  | // Expect.isTrue(entry.column < target[line.line].length); | 
|  | Expect.isTrue(entry.column >= 0); | 
|  | Expect.isTrue( | 
|  | urlIndex == null || (urlIndex >= 0 && urlIndex < urlsLength)); | 
|  | Expect.isTrue(entry.sourceLine == null || | 
|  | (entry.sourceLine >= 0 && | 
|  | entry.sourceLine < sources[urlIndex].length)); | 
|  | Expect.isTrue(entry.sourceColumn == null || | 
|  | (entry.sourceColumn >= 0 && | 
|  | entry.sourceColumn < sources[urlIndex][entry.sourceLine].length)); | 
|  | Expect.isTrue(entry.sourceNameId == null || | 
|  | (entry.sourceNameId >= 0 && | 
|  | entry.sourceNameId < sourceMap.names.length)); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | checkFileReferences(Uri targetUri, Uri mapUri, SingleMapping sourceMap) { | 
|  | Expect.equals(targetUri, mapUri.resolve(sourceMap.targetUrl)); | 
|  | print('Checking sources'); | 
|  | sourceMap.urls.forEach((String url) { | 
|  | Expect.isTrue(new File.fromUri(mapUri.resolve(url)).existsSync()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | checkRedundancy(SingleMapping sourceMap) { | 
|  | sourceMap.lines.forEach((TargetLineEntry line) { | 
|  | TargetEntry previous = null; | 
|  | for (TargetEntry next in line.entries) { | 
|  | if (previous != null) { | 
|  | Expect.isFalse( | 
|  | sameSourcePoint(previous, next), | 
|  | '$previous and $next are consecutive entries on line $line in the ' | 
|  | 'source map but point to same source locations'); | 
|  | } | 
|  | previous = next; | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | checkNames( | 
|  | Uri targetUri, Uri mapUri, SingleMapping sourceMap, CompilerImpl compiler) { | 
|  | // TODO(johnniwinther): Port this to work on kernel based elements. | 
|  | /* | 
|  | Map<Uri, CompilationUnitElement> compilationUnitMap = {}; | 
|  |  | 
|  | void mapCompilationUnits(LibraryElement library) { | 
|  | library.compilationUnits.forEach((CompilationUnitElement compilationUnit) { | 
|  | compilationUnitMap[compilationUnit.script.readableUri] = compilationUnit; | 
|  | }); | 
|  | } | 
|  |  | 
|  | compiler.libraryLoader.libraries.forEach((_library) { | 
|  | LibraryElement library = _library; | 
|  | mapCompilationUnits(library); | 
|  | if (library.patch != null) { | 
|  | mapCompilationUnits(library.patch); | 
|  | } | 
|  | }); | 
|  |  | 
|  | sourceMap.lines.forEach((TargetLineEntry line) { | 
|  | for (TargetEntry entry in line.entries) { | 
|  | if (entry.sourceNameId != null) { | 
|  | Uri uri = mapUri.resolve(sourceMap.urls[entry.sourceUrlId]); | 
|  | Position sourcePosition = | 
|  | new Position(entry.sourceLine, entry.sourceColumn); | 
|  | String name = sourceMap.names[entry.sourceNameId]; | 
|  |  | 
|  | CompilationUnitElement compilationUnit = compilationUnitMap[uri]; | 
|  | Expect.isNotNull( | 
|  | compilationUnit, "No compilation unit found for $uri."); | 
|  |  | 
|  | SourceFile sourceFile = compilationUnit.script.file; | 
|  |  | 
|  | Position positionFromOffset(int offset) { | 
|  | Location location = sourceFile.getLocation(offset); | 
|  | int line = location.line - 1; | 
|  | int column = location.column - 1; | 
|  | return new Position(line, column); | 
|  | } | 
|  |  | 
|  | Interval intervalFromElement(AstElement element) { | 
|  | if (!element.hasNode) return null; | 
|  |  | 
|  | var begin = 0; | 
|  | int end = 0; | 
|  | return new Interval( | 
|  | positionFromOffset(begin), positionFromOffset(end)); | 
|  | } | 
|  |  | 
|  | AstElement findInnermost(AstElement element) { | 
|  | bool isInsideElement(FunctionElement closure) { | 
|  | Element enclosing = closure; | 
|  | while (enclosing != null) { | 
|  | if (enclosing == element) return true; | 
|  | enclosing = enclosing.enclosingElement; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (element is MemberElement) { | 
|  | MemberElement member = element; | 
|  | member.nestedClosures.forEach((dynamic closure) { | 
|  | var localFunction = closure.expression; | 
|  | Interval interval = intervalFromElement(localFunction); | 
|  | if (interval != null && | 
|  | interval.contains(sourcePosition) && | 
|  | isInsideElement(localFunction)) { | 
|  | element = localFunction; | 
|  | } | 
|  | }); | 
|  | } | 
|  | return element; | 
|  | } | 
|  |  | 
|  | void match(Element _element) { | 
|  | AstElement element = _element; | 
|  | Interval interval = intervalFromElement(element); | 
|  | if (interval != null && interval.contains(sourcePosition)) { | 
|  | AstElement innerElement = findInnermost(element); | 
|  | String expectedName = computeElementNameForSourceMaps(innerElement); | 
|  | int stubIndex = name.indexOf('[function-entry'); | 
|  | if (stubIndex != -1) { | 
|  | Expect.isTrue(innerElement is FunctionElement, | 
|  | "Unexpected element $innerElement for stub '$name'."); | 
|  | name = name.substring(0, stubIndex); | 
|  | } | 
|  | if (name != expectedName) { | 
|  | // For the code | 
|  | //    (){}(); | 
|  | //    ^ | 
|  | // the indicated position is within the scope of the local | 
|  | // function but it is also the position for the invocation of it. | 
|  | // Allow name to be either from the local or from its calling | 
|  | // context. | 
|  | if (innerElement.isLocal && innerElement.isFunction) { | 
|  | var enclosingElement = innerElement.enclosingElement; | 
|  | String expectedName2 = | 
|  | computeElementNameForSourceMaps(enclosingElement); | 
|  | Expect.isTrue( | 
|  | name == expectedName2, | 
|  | "Unexpected name '${name}', " | 
|  | "expected '${expectedName}' for $innerElement " | 
|  | "or '${expectedName2}' for $enclosingElement."); | 
|  | } else { | 
|  | Expect.equals( | 
|  | expectedName, | 
|  | name, | 
|  | "Unexpected name '${name}', " | 
|  | "expected '${expectedName}' or for $innerElement."); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | compilationUnit.forEachLocalMember((Element element) { | 
|  | if (element.isClass) { | 
|  | ClassElement classElement = element; | 
|  | classElement.forEachLocalMember(match); | 
|  | } else { | 
|  | match(element); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | });*/ | 
|  | } | 
|  |  | 
|  | RegExp mainSignaturePrefix = new RegExp(r'main: \[?function\('); | 
|  |  | 
|  | // Check that the line pointing to by [mainPosition] in [mainUri] contains | 
|  | // the main function signature. | 
|  | checkMainPosition(Uri targetUri, List<String> targetLines, | 
|  | SingleMapping sourceMap, Uri mainUri, Position mainPosition) { | 
|  | bool mainPositionFound = false; | 
|  | sourceMap.lines.forEach((TargetLineEntry lineEntry) { | 
|  | lineEntry.entries.forEach((TargetEntry entry) { | 
|  | if (entry.sourceLine == null || entry.sourceUrlId == null) return; | 
|  | Uri sourceUri = targetUri.resolve(sourceMap.urls[entry.sourceUrlId]); | 
|  | if (sourceUri != mainUri) return; | 
|  | if (entry.sourceLine + 1 == mainPosition.line && | 
|  | entry.sourceColumn + 1 == mainPosition.column) { | 
|  | Expect.isNotNull(entry.sourceNameId, "Main position has no name."); | 
|  | String name = sourceMap.names[entry.sourceNameId]; | 
|  | Expect.equals( | 
|  | 'main', name, "Main position name is not '$name', not 'main'."); | 
|  | String line = targetLines[lineEntry.line]; | 
|  | Expect.isTrue( | 
|  | line.contains(mainSignaturePrefix), | 
|  | "Line mapped to main position " | 
|  | "([${lineEntry.line + 1},${entry.column + 1}]) " | 
|  | "expected to contain '${mainSignaturePrefix.pattern}':\n$line\n"); | 
|  | mainPositionFound = true; | 
|  | } | 
|  | }); | 
|  | }); | 
|  | Expect.isTrue( | 
|  | mainPositionFound, 'No main position $mainPosition found in $mainUri'); | 
|  | } | 
|  |  | 
|  | sameSourcePoint(TargetEntry entry, TargetEntry otherEntry) { | 
|  | return (entry.sourceUrlId == otherEntry.sourceUrlId) && | 
|  | (entry.sourceLine == otherEntry.sourceLine) && | 
|  | (entry.sourceColumn == otherEntry.sourceColumn) && | 
|  | (entry.sourceNameId == otherEntry.sourceNameId); | 
|  | } | 
|  |  | 
|  | Uri getMapUri(Uri targetUri) { | 
|  | print('Accessing $targetUri'); | 
|  | File targetFile = new File.fromUri(targetUri); | 
|  | Expect.isTrue(targetFile.existsSync(), "File '$targetUri' doesn't exist."); | 
|  | List<String> target = targetFile.readAsStringSync().split('\n'); | 
|  | String mapReference = target[target.length - 2]; // #sourceMappingURL=<url> | 
|  | Expect.isTrue(mapReference.startsWith('//# sourceMappingURL=')); | 
|  | String mapName = mapReference.substring(mapReference.indexOf('=') + 1); | 
|  | return targetUri.resolve(mapName); | 
|  | } | 
|  |  | 
|  | SingleMapping getSourceMap(Uri mapUri) { | 
|  | print('Accessing $mapUri'); | 
|  | File mapFile = new File.fromUri(mapUri); | 
|  | Expect.isTrue(mapFile.existsSync()); | 
|  | return new SingleMapping.fromJson(json.decode(mapFile.readAsStringSync())); | 
|  | } | 
|  |  | 
|  | copyDirectory(Directory sourceDir, Directory destinationDir) { | 
|  | sourceDir.listSync().forEach((FileSystemEntity element) { | 
|  | String newPath = | 
|  | path.join(destinationDir.path, path.basename(element.path)); | 
|  | if (element is File) { | 
|  | element.copySync(newPath); | 
|  | } else if (element is Directory) { | 
|  | Directory newDestinationDir = new Directory(newPath); | 
|  | newDestinationDir.createSync(); | 
|  | copyDirectory(element, newDestinationDir); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | Future<Directory> createTempDir() { | 
|  | return Directory.systemTemp | 
|  | .createTemp('sourceMap_test-') | 
|  | .then((Directory dir) { | 
|  | return dir; | 
|  | }); | 
|  | } | 
|  |  | 
|  | class Position { | 
|  | final int line; | 
|  | final int column; | 
|  |  | 
|  | const Position(this.line, this.column); | 
|  |  | 
|  | bool operator <=(Position other) { | 
|  | return line < other.line || line == other.line && column <= other.column; | 
|  | } | 
|  |  | 
|  | String toString() => '[${line + 1},${column + 1}]'; | 
|  | } | 
|  |  | 
|  | class Interval { | 
|  | final Position begin; | 
|  | final Position end; | 
|  |  | 
|  | Interval(this.begin, this.end); | 
|  |  | 
|  | bool contains(Position other) { | 
|  | return begin <= other && other <= end; | 
|  | } | 
|  |  | 
|  | String toString() => '$begin-$end'; | 
|  | } |