Line data Source code
1 : // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
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.
4 :
5 : /// Tools to help implement refactoring like transformations to Dart code.
6 : ///
7 : /// [TextEditTransaction] supports making a series of changes to a text buffer.
8 : /// [guessIndent] helps to guess the appropriate indentiation for the new code.
9 : library source_maps.refactor;
10 :
11 : import 'package:source_span/source_span.dart';
12 :
13 : import 'printer.dart';
14 :
15 : /// Editable text transaction.
16 : ///
17 : /// Applies a series of edits using original location
18 : /// information, and composes them into the edited string.
19 : class TextEditTransaction {
20 : final SourceFile file;
21 : final String original;
22 : final _edits = <_TextEdit>[];
23 :
24 : /// Creates a new transaction.
25 0 : TextEditTransaction(this.original, this.file);
26 :
27 0 : bool get hasEdits => _edits.length > 0;
28 :
29 : /// Edit the original text, replacing text on the range [begin] and [end]
30 : /// with the [replacement]. [replacement] can be either a string or a
31 : /// [NestedPrinter].
32 : void edit(int begin, int end, replacement) {
33 0 : _edits.add(new _TextEdit(begin, end, replacement));
34 : }
35 :
36 : /// Create a source map [SourceLocation] for [offset].
37 : SourceLocation _loc(int offset) =>
38 0 : file != null ? file.location(offset) : null;
39 :
40 : /// Applies all pending [edit]s and returns a [NestedPrinter] containing the
41 : /// rewritten string and source map information. [filename] is given to the
42 : /// underlying printer to indicate the name of the generated file that will
43 : /// contains the source map information.
44 : ///
45 : /// Throws [UnsupportedError] if the edits were overlapping. If no edits were
46 : /// made, the printer simply contains the original string.
47 : NestedPrinter commit() {
48 0 : var printer = new NestedPrinter();
49 0 : if (_edits.length == 0) {
50 0 : return printer..add(original, location: _loc(0), isOriginal: true);
51 : }
52 :
53 : // Sort edits by start location.
54 0 : _edits.sort();
55 :
56 : int consumed = 0;
57 0 : for (var edit in _edits) {
58 0 : if (consumed > edit.begin) {
59 0 : var sb = new StringBuffer();
60 0 : sb..write(file.location(edit.begin).toolString)
61 0 : ..write(': overlapping edits. Insert at offset ')
62 0 : ..write(edit.begin)
63 0 : ..write(' but have consumed ')
64 0 : ..write(consumed)
65 0 : ..write(' input characters. List of edits:');
66 0 : for (var e in _edits) sb..write('\n ')..write(e);
67 0 : throw new UnsupportedError(sb.toString());
68 : }
69 :
70 : // Add characters from the original string between this edit and the last
71 : // one, if any.
72 0 : var betweenEdits = original.substring(consumed, edit.begin);
73 0 : printer..add(betweenEdits, location: _loc(consumed), isOriginal: true)
74 0 : ..add(edit.replace, location: _loc(edit.begin));
75 0 : consumed = edit.end;
76 : }
77 :
78 : // Add any text from the end of the original string that was not replaced.
79 0 : printer.add(original.substring(consumed),
80 0 : location: _loc(consumed), isOriginal: true);
81 : return printer;
82 : }
83 : }
84 :
85 : class _TextEdit implements Comparable<_TextEdit> {
86 : final int begin;
87 : final int end;
88 :
89 : /// The replacement used by the edit, can be a string or a [NestedPrinter].
90 : final replace;
91 :
92 0 : _TextEdit(this.begin, this.end, this.replace);
93 :
94 0 : int get length => end - begin;
95 :
96 0 : String toString() => '(Edit @ $begin,$end: "$replace")';
97 :
98 : int compareTo(_TextEdit other) {
99 0 : int diff = begin - other.begin;
100 0 : if (diff != 0) return diff;
101 0 : return end - other.end;
102 : }
103 : }
104 :
105 : /// Returns all whitespace characters at the start of [charOffset]'s line.
106 : String guessIndent(String code, int charOffset) {
107 : // Find the beginning of the line
108 : int lineStart = 0;
109 0 : for (int i = charOffset - 1; i >= 0; i--) {
110 0 : var c = code.codeUnitAt(i);
111 0 : if (c == _LF || c == _CR) {
112 0 : lineStart = i + 1;
113 : break;
114 : }
115 : }
116 :
117 : // Grab all the whitespace
118 0 : int whitespaceEnd = code.length;
119 0 : for (int i = lineStart; i < code.length; i++) {
120 0 : var c = code.codeUnitAt(i);
121 0 : if (c != _SPACE && c != _TAB) {
122 : whitespaceEnd = i;
123 : break;
124 : }
125 : }
126 :
127 0 : return code.substring(lineStart, whitespaceEnd);
128 : }
129 :
130 : const int _CR = 13;
131 : const int _LF = 10;
132 : const int _TAB = 9;
133 : const int _SPACE = 32;
|