Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(360)

Side by Side Diff: pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart

Issue 2917943002: Postfix completion (Closed)
Patch Set: Make .try work on entire lines Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698