Version 3.2.0-139.0.dev
Merge 826e97058889c1324fe5864dbd8f2099a6e805be into dev
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
index ea205a2..b6d0530 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
@@ -47,9 +47,10 @@
/// occurring.
///
/// This is normally the covering node, but if the covering node begins with
- /// an identifier (or keyword) and the [offset] is covered by the identifier,
- /// then we look for the highest node that also begins with the same token and
- /// use the parent of that node.
+ /// an identifier (or keyword) and the [offset] is covered by the identifier
+ /// or keyword, then we look for the highest node that also begins with the
+ /// same token, but that isn't part of a list of nodes, and use the parent of
+ /// that node.
///
/// This allows us more context for completing what the user might be trying
/// to write and also reduces the complexity of the visitor and reduces the
@@ -64,12 +65,15 @@
}
var child = coveringNode;
var parent = child.parent;
- while (parent != null && parent.beginToken == beginToken) {
+ while (parent != null &&
+ parent.beginToken == beginToken &&
+ !(child is! SimpleIdentifier && parent.isChildInList(child))) {
child = parent;
parent = child.parent;
}
// The [child] is now the highest node that starts with the [beginToken].
- if (parent != null) {
+ if (parent != null &&
+ !(child is! SimpleIdentifier && parent.isChildInList(child))) {
return parent;
}
return child;
@@ -103,6 +107,20 @@
}
@override
+ void visitAssertInitializer(AssertInitializer node) {
+ collector.completionLocation = 'ConstructorDeclaration_initializer';
+ keywordHelper.addConstructorInitializerKeywords(
+ node.parent as ConstructorDeclaration);
+ }
+
+ @override
+ void visitAssertStatement(AssertStatement node) {
+ if (offset <= node.assertKeyword.end) {
+ keywordHelper.addKeyword(Keyword.ASSERT);
+ }
+ }
+
+ @override
void visitAssignmentExpression(AssignmentExpression node) {
collector.completionLocation = 'AssignmentExpression_rightHandSide';
_forExpression(node);
@@ -161,6 +179,13 @@
}
@override
+ void visitBreakStatement(BreakStatement node) {
+ if (offset <= node.breakKeyword.end) {
+ keywordHelper.addKeyword(Keyword.BREAK);
+ }
+ }
+
+ @override
void visitCascadeExpression(CascadeExpression node) {
collector.completionLocation = 'CascadeExpression_cascadeSection';
_forExpression(node);
@@ -173,6 +198,24 @@
}
@override
+ void visitCatchClause(CatchClause node) {
+ var onKeyword = node.onKeyword;
+ var catchKeyword = node.catchKeyword;
+ if (onKeyword != null) {
+ if (offset <= onKeyword.end) {
+ keywordHelper.addKeyword(Keyword.ON);
+ } else if (catchKeyword != null && offset < catchKeyword.offset) {
+ _forTypeAnnotation();
+ }
+ }
+ if (catchKeyword != null &&
+ offset >= catchKeyword.offset &&
+ offset <= catchKeyword.end) {
+ keywordHelper.addKeyword(Keyword.CATCH);
+ }
+ }
+
+ @override
void visitClassDeclaration(ClassDeclaration node) {
if (offset < node.classKeyword.offset) {
keywordHelper.addClassModifiers(node);
@@ -234,11 +277,25 @@
}
@override
+ void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
+ collector.completionLocation = 'ConstructorDeclaration_initializer';
+ keywordHelper.addConstructorInitializerKeywords(
+ node.parent as ConstructorDeclaration);
+ }
+
+ @override
void visitConstructorReference(ConstructorReference node) {
_forExpression(node);
}
@override
+ void visitContinueStatement(ContinueStatement node) {
+ if (offset <= node.continueKeyword.end) {
+ keywordHelper.addKeyword(Keyword.CONTINUE);
+ }
+ }
+
+ @override
void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
var name = node.name;
if (name is SyntheticStringToken) {
@@ -246,7 +303,7 @@
}
if (node.keyword != null) {
var type = node.type;
- if (!type.isSimpleIdentifier && name.coversOffset(offset)) {
+ if (!type.isSingleIdentifier && name.coversOffset(offset)) {
// Don't suggest a name for the variable.
return;
}
@@ -269,6 +326,13 @@
}
@override
+ void visitDoStatement(DoStatement node) {
+ if (offset <= node.doKeyword.end) {
+ _forStatement(node);
+ }
+ }
+
+ @override
void visitDoubleLiteral(DoubleLiteral node) {
_visitParentIfAtOrBeforeNode(node);
}
@@ -339,6 +403,20 @@
}
@override
+ void visitExpressionStatement(ExpressionStatement node) {
+ collector.completionLocation = 'ExpressionStatement_expression';
+ if (_forIncompletePreceedingStatement(node)) {
+ if (node.isSingleIdentifier) {
+ var preceedingStatement = node.preceedingStatement;
+ if (preceedingStatement is TryStatement) {
+ return;
+ }
+ }
+ }
+ _forStatement(node);
+ }
+
+ @override
void visitExtensionDeclaration(ExtensionDeclaration node) {
if (offset < node.extensionKeyword.offset) {
// There are no modifiers for extensions.
@@ -376,6 +454,26 @@
}
@override
+ void visitFieldDeclaration(FieldDeclaration node) {
+ _forIncompletePreceedingClassMember(node);
+ var fields = node.fields;
+ var type = fields.type;
+ if (type == null) {
+ var firstField = fields.variables.firstOrNull;
+ if (firstField != null && offset <= firstField.name.end) {
+ keywordHelper.addFieldDeclarationKeywords(node);
+ }
+ } else {
+ if (offset <= type.end) {
+ keywordHelper.addFieldDeclarationKeywords(node);
+ keywordHelper.addKeyword(Keyword.DYNAMIC);
+ keywordHelper.addKeyword(Keyword.VAR);
+ keywordHelper.addKeyword(Keyword.VOID);
+ }
+ }
+ }
+
+ @override
void visitForElement(ForElement node) {
var literal = node.thisOrAncestorOfType<TypedLiteral>();
if (literal is ListLiteral) {
@@ -396,6 +494,13 @@
}
@override
+ void visitForStatement(ForStatement node) {
+ if (offset <= node.forKeyword.end) {
+ _forStatement(node);
+ }
+ }
+
+ @override
void visitFunctionDeclaration(FunctionDeclaration node) {
// If the cursor is at the beginning of the declaration, include the
// compilation unit keywords. See dartbug.com/41039.
@@ -488,7 +593,10 @@
return;
}
var expression = node.expression;
- if (offset > expression.end && offset <= node.rightParenthesis.offset) {
+ if (offset <= node.ifKeyword.end) {
+ _forStatement(node);
+ } else if (offset > expression.end &&
+ offset <= node.rightParenthesis.offset) {
var caseClause = node.caseClause;
if (caseClause == null) {
keywordHelper.addKeyword(Keyword.CASE);
@@ -662,6 +770,12 @@
}
@override
+ void visitNamedType(NamedType node) {
+ keywordHelper.addKeyword(Keyword.DYNAMIC);
+ keywordHelper.addKeyword(Keyword.VOID);
+ }
+
+ @override
void visitNullLiteral(NullLiteral node) {
_forExpression(node);
}
@@ -696,8 +810,12 @@
@override
void visitPatternField(PatternField node) {
- collector.completionLocation = 'PatternField_pattern';
var name = node.name;
+ if (name != null && offset <= name.colon.offset) {
+ // TODO(brianwilkerson) Suggest the properties of the object or fields of
+ // the record.
+ return;
+ }
if (name == null) {
var parent = node.parent;
if (parent is ObjectPattern) {
@@ -709,8 +827,10 @@
// names of any named fields.
}
} else if (name.name == null) {
+ collector.completionLocation = 'PatternField_pattern';
_forVariablePattern();
} else {
+ collector.completionLocation = 'PatternField_pattern';
_forPattern();
}
}
@@ -752,6 +872,20 @@
}
@override
+ void visitRecordPattern(RecordPattern node) {
+ _forExpression(node);
+ keywordHelper.addKeyword(Keyword.DYNAMIC);
+ }
+
+ @override
+ void visitRedirectingConstructorInvocation(
+ RedirectingConstructorInvocation node) {
+ collector.completionLocation = 'ConstructorDeclaration_initializer';
+ keywordHelper.addConstructorInitializerKeywords(
+ node.parent as ConstructorDeclaration);
+ }
+
+ @override
void visitRestPatternElement(RestPatternElement node) {
collector.completionLocation = 'RestPatternElement_pattern';
_forPattern();
@@ -760,7 +894,11 @@
@override
void visitReturnStatement(ReturnStatement node) {
collector.completionLocation = 'ReturnStatement_expression';
- _forExpression(node.expression ?? node);
+ if (offset <= node.returnKeyword.end) {
+ _forStatement(node);
+ } else {
+ _forExpression(node.expression ?? node);
+ }
}
@override
@@ -789,11 +927,32 @@
}
@override
+ void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
+ collector.completionLocation = 'ConstructorDeclaration_initializer';
+ keywordHelper.addConstructorInitializerKeywords(
+ node.parent as ConstructorDeclaration);
+ }
+
+ @override
void visitSwitchCase(SwitchCase node) {
_forStatement(node);
}
@override
+ void visitSwitchDefault(SwitchDefault node) {
+ if (offset <= node.keyword.offset) {
+ keywordHelper.addKeyword(Keyword.CASE);
+ keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':');
+ } else if (offset <= node.keyword.end) {
+ if (node.colon.isSynthetic) {
+ keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':');
+ } else {
+ keywordHelper.addKeyword(Keyword.DEFAULT);
+ }
+ }
+ }
+
+ @override
void visitSwitchExpression(SwitchExpression node) {
if (offset >= node.leftParenthesis.end &&
offset <= node.rightParenthesis.offset) {
@@ -803,7 +962,9 @@
@override
void visitSwitchPatternCase(SwitchPatternCase node) {
- if (offset <= node.colon.offset) {
+ if (offset <= node.keyword.end) {
+ keywordHelper.addKeyword(Keyword.CASE);
+ } else if (offset <= node.colon.offset) {
var previous = node.colon.previous!;
var previousKeyword = previous.keyword;
if (previousKeyword == null) {
@@ -832,7 +993,9 @@
@override
void visitSwitchStatement(SwitchStatement node) {
- if (offset >= node.leftParenthesis.end &&
+ if (offset <= node.switchKeyword.end) {
+ _forStatement(node);
+ } else if (offset >= node.leftParenthesis.end &&
offset <= node.rightParenthesis.offset) {
_forExpression(node);
} else if (offset >= node.leftBracket.end &&
@@ -870,6 +1033,16 @@
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+ if (_forIncompletePreceedingUnitMember(node)) {
+ return;
+ } else if (node.isSingleIdentifier) {
+ // The parser recovers from a simple identifier by assuming that it's a
+ // variable declaration. But a simple identifier could be the start of
+ // any kind of member, so defer to the compilation unit.
+ node.parent?.accept(this);
+ return;
+ }
+
var variableDeclarationList = node.variables;
var variables = variableDeclarationList.variables;
if (variables.isEmpty || offset > variables.first.beginToken.end) {
@@ -892,7 +1065,9 @@
@override
void visitTryStatement(TryStatement node) {
- if (offset >= node.body.end) {
+ if (offset <= node.tryKeyword.end) {
+ _forStatement(node);
+ } else if (offset >= node.body.end) {
var finallyKeyword = node.finallyKeyword;
if (finallyKeyword == null) {
var catchClauses = node.catchClauses;
@@ -922,6 +1097,92 @@
@override
void visitVariableDeclaration(VariableDeclaration node) {
+ // The parser often recovers from incomplete code by creating a variable
+ // declaration. Start by checking to see whether the variable declaration is
+ // likely only there for recovery.
+ var parent = node.parent;
+ if (parent is! VariableDeclarationList) {
+ return;
+ }
+ var grandparent = parent.parent;
+ if (grandparent is FieldDeclaration) {
+ // The order of these conditions is critical. We need to check for an
+ // incomplete preceeding member even when the grandparent isn't a single
+ // identifier, but want to return only if both conditions are true.
+ if (_forIncompletePreceedingClassMember(grandparent) &&
+ grandparent.isSingleIdentifier) {
+ return;
+ }
+ } else if (grandparent is ForPartsWithDeclarations) {
+ if (node.equals == null &&
+ parent.variables.length == 1 &&
+ parent.type is RecordTypeAnnotation) {
+ keywordHelper.addKeyword(Keyword.IN);
+ }
+ } else if (grandparent is TopLevelVariableDeclaration) {
+ // The order of these conditions is critical. We need to check for an
+ // incomplete preceeding member even when the grandparent isn't a single
+ // identifier, but want to return only if both conditions are true.
+ if (_forIncompletePreceedingUnitMember(grandparent) &&
+ grandparent.isSingleIdentifier) {
+ return;
+ }
+ }
+
+ if (offset <= node.name.end) {
+ var container = grandparent?.parent;
+ var keyword = parent.keyword;
+ if (parent.type == null) {
+ if (keyword == null) {
+ keywordHelper.addKeyword(Keyword.CONST);
+ keywordHelper.addKeyword(Keyword.FINAL);
+ keywordHelper.addKeyword(Keyword.VAR);
+ }
+ if (keyword == null || keyword.keyword != Keyword.VAR) {
+ _forTypeAnnotation();
+ }
+ }
+ if (grandparent is FieldDeclaration) {
+ if (grandparent.externalKeyword == null) {
+ keywordHelper.addKeyword(Keyword.EXTERNAL);
+ }
+ if (grandparent.staticKeyword == null) {
+ keywordHelper.addKeyword(Keyword.STATIC);
+ if (container is ClassDeclaration || container is MixinDeclaration) {
+ if (grandparent.abstractKeyword == null) {
+ keywordHelper.addKeyword(Keyword.ABSTRACT);
+ }
+ if (grandparent.covariantKeyword == null) {
+ keywordHelper.addKeyword(Keyword.COVARIANT);
+ }
+ }
+ if (parent.lateKeyword == null &&
+ container is! ExtensionDeclaration) {
+ keywordHelper.addKeyword(Keyword.LATE);
+ }
+ }
+ if (node.name == grandparent.beginToken) {
+ // The parser often recovers from incomplete code by assuming that
+ // the user is typing a field declaration, but it's quite possible
+ // that the user is trying to type a different kind of declaration.
+ keywordHelper.addKeyword(Keyword.CONST);
+ if (container is ClassDeclaration) {
+ keywordHelper.addKeyword(Keyword.FACTORY);
+ }
+ keywordHelper.addKeyword(Keyword.GET);
+ keywordHelper.addKeyword(Keyword.OPERATOR);
+ keywordHelper.addKeyword(Keyword.SET);
+ }
+ } else if (grandparent is TopLevelVariableDeclaration) {
+ if (grandparent.externalKeyword == null) {
+ keywordHelper.addKeyword(Keyword.EXTERNAL);
+ }
+ if (parent.lateKeyword == null && container is! ExtensionDeclaration) {
+ keywordHelper.addKeyword(Keyword.LATE);
+ }
+ }
+ return;
+ }
var equals = node.equals;
if (equals != null && offset >= equals.end) {
collector.completionLocation = 'VariableDeclaration_initializer';
@@ -947,6 +1208,14 @@
}
@override
+ void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+ _forIncompletePreceedingStatement(node);
+ if (offset <= node.beginToken.end) {
+ _forStatement(node);
+ }
+ }
+
+ @override
void visitWhenClause(WhenClause node) {
var whenKeyword = node.whenKeyword;
if (!whenKeyword.isSynthetic && offset > whenKeyword.end) {
@@ -954,6 +1223,13 @@
}
}
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ if (offset <= node.whileKeyword.end) {
+ _forStatement(node);
+ }
+ }
+
/// Add the suggestions that are appropriate when the selection is at the
/// beginning of a class member.
void _forClassMember() {
@@ -1009,7 +1285,107 @@
/// Add the suggestions that are appropriate when the selection is at the
/// beginning of an extension member.
void _forExtensionMember() {
- keywordHelper.addExtensionMemberKeywords();
+ keywordHelper.addExtensionMemberKeywords(isStatic: false);
+ }
+
+ /// Return `true` if the preceeding member is incomplete.
+ ///
+ /// If the completion offset is within the first token of the given [member],
+ /// then check to see whether the preceeding member is incomplete. If it is,
+ /// then the user might be attempting to complete the preceeding member rather
+ /// than attempting to prepend something to the given [member], so add the
+ /// suggestions appropriate for that situation.
+ bool _forIncompletePreceedingClassMember(ClassMember member) {
+ if (offset <= member.beginToken.end) {
+ var preceedingMember = member.preceedingMember;
+ if (preceedingMember == null) {
+ return false;
+ }
+ // Ideally we'd visit the preceeding member in order to avoid
+ // duplicating code, but the offset will be past where the parser
+ // inserted sythetic tokens, preventing that from working.
+ switch (preceedingMember) {
+ // TODO(brianwilkerson) Add support for other kinds of declarations.
+ case MethodDeclaration declaration:
+ if (declaration.body.isFullySynthetic) {
+ keywordHelper.addFunctionBodyModifiers(declaration.body);
+ return true;
+ }
+ case _:
+ }
+ }
+ return false;
+ }
+
+ /// If the completion offset is within the first token of the given
+ /// [statement], then check to see whether the preceeding statement is
+ /// incomplete. If it is, then the user might be attempting to complete the
+ /// preceeding statement rather than attempting to prepend something to the
+ /// given [statement], so add the suggestions appropriate for that situation.
+ bool _forIncompletePreceedingStatement(Statement statement) {
+ if (offset <= statement.beginToken.end) {
+ var preceedingStatement = statement.preceedingStatement;
+ if (preceedingStatement == null) {
+ return false;
+ }
+ // Ideally we'd visit the preceeding member in order to avoid
+ // duplicating code, but the offset will be past where the parser
+ // inserted sythetic tokens, preventing that from working.
+ switch (preceedingStatement) {
+ // TODO(brianwilkerson) Add support for other kinds of declarations.
+ case IfStatement declaration:
+ if (declaration.elseKeyword == null) {
+ keywordHelper.addKeyword(Keyword.ELSE);
+ return true;
+ }
+ case TryStatement declaration:
+ if (declaration.finallyBlock == null) {
+ visitTryStatement(declaration);
+ return true;
+ }
+ case _:
+ }
+ }
+ return false;
+ }
+
+ /// Return `true` if the preceeding member is incomplete.
+ ///
+ /// If the completion offset is within the first token of the given [member],
+ /// then check to see whether the preceeding member is incomplete. If it is,
+ /// then the user might be attempting to complete the preceeding member rather
+ /// than attempting to prepend something to the given [member], so add the
+ /// suggestions appropriate for that situation.
+ bool _forIncompletePreceedingUnitMember(AstNode member) {
+ if (offset <= member.beginToken.end) {
+ var parent = member.parent;
+ if (parent is! CompilationUnit) {
+ return false;
+ }
+ var members = parent.sortedDirectivesAndDeclarations;
+ var index = members.indexOf(member);
+ if (index <= 0) {
+ return false;
+ }
+ var preceedingMember = members[index - 1];
+ // Ideally we'd visit the preceeding member in order to avoid duplicating
+ // code, but in some cases the offset will be past where the parser
+ // inserted sythetic tokens, preventing that from working.
+ switch (preceedingMember) {
+ // TODO(brianwilkerson) Add support for other kinds of declarations.
+ case ClassDeclaration declaration:
+ if (declaration.hasNoBody) {
+ keywordHelper.addClassDeclarationKeywords(declaration);
+ return true;
+ }
+ case ImportDirective directive:
+ if (directive.semicolon.isSynthetic) {
+ visitImportDirective(directive);
+ return true;
+ }
+ }
+ }
+ return false;
}
/// Add the suggestions that are appropriate when the selection is at the
@@ -1032,10 +1408,19 @@
}
/// Add the suggestions that are appropriate when the selection is at the
+ /// beginning of a type annotation.
+ void _forTypeAnnotation() {
+ keywordHelper.addKeyword(Keyword.DYNAMIC);
+ keywordHelper.addKeyword(Keyword.VOID);
+ // TODO(brianwilkerson) Suggest the types available in the current scope.
+ // _addTypesInScope();
+ }
+
+ /// Add the suggestions that are appropriate when the selection is at the
/// beginning of a variable pattern.
void _forVariablePattern() {
keywordHelper.addVariablePatternKeywords();
- // TODO(brianwilkerson) Suggest the types available in the currentscope.
+ // TODO(brianwilkerson) Suggest the types available in the current scope.
// _addTypesInScope();
}
@@ -1048,6 +1433,164 @@
}
}
+extension on AstNode {
+ /// Return `true` if all of the tokens in this node are synthetic.
+ bool get isFullySynthetic {
+ var current = beginToken;
+ var stop = endToken.next!;
+ while (current != stop) {
+ if (!current.isSynthetic) {
+ return false;
+ }
+ current = current.next!;
+ }
+ return true;
+ }
+
+ /// Return `true` if the [child] is an element in a list of children of this
+ /// node.
+ bool isChildInList(AstNode child) {
+ return switch (this) {
+ AdjacentStrings(:var strings) => strings.contains(child),
+ ArgumentList(:var arguments) => arguments.contains(child),
+ AugmentationImportDirective(:var metadata) => metadata.contains(child),
+ Block(:var statements) => statements.contains(child),
+ CascadeExpression(:var cascadeSections) =>
+ cascadeSections.contains(child),
+ ClassDeclaration(:var members, :var metadata) =>
+ members.contains(child) || metadata.contains(child),
+ ClassTypeAlias(:var metadata) => metadata.contains(child),
+ Comment(:var references) => references.contains(child),
+ CompilationUnit(:var directives, :var declarations) =>
+ directives.contains(child) || declarations.contains(child),
+ ConstructorDeclaration(:var initializers, :var metadata) =>
+ initializers.contains(child) || metadata.contains(child),
+ DeclaredIdentifier(:var metadata) => metadata.contains(child),
+ DottedName(:var components) => components.contains(child),
+ EnumConstantDeclaration(:var metadata) => metadata.contains(child),
+ EnumDeclaration(:var constants, :var members, :var metadata) =>
+ constants.contains(child) ||
+ members.contains(child) ||
+ metadata.contains(child),
+ ExportDirective(:var combinators, :var configurations, :var metadata) =>
+ combinators.contains(child) ||
+ configurations.contains(child) ||
+ metadata.contains(child),
+ ExtensionDeclaration(:var members, :var metadata) =>
+ members.contains(child) || metadata.contains(child),
+ ExtensionTypeDeclaration(:var members, :var metadata) =>
+ members.contains(child) || metadata.contains(child),
+ FieldDeclaration(:var metadata) => metadata.contains(child),
+ ForEachPartsWithPattern(:var metadata) => metadata.contains(child),
+ FormalParameter(:var metadata) => metadata.contains(child),
+ FormalParameterList(:var parameters) => parameters.contains(child),
+ ForParts(:var updaters) => updaters.contains(child),
+ FunctionDeclaration(:var metadata) => metadata.contains(child),
+ FunctionTypeAlias(:var metadata) => metadata.contains(child),
+ GenericTypeAlias(:var metadata) => metadata.contains(child),
+ HideCombinator(:var hiddenNames) => hiddenNames.contains(child),
+ ImplementsClause(:var interfaces) => interfaces.contains(child),
+ ImportDirective(:var combinators, :var configurations, :var metadata) =>
+ combinators.contains(child) ||
+ configurations.contains(child) ||
+ metadata.contains(child),
+ LabeledStatement(:var labels) => labels.contains(child),
+ LibraryAugmentationDirective(:var metadata) => metadata.contains(child),
+ LibraryDirective(:var metadata) => metadata.contains(child),
+ LibraryIdentifier(:var components) => components.contains(child),
+ ListLiteral(:var elements) => elements.contains(child),
+ ListPattern(:var elements) => elements.contains(child),
+ MapPattern(:var elements) => elements.contains(child),
+ MethodDeclaration(:var metadata) => metadata.contains(child),
+ MixinDeclaration(:var members, :var metadata) =>
+ members.contains(child) || metadata.contains(child),
+ ObjectPattern(:var fields) => fields.contains(child),
+ OnClause(:var superclassConstraints) =>
+ superclassConstraints.contains(child),
+ PartDirective(:var metadata) => metadata.contains(child),
+ PartOfDirective(:var metadata) => metadata.contains(child),
+ PatternVariableDeclaration(:var metadata) => metadata.contains(child),
+ RecordLiteral(:var fields) => fields.contains(child),
+ RecordPattern(:var fields) => fields.contains(child),
+ RecordTypeAnnotation(:var positionalFields) =>
+ positionalFields.contains(child),
+ RecordTypeAnnotationField(:var metadata) => metadata.contains(child),
+ RecordTypeAnnotationNamedFields(:var fields) => fields.contains(child),
+ RepresentationDeclaration(:var fieldMetadata) =>
+ fieldMetadata.contains(child),
+ SetOrMapLiteral(:var elements) => elements.contains(child),
+ ShowCombinator(:var shownNames) => shownNames.contains(child),
+ SwitchExpression(:var cases) => cases.contains(child),
+ SwitchMember(:var labels, :var statements) =>
+ labels.contains(child) || statements.contains(child),
+ SwitchStatement(:var members) => members.contains(child),
+ TopLevelVariableDeclaration(:var metadata) => metadata.contains(child),
+ TryStatement(:var catchClauses) => catchClauses.contains(child),
+ TypeArgumentList(:var arguments) => arguments.contains(child),
+ TypeParameter(:var metadata) => metadata.contains(child),
+ TypeParameterList(:var typeParameters) => typeParameters.contains(child),
+ VariableDeclaration(:var metadata) => metadata.contains(child),
+ VariableDeclarationList(:var metadata, :var variables) =>
+ metadata.contains(child) || variables.contains(child),
+ WithClause(:var mixinTypes) => mixinTypes.contains(child),
+ AstNode() => false,
+ };
+ }
+}
+
+extension on ClassDeclaration {
+ /// Return `true` if this class declaration doesn't have a body.
+ bool get hasNoBody {
+ return leftBracket.isSynthetic && rightBracket.isSynthetic;
+ }
+}
+
+extension on ClassMember {
+ /// Return the member before `this`, or `null` if this is the first member in
+ /// the body.
+ ClassMember? get preceedingMember {
+ final parent = this.parent;
+ var members = switch (parent) {
+ ClassDeclaration() => parent.members,
+ EnumDeclaration() => parent.members,
+ ExtensionDeclaration() => parent.members,
+ ExtensionTypeDeclaration() => parent.members,
+ MixinDeclaration() => parent.members,
+ _ => null
+ };
+ if (members == null) {
+ return null;
+ }
+ var index = members.indexOf(this);
+ if (index <= 0) {
+ return null;
+ }
+ return members[index - 1];
+ }
+}
+
+extension on ExpressionStatement {
+ /// Return `true` if this statement consists of a single identifier.
+ bool get isSingleIdentifier {
+ var first = beginToken;
+ var last = endToken;
+ return first.isKeywordOrIdentifier &&
+ last.isSynthetic &&
+ first.next == last;
+ }
+}
+
+extension on FieldDeclaration {
+ /// Return `true` if this field declaration consists of a single identifier.
+ bool get isSingleIdentifier {
+ var first = beginToken;
+ var last = endToken;
+ return first.isKeywordOrIdentifier &&
+ last.isSynthetic &&
+ first.next == last;
+ }
+}
+
extension on GuardedPattern {
/// Return `true` if this pattern has, or might have, a `when` keyword.
bool get hasWhen {
@@ -1067,6 +1610,23 @@
}
}
+extension on Statement {
+ /// Return the statement before `this`, or `null` if this is the first statement in
+ /// the block.
+ Statement? get preceedingStatement {
+ final parent = this.parent;
+ if (parent is! Block) {
+ return null;
+ }
+ var statements = parent.statements;
+ var index = statements.indexOf(this);
+ if (index <= 0) {
+ return null;
+ }
+ return statements[index - 1];
+ }
+}
+
extension on SyntacticEntity? {
/// Return `true` if the receiver covers the [offset].
bool coversOffset(int offset) {
@@ -1075,8 +1635,21 @@
}
}
+extension on TopLevelVariableDeclaration {
+ /// Return `true` if this top level variable declaration consists of a single
+ /// identifier.
+ bool get isSingleIdentifier {
+ var first = beginToken;
+ var last = endToken;
+ return first.isKeywordOrIdentifier &&
+ last.isSynthetic &&
+ first.next == last;
+ }
+}
+
extension on TypeAnnotation? {
- bool get isSimpleIdentifier {
+ /// Return `true` if this type annotation consists of a single identifier.
+ bool get isSingleIdentifier {
var self = this;
return self is NamedType &&
self.question == null &&
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
index 9342047..e9e7878 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
@@ -394,12 +394,6 @@
}
@override
- void visitRecordPattern(RecordPattern node) {
- _addExpressionKeywords(node);
- _addSuggestions([Keyword.DYNAMIC]);
- }
-
- @override
void visitRelationalPattern(RelationalPattern node) {
var operator = node.operator;
if (request.offset >= operator.end) {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart
index a9692f7..149eef1 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart
@@ -232,7 +232,7 @@
}
if (node is Expression) {
return !node.inConstantContext;
- } else if (node is IfStatement) {
+ } else if (node is ExpressionStatement || node is IfStatement) {
return true;
} else if (node is PatternVariableDeclaration) {
return true;
@@ -295,19 +295,53 @@
/// Add the keywords that are appropriate when the selection is at the
/// beginning of a member in an extension.
- void addExtensionMemberKeywords() {
+ void addExtensionMemberKeywords({required bool isStatic}) {
addKeyword(Keyword.CONST);
addKeyword(Keyword.DYNAMIC);
addKeyword(Keyword.FINAL);
addKeyword(Keyword.GET);
- addKeyword(Keyword.OPERATOR);
+ if (!isStatic) addKeyword(Keyword.OPERATOR);
addKeyword(Keyword.SET);
- addKeyword(Keyword.STATIC);
+ if (!isStatic) addKeyword(Keyword.STATIC);
addKeyword(Keyword.VAR);
addKeyword(Keyword.VOID);
- if (featureSet.isEnabled(Feature.non_nullable)) {
+ }
+
+ /// Add the keywords that are appropriate when the selection is at the
+ /// beginning of field declaration.
+ void addFieldDeclarationKeywords(FieldDeclaration node) {
+ if (node.abstractKeyword == null) {
+ addKeyword(Keyword.ABSTRACT);
+ }
+ if (node.covariantKeyword == null) {
+ addKeyword(Keyword.COVARIANT);
+ }
+ if (node.externalKeyword == null) {
+ addKeyword(Keyword.EXTERNAL);
+ }
+ if (node.fields.lateKeyword == null &&
+ featureSet.isEnabled(Feature.non_nullable)) {
addKeyword(Keyword.LATE);
}
+ if (node.fields.type == null) {
+ addKeyword(Keyword.DYNAMIC);
+ }
+ if (!node.isStatic) {
+ addKeyword(Keyword.STATIC);
+ }
+ var fields = node.fields;
+ if (fields.type == null) {
+ addKeyword(Keyword.VAR);
+ }
+ var firstField = fields.variables.firstOrNull;
+ if (firstField != null) {
+ if (!firstField.isConst) {
+ addKeyword(Keyword.CONST);
+ }
+ if (!firstField.isFinal) {
+ addKeyword(Keyword.FINAL);
+ }
+ }
}
/// Add the keywords that are appropriate when the selection is before the `{`
diff --git a/pkg/analysis_server/test/client/completion_driver_test.dart b/pkg/analysis_server/test/client/completion_driver_test.dart
index 5fc9af0..30a588a 100644
--- a/pkg/analysis_server/test/client/completion_driver_test.dart
+++ b/pkg/analysis_server/test/client/completion_driver_test.dart
@@ -108,7 +108,7 @@
/// Asserts that the [response] has the [expected] textual dump produced
/// using [printerConfiguration].
- void assertResponse(String expected) {
+ void assertResponse(String expected, {String where = ''}) {
final buffer = StringBuffer();
printer.CompletionResponsePrinter(
buffer: buffer,
@@ -118,12 +118,14 @@
final actual = buffer.toString();
if (actual != expected) {
- var target = driver.server.server.completionState.currentRequest?.target;
- var where = '';
- if (target != null) {
- var containingNode = target.containingNode.runtimeType;
- var entity = target.entity;
- where = ' (containingNode = $containingNode, entity = $entity)';
+ if (where.isEmpty) {
+ var target =
+ driver.server.server.completionState.currentRequest?.target;
+ if (target != null) {
+ var containingNode = target.containingNode.runtimeType;
+ var entity = target.entity;
+ where = ' (containingNode = $containingNode, entity = $entity)';
+ }
}
TextExpectationsCollector.add(actual);
fail('''
diff --git a/pkg/analysis_server/test/completion_test.dart b/pkg/analysis_server/test/completion_test.dart
index 952b392..7882958 100644
--- a/pkg/analysis_server/test/completion_test.dart
+++ b/pkg/analysis_server/test/completion_test.dart
@@ -2105,7 +2105,7 @@
'K+else',
'L+return'
],
- failingTests: '3CK');
+ failingTests: '3K');
// operators in function
buildTests('test015', '''f(a,b,c) => a + b * c !1;''', <String>['1+=='],
diff --git a/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart b/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart
index f0d74d2..e701163 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart
@@ -257,7 +257,7 @@
if (context.isClass) Keyword.FACTORY,
Keyword.FINAL,
Keyword.GET,
- Keyword.LATE,
+ if (!context.isExtension) Keyword.LATE,
Keyword.OPERATOR,
Keyword.SET,
Keyword.STATIC,
@@ -270,7 +270,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
},
);
}
@@ -291,7 +291,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
},
);
}
@@ -308,12 +308,13 @@
suggestions
Object
kind: class
-''');
+''', where: context.where);
} else {
_printKeywordsOrClass();
final keywords = {
Keyword.DYNAMIC,
+ Keyword.EXTERNAL,
Keyword.VOID,
};
@@ -324,7 +325,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
}
},
);
@@ -346,7 +347,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
},
);
}
@@ -371,7 +372,7 @@
FutureOr
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
} else {
// TODO(scheglov) This is wrong.
final keywords = {
@@ -382,6 +383,8 @@
Keyword.EXTERNAL,
Keyword.FINAL,
Keyword.LATE,
+ Keyword.VAR,
+ Keyword.VOID,
};
assertResponse('''
@@ -391,7 +394,7 @@
FutureOr
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
}
},
);
@@ -413,7 +416,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
},
);
}
@@ -436,7 +439,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
},
);
}
@@ -458,6 +461,7 @@
Keyword.EXTERNAL,
Keyword.FINAL,
Keyword.LATE,
+ Keyword.VAR,
};
assertResponse('''
@@ -465,7 +469,7 @@
Object
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
},
);
}
@@ -491,17 +495,18 @@
String
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
} else {
final keywords = {
- // TODO(scheglov) Not quite right, without static.
+ if (context.isClass || context.isMixin) Keyword.ABSTRACT,
Keyword.CONST,
if (context.isClass || context.isMixin) Keyword.COVARIANT,
Keyword.DYNAMIC,
+ Keyword.EXTERNAL,
if (context.isClass) Keyword.FACTORY,
Keyword.FINAL,
Keyword.GET,
- Keyword.LATE,
+ if (!context.isExtension) Keyword.LATE,
Keyword.OPERATOR,
Keyword.SET,
Keyword.STATIC,
@@ -516,7 +521,7 @@
String
kind: class
${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
}
},
);
@@ -543,7 +548,7 @@
$line
}
''');
- validator(_Context());
+ validator(_Context(isEnum: true));
}
// extension
{
@@ -552,7 +557,7 @@
$line
}
''');
- validator(_Context());
+ validator(_Context(isExtension: true));
}
// mixin
{
@@ -1175,12 +1180,24 @@
class _Context {
final bool isClass;
+ final bool isEnum;
+ final bool isExtension;
final bool isMixin;
_Context({
this.isClass = false,
+ this.isEnum = false,
+ this.isExtension = false,
this.isMixin = false,
});
+
+ String get where => isClass
+ ? ' in class'
+ : isEnum
+ ? ' in enum'
+ : isExtension
+ ? ' in extension'
+ : ' in mixin';
}
extension on Iterable<Keyword> {
diff --git a/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart
index 17fed24..04ee4bc 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart
@@ -205,6 +205,8 @@
kind: keyword
implements
kind: keyword
+ with
+ kind: keyword
''');
}
}
@@ -230,6 +232,8 @@
kind: keyword
implements
kind: keyword
+ with
+ kind: keyword
''');
}
}
diff --git a/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart b/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart
index 749a746..e2e354d 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart
@@ -308,6 +308,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -370,6 +372,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -425,6 +429,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -528,6 +534,8 @@
kind: keyword
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
interface
@@ -636,6 +644,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -781,6 +791,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -904,6 +916,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -1198,6 +1212,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -1263,6 +1279,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
@@ -1432,6 +1450,8 @@
selection: 8
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
import '';
diff --git a/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart b/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart
index 228ab7f..3c8fbaa 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart
@@ -42,8 +42,6 @@
kind: keyword
get
kind: keyword
- late
- kind: keyword
operator
kind: keyword
set
@@ -71,8 +69,6 @@
kind: keyword
get
kind: keyword
- late
- kind: keyword
operator
kind: keyword
set
@@ -100,8 +96,6 @@
kind: keyword
get
kind: keyword
- late
- kind: keyword
operator
kind: keyword
set
@@ -129,8 +123,6 @@
kind: keyword
get
kind: keyword
- late
- kind: keyword
operator
kind: keyword
set
diff --git a/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart
index b797eca..6ae6911 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart
@@ -80,6 +80,10 @@
kind: keyword
late
kind: keyword
+ var
+ kind: keyword
+ void
+ kind: keyword
''');
}
}
@@ -148,6 +152,10 @@
kind: keyword
late
kind: keyword
+ var
+ kind: keyword
+ void
+ kind: keyword
''');
}
}
@@ -239,6 +247,10 @@
kind: keyword
late
kind: keyword
+ var
+ kind: keyword
+ void
+ kind: keyword
''');
}
}
@@ -276,6 +288,10 @@
kind: keyword
late
kind: keyword
+ var
+ kind: keyword
+ void
+ kind: keyword
''');
}
}
diff --git a/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart
index aa885f4..dc58b73 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart
@@ -96,6 +96,8 @@
kind: keyword
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
interface
@@ -189,6 +191,8 @@
kind: keyword
extension
kind: keyword
+ external
+ kind: keyword
final
kind: keyword
interface
diff --git a/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart b/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart
index 5f47757..fbcd1cf 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart
@@ -161,7 +161,7 @@
''');
}
- Future<void> test_afterThen_beforeComma_partial() async {
+ Future<void> test_afterThen_beforeComma_partial_list() async {
await computeSuggestions('''
void f(int i) {
[if (true) 1 e^, i];
@@ -390,7 +390,7 @@
}
mixin IfElementInSetTestCases on AbstractCompletionDriverTest {
- Future<void> test_afterThen_beforeComma_partial() async {
+ Future<void> test_afterThen_beforeComma_partial_set() async {
await computeSuggestions('''
void f(int i) {
<int>{if (true) 1 e^, i};
diff --git a/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart
index 811c72c..8857ddf 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart
@@ -68,32 +68,8 @@
kind: keyword
async*
kind: keyword
- const
- kind: keyword
- covariant
- kind: keyword
- dynamic
- kind: keyword
- factory
- kind: keyword
- final
- kind: keyword
- get
- kind: keyword
- late
- kind: keyword
- operator
- kind: keyword
- set
- kind: keyword
- static
- kind: keyword
sync*
kind: keyword
- var
- kind: keyword
- void
- kind: keyword
''');
}
}
@@ -446,6 +422,8 @@
replacement
left: 1
suggestions
+ abstract
+ kind: keyword
async
kind: keyword
async*
@@ -487,6 +465,8 @@
}
kind: override
selection: 90 38
+ abstract
+ kind: keyword
async
kind: keyword
async*
@@ -497,18 +477,12 @@
kind: keyword
dynamic
kind: keyword
- factory
+ external
kind: keyword
final
kind: keyword
- get
- kind: keyword
late
kind: keyword
- operator
- kind: keyword
- set
- kind: keyword
static
kind: keyword
sync*
diff --git a/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart b/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart
index aa63172..35ebe88 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart
@@ -39,50 +39,18 @@
suggestions
catch
kind: keyword
- const
- kind: keyword
''');
} else {
assertResponse(r'''
replacement
left: 1
suggestions
- assert
- kind: keyword
catch
kind: keyword
- const
- kind: keyword
- do
- kind: keyword
- dynamic
- kind: keyword
- final
- kind: keyword
finally
kind: keyword
- for
- kind: keyword
- if
- kind: keyword
- late
- kind: keyword
on
kind: keyword
- return
- kind: keyword
- switch
- kind: keyword
- throw
- kind: keyword
- try
- kind: keyword
- var
- kind: keyword
- void
- kind: keyword
- while
- kind: keyword
''');
}
}
@@ -98,50 +66,18 @@
suggestions
catch
kind: keyword
- const
- kind: keyword
''');
} else {
assertResponse(r'''
replacement
left: 1
suggestions
- assert
- kind: keyword
catch
kind: keyword
- const
- kind: keyword
- do
- kind: keyword
- dynamic
- kind: keyword
- final
- kind: keyword
finally
kind: keyword
- for
- kind: keyword
- if
- kind: keyword
- late
- kind: keyword
on
kind: keyword
- return
- kind: keyword
- switch
- kind: keyword
- throw
- kind: keyword
- try
- kind: keyword
- var
- kind: keyword
- void
- kind: keyword
- while
- kind: keyword
''');
}
}
@@ -553,6 +489,8 @@
}
Future<void> test_afterTryBlock_beforeOn_partial() async {
+ // This is an odd test because the `catch` belongs after the `on` clause,
+ // which makes it hard to know what the user might be trying to type.
await computeSuggestions('''
void f() {try {} c^ on SomeException {}}
''');
@@ -563,18 +501,50 @@
suggestions
catch
kind: keyword
+ const
+ kind: keyword
''');
} else {
assertResponse(r'''
replacement
left: 1
suggestions
+ assert
+ kind: keyword
catch
kind: keyword
+ const
+ kind: keyword
+ do
+ kind: keyword
+ dynamic
+ kind: keyword
+ final
+ kind: keyword
finally
kind: keyword
+ for
+ kind: keyword
+ if
+ kind: keyword
+ late
+ kind: keyword
on
kind: keyword
+ return
+ kind: keyword
+ switch
+ kind: keyword
+ throw
+ kind: keyword
+ try
+ kind: keyword
+ var
+ kind: keyword
+ void
+ kind: keyword
+ while
+ kind: keyword
''');
}
}
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 201717d..f50e16a 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -736,6 +736,15 @@
await produceGlobalTypeInferenceResults(closedWorldAndIndices!);
if (shouldStopAfterGlobalTypeInference) return;
+ // Allow the original references to these to be GCed and only hold
+ // references to them if we are actually running the dump info task later.
+ JClosedWorld? closedWorldForDumpInfo;
+ DataSourceIndices? globalInferenceIndicesForDumpInfo;
+ if (options.dumpInfoWriteUri != null || options.dumpInfoReadUri != null) {
+ closedWorldForDumpInfo = closedWorldAndIndices.data;
+ globalInferenceIndicesForDumpInfo = globalTypeInferenceResults.indices;
+ }
+
// Run codegen.
final sourceLookup = SourceLookup(output.component);
CodegenResults codegenResults =
@@ -746,8 +755,8 @@
final dumpInfoData =
await serializationTask.deserializeDumpInfoProgramData(
backendStrategy,
- closedWorldAndIndices.data!,
- globalTypeInferenceResults.indices);
+ closedWorldForDumpInfo!,
+ globalInferenceIndicesForDumpInfo);
await runDumpInfo(codegenResults, dumpInfoData);
} else {
// Link.
@@ -762,8 +771,8 @@
serializationTask.serializeDumpInfoProgramData(
backendStrategy,
dumpInfoData,
- closedWorldAndIndices.data!,
- globalTypeInferenceResults.indices);
+ closedWorldForDumpInfo!,
+ globalInferenceIndicesForDumpInfo);
}
}
}
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 9744d31..443784a 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4327,9 +4327,11 @@
bool _reifyGenericFunction(Member? m) =>
m == null ||
- !m.enclosingLibrary.importUri.isScheme('dart') ||
- !m.annotations
- .any((a) => isBuiltinAnnotation(a, '_js_helper', 'NoReifyGeneric'));
+ // JS interop members should not pass type arguments.
+ !isJsMember(m) &&
+ !(m.enclosingLibrary.importUri.isScheme('dart') &&
+ m.annotations.any((a) =>
+ isBuiltinAnnotation(a, '_js_helper', 'NoReifyGeneric')));
js_ast.Statement _nullParameterCheck(js_ast.Expression param) {
var call = runtimeCall('argumentError((#))', [param]);
diff --git a/pkg/dev_compiler/lib/src/kernel/js_interop.dart b/pkg/dev_compiler/lib/src/kernel/js_interop.dart
index 1f2520c..e6c13da 100644
--- a/pkg/dev_compiler/lib/src/kernel/js_interop.dart
+++ b/pkg/dev_compiler/lib/src/kernel/js_interop.dart
@@ -47,11 +47,13 @@
}
bool isJsMember(Member member) {
- // TODO(vsm): If we ever use external outside the SDK for non-JS interop,
- // we're need to fix this.
- return !_isLibrary(member.enclosingLibrary, ['dart:*']) &&
- member.isExternal &&
- !isNative(member);
+ return member.isExternal &&
+ !_isLibrary(member.enclosingLibrary, ['dart:*']) &&
+ !isNative(member) &&
+ // Non-JS interop external members might exist e.g. the ones generated by
+ // Angular, so we should check to see that this member actually uses JS
+ // interop.
+ usesJSInterop(member);
}
bool _annotationIsFromJSLibrary(String expectedName, Expression value) {
@@ -120,7 +122,7 @@
bool isUndefinedAnnotation(Expression value) =>
isBuiltinAnnotation(value, '_js_helper', '_Undefined');
-/// Returns true iff the class has an `@JS(...)` annotation from
+/// Returns true iff the annotatable has an `@JS(...)` annotation from
/// `package:js`, `dart:_js_annotations`, or `dart:js_interop`.
///
/// Note: usually [usesJSInterop] should be used instead of this.
@@ -132,8 +134,11 @@
// class itself, other places we require it on the library. Also members are
// inconsistent: sometimes they need to have `@JS` on them, other times they
// need to be `external` in an `@JS` class.
-bool hasJSInteropAnnotation(Class c) =>
- c.annotations.any(isJSInteropAnnotation);
+//
+// TODO(srujzs): We should replace many of these helpers with the ones defined
+// in pkg:_js_interop_checks.
+bool hasJSInteropAnnotation(Annotatable a) =>
+ a.annotations.any(isJSInteropAnnotation);
/// Returns true iff this element is a JS interop member.
///
@@ -144,9 +149,10 @@
/// the class or library.
bool usesJSInterop(NamedNode n) {
if (n is Member && n.isExternal) {
- return n.enclosingLibrary.annotations.any(isJSInteropAnnotation) ||
- n.annotations.any(isJSInteropAnnotation) ||
- (n.enclosingClass?.annotations.any(isJSInteropAnnotation) ?? false);
+ return hasJSInteropAnnotation(n) ||
+ (n.enclosingClass == null
+ ? hasJSInteropAnnotation(n.enclosingLibrary)
+ : hasJSInteropAnnotation(n.enclosingClass!));
} else if (n is Class) {
return n.annotations.any(isJSInteropAnnotation);
}
diff --git a/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart b/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
index 1401a6c..1221b62 100644
--- a/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
+++ b/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
@@ -86,6 +86,7 @@
if (result.isRequestedName) {
var element = result.element;
return element is ClassElement ||
+ element is ExtensionTypeElement ||
element is TypeAliasElement ||
element is TypeParameterElement;
}
diff --git a/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart b/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
index 331db90..8c717f7 100644
--- a/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
+++ b/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
@@ -17,6 +17,16 @@
@override
String get lintRule => 'avoid_types_as_parameter_names';
+ test_extensionType() async {
+ await assertDiagnostics(r'''
+extension type E(int i) { }
+
+void f(E) { }
+''', [
+ lint(36, 1),
+ ]);
+ }
+
test_super() async {
await assertDiagnostics(r'''
class A {
diff --git a/tests/lib/js/type_parameter_lowering_test.dart b/tests/lib/js/type_parameter_lowering_test.dart
new file mode 100644
index 0000000..35e1f835
--- /dev/null
+++ b/tests/lib/js/type_parameter_lowering_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Check that the compilers lower interop calls that use type parameters do not
+// pass the type parameter.
+
+@JS()
+library type_parameter_lowering_test;
+
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+@JS()
+external void topLevelMethod<T extends int>(T t);
+
+@JS()
+class TypeParam<T extends int> {
+ external TypeParam(T t);
+ external static void staticMethod<U extends int>(U u);
+ external static void staticMethodShadow<T extends int>(T t);
+ external void genericMethod<U extends int>(U u);
+ external void genericMethodShadow<T extends int>(T t);
+}
+
+void main() {
+ eval('''
+ const checkValue = function(value) {
+ if (value != 0) {
+ throw new Error(`Expected value to be 0, but got \${value}.`);
+ }
+ }
+ globalThis.topLevelMethod = checkValue;
+ globalThis.TypeParam = function (value) {
+ checkValue(value);
+ this.genericMethod = checkValue;
+ this.genericMethodShadow = checkValue;
+ }
+ globalThis.TypeParam.staticMethod = checkValue;
+ globalThis.TypeParam.staticMethodShadow = checkValue;
+ ''');
+ topLevelMethod(0);
+ final typeParam = TypeParam(0);
+ TypeParam.staticMethod(0);
+ TypeParam.staticMethodShadow(0);
+ typeParam.genericMethod(0);
+ typeParam.genericMethodShadow(0);
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 691a449..56a005c 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -86,6 +86,7 @@
js/static_interop_test/native_error_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/static_interop_test/typed_data_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/type_parameter_lowering_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
[ $simulator ]
convert/utf85_test: Skip # Pass, Slow Issue 20111.
diff --git a/tests/lib_2/js/type_parameter_lowering_test.dart b/tests/lib_2/js/type_parameter_lowering_test.dart
new file mode 100644
index 0000000..35e1f835
--- /dev/null
+++ b/tests/lib_2/js/type_parameter_lowering_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Check that the compilers lower interop calls that use type parameters do not
+// pass the type parameter.
+
+@JS()
+library type_parameter_lowering_test;
+
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+@JS()
+external void topLevelMethod<T extends int>(T t);
+
+@JS()
+class TypeParam<T extends int> {
+ external TypeParam(T t);
+ external static void staticMethod<U extends int>(U u);
+ external static void staticMethodShadow<T extends int>(T t);
+ external void genericMethod<U extends int>(U u);
+ external void genericMethodShadow<T extends int>(T t);
+}
+
+void main() {
+ eval('''
+ const checkValue = function(value) {
+ if (value != 0) {
+ throw new Error(`Expected value to be 0, but got \${value}.`);
+ }
+ }
+ globalThis.topLevelMethod = checkValue;
+ globalThis.TypeParam = function (value) {
+ checkValue(value);
+ this.genericMethod = checkValue;
+ this.genericMethodShadow = checkValue;
+ }
+ globalThis.TypeParam.staticMethod = checkValue;
+ globalThis.TypeParam.staticMethodShadow = checkValue;
+ ''');
+ topLevelMethod(0);
+ final typeParam = TypeParam(0);
+ TypeParam.staticMethod(0);
+ TypeParam.staticMethodShadow(0);
+ typeParam.genericMethod(0);
+ typeParam.genericMethodShadow(0);
+}
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index 1382f5f..05c235d 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -67,6 +67,7 @@
js/static_interop_test/external_static_member_lowerings_trusttypes_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/type_parameter_lowering_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
[ $simulator ]
convert/utf85_test: Skip # Pass, Slow Issue 20111.
diff --git a/tools/VERSION b/tools/VERSION
index 64a456e..43571ff 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 3
MINOR 2
PATCH 0
-PRERELEASE 138
+PRERELEASE 139
PRERELEASE_PATCH 0