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

Side by Side Diff: pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart

Issue 2824233002: Complete for-statement (Closed)
Patch Set: Replace empty with synthetic Created 3 years, 8 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
« no previous file with comments | « no previous file | pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library services.src.completion.statement; 5 library services.src.completion.statement;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import 'package:analysis_server/plugin/protocol/protocol.dart'; 9 import 'package:analysis_server/plugin/protocol/protocol.dart';
10 import 'package:analysis_server/src/protocol_server.dart' hide Element; 10 import 'package:analysis_server/src/protocol_server.dart' hide Element;
(...skipping 19 matching lines...) Expand all
30 static const NO_COMPLETION = 30 static const NO_COMPLETION =
31 const StatementCompletionKind('No_COMPLETION', 'No completion available'); 31 const StatementCompletionKind('No_COMPLETION', 'No completion available');
32 static const SIMPLE_ENTER = const StatementCompletionKind( 32 static const SIMPLE_ENTER = const StatementCompletionKind(
33 'SIMPLE_ENTER', "Insert a newline at the end of the current line"); 33 'SIMPLE_ENTER', "Insert a newline at the end of the current line");
34 static const SIMPLE_SEMICOLON = const StatementCompletionKind( 34 static const SIMPLE_SEMICOLON = const StatementCompletionKind(
35 'SIMPLE_SEMICOLON', "Add a semicolon and newline"); 35 'SIMPLE_SEMICOLON', "Add a semicolon and newline");
36 static const COMPLETE_DO_STMT = const StatementCompletionKind( 36 static const COMPLETE_DO_STMT = const StatementCompletionKind(
37 'COMPLETE_DO_STMT', "Complete do-statement"); 37 'COMPLETE_DO_STMT', "Complete do-statement");
38 static const COMPLETE_IF_STMT = const StatementCompletionKind( 38 static const COMPLETE_IF_STMT = const StatementCompletionKind(
39 'COMPLETE_IF_STMT', "Complete if-statement"); 39 'COMPLETE_IF_STMT', "Complete if-statement");
40 static const COMPLETE_FOR_STMT = const StatementCompletionKind(
41 'COMPLETE_FOR_STMT', "Complete for-statement");
40 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( 42 static const COMPLETE_WHILE_STMT = const StatementCompletionKind(
41 'COMPLETE_WHILE_STMT', "Complete while-statement"); 43 'COMPLETE_WHILE_STMT', "Complete while-statement");
42 } 44 }
43 45
44 /** 46 /**
45 * A description of a statement completion. 47 * A description of a statement completion.
46 * 48 *
47 * Clients may not extend, implement or mix-in this class. 49 * Clients may not extend, implement or mix-in this class.
48 */ 50 */
49 class StatementCompletion { 51 class StatementCompletion {
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after
172 error.offset <= node.offset + node.length) { 174 error.offset <= node.offset + node.length) {
173 if (error.errorCode is! HintCode) { 175 if (error.errorCode is! HintCode) {
174 errors.add(error); 176 errors.add(error);
175 } 177 }
176 } 178 }
177 } 179 }
178 180
179 // TODO(messick) Consider changing (some of) this to a visitor. 181 // TODO(messick) Consider changing (some of) this to a visitor.
180 if (_complete_ifStatement() || 182 if (_complete_ifStatement() ||
181 _complete_doStatement() || 183 _complete_doStatement() ||
184 _complete_forStatement() ||
185 _complete_forEachStatement() ||
186 _complete_switchStatement() ||
187 _complete_tryStatement() ||
182 _complete_whileStatement() || 188 _complete_whileStatement() ||
183 _complete_simpleSemicolon() || 189 _complete_simpleSemicolon() ||
184 _complete_simpleEnter()) { 190 _complete_simpleEnter()) {
185 return completion; 191 return completion;
186 } 192 }
187 return NO_COMPLETION; 193 return NO_COMPLETION;
188 } 194 }
189 195
190 void _addIndentEdit(SourceRange range, String oldIndent, String newIndent) {
191 SourceEdit edit = utils.createIndentEdit(range, oldIndent, newIndent);
192 doSourceChange_addElementEdit(change, unitElement, edit);
193 }
194
195 void _addInsertEdit(int offset, String text) { 196 void _addInsertEdit(int offset, String text) {
196 SourceEdit edit = new SourceEdit(offset, 0, text); 197 SourceEdit edit = new SourceEdit(offset, 0, text);
197 doSourceChange_addElementEdit(change, unitElement, edit); 198 doSourceChange_addElementEdit(change, unitElement, edit);
198 } 199 }
199 200
200 void _addReplaceEdit(SourceRange range, String text) { 201 void _addReplaceEdit(SourceRange range, String text) {
201 SourceEdit edit = new SourceEdit(range.offset, range.length, text); 202 SourceEdit edit = new SourceEdit(range.offset, range.length, text);
202 doSourceChange_addElementEdit(change, unitElement, edit); 203 doSourceChange_addElementEdit(change, unitElement, edit);
203 } 204 }
204 205
(...skipping 26 matching lines...) Expand all
231 text = text.substring(0, text.length - eol.length); 232 text = text.substring(0, text.length - eol.length);
232 } 233 }
233 return text; 234 return text;
234 } 235 }
235 236
236 bool _complete_doStatement() { 237 bool _complete_doStatement() {
237 if (errors.isEmpty || node is! DoStatement) { 238 if (errors.isEmpty || node is! DoStatement) {
238 return false; 239 return false;
239 } 240 }
240 DoStatement statement = node; 241 DoStatement statement = node;
241 var stmt = new _DoIfWhileStructure(
242 statement.whileKeyword,
243 statement.leftParenthesis,
244 statement.condition,
245 statement.rightParenthesis,
246 null);
247 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); 242 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword);
248 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; 243 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while";
249 int exitDelta = 0; 244 int exitDelta = 0;
250 if (statement.body is EmptyStatement) { 245 if (statement.body is EmptyStatement) {
251 String text = utils.getNodeText(statement.body); 246 String text = utils.getNodeText(statement.body);
252 int delta = 0; 247 int delta = 0;
253 if (text.startsWith(';')) { 248 if (text.startsWith(';')) {
254 delta = 1; 249 delta = 1;
255 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); 250 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), '');
256 if (hasWhileKeyword) { 251 if (hasWhileKeyword) {
257 text = utils.getNodeText(statement); 252 text = utils.getNodeText(statement);
258 if (text.indexOf(new RegExp(r'do\s*;\s*while')) == 0) { 253 if (text.indexOf(new RegExp(r'do\s*;\s*while')) == 0) {
259 int end = text.indexOf('while'); 254 int end = text.indexOf('while');
260 int start = text.indexOf(';') + 1; 255 int start = text.indexOf(';') + 1;
261 delta += end - start - 1; 256 delta += end - start - 1;
262 _addReplaceEdit( 257 _addReplaceEdit(
263 rangeStartLength(start + statement.offset, end - start), ' '); 258 rangeStartLength(start + statement.offset, end - start), ' ');
264 } 259 }
265 } 260 }
266 sb = new SourceBuilder(file, sb.offset + delta); 261 sb = new SourceBuilder(file, sb.offset + delta);
267 sb.append(' '); 262 sb.append(' ');
268 } 263 }
269 _appendEmptyBraces( 264 _appendEmptyBraces(sb,
270 sb, !(hasWhileKeyword && _isEmptyExpression(statement.condition))); 265 !(hasWhileKeyword && _isSyntheticExpression(statement.condition)));
271 if (delta != 0) { 266 if (delta != 0) {
272 exitDelta = sb.length - delta; 267 exitDelta = sb.length - delta;
273 } 268 }
274 } else if (_isEmptyBlock(statement.body)) { 269 } else if (_isEmptyBlock(statement.body)) {
275 sb = new SourceBuilder(sb.file, statement.body.end); 270 sb = new SourceBuilder(sb.file, statement.body.end);
276 } 271 }
277 SourceBuilder sb2; 272 SourceBuilder sb2;
278 if (hasWhileKeyword) { 273 if (hasWhileKeyword) {
274 var stmt = new _KeywordConditionBlockStructure(
275 statement.whileKeyword,
276 statement.leftParenthesis,
277 statement.condition,
278 statement.rightParenthesis,
279 null);
279 sb2 = _complete_keywordCondition(stmt); 280 sb2 = _complete_keywordCondition(stmt);
280 if (sb2.length == 0) { 281 if (sb2.length == 0) {
281 // true if condition is '()' 282 // true if condition is '()'
282 if (exitPosition != null) { 283 if (exitPosition != null) {
283 if (statement.semicolon.lexeme.isEmpty) { 284 if (statement.semicolon.isSynthetic) {
284 _insertBuilder(sb); 285 _insertBuilder(sb);
285 sb = new SourceBuilder(file, exitPosition.offset + 1); 286 sb = new SourceBuilder(file, exitPosition.offset + 1);
286 sb.append(';'); 287 sb.append(';');
287 } 288 }
288 } 289 }
289 } else { 290 } else {
290 if (sb.exitOffset == null && sb2?.exitOffset != null) { 291 if (sb.exitOffset == null && sb2?.exitOffset != null) {
291 _insertBuilder(sb); 292 _insertBuilder(sb);
292 sb = sb2; 293 sb = sb2;
293 sb.append(';'); 294 sb.append(';');
294 } else { 295 } else {
295 sb.append(sb2.toString()); 296 sb.append(sb2.toString());
296 } 297 }
297 } 298 }
298 } else { 299 } else {
299 sb.append(" while ("); 300 sb.append(" while (");
300 sb.setExitOffset(); 301 sb.setExitOffset();
301 sb.append(");"); 302 sb.append(");");
302 } 303 }
303 _insertBuilder(sb); 304 _insertBuilder(sb);
304 if (exitDelta != 0) { 305 if (exitDelta != 0) {
305 exitPosition = 306 exitPosition =
306 new Position(exitPosition.file, exitPosition.offset + exitDelta); 307 new Position(exitPosition.file, exitPosition.offset + exitDelta);
307 } 308 }
308 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); 309 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT);
309 return true; 310 return true;
310 } 311 }
311 312
313 bool _complete_forEachStatement() {
314 // TODO(messick) Implement _complete_forEachStatement
315 return false;
316 }
317
318 bool _complete_forStatement() {
319 if (errors.isEmpty || node is! ForStatement) {
320 return false;
321 }
322 ForStatement forNode = node;
323 SourceBuilder sb;
324 int delta = 0;
325 if (forNode.leftParenthesis.isSynthetic) {
326 if (!forNode.rightParenthesis.isSynthetic) {
327 return false;
328 }
329 // keywordOnly (unit test name suffix that exercises this branch)
330 sb = _sourceBuilderAfterKeyword(forNode.forKeyword);
331 sb.append('(');
332 sb.setExitOffset();
333 sb.append(')');
334 } else {
335 if (!forNode.rightSeparator.isSynthetic) {
336 // Fully-defined init, cond, updaters so nothing more needed here.
337 // emptyParts
338 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1);
339 } else if (!forNode.leftSeparator.isSynthetic) {
340 if (_isSyntheticExpression(forNode.condition)) {
341 exitPosition = _newPosition(forNode.leftSeparator.offset + 1);
342 String text = utils
343 .getNodeText(forNode)
344 .substring(forNode.leftSeparator.offset - forNode.offset);
345 if (text.startsWith(new RegExp(r';\s*\)'))) {
346 // emptyCondition
347 int end = text.indexOf(')');
348 sb = new SourceBuilder(file, forNode.leftSeparator.offset);
349 // TODO(messick) Consider adding two semicolons here.
350 _addReplaceEdit(rangeStartLength(sb.offset, end), '; ');
351 delta = end - '; '.length;
352 } else {
353 // emptyInitializersEmptyCondition
354 exitPosition = _newPosition(forNode.rightParenthesis.offset);
355 sb = new SourceBuilder(file, forNode.rightParenthesis.offset);
356 }
357 } else {
358 // emptyUpdaters
359 exitPosition = _newPosition(forNode.rightSeparator.offset);
360 sb = new SourceBuilder(file, forNode.rightSeparator.offset);
361 _addReplaceEdit(rangeStartLength(sb.offset, 0), '; ');
362 delta = -'; '.length;
363 }
364 } else if (_isSyntheticExpression(forNode.initialization)) {
365 // emptyInitializers
366 exitPosition = _newPosition(forNode.rightParenthesis.offset);
367 sb = new SourceBuilder(file, forNode.rightParenthesis.offset);
368 } else {
369 int start = forNode.condition.offset + forNode.condition.length;
370 String text =
371 utils.getNodeText(forNode).substring(start - forNode.offset);
372 if (text.startsWith(new RegExp(r'\s*\)'))) {
373 // missingLeftSeparator
374 int end = text.indexOf(')');
375 sb = new SourceBuilder(file, start);
376 _addReplaceEdit(rangeStartLength(start, end), '; ');
377 delta = end - '; '.length;
378 exitPosition = new Position(file, start);
379 } else {
380 // Not possible; any comment following init is attached to init.
381 exitPosition = _newPosition(forNode.rightParenthesis.offset);
382 sb = new SourceBuilder(file, forNode.rightParenthesis.offset);
383 }
384 }
385 }
386 if (forNode.body is EmptyStatement) {
387 // keywordOnly
388 sb.append(' ');
389 _appendEmptyBraces(sb, exitPosition == null);
390 }
391 if (delta != 0 && exitPosition != null) {
392 // missingLeftSeparator
393 exitPosition = new Position(file, exitPosition.offset - delta);
394 }
395 _insertBuilder(sb);
396 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT);
397 return true;
398 }
399
312 bool _complete_ifOrWhileStatement( 400 bool _complete_ifOrWhileStatement(
313 _DoIfWhileStructure statement, StatementCompletionKind kind) { 401 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) {
314 SourceBuilder sb = _complete_keywordCondition(statement); 402 SourceBuilder sb = _complete_keywordCondition(statement);
315 if (statement.block is EmptyStatement) { 403 if (statement.block is EmptyStatement) {
316 sb.append(' '); 404 sb.append(' ');
317 _appendEmptyBraces(sb, exitPosition == null); 405 _appendEmptyBraces(sb, exitPosition == null);
318 } 406 }
319 _insertBuilder(sb); 407 _insertBuilder(sb);
320 _setCompletion(kind); 408 _setCompletion(kind);
321 return true; 409 return true;
322 } 410 }
323 411
324 bool _complete_ifStatement() { 412 bool _complete_ifStatement() {
325 if (errors.isEmpty || node is! IfStatement) { 413 if (errors.isEmpty || node is! IfStatement) {
326 return false; 414 return false;
327 } 415 }
328 IfStatement ifNode = node; 416 IfStatement ifNode = node;
329 if (ifNode != null) { 417 if (ifNode != null) {
330 if (ifNode.elseKeyword != null) { 418 if (ifNode.elseKeyword != null) {
331 return false; 419 return false;
332 } 420 }
333 var stmt = new _DoIfWhileStructure( 421 var stmt = new _KeywordConditionBlockStructure(
334 ifNode.ifKeyword, 422 ifNode.ifKeyword,
335 ifNode.leftParenthesis, 423 ifNode.leftParenthesis,
336 ifNode.condition, 424 ifNode.condition,
337 ifNode.rightParenthesis, 425 ifNode.rightParenthesis,
338 ifNode.thenStatement); 426 ifNode.thenStatement);
339 return _complete_ifOrWhileStatement( 427 return _complete_ifOrWhileStatement(
340 stmt, DartStatementCompletion.COMPLETE_IF_STMT); 428 stmt, DartStatementCompletion.COMPLETE_IF_STMT);
341 } 429 }
342 return false; 430 return false;
343 } 431 }
344 432
345 SourceBuilder _complete_keywordCondition(_DoIfWhileStructure statement) { 433 SourceBuilder _complete_keywordCondition(
434 _KeywordConditionBlockStructure statement) {
346 SourceBuilder sb; 435 SourceBuilder sb;
347 String text = _baseNodeText(node); 436 if (statement.leftParenthesis.isSynthetic) {
348 if (statement.leftParenthesis.lexeme.isEmpty) { 437 if (!statement.rightParenthesis.isSynthetic) {
349 if (!statement.rightParenthesis.lexeme.isEmpty) {
350 // Quite unlikely to see this so don't try to fix it. 438 // Quite unlikely to see this so don't try to fix it.
351 return null; 439 return null;
352 } 440 }
353 sb = _sourceBuilderAfterKeyword(statement.keyword); 441 sb = _sourceBuilderAfterKeyword(statement.keyword);
354 sb.append('('); 442 sb.append('(');
355 sb.setExitOffset(); 443 sb.setExitOffset();
356 sb.append(')'); 444 sb.append(')');
357 } else { 445 } else {
358 if (_isEmptyExpression(statement.condition)) { 446 if (_isSyntheticExpression(statement.condition)) {
359 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); 447 exitPosition = _newPosition(statement.leftParenthesis.offset + 1);
360 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); 448 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1);
361 } else { 449 } else {
362 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); 450 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1);
363 } 451 }
364 } 452 }
365 return sb; 453 return sb;
366 } 454 }
367 455
368 bool _complete_simpleEnter() { 456 bool _complete_simpleEnter() {
(...skipping 18 matching lines...) Expand all
387 if (error != null) { 475 if (error != null) {
388 int insertOffset = error.offset + error.length; 476 int insertOffset = error.offset + error.length;
389 _addInsertEdit(insertOffset, ';'); 477 _addInsertEdit(insertOffset, ';');
390 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; 478 int offset = _appendNewlinePlusIndent() + 1 /* ';' */;
391 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); 479 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset);
392 return true; 480 return true;
393 } 481 }
394 return false; 482 return false;
395 } 483 }
396 484
485 bool _complete_switchStatement() {
486 // TODO(messick) Implement _complete_switchStatement
487 return false;
488 }
489
490 bool _complete_tryStatement() {
491 // TODO(messick) Implement _complete_tryStatement
492 return false;
493 }
494
397 bool _complete_whileStatement() { 495 bool _complete_whileStatement() {
398 if (errors.isEmpty || node is! WhileStatement) { 496 if (errors.isEmpty || node is! WhileStatement) {
399 return false; 497 return false;
400 } 498 }
401 WhileStatement whileNode = node; 499 WhileStatement whileNode = node;
402 if (whileNode != null) { 500 if (whileNode != null) {
403 var stmt = new _DoIfWhileStructure( 501 var stmt = new _KeywordConditionBlockStructure(
404 whileNode.whileKeyword, 502 whileNode.whileKeyword,
405 whileNode.leftParenthesis, 503 whileNode.leftParenthesis,
406 whileNode.condition, 504 whileNode.condition,
407 whileNode.rightParenthesis, 505 whileNode.rightParenthesis,
408 whileNode.body); 506 whileNode.body);
409 return _complete_ifOrWhileStatement( 507 return _complete_ifOrWhileStatement(
410 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); 508 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT);
411 } 509 }
412 return false; 510 return false;
413 } 511 }
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
455 if (exitOffset != null) { 553 if (exitOffset != null) {
456 exitPosition = _newPosition(exitOffset); 554 exitPosition = _newPosition(exitOffset);
457 } 555 }
458 } 556 }
459 } 557 }
460 558
461 bool _isEmptyBlock(AstNode stmt) { 559 bool _isEmptyBlock(AstNode stmt) {
462 return stmt is Block && stmt.statements.isEmpty; 560 return stmt is Block && stmt.statements.isEmpty;
463 } 561 }
464 562
465 bool _isEmptyExpression(Expression expr) { 563 bool _isSyntheticExpression(Expression expr) {
466 if (expr is! SimpleIdentifier) { 564 return expr is SimpleIdentifier && expr.isSynthetic;
467 return false;
468 }
469 SimpleIdentifier id = expr as SimpleIdentifier;
470 return id.length == 0;
471 } 565 }
472 566
473 bool _isEmptyStatement(AstNode stmt) { 567 bool _isEmptyStatement(AstNode stmt) {
474 return stmt is EmptyStatement || _isEmptyBlock(stmt); 568 return stmt is EmptyStatement || _isEmptyBlock(stmt);
475 } 569 }
476 570
477 Position _newPosition(int offset) { 571 Position _newPosition(int offset) {
478 return new Position(file, offset); 572 return new Position(file, offset);
479 } 573 }
480 574
(...skipping 21 matching lines...) Expand all
502 sb = new SourceBuilder(file, keyword.offset + len); 596 sb = new SourceBuilder(file, keyword.offset + len);
503 sb.append(' '); 597 sb.append(' ');
504 } else { 598 } else {
505 sb = new SourceBuilder(file, keyword.offset + len + 1); 599 sb = new SourceBuilder(file, keyword.offset + len + 1);
506 } 600 }
507 return sb; 601 return sb;
508 } 602 }
509 } 603 }
510 604
511 // Encapsulate common structure of if-statement and while-statement. 605 // Encapsulate common structure of if-statement and while-statement.
512 class _DoIfWhileStructure { 606 class _KeywordConditionBlockStructure {
513 final Token keyword; 607 final Token keyword;
514 final Token leftParenthesis, rightParenthesis; 608 final Token leftParenthesis, rightParenthesis;
515 final Expression condition; 609 final Expression condition;
516 final Statement block; 610 final Statement block;
517 611
518 _DoIfWhileStructure(this.keyword, this.leftParenthesis, this.condition, 612 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis,
519 this.rightParenthesis, this.block); 613 this.condition, this.rightParenthesis, this.block);
520 614
521 int get offset => keyword.offset; 615 int get offset => keyword.offset;
522 } 616 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698