Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2017, 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 import 'dart:async'; | |
| 6 | |
| 7 import 'package:analysis_server/src/protocol_server.dart' hide Element; | |
| 8 import 'package:analysis_server/src/services/correction/util.dart'; | |
| 9 import 'package:analyzer/dart/ast/ast.dart'; | |
| 10 import 'package:analyzer/dart/element/element.dart'; | |
| 11 import 'package:analyzer/dart/element/type.dart'; | |
| 12 import 'package:analyzer/error/error.dart' as engine; | |
| 13 import 'package:analyzer/src/dart/analysis/driver.dart'; | |
| 14 import 'package:analyzer/src/dart/ast/utilities.dart'; | |
| 15 import 'package:analyzer/src/generated/engine.dart'; | |
| 16 import 'package:analyzer/src/generated/java_core.dart'; | |
| 17 import 'package:analyzer/src/generated/resolver.dart'; | |
| 18 import 'package:analyzer/src/generated/source.dart'; | |
| 19 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dar t'; | |
| 20 import 'package:analyzer_plugin/utilities/range_factory.dart'; | |
| 21 | |
| 22 /** | |
| 23 * An instance of [RangeFactory] made available for convenience. | |
| 24 */ | |
| 25 final RangeFactory range = new RangeFactory(); | |
|
Brian Wilkerson
2017/06/22 14:54:40
This is already provided by 'package:analyzer_plug
messick
2017/06/22 16:12:32
Done.
| |
| 26 | |
| 27 /** | |
| 28 * An enumeration of possible postfix completion kinds. | |
| 29 */ | |
| 30 class DartPostfixCompletion { | |
| 31 static const NO_TEMPLATE = | |
| 32 const PostfixCompletionKind('', 'no change', null, null); | |
| 33 static const ALL_TEMPLATES = const [ | |
| 34 const PostfixCompletionKind("assert", "expr.assert -> assert(expr);", | |
| 35 isAssertContext, expandAssert), | |
| 36 const PostfixCompletionKind( | |
| 37 "fori", | |
|
Brian Wilkerson
2017/06/22 14:54:40
I initially assumed the "i" was for "in", but it a
messick
2017/06/22 16:12:32
Yes, the Java version uses 'for' and 'fori' the sa
| |
| 38 "limit.fori -> for(var i = 0; i < limit; i++) {}", | |
| 39 isIntContext, | |
| 40 expandFori), | |
| 41 const PostfixCompletionKind( | |
| 42 "for", | |
| 43 "values.for -> for(var value in values) {}", | |
| 44 isIterableContext, | |
| 45 expandFor), | |
| 46 const PostfixCompletionKind( | |
| 47 "iter", | |
| 48 "values.iter -> for(var value in values) {}", | |
| 49 isIterableContext, | |
| 50 expandFor), | |
| 51 const PostfixCompletionKind( | |
| 52 "not", "bool.not -> !bool", isBoolContext, expandNegate), | |
| 53 const PostfixCompletionKind( | |
| 54 "!", "bool! -> !bool", isBoolContext, expandNegate), | |
| 55 const PostfixCompletionKind( | |
| 56 "else", "bool.else -> if (!bool) {}", isBoolContext, expandElse), | |
| 57 const PostfixCompletionKind( | |
| 58 "if", "bool.if -> if (bool) {}", isBoolContext, expandIf), | |
| 59 const PostfixCompletionKind("nn", "expr.nn -> if (expr != null) {}", | |
| 60 isObjectContext, expandNotNull), | |
| 61 const PostfixCompletionKind("notnull", | |
| 62 "expr.notnull -> if (expr != null) {}", isObjectContext, expandNotNull), | |
| 63 const PostfixCompletionKind("null", "expr.null -> if (expr == null) {}", | |
| 64 isObjectContext, expandNull), | |
| 65 const PostfixCompletionKind( | |
| 66 "par", "expr.par -> (expr)", isObjectContext, expandParen), | |
| 67 const PostfixCompletionKind( | |
| 68 "return", "expr.return -> return expr", isObjectContext, expandReturn), | |
| 69 const PostfixCompletionKind("switch", "expr.switch -> switch (expr) {}", | |
| 70 isSwitchContext, expandSwitch), | |
| 71 const PostfixCompletionKind("try", "stmt.try -> try {stmt} catch (e,s) {}", | |
| 72 isStatementContext, expandTry), | |
| 73 const PostfixCompletionKind( | |
| 74 "tryon", | |
|
Brian Wilkerson
2017/06/22 14:54:40
Should we add a try-finally kind (in a later CL)?
messick
2017/06/22 16:12:32
Possibly; I'm undecided. Java has no generators fo
| |
| 75 "stmt.try -> try {stmt} on Exception catch (e,s) {}", | |
| 76 isStatementContext, | |
| 77 expandTryon), | |
| 78 const PostfixCompletionKind( | |
| 79 "while", "expr.while -> while (expr) {}", isBoolContext, expandWhile), | |
| 80 ]; | |
| 81 | |
| 82 static Future<PostfixCompletion> expandAssert( | |
| 83 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 84 return processor.expand(kind, processor.findAssertExpression, (expr) { | |
| 85 return "assert(${processor.utils.getNodeText(expr)});"; | |
| 86 }, withBraces: false); | |
| 87 } | |
| 88 | |
| 89 static Future<PostfixCompletion> expandElse( | |
| 90 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 91 return processor.expand(kind, processor.findBoolExpression, | |
| 92 (expr) => "if (${processor.makeNegatedBoolExpr(expr)})"); | |
| 93 } | |
| 94 | |
| 95 static Future<PostfixCompletion> expandFor( | |
| 96 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 97 return processor.expand(kind, processor.findIterableExpression, (expr) { | |
| 98 String value = processor.newVariable("value"); | |
| 99 return "for (var $value in ${processor.utils.getNodeText(expr)})"; | |
| 100 }); | |
| 101 } | |
| 102 | |
| 103 static Future<PostfixCompletion> expandFori( | |
| 104 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 105 return processor.expand(kind, processor.findIntExpression, (expr) { | |
| 106 String index = processor.newVariable("i"); | |
| 107 return "for (int $index = 0; $index < ${processor.utils.getNodeText( | |
| 108 expr)}; $index++)"; | |
| 109 }); | |
| 110 } | |
| 111 | |
| 112 static Future<PostfixCompletion> expandIf( | |
| 113 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 114 return processor.expand(kind, processor.findBoolExpression, | |
| 115 (expr) => "if (${processor.utils.getNodeText(expr)})"); | |
| 116 } | |
| 117 | |
| 118 static Future<PostfixCompletion> expandNegate( | |
| 119 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 120 return processor.expand(kind, processor.findBoolExpression, | |
| 121 (expr) => processor.makeNegatedBoolExpr(expr), | |
| 122 withBraces: false); | |
| 123 } | |
| 124 | |
| 125 static Future<PostfixCompletion> expandNotNull( | |
| 126 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 127 return processor.expand(kind, processor.findObjectExpression, (expr) { | |
| 128 return expr is NullLiteral | |
| 129 ? "if (false)" | |
| 130 : "if (${processor.utils.getNodeText(expr)} != null)"; | |
| 131 }); | |
| 132 } | |
| 133 | |
| 134 static Future<PostfixCompletion> expandNull( | |
| 135 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 136 return processor.expand(kind, processor.findObjectExpression, (expr) { | |
| 137 return expr is NullLiteral | |
| 138 ? "if (true)" | |
| 139 : "if (${processor.utils.getNodeText(expr)} == null)"; | |
| 140 }); | |
| 141 } | |
| 142 | |
| 143 static Future<PostfixCompletion> expandParen( | |
| 144 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 145 return processor.expand(kind, processor.findObjectExpression, | |
| 146 (expr) => "(${processor.utils.getNodeText(expr)})", | |
| 147 withBraces: false); | |
| 148 } | |
| 149 | |
| 150 static Future<PostfixCompletion> expandReturn( | |
| 151 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 152 return processor.expand(kind, processor.findObjectExpression, | |
| 153 (expr) => "return ${processor.utils.getNodeText(expr)};", | |
| 154 withBraces: false); | |
| 155 } | |
| 156 | |
| 157 static Future<PostfixCompletion> expandSwitch( | |
| 158 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 159 return processor.expand(kind, processor.findObjectExpression, | |
| 160 (expr) => "switch (${processor.utils.getNodeText(expr)})"); | |
| 161 } | |
| 162 | |
| 163 static Future<PostfixCompletion> expandTry( | |
| 164 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 165 return processor.expandTry(kind, processor.findStatement, withOn: false); | |
| 166 } | |
| 167 | |
| 168 static Future<PostfixCompletion> expandTryon( | |
| 169 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 170 return processor.expandTry(kind, processor.findStatement, withOn: true); | |
| 171 } | |
| 172 | |
| 173 static Future<PostfixCompletion> expandWhile( | |
| 174 PostfixCompletionProcessor processor, PostfixCompletionKind kind) async { | |
| 175 return processor.expand(kind, processor.findBoolExpression, | |
| 176 (expr) => "while (${processor.utils.getNodeText(expr)})"); | |
| 177 } | |
| 178 | |
| 179 static PostfixCompletionKind forKey(String key) => | |
| 180 ALL_TEMPLATES.firstWhere((kind) => kind.key == key, orElse: () => null); | |
| 181 | |
| 182 static bool isAssertContext(PostfixCompletionProcessor processor) { | |
| 183 return processor.findAssertExpression() != null; | |
| 184 } | |
| 185 | |
| 186 static bool isBoolContext(PostfixCompletionProcessor processor) { | |
| 187 return processor.findBoolExpression() != null; | |
| 188 } | |
| 189 | |
| 190 static bool isIntContext(PostfixCompletionProcessor processor) { | |
| 191 return processor.findIntExpression() != null; | |
| 192 } | |
| 193 | |
| 194 static bool isIterableContext(PostfixCompletionProcessor processor) { | |
| 195 return processor.findIterableExpression() != null; | |
| 196 } | |
| 197 | |
| 198 static bool isObjectContext(PostfixCompletionProcessor processor) { | |
| 199 return processor.findObjectExpression() != null; | |
| 200 } | |
| 201 | |
| 202 static bool isStatementContext(PostfixCompletionProcessor processor) { | |
| 203 return processor.findStatement() != null; | |
| 204 } | |
| 205 | |
| 206 static bool isSwitchContext(PostfixCompletionProcessor processor) { | |
| 207 return processor.findObjectExpression() != null; | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 /** | |
| 212 * A description of a postfix completion. | |
| 213 * | |
| 214 * Clients may not extend, implement or mix-in this class. | |
| 215 */ | |
| 216 class PostfixCompletion { | |
| 217 /** | |
| 218 * A description of the assist being proposed. | |
| 219 */ | |
| 220 final PostfixCompletionKind kind; | |
| 221 | |
| 222 /** | |
| 223 * The change to be made in order to apply the assist. | |
| 224 */ | |
| 225 final SourceChange change; | |
| 226 | |
| 227 /** | |
| 228 * Initialize a newly created completion to have the given [kind] and [change] . | |
| 229 */ | |
| 230 PostfixCompletion(this.kind, this.change); | |
| 231 } | |
| 232 | |
| 233 /** | |
| 234 * The context for computing a postfix completion. | |
| 235 */ | |
| 236 class PostfixCompletionContext { | |
| 237 final String file; | |
| 238 final LineInfo lineInfo; | |
| 239 final int selectionOffset; | |
| 240 final String key; | |
| 241 final AnalysisDriver driver; | |
| 242 final CompilationUnit unit; | |
| 243 final CompilationUnitElement unitElement; | |
| 244 final List<engine.AnalysisError> errors; | |
| 245 | |
| 246 PostfixCompletionContext(this.file, this.lineInfo, this.selectionOffset, | |
| 247 this.key, this.driver, this.unit, this.unitElement, this.errors) { | |
| 248 if (unitElement.context == null) { | |
| 249 throw new Error(); // not reached | |
| 250 } | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 class PostfixCompletionKind { | |
|
Brian Wilkerson
2017/06/22 14:54:40
Comment?
messick
2017/06/22 16:12:32
Done.
| |
| 255 final String name, example; | |
| 256 final Function selector; | |
| 257 final Function computer; | |
| 258 | |
| 259 const PostfixCompletionKind( | |
| 260 this.name, this.example, this.selector, this.computer); | |
| 261 | |
| 262 String get key => name == '!' ? name : '.$name'; | |
| 263 | |
| 264 String get message => 'Expand $key'; | |
| 265 | |
| 266 @override | |
| 267 String toString() => name; | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * The computer for Dart postfix completions. | |
| 272 */ | |
| 273 class PostfixCompletionProcessor { | |
| 274 static final NO_COMPLETION = new PostfixCompletion( | |
| 275 DartPostfixCompletion.NO_TEMPLATE, new SourceChange("", edits: [])); | |
| 276 | |
| 277 final PostfixCompletionContext completionContext; | |
| 278 final AnalysisContext analysisContext; | |
| 279 final CorrectionUtils utils; | |
| 280 int fileStamp; | |
| 281 AstNode node; | |
| 282 PostfixCompletion completion; | |
| 283 SourceChange change = new SourceChange('postfix-completion'); | |
| 284 final Map<String, LinkedEditGroup> linkedPositionGroups = | |
| 285 <String, LinkedEditGroup>{}; | |
| 286 Position exitPosition = null; | |
| 287 TypeProvider _typeProvider; | |
| 288 | |
| 289 PostfixCompletionProcessor(this.completionContext) | |
| 290 : analysisContext = completionContext.unitElement.context, | |
| 291 utils = new CorrectionUtils(completionContext.unit) { | |
| 292 fileStamp = _modificationStamp(file); | |
| 293 } | |
| 294 | |
| 295 AnalysisDriver get driver => completionContext.driver; | |
| 296 | |
| 297 String get eol => utils.endOfLine; | |
| 298 | |
| 299 String get file => completionContext.file; | |
| 300 | |
| 301 String get key => completionContext.key; | |
| 302 | |
| 303 LineInfo get lineInfo => completionContext.lineInfo; | |
| 304 | |
| 305 int get requestLine => lineInfo.getLocation(selectionOffset).lineNumber; | |
| 306 | |
| 307 int get selectionOffset => completionContext.selectionOffset; | |
| 308 | |
| 309 Source get source => completionContext.unitElement.source; | |
| 310 | |
| 311 TypeProvider get typeProvider { | |
| 312 return _typeProvider ??= unitElement.context.typeProvider; | |
| 313 } | |
| 314 | |
| 315 CompilationUnit get unit => completionContext.unit; | |
| 316 | |
| 317 CompilationUnitElement get unitElement => completionContext.unitElement; | |
| 318 | |
| 319 Future<PostfixCompletion> compute() async { | |
| 320 // If the source was changed between the constructor and running | |
| 321 // this asynchronous method, it is not safe to use the unit. | |
| 322 if (_modificationStamp(file) != fileStamp) { | |
| 323 return NO_COMPLETION; | |
| 324 } | |
| 325 node = _selectedNode(); | |
| 326 if (node == null) { | |
| 327 return NO_COMPLETION; | |
| 328 } | |
| 329 PostfixCompletionKind completer = DartPostfixCompletion.forKey(key); | |
| 330 return completer?.computer(this, completer) ?? NO_COMPLETION; | |
| 331 } | |
| 332 | |
| 333 Future<PostfixCompletion> expand( | |
| 334 PostfixCompletionKind kind, Function contexter, Function sourcer, | |
| 335 {bool withBraces: true}) async { | |
| 336 AstNode expr = contexter(); | |
| 337 if (expr == null) { | |
| 338 return null; | |
| 339 } | |
| 340 | |
| 341 DartChangeBuilder changeBuilder = new DartChangeBuilder(driver); | |
| 342 await changeBuilder.addFileEdit(file, fileStamp, | |
| 343 (DartFileEditBuilder builder) { | |
| 344 builder.addReplacement(range.node(expr), (DartEditBuilder builder) { | |
| 345 String newSrc = sourcer(expr); | |
| 346 if (newSrc == null) { | |
| 347 return null; | |
| 348 } | |
| 349 builder.write(newSrc); | |
| 350 if (withBraces) { | |
| 351 builder.write(" {"); | |
| 352 builder.write(eol); | |
| 353 String indent = utils.getNodePrefix(expr); | |
| 354 builder.write(indent); | |
| 355 builder.write(utils.getIndent(1)); | |
| 356 builder.selectHere(); | |
| 357 builder.write(eol); | |
| 358 builder.write(indent); | |
| 359 builder.write("}"); | |
| 360 } else { | |
| 361 builder.selectHere(); | |
| 362 } | |
| 363 }); | |
| 364 }); | |
| 365 _setCompletionFromBuilder(changeBuilder, kind); | |
| 366 return completion; | |
| 367 } | |
| 368 | |
| 369 Future<PostfixCompletion> expandTry( | |
| 370 PostfixCompletionKind kind, Function contexter, | |
| 371 {bool withOn: false}) async { | |
| 372 AstNode stmt = contexter(); | |
| 373 if (stmt == null) { | |
| 374 return null; | |
| 375 } | |
| 376 | |
| 377 DartChangeBuilder changeBuilder = new DartChangeBuilder(driver); | |
| 378 await changeBuilder.addFileEdit(file, fileStamp, | |
| 379 (DartFileEditBuilder builder) { | |
| 380 // Embed the full line(s) of the statement in the try block. | |
| 381 var startLine = lineInfo.getLocation(stmt.offset).lineNumber - 1; | |
| 382 var endLine = lineInfo.getLocation(stmt.end).lineNumber - 1; | |
| 383 if (stmt is ExpressionStatement && !stmt.semicolon.isSynthetic) { | |
| 384 endLine += 1; | |
| 385 } | |
| 386 var startOffset = lineInfo.getOffsetOfLine(startLine); | |
| 387 var endOffset = lineInfo.getOffsetOfLine(endLine); | |
| 388 var src = utils.getText(startOffset, endOffset - startOffset); | |
| 389 String indent = utils.getLinePrefix(stmt.offset); | |
| 390 builder.addReplacement(range.startOffsetEndOffset(startOffset, endOffset), | |
| 391 (DartEditBuilder builder) { | |
| 392 builder.write(indent); | |
| 393 builder.write('try {'); | |
| 394 builder.write(eol); | |
| 395 builder.write(src.replaceAll(new RegExp("^$indent", multiLine: true), | |
| 396 "$indent${utils.getIndent(1)}")); | |
| 397 builder.selectHere(); | |
| 398 builder.write(indent); | |
| 399 builder.write('}'); | |
| 400 if (withOn) { | |
| 401 builder.write(' on '); | |
| 402 builder.addSimpleLinkedEdit('NAME', nameOfExceptionThrownBy(stmt)); | |
| 403 } | |
| 404 builder.write(' catch (e, s) {'); | |
| 405 builder.write(eol); | |
| 406 builder.write(indent); | |
| 407 builder.write(utils.getIndent(1)); | |
| 408 builder.write('print(s);'); | |
| 409 builder.write(eol); | |
| 410 builder.write(indent); | |
| 411 builder.write("}"); | |
| 412 builder.write(eol); | |
| 413 }); | |
| 414 }); | |
| 415 _setCompletionFromBuilder(changeBuilder, kind); | |
| 416 return completion; | |
| 417 } | |
| 418 | |
| 419 Expression findAssertExpression() { | |
| 420 if (node is Expression) { | |
| 421 Expression boolExpr = _findOuterExpression(node, typeProvider.boolType); | |
| 422 if (boolExpr == null) { | |
| 423 return null; | |
| 424 } | |
| 425 if (boolExpr.parent is ExpressionFunctionBody && | |
| 426 boolExpr.parent.parent is FunctionExpression) { | |
| 427 FunctionExpression fnExpr = boolExpr.parent.parent; | |
| 428 var type = fnExpr.bestType; | |
| 429 if (type is! FunctionType) { | |
| 430 return boolExpr; | |
| 431 } | |
| 432 FunctionType fnType = type; | |
| 433 if (fnType.returnType == typeProvider.boolType) { | |
| 434 return fnExpr; | |
| 435 } | |
| 436 } | |
| 437 if (boolExpr.bestType == typeProvider.boolType) { | |
| 438 return boolExpr; | |
| 439 } | |
| 440 } | |
| 441 return null; | |
| 442 } | |
| 443 | |
| 444 Expression findBoolExpression() => | |
| 445 _findOuterExpression(node, typeProvider.boolType); | |
| 446 | |
| 447 Expression findIntExpression() => | |
| 448 _findOuterExpression(node, typeProvider.intType); | |
| 449 | |
| 450 Expression findIterableExpression() => | |
| 451 _findOuterExpression(node, typeProvider.iterableType); | |
| 452 | |
| 453 Expression findObjectExpression() => | |
| 454 _findOuterExpression(node, typeProvider.objectType); | |
| 455 | |
| 456 AstNode findStatement() { | |
| 457 var astNode = node; | |
| 458 while (astNode != null) { | |
| 459 if (astNode is Statement && astNode is! Block) { | |
| 460 // Disallow control-flow statements. | |
| 461 if (astNode is DoStatement || | |
| 462 astNode is IfStatement || | |
| 463 astNode is ForEachStatement || | |
| 464 astNode is ForStatement || | |
| 465 astNode is SwitchStatement || | |
| 466 astNode is TryStatement || | |
| 467 astNode is WhileStatement) { | |
| 468 return null; | |
| 469 } | |
| 470 return astNode; | |
| 471 } | |
| 472 astNode = astNode.parent; | |
| 473 } | |
| 474 return null; | |
| 475 } | |
| 476 | |
| 477 Future<bool> isApplicable() async { | |
| 478 if (_modificationStamp(file) != fileStamp) { | |
| 479 return false; | |
| 480 } | |
| 481 node = _selectedNode(); | |
| 482 if (node == null) { | |
| 483 return false; | |
| 484 } | |
| 485 PostfixCompletionKind completer = DartPostfixCompletion.forKey(key); | |
| 486 return completer?.selector(this); | |
| 487 } | |
| 488 | |
| 489 String makeNegatedBoolExpr(Expression expr) { | |
| 490 String originalSrc = utils.getNodeText(expr); | |
| 491 String newSrc = utils.invertCondition(expr); | |
| 492 if (newSrc != originalSrc) { | |
| 493 return newSrc; | |
| 494 } else { | |
| 495 return "!${utils.getNodeText(expr)}"; | |
| 496 } | |
| 497 } | |
| 498 | |
| 499 String nameOfExceptionThrownBy(AstNode astNode) { | |
| 500 if (astNode is ExpressionStatement) { | |
| 501 astNode = (astNode as ExpressionStatement).expression; | |
| 502 } | |
| 503 if (astNode is ThrowExpression) { | |
| 504 ThrowExpression expr = astNode; | |
| 505 var type = expr.expression.bestType; | |
| 506 return type.displayName; | |
| 507 } | |
| 508 return 'Exception'; | |
| 509 } | |
| 510 | |
| 511 String newVariable(String base) { | |
| 512 String name = base; | |
| 513 int i = 1; | |
| 514 Set<String> vars = | |
| 515 utils.findPossibleLocalVariableConflicts(selectionOffset); | |
| 516 while (vars.contains(name)) { | |
| 517 name = "$base${i++}"; | |
| 518 } | |
| 519 return name; | |
| 520 } | |
| 521 | |
| 522 Expression _findOuterExpression(AstNode start, InterfaceType builtInType) { | |
| 523 AstNode parent; | |
| 524 if (start is Expression) { | |
| 525 parent = start; | |
| 526 } else if (start is ArgumentList) { | |
| 527 parent = start.parent; | |
| 528 } | |
| 529 if (parent == null) { | |
| 530 return null; | |
| 531 } | |
| 532 var list = <Expression>[]; | |
| 533 while (parent is Expression) { | |
| 534 list.add(parent); | |
| 535 parent = parent.parent; | |
| 536 } | |
| 537 Expression expr = list.firstWhere((expr) { | |
| 538 DartType type = expr.bestType; | |
| 539 if (type.isSubtypeOf(builtInType)) return true; | |
| 540 Element element = type.element; | |
| 541 if (element is TypeDefiningElement) { | |
| 542 TypeDefiningElement typeDefElem = element; | |
| 543 type = typeDefElem.type; | |
| 544 if (type is ParameterizedType) { | |
| 545 ParameterizedType pType = type; | |
| 546 type = pType.instantiate(new List.filled( | |
| 547 pType.typeParameters.length, typeProvider.dynamicType)); | |
| 548 } | |
| 549 } | |
| 550 return type.isSubtypeOf(builtInType); | |
| 551 }, orElse: () => null); | |
| 552 if (expr is SimpleIdentifier && expr.parent is PropertyAccess) { | |
| 553 expr = expr.parent; | |
| 554 } | |
| 555 if (expr?.parent is CascadeExpression) { | |
| 556 expr = expr.parent; | |
| 557 } | |
| 558 return expr; | |
| 559 } | |
| 560 | |
| 561 int _modificationStamp(String filePath) { | |
| 562 // TODO(brianwilkerson) We have lost the ability for clients to know whether | |
| 563 // it is safe to apply an edit. | |
| 564 return driver.fsState.getFileForPath(filePath).exists ? 0 : -1; | |
| 565 } | |
| 566 | |
| 567 AstNode _selectedNode({int at: null}) => | |
| 568 new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit); | |
| 569 | |
| 570 void _setCompletionFromBuilder( | |
| 571 DartChangeBuilder builder, PostfixCompletionKind kind, | |
| 572 [List args]) { | |
| 573 SourceChange change = builder.sourceChange; | |
| 574 if (change.edits.isEmpty) { | |
| 575 completion = null; | |
| 576 return; | |
| 577 } | |
| 578 change.message = formatList(kind.message, args); | |
| 579 completion = new PostfixCompletion(kind, change); | |
| 580 } | |
| 581 } | |
| OLD | NEW |