| // Copyright (c) 2017, 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 'package:source_maps/source_maps.dart' hide Printer; |
| import 'package:source_span/source_span.dart' show SourceLocation; |
| import 'package:kernel/kernel.dart'; |
| |
| import '../js_ast/js_ast.dart' as JS; |
| |
| class SourceMapPrintingContext extends JS.SimpleJavaScriptPrintingContext { |
| /// Current line in the buffer. |
| int _line = 0; |
| |
| /// Current column in the buffer. |
| int _column = 0; |
| |
| /// The source_maps builder we write JavaScript code to. |
| final sourceMap = new SourceMapBuilder(); |
| |
| /// The last marked line in the buffer. |
| int _previousLine = -1; |
| |
| /// The last marked column in the buffer. |
| int _previousColumn = -1; |
| |
| List<FileUriNode> parentsStack = []; |
| |
| @override |
| void emit(String code) { |
| var chars = code.runes.toList(); |
| var length = chars.length; |
| for (int i = 0; i < length; i++) { |
| var c = chars[i]; |
| if (c == _LF || (c == _CR && (i + 1 == length || chars[i + 1] != _LF))) { |
| // Return not followed by line-feed is treated as a new line. |
| _line++; |
| _column = 0; |
| } else { |
| _column++; |
| } |
| } |
| super.emit(code); |
| } |
| |
| void enterNode(JS.Node jsNode) { |
| var srcInfo = jsNode.sourceInformation; |
| if (srcInfo == null) return; |
| |
| int offset; |
| bool mark = true; |
| if (srcInfo is TreeNode) { |
| offset = srcInfo.fileOffset; |
| |
| if (srcInfo is FileUriNode) { |
| parentsStack.add(srcInfo); |
| if (srcInfo is Procedure || |
| srcInfo is Class || |
| srcInfo is Constructor) { |
| mark = false; |
| } |
| } |
| if (mark && srcInfo is Block) mark = false; |
| } else { |
| throw "Unexpected source information: ${srcInfo.runtimeType}"; |
| } |
| |
| if (offset == -1 || !mark) return; |
| |
| _mark(offset, false); |
| } |
| |
| void exitNode(JS.Node jsNode) { |
| var srcInfo = jsNode.sourceInformation; |
| if (srcInfo == null) return; |
| |
| int offset = -1; |
| if (srcInfo is Member) { |
| offset = srcInfo.fileEndOffset; |
| } else if (srcInfo is FunctionNode) { |
| offset = srcInfo.fileEndOffset; |
| } else if (srcInfo is Class) { |
| offset = srcInfo.fileEndOffset; |
| } |
| if (offset == -1 && srcInfo is Constructor) { |
| // Probably default constructor that the user didn't write. Point to the |
| // end brace on the class instead. |
| Class parent = srcInfo.parent; |
| offset = parent.fileEndOffset; |
| } |
| |
| // Any ending brace or semicolon is already in the output. |
| // Adjust column accordingly. |
| if (offset != -1) _mark(offset, true); |
| |
| if (srcInfo is FileUriNode) { |
| parentsStack.removeLast(); |
| } |
| } |
| |
| void _mark(int offset, bool adjustColumn) { |
| int adjustedColumn = _column - (adjustColumn ? 1 : 0); |
| if (adjustedColumn < 0) return; |
| if ((_previousColumn == _column || _previousColumn == adjustedColumn) && |
| _previousLine == _line) return; |
| |
| if (parentsStack.isEmpty) { |
| // TODO(jensj) |
| return; |
| } |
| |
| FileUriNode fileParent = parentsStack.last; |
| Program p = fileParent.enclosingProgram; |
| Uri fileUri = fileParent.fileUri; |
| while (fileUri == null && fileParent.parent is FileUriNode) { |
| fileParent = fileParent.parent; |
| fileUri = fileParent.fileUri; |
| } |
| if (fileUri == null) return; |
| |
| var loc = p.getLocation(fileUri, offset); |
| if (loc == null) return; |
| _previousLine = _line; |
| _previousColumn = adjustedColumn; |
| sourceMap.addLocation( |
| new SourceLocation(offset, |
| sourceUrl: fileUri, line: loc.line - 1, column: loc.column - 1), |
| new SourceLocation(buffer.length - (adjustColumn ? 1 : 0), |
| line: _line, column: adjustedColumn), |
| null); |
| } |
| } |
| |
| const int _LF = 10; |
| const int _CR = 13; |