OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library services.src.correction.util; | |
6 | |
7 import 'package:analysis_services/correction/change.dart'; | |
8 import 'package:analysis_services/src/correction/source_range.dart'; | |
9 import 'package:analysis_services/src/correction/strings.dart'; | |
10 import 'package:analyzer/src/generated/ast.dart'; | |
11 import 'package:analyzer/src/generated/element.dart'; | |
12 import 'package:analyzer/src/generated/engine.dart'; | |
13 import 'package:analyzer/src/generated/resolver.dart'; | |
14 import 'package:analyzer/src/generated/scanner.dart'; | |
15 import 'package:analyzer/src/generated/source.dart'; | |
16 | |
17 | |
18 /** | |
19 * TODO(scheglov) replace with nodes once there will be [CompilationUnit#getComm
ents]. | |
20 * | |
21 * Returns [SourceRange]s of all comments in [unit]. | |
22 */ | |
23 List<SourceRange> getCommentRanges(CompilationUnit unit) { | |
24 List<SourceRange> ranges = <SourceRange>[]; | |
25 Token token = unit.beginToken; | |
26 while (token != null && token.type != TokenType.EOF) { | |
27 Token commentToken = token.precedingComments; | |
28 while (commentToken != null) { | |
29 ranges.add(rangeToken(commentToken)); | |
30 commentToken = commentToken.next; | |
31 } | |
32 token = token.next; | |
33 } | |
34 return ranges; | |
35 } | |
36 | |
37 | |
38 String getDefaultValueCode(DartType type) { | |
39 if (type != null) { | |
40 String typeName = type.displayName; | |
41 if (typeName == "bool") { | |
42 return "false"; | |
43 } | |
44 if (typeName == "int") { | |
45 return "0"; | |
46 } | |
47 if (typeName == "double") { | |
48 return "0.0"; | |
49 } | |
50 if (typeName == "String") { | |
51 return "''"; | |
52 } | |
53 } | |
54 // no better guess | |
55 return "null"; | |
56 } | |
57 | |
58 | |
59 /** | |
60 * Return the name of the [Element] kind. | |
61 */ | |
62 String getElementKindName(Element element) { | |
63 return element.kind.displayName; | |
64 } | |
65 | |
66 | |
67 /** | |
68 * Returns the name to display in the UI for the given [Element]. | |
69 */ | |
70 String getElementQualifiedName(Element element) { | |
71 ElementKind kind = element.kind; | |
72 if (kind == ElementKind.FIELD || kind == ElementKind.METHOD) { | |
73 return '${element.enclosingElement.displayName}.${element.displayName}'; | |
74 } else { | |
75 return element.displayName; | |
76 } | |
77 } | |
78 | |
79 | |
80 /** | |
81 * @return the [ExecutableElement] of the enclosing executable [AstNode]. | |
82 */ | |
83 ExecutableElement getEnclosingExecutableElement(AstNode node) { | |
84 while (node != null) { | |
85 if (node is FunctionDeclaration) { | |
86 return node.element; | |
87 } | |
88 if (node is ConstructorDeclaration) { | |
89 return node.element; | |
90 } | |
91 if (node is MethodDeclaration) { | |
92 return node.element; | |
93 } | |
94 node = node.parent; | |
95 } | |
96 return null; | |
97 } | |
98 | |
99 /** | |
100 * Returns a namespace of the given [ExportElement]. | |
101 */ | |
102 Map<String, Element> getExportNamespaceForDirective(ExportElement exp) { | |
103 Namespace namespace = | |
104 new NamespaceBuilder().createExportNamespaceForDirective(exp); | |
105 return namespace.definedNames; | |
106 } | |
107 | |
108 | |
109 /** | |
110 * Returns a export namespace of the given [LibraryElement]. | |
111 */ | |
112 Map<String, Element> getExportNamespaceForLibrary(LibraryElement library) { | |
113 Namespace namespace = | |
114 new NamespaceBuilder().createExportNamespaceForLibrary(library); | |
115 return namespace.definedNames; | |
116 } | |
117 | |
118 /** | |
119 * Returns an [Element] exported from the given [LibraryElement]. | |
120 */ | |
121 Element getExportedElement(LibraryElement library, String name) { | |
122 if (library == null) { | |
123 return null; | |
124 } | |
125 return getExportNamespaceForLibrary(library)[name]; | |
126 } | |
127 | |
128 /** | |
129 * Returns [getExpressionPrecedence] for the parent of [node], | |
130 * or `0` if the parent node is [ParenthesizedExpression]. | |
131 * | |
132 * The reason is that `(expr)` is always executed after `expr`. | |
133 */ | |
134 int getExpressionParentPrecedence(AstNode node) { | |
135 AstNode parent = node.parent; | |
136 if (parent is ParenthesizedExpression) { | |
137 return 0; | |
138 } | |
139 return getExpressionPrecedence(parent); | |
140 } | |
141 | |
142 /** | |
143 * Returns the precedence of [node] it is an [Expression], negative otherwise. | |
144 */ | |
145 int getExpressionPrecedence(AstNode node) { | |
146 if (node is Expression) { | |
147 return node.precedence; | |
148 } | |
149 return -1000; | |
150 } | |
151 | |
152 /** | |
153 * Returns the namespace of the given [ImportElement]. | |
154 */ | |
155 Map<String, Element> getImportNamespace(ImportElement imp) { | |
156 NamespaceBuilder builder = new NamespaceBuilder(); | |
157 Namespace namespace = builder.createImportNamespaceForDirective(imp); | |
158 return namespace.definedNames; | |
159 } | |
160 | |
161 | |
162 /** | |
163 * If given [AstNode] is name of qualified property extraction, returns target f
rom which | |
164 * this property is extracted. Otherwise `null`. | |
165 */ | |
166 Expression getQualifiedPropertyTarget(AstNode node) { | |
167 AstNode parent = node.parent; | |
168 if (parent is PrefixedIdentifier) { | |
169 PrefixedIdentifier prefixed = parent; | |
170 if (prefixed.identifier == node) { | |
171 return parent.prefix; | |
172 } | |
173 } | |
174 if (parent is PropertyAccess) { | |
175 PropertyAccess access = parent; | |
176 if (access.propertyName == node) { | |
177 return access.realTarget; | |
178 } | |
179 } | |
180 return null; | |
181 } | |
182 | |
183 | |
184 /** | |
185 * Returns the given [Statement] if not a [Block], or the first child | |
186 * [Statement] if a [Block], or `null` if more than one child. | |
187 */ | |
188 Statement getSingleStatement(Statement statement) { | |
189 if (statement is Block) { | |
190 List<Statement> blockStatements = statement.statements; | |
191 if (blockStatements.length != 1) { | |
192 return null; | |
193 } | |
194 return blockStatements[0]; | |
195 } | |
196 return statement; | |
197 } | |
198 | |
199 | |
200 /** | |
201 * Returns the [String] content of the given [Source]. | |
202 */ | |
203 String getSourceContent(AnalysisContext context, Source source) { | |
204 return context.getContents(source).data; | |
205 } | |
206 | |
207 | |
208 /** | |
209 * Returns the given [Statement] if not a [Block], or all the children | |
210 * [Statement]s if a [Block]. | |
211 */ | |
212 List<Statement> getStatements(Statement statement) { | |
213 if (statement is Block) { | |
214 return statement.statements; | |
215 } | |
216 return [statement]; | |
217 } | |
218 | |
219 | |
220 /** | |
221 * Checks if the given [Element]'s display name equals to the given name. | |
222 */ | |
223 bool hasDisplayName(Element element, String name) { | |
224 if (element == null) { | |
225 return false; | |
226 } | |
227 return element.displayName == name; | |
228 } | |
229 | |
230 | |
231 class CorrectionUtils { | |
232 final CompilationUnit unit; | |
233 | |
234 LibraryElement _library; | |
235 String _buffer; | |
236 String _endOfLine; | |
237 | |
238 CorrectionUtils(this.unit) { | |
239 CompilationUnitElement unitElement = unit.element; | |
240 this._library = unitElement.library; | |
241 this._buffer = unitElement.context.getContents(unitElement.source).data; | |
242 } | |
243 | |
244 /** | |
245 * Returns the EOL to use for this [CompilationUnit]. | |
246 */ | |
247 String get endOfLine { | |
248 if (_endOfLine == null) { | |
249 if (_buffer.contains("\r\n")) { | |
250 _endOfLine = "\r\n"; | |
251 } else { | |
252 _endOfLine = "\n"; | |
253 } | |
254 } | |
255 return _endOfLine; | |
256 } | |
257 | |
258 /** | |
259 * Returns an [Edit] that changes indentation of the source of the given | |
260 * [SourceRange] from [oldIndent] to [newIndent], keeping indentation of lines | |
261 * relative to each other. | |
262 */ | |
263 Edit createIndentEdit(SourceRange range, String oldIndent, String newIndent) { | |
264 String newSource = replaceSourceRangeIndent(range, oldIndent, newIndent); | |
265 return new Edit(range.offset, range.length, newSource); | |
266 } | |
267 | |
268 /** | |
269 * Returns the actual type source of the given [Expression], may be `null` | |
270 * if can not be resolved, should be treated as the `dynamic` type. | |
271 */ | |
272 String getExpressionTypeSource(Expression expression) { | |
273 if (expression == null) { | |
274 return null; | |
275 } | |
276 DartType type = expression.bestType; | |
277 if (type.isDynamic) { | |
278 return null; | |
279 } | |
280 return getTypeSource(type); | |
281 } | |
282 | |
283 /** | |
284 * Returns the indentation with the given level. | |
285 */ | |
286 String getIndent(int level) => repeat(' ', level); | |
287 | |
288 /** | |
289 * Returns a [InsertDesc] describing where to insert a new library-related | |
290 * directive. | |
291 */ | |
292 CorrectionUtils_InsertDesc getInsertDescImport() { | |
293 // analyze directives | |
294 Directive prevDirective = null; | |
295 for (Directive directive in unit.directives) { | |
296 if (directive is LibraryDirective || | |
297 directive is ImportDirective || | |
298 directive is ExportDirective) { | |
299 prevDirective = directive; | |
300 } | |
301 } | |
302 // insert after last library-related directive | |
303 if (prevDirective != null) { | |
304 CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc(); | |
305 result.offset = prevDirective.end; | |
306 String eol = endOfLine; | |
307 if (prevDirective is LibraryDirective) { | |
308 result.prefix = "${eol}${eol}"; | |
309 } else { | |
310 result.prefix = eol; | |
311 } | |
312 return result; | |
313 } | |
314 // no directives, use "top" location | |
315 return getInsertDescTop(); | |
316 } | |
317 | |
318 /** | |
319 * Returns a [InsertDesc] describing where to insert a new 'part' directive. | |
320 */ | |
321 CorrectionUtils_InsertDesc getInsertDescPart() { | |
322 // analyze directives | |
323 Directive prevDirective = null; | |
324 for (Directive directive in unit.directives) { | |
325 prevDirective = directive; | |
326 } | |
327 // insert after last directive | |
328 if (prevDirective != null) { | |
329 CorrectionUtils_InsertDesc result = new CorrectionUtils_InsertDesc(); | |
330 result.offset = prevDirective.end; | |
331 String eol = endOfLine; | |
332 if (prevDirective is PartDirective) { | |
333 result.prefix = eol; | |
334 } else { | |
335 result.prefix = "${eol}${eol}"; | |
336 } | |
337 return result; | |
338 } | |
339 // no directives, use "top" location | |
340 return getInsertDescTop(); | |
341 } | |
342 | |
343 /** | |
344 * Returns a [InsertDesc] describing where to insert a new directive or a | |
345 * top-level declaration at the top of the file. | |
346 */ | |
347 CorrectionUtils_InsertDesc getInsertDescTop() { | |
348 // skip leading line comments | |
349 int offset = 0; | |
350 bool insertEmptyLineBefore = false; | |
351 bool insertEmptyLineAfter = false; | |
352 String source = _buffer; | |
353 // skip hash-bang | |
354 if (offset < source.length - 2) { | |
355 String linePrefix = getText(offset, 2); | |
356 if (linePrefix == "#!") { | |
357 insertEmptyLineBefore = true; | |
358 offset = getLineNext(offset); | |
359 // skip empty lines to first line comment | |
360 int emptyOffset = offset; | |
361 while (emptyOffset < source.length - 2) { | |
362 int nextLineOffset = getLineNext(emptyOffset); | |
363 String line = source.substring(emptyOffset, nextLineOffset); | |
364 if (line.trim().isEmpty) { | |
365 emptyOffset = nextLineOffset; | |
366 continue; | |
367 } else if (line.startsWith("//")) { | |
368 offset = emptyOffset; | |
369 break; | |
370 } else { | |
371 break; | |
372 } | |
373 } | |
374 } | |
375 } | |
376 // skip line comments | |
377 while (offset < source.length - 2) { | |
378 String linePrefix = getText(offset, 2); | |
379 if (linePrefix == "//") { | |
380 insertEmptyLineBefore = true; | |
381 offset = getLineNext(offset); | |
382 } else { | |
383 break; | |
384 } | |
385 } | |
386 // determine if empty line is required after | |
387 int nextLineOffset = getLineNext(offset); | |
388 String insertLine = source.substring(offset, nextLineOffset); | |
389 if (!insertLine.trim().isEmpty) { | |
390 insertEmptyLineAfter = true; | |
391 } | |
392 // fill InsertDesc | |
393 CorrectionUtils_InsertDesc desc = new CorrectionUtils_InsertDesc(); | |
394 desc.offset = offset; | |
395 if (insertEmptyLineBefore) { | |
396 desc.prefix = endOfLine; | |
397 } | |
398 if (insertEmptyLineAfter) { | |
399 desc.suffix = endOfLine; | |
400 } | |
401 return desc; | |
402 } | |
403 | |
404 /** | |
405 * Skips whitespace characters and single EOL on the right from [index]. | |
406 * | |
407 * If [index] the end of a statement or method, then in the most cases it is | |
408 * a start of the next line. | |
409 */ | |
410 int getLineContentEnd(int index) { | |
411 int length = _buffer.length; | |
412 // skip whitespace characters | |
413 while (index < length) { | |
414 int c = _buffer.codeUnitAt(index); | |
415 if (!isWhitespace(c) || c == 0x0D || c == 0x0A) { | |
416 break; | |
417 } | |
418 index++; | |
419 } | |
420 // skip single \r | |
421 if (index < length && _buffer.codeUnitAt(index) == 0x0D) { | |
422 index++; | |
423 } | |
424 // skip single \n | |
425 if (index < length && _buffer.codeUnitAt(index) == 0x0A) { | |
426 index++; | |
427 } | |
428 // done | |
429 return index; | |
430 } | |
431 | |
432 /** | |
433 * Skips spaces and tabs on the left from [index]. | |
434 * | |
435 * If [index] is the start or a statement, then in the most cases it is a | |
436 * start on its line. | |
437 */ | |
438 int getLineContentStart(int index) { | |
439 while (index > 0) { | |
440 int c = _buffer.codeUnitAt(index - 1); | |
441 if (!isSpace(c)) { | |
442 break; | |
443 } | |
444 index--; | |
445 } | |
446 return index; | |
447 } | |
448 | |
449 /** | |
450 * Returns a start index of the next line after the line which contains the | |
451 * given index. | |
452 */ | |
453 int getLineNext(int index) { | |
454 int length = _buffer.length; | |
455 // skip to the end of the line | |
456 while (index < length) { | |
457 int c = _buffer.codeUnitAt(index); | |
458 if (c == 0xD || c == 0xA) { | |
459 break; | |
460 } | |
461 index++; | |
462 } | |
463 // skip single \r | |
464 if (index < length && _buffer.codeUnitAt(index) == 0xD) { | |
465 index++; | |
466 } | |
467 // skip single \n | |
468 if (index < length && _buffer.codeUnitAt(index) == 0xA) { | |
469 index++; | |
470 } | |
471 // done | |
472 return index; | |
473 } | |
474 | |
475 /** | |
476 * Returns the whitespace prefix of the line which contains given offset. | |
477 */ | |
478 String getLinePrefix(int index) { | |
479 int lineStart = getLineThis(index); | |
480 int length = _buffer.length; | |
481 int lineNonWhitespace = lineStart; | |
482 while (lineNonWhitespace < length) { | |
483 int c = _buffer.codeUnitAt(lineNonWhitespace); | |
484 if (c == 0xD || c == 0xA) { | |
485 break; | |
486 } | |
487 if (!isWhitespace(c)) { | |
488 break; | |
489 } | |
490 lineNonWhitespace++; | |
491 } | |
492 return getText(lineStart, lineNonWhitespace - lineStart); | |
493 } | |
494 | |
495 /** | |
496 * Returns the start index of the line which contains given index. | |
497 */ | |
498 int getLineThis(int index) { | |
499 while (index > 0) { | |
500 int c = _buffer.codeUnitAt(index - 1); | |
501 if (c == 0xD || c == 0xA) { | |
502 break; | |
503 } | |
504 index--; | |
505 } | |
506 return index; | |
507 } | |
508 | |
509 /** | |
510 * Returns a [SourceRange] that covers [range] and extends (if possible) to | |
511 * cover whole lines. | |
512 */ | |
513 SourceRange getLinesRange(SourceRange range) { | |
514 // start | |
515 int startOffset = range.offset; | |
516 int startLineOffset = getLineContentStart(startOffset); | |
517 // end | |
518 int endOffset = range.end; | |
519 int afterEndLineOffset = getLineContentEnd(endOffset); | |
520 // range | |
521 return rangeStartEnd(startLineOffset, afterEndLineOffset); | |
522 } | |
523 | |
524 /** | |
525 * Returns a [SourceRange] that covers all the given [Statement]s. | |
526 */ | |
527 SourceRange getLinesRangeStatements(List<Statement> statements) { | |
528 SourceRange range = rangeNodes(statements); | |
529 return getLinesRange(range); | |
530 } | |
531 | |
532 /** | |
533 * Returns the line prefix consisting of spaces and tabs on the left from the
given | |
534 * [AstNode]. | |
535 */ | |
536 String getNodePrefix(AstNode node) { | |
537 int offset = node.offset; | |
538 // function literal is special, it uses offset of enclosing line | |
539 if (node is FunctionExpression) { | |
540 return getLinePrefix(offset); | |
541 } | |
542 // use just prefix directly before node | |
543 return getPrefix(offset); | |
544 } | |
545 | |
546 /** | |
547 * Returns the text of the given [AstNode] in the unit. | |
548 */ | |
549 String getNodeText(AstNode node) { | |
550 return getText(node.offset, node.length); | |
551 } | |
552 | |
553 /** | |
554 * @return the source for the parameter with the given type and name. | |
555 */ | |
556 String getParameterSource(DartType type, String name) { | |
557 // no type | |
558 if (type == null || type.isDynamic) { | |
559 return name; | |
560 } | |
561 // function type | |
562 if (type is FunctionType) { | |
563 FunctionType functionType = type; | |
564 StringBuffer sb = new StringBuffer(); | |
565 // return type | |
566 DartType returnType = functionType.returnType; | |
567 if (returnType != null && !returnType.isDynamic) { | |
568 sb.write(getTypeSource(returnType)); | |
569 sb.write(' '); | |
570 } | |
571 // parameter name | |
572 sb.write(name); | |
573 // parameters | |
574 sb.write('('); | |
575 List<ParameterElement> fParameters = functionType.parameters; | |
576 for (int i = 0; i < fParameters.length; i++) { | |
577 ParameterElement fParameter = fParameters[i]; | |
578 if (i != 0) { | |
579 sb.write(", "); | |
580 } | |
581 sb.write(getParameterSource(fParameter.type, fParameter.name)); | |
582 } | |
583 sb.write(')'); | |
584 // done | |
585 return sb.toString(); | |
586 } | |
587 // simple type | |
588 return "${getTypeSource(type)} ${name}"; | |
589 } | |
590 | |
591 /** | |
592 * Returns the line prefix consisting of spaces and tabs on the left from the | |
593 * given offset. | |
594 */ | |
595 String getPrefix(int endIndex) { | |
596 int startIndex = getLineContentStart(endIndex); | |
597 return _buffer.substring(startIndex, endIndex); | |
598 } | |
599 | |
600 /** | |
601 * Returns the text of the given range in the unit. | |
602 */ | |
603 String getRangeText(SourceRange range) { | |
604 return getText(range.offset, range.length); | |
605 } | |
606 | |
607 /** | |
608 * Returns the text of the given range in the unit. | |
609 */ | |
610 String getText(int offset, int length) { | |
611 return _buffer.substring(offset, offset + length); | |
612 } | |
613 | |
614 /** | |
615 * Returns the source to reference [type] in this [CompilationUnit]. | |
616 */ | |
617 String getTypeSource(DartType type) { | |
618 StringBuffer sb = new StringBuffer(); | |
619 // just some Function, maybe find Function Type Alias later | |
620 if (type is FunctionType) { | |
621 return "Function"; | |
622 } | |
623 // prepare element | |
624 Element element = type.element; | |
625 if (element == null) { | |
626 String source = type.toString(); | |
627 source = source.replaceAll('<dynamic>', ''); | |
628 source = source.replaceAll('<dynamic, dynamic>', ''); | |
629 return source; | |
630 } | |
631 // append prefix | |
632 { | |
633 ImportElement imp = _getImportElement(element); | |
634 if (imp != null && imp.prefix != null) { | |
635 sb.write(imp.prefix.displayName); | |
636 sb.write("."); | |
637 } | |
638 } | |
639 // append simple name | |
640 String name = element.displayName; | |
641 sb.write(name); | |
642 // may be type arguments | |
643 if (type is InterfaceType) { | |
644 InterfaceType interfaceType = type; | |
645 List<DartType> arguments = interfaceType.typeArguments; | |
646 // check if has arguments | |
647 bool hasArguments = false; | |
648 for (DartType argument in arguments) { | |
649 if (!argument.isDynamic) { | |
650 hasArguments = true; | |
651 break; | |
652 } | |
653 } | |
654 // append type arguments | |
655 if (hasArguments) { | |
656 sb.write("<"); | |
657 for (int i = 0; i < arguments.length; i++) { | |
658 DartType argument = arguments[i]; | |
659 if (i != 0) { | |
660 sb.write(", "); | |
661 } | |
662 sb.write(getTypeSource(argument)); | |
663 } | |
664 sb.write(">"); | |
665 } | |
666 } | |
667 // done | |
668 return sb.toString(); | |
669 } | |
670 | |
671 /** | |
672 * Indents given source left or right. | |
673 */ | |
674 String indentSourceLeftRight(String source, bool right) { | |
675 StringBuffer sb = new StringBuffer(); | |
676 String indent = getIndent(1); | |
677 String eol = endOfLine; | |
678 List<String> lines = source.split(eol); | |
679 for (int i = 0; i < lines.length; i++) { | |
680 String line = lines[i]; | |
681 // last line, stop if empty | |
682 if (i == lines.length - 1 && isEmpty(line)) { | |
683 break; | |
684 } | |
685 // update line | |
686 if (right) { | |
687 line = "${indent}${line}"; | |
688 } else { | |
689 line = removeStart(line, indent); | |
690 } | |
691 // append line | |
692 sb.write(line); | |
693 sb.write(eol); | |
694 } | |
695 return sb.toString(); | |
696 } | |
697 | |
698 /** | |
699 * @return the source of the inverted condition for the given logical expressi
on. | |
700 */ | |
701 String invertCondition(Expression expression) => | |
702 _invertCondition0(expression)._source; | |
703 | |
704 /** | |
705 * Returns the source with indentation changed from [oldIndent] to | |
706 * [newIndent], keeping indentation of lines relative to each other. | |
707 */ | |
708 String replaceSourceIndent(String source, String oldIndent, | |
709 String newIndent) { | |
710 // prepare STRING token ranges | |
711 List<SourceRange> lineRanges = []; | |
712 { | |
713 var token = unit.beginToken; | |
714 while (token != null && token.type != TokenType.EOF) { | |
715 if (token.type == TokenType.STRING) { | |
716 lineRanges.add(rangeToken(token)); | |
717 } | |
718 token = token.next; | |
719 } | |
720 } | |
721 // re-indent lines | |
722 StringBuffer sb = new StringBuffer(); | |
723 String eol = endOfLine; | |
724 List<String> lines = source.split(eol); | |
725 int lineOffset = 0; | |
726 for (int i = 0; i < lines.length; i++) { | |
727 String line = lines[i]; | |
728 // last line, stop if empty | |
729 if (i == lines.length - 1 && isEmpty(line)) { | |
730 break; | |
731 } | |
732 // check if "offset" is in one of the String ranges | |
733 bool inString = false; | |
734 for (SourceRange lineRange in lineRanges) { | |
735 if (lineOffset > lineRange.offset && lineOffset < lineRange.end) { | |
736 inString = true; | |
737 } | |
738 if (lineOffset > lineRange.end) { | |
739 break; | |
740 } | |
741 } | |
742 lineOffset += line.length + eol.length; | |
743 // update line indent | |
744 if (!inString) { | |
745 line = "${newIndent}${removeStart(line, oldIndent)}"; | |
746 } | |
747 // append line | |
748 sb.write(line); | |
749 sb.write(eol); | |
750 } | |
751 return sb.toString(); | |
752 } | |
753 | |
754 /** | |
755 * Returns the source of the given [SourceRange] with indentation changed | |
756 * from [oldIndent] to [newIndent], keeping indentation of lines relative | |
757 * to each other. | |
758 */ | |
759 String replaceSourceRangeIndent(SourceRange range, String oldIndent, | |
760 String newIndent) { | |
761 String oldSource = getRangeText(range); | |
762 return replaceSourceIndent(oldSource, oldIndent, newIndent); | |
763 } | |
764 | |
765 /** | |
766 * @return the [ImportElement] used to import given [Element] into [library]. | |
767 * May be `null` if was not imported, i.e. declared in the same librar
y. | |
768 */ | |
769 ImportElement _getImportElement(Element element) { | |
770 for (ImportElement imp in _library.imports) { | |
771 Map<String, Element> definedNames = getImportNamespace(imp); | |
772 if (definedNames.containsValue(element)) { | |
773 return imp; | |
774 } | |
775 } | |
776 return null; | |
777 } | |
778 | |
779 /** | |
780 * @return the [InvertedCondition] for the given logical expression. | |
781 */ | |
782 _InvertedCondition _invertCondition0(Expression expression) { | |
783 if (expression is BooleanLiteral) { | |
784 BooleanLiteral literal = expression; | |
785 if (literal.value) { | |
786 return _InvertedCondition._simple("false"); | |
787 } else { | |
788 return _InvertedCondition._simple("true"); | |
789 } | |
790 } | |
791 if (expression is BinaryExpression) { | |
792 BinaryExpression binary = expression; | |
793 TokenType operator = binary.operator.type; | |
794 Expression le = binary.leftOperand; | |
795 Expression re = binary.rightOperand; | |
796 _InvertedCondition ls = _invertCondition0(le); | |
797 _InvertedCondition rs = _invertCondition0(re); | |
798 if (operator == TokenType.LT) { | |
799 return _InvertedCondition._binary2(ls, " >= ", rs); | |
800 } | |
801 if (operator == TokenType.GT) { | |
802 return _InvertedCondition._binary2(ls, " <= ", rs); | |
803 } | |
804 if (operator == TokenType.LT_EQ) { | |
805 return _InvertedCondition._binary2(ls, " > ", rs); | |
806 } | |
807 if (operator == TokenType.GT_EQ) { | |
808 return _InvertedCondition._binary2(ls, " < ", rs); | |
809 } | |
810 if (operator == TokenType.EQ_EQ) { | |
811 return _InvertedCondition._binary2(ls, " != ", rs); | |
812 } | |
813 if (operator == TokenType.BANG_EQ) { | |
814 return _InvertedCondition._binary2(ls, " == ", rs); | |
815 } | |
816 if (operator == TokenType.AMPERSAND_AMPERSAND) { | |
817 return _InvertedCondition._binary( | |
818 TokenType.BAR_BAR.precedence, | |
819 ls, | |
820 " || ", | |
821 rs); | |
822 } | |
823 if (operator == TokenType.BAR_BAR) { | |
824 return _InvertedCondition._binary( | |
825 TokenType.AMPERSAND_AMPERSAND.precedence, | |
826 ls, | |
827 " && ", | |
828 rs); | |
829 } | |
830 } | |
831 if (expression is IsExpression) { | |
832 IsExpression isExpression = expression; | |
833 String expressionSource = getNodeText(isExpression.expression); | |
834 String typeSource = getNodeText(isExpression.type); | |
835 if (isExpression.notOperator == null) { | |
836 return _InvertedCondition._simple( | |
837 "${expressionSource} is! ${typeSource}"); | |
838 } else { | |
839 return _InvertedCondition._simple( | |
840 "${expressionSource} is ${typeSource}"); | |
841 } | |
842 } | |
843 if (expression is PrefixExpression) { | |
844 PrefixExpression prefixExpression = expression; | |
845 TokenType operator = prefixExpression.operator.type; | |
846 if (operator == TokenType.BANG) { | |
847 Expression operand = prefixExpression.operand; | |
848 while (operand is ParenthesizedExpression) { | |
849 ParenthesizedExpression pe = operand as ParenthesizedExpression; | |
850 operand = pe.expression; | |
851 } | |
852 return _InvertedCondition._simple(getNodeText(operand)); | |
853 } | |
854 } | |
855 if (expression is ParenthesizedExpression) { | |
856 ParenthesizedExpression pe = expression; | |
857 Expression innerExpresion = pe.expression; | |
858 while (innerExpresion is ParenthesizedExpression) { | |
859 innerExpresion = (innerExpresion as ParenthesizedExpression).expression; | |
860 } | |
861 return _invertCondition0(innerExpresion); | |
862 } | |
863 DartType type = expression.bestType; | |
864 if (type.displayName == "bool") { | |
865 return _InvertedCondition._simple("!${getNodeText(expression)}"); | |
866 } | |
867 return _InvertedCondition._simple(getNodeText(expression)); | |
868 } | |
869 } | |
870 | |
871 | |
872 /** | |
873 * Describes where to insert new directive or top-level declaration. | |
874 */ | |
875 class CorrectionUtils_InsertDesc { | |
876 int offset = 0; | |
877 String prefix = ""; | |
878 String suffix = ""; | |
879 } | |
880 | |
881 | |
882 /** | |
883 * A container with a source and its precedence. | |
884 */ | |
885 class _InvertedCondition { | |
886 final int _precedence; | |
887 | |
888 final String _source; | |
889 | |
890 _InvertedCondition(this._precedence, this._source); | |
891 | |
892 static _InvertedCondition _binary(int precedence, _InvertedCondition left, | |
893 String operation, _InvertedCondition right) { | |
894 String src = | |
895 _parenthesizeIfRequired(left, precedence) + | |
896 operation + | |
897 _parenthesizeIfRequired(right, precedence); | |
898 return new _InvertedCondition(precedence, src); | |
899 } | |
900 | |
901 static _InvertedCondition _binary2(_InvertedCondition left, String operation, | |
902 _InvertedCondition right) { | |
903 // TODO(scheglov) conside merging with "_binary()" after testing | |
904 return new _InvertedCondition( | |
905 1 << 20, | |
906 "${left._source}${operation}${right._source}"); | |
907 } | |
908 | |
909 /** | |
910 * Adds enclosing parenthesis if the precedence of the [_InvertedCondition] if
less than the | |
911 * precedence of the expression we are going it to use in. | |
912 */ | |
913 static String _parenthesizeIfRequired(_InvertedCondition expr, | |
914 int newOperatorPrecedence) { | |
915 if (expr._precedence < newOperatorPrecedence) { | |
916 return "(${expr._source})"; | |
917 } | |
918 return expr._source; | |
919 } | |
920 | |
921 static _InvertedCondition _simple(String source) => | |
922 new _InvertedCondition(2147483647, source); | |
923 } | |
OLD | NEW |