提交 35ab0f51 编写于 作者: A Artur Spychaj

PR changes

Improves comments around the IsCompleteSubmission.
Throws argument exception when the syntax is not a submission.
Uses the Parser.CheckFeatureAvailability to check multiline strings.
Renames the IsScriptOrInteractive to IsScript.
Handle incomplete members such as an annotation.
Do not show completions when inside of a directive.
Add tests for symbol completion provider.
上级 ae7bb993
......@@ -9764,6 +9764,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Syntax tree should be created from a submission..
/// </summary>
internal static string SyntaxTreeIsNotASubmission {
get {
return ResourceManager.GetString("SyntaxTreeIsNotASubmission", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to SyntaxTree &apos;{0}&apos; not found to remove.
/// </summary>
......
......@@ -4669,4 +4669,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_InvalidReal" xml:space="preserve">
<value>Invalid real literal.</value>
</data>
</root>
<data name="SyntaxTreeIsNotASubmission" xml:space="preserve">
<value>Syntax tree should be created from a submission.</value>
</data>
</root>
\ No newline at end of file
......@@ -1759,6 +1759,7 @@ public static ExpressionSyntax GetNonGenericExpression(ExpressionSyntax expressi
/// <summary>
/// Determines whether the given text is considered a syntactically complete submission.
/// Throws <see cref="ArgumentException"/> if the tree was not compiled as an interactive submission.
/// </summary>
public static bool IsCompleteSubmission(SyntaxTree tree)
{
......@@ -1766,6 +1767,10 @@ public static bool IsCompleteSubmission(SyntaxTree tree)
{
throw new ArgumentNullException(nameof(tree));
}
if (tree.Options.Kind != SourceCodeKind.Script)
{
throw new ArgumentException(CSharpResources.SyntaxTreeIsNotASubmission);
}
if (!tree.HasCompilationUnitRoot)
{
......
......@@ -11,6 +11,7 @@
using Xunit;
using Microsoft.CodeAnalysis.UnitTests;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.Semantics
{
......@@ -64,7 +65,9 @@ public void CompilationDoesNotAcceptArbitrarilyRootedTree()
[Fact]
public void SyntaxFactoryIsCompleteSubmissionShouldNotThrowForArbitrarilyRootedTree()
{
var tree = SyntaxFactory.SyntaxTree(SyntaxFactory.LetClause("Blah", SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(54))));
var tree = SyntaxFactory.SyntaxTree(
SyntaxFactory.LetClause("Blah", SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(54))),
options: TestOptions.Script);
SyntaxFactory.IsCompleteSubmission(tree);
}
......
......@@ -23,6 +23,9 @@ private static void AssertCompleteSubmission(string code, bool isComplete = true
public void TextIsCompleteSubmission()
{
Assert.Throws<ArgumentNullException>(() => SyntaxFactory.IsCompleteSubmission(null));
Assert.Throws<ArgumentException>(() =>
SyntaxFactory.IsCompleteSubmission(SyntaxFactory.ParseSyntaxTree("", options: TestOptions.Regular)));
AssertCompleteSubmission("");
AssertCompleteSubmission("//hello");
AssertCompleteSubmission("@");
......
......@@ -2,6 +2,8 @@
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports VbObjectDisplay = Microsoft.CodeAnalysis.VisualBasic.ObjectDisplay.ObjectDisplay
Imports Parser = Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.Parser
Imports Feature = Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.Feature
Namespace Microsoft.CodeAnalysis.VisualBasic
......@@ -533,48 +535,69 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Function
''' <summary>
''' Determines if a syntax node is a part of a LINQ query.
''' If so then a query is not terminated the syntax tree ends with an empty line.
''' Determines if a submission contains an LINQ query not followed by an empty line.
'''
''' Examples:
''' 1. <c>Dim x = 1</c> returns false since the statement is not a LINQ query
''' 2. <c>
''' Dim x = FROM {1, 2, 3}
'''
''' </c> return false since the statement is followed by an empty new line.
''' 3. <c>Dim x = FROM {1, 2, 3}</c> returns true since the LINQ statement is not followed by an empty new line.
''' </summary>
''' <returns>True if the token is part of a LINQ query and syntax tree ends with an empty line.</returns>
Private Shared Function IsPartOfUnterminatedLinqQuery(token As SyntaxToken, statementNode As SyntaxNode, endOfFileToken As SyntaxToken) As Boolean
''' <param name="token">Last expected token of the last statement in a submission.</param>
''' <param name="statementNode">Top level statement which token is part of.</param>
''' <param name="endOfFileToken">Token that marks the end of submission.</param>
Private Shared Function IsPartOfLinqQueryNotFollowedByNewLine(token As SyntaxToken, statementNode As SyntaxNode, endOfFileToken As SyntaxToken) As Boolean
' Checking if the submission ends with a new line.
For Each leadingTrivia In endOfFileToken.LeadingTrivia
If leadingTrivia.IsKind(SyntaxKind.EndOfLineTrivia) Then
Return False
End If
Next
' Checking if the last token is part of a LINQ query.
Dim node = token.Parent
Do
If node.IsKind(SyntaxKind.QueryExpression) Then
Return Not endOfFileToken.LeadingTrivia.Contains(Function(trivia) trivia.IsKind(SyntaxKind.EndOfLineTrivia))
Return True
End If
If node Is statementNode Then
Exit Do
Return False
End If
node = node.Parent
Loop
Return False
End Function
''' <summary>
''' Determines if a submission is complete.
''' Returns false if the syntax is valid but incomplete.
''' Returns true if the syntax is invalid or complete.
''' Throws <see cref="ArgumentNullException"/> in case the tree is null.
''' Throws <see cref="ArgumentException"/> in case the tree is not a submission.
''' </summary>
''' <param name="tree">Syntax tree.</param>
Public Shared Function IsCompleteSubmission(tree As SyntaxTree) As Boolean
Dim options As VisualBasicParseOptions = DirectCast(tree.Options, VisualBasicParseOptions)
Dim languageVersion As LanguageVersion = options.LanguageVersion
If (tree Is Nothing) Then
If tree Is Nothing Then
Throw New ArgumentNullException(NameOf(tree))
End If
If (Not tree.HasCompilationUnitRoot) Then
Dim options As VisualBasicParseOptions = DirectCast(tree.Options, VisualBasicParseOptions)
If options.Kind = SourceCodeKind.Regular Then
Throw New ArgumentException(VBResources.SyntaxTreeIsNotASubmission)
End If
Dim languageVersion As LanguageVersion = options.LanguageVersion
If Not tree.HasCompilationUnitRoot Then
Return False
End If
Dim compilation As CompilationUnitSyntax = DirectCast(tree.GetRoot(), CompilationUnitSyntax)
Dim compilationUnit As CompilationUnitSyntax = DirectCast(tree.GetRoot(), CompilationUnitSyntax)
For Each err In compilation.GetDiagnostics()
For Each err In compilationUnit.GetDiagnostics()
Select Case DirectCast(err.Code, ERRID)
Case ERRID.ERR_LbExpectedEndIf,
ERRID.ERR_ExpectedEndRegion
......@@ -586,36 +609,36 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Select
Next
Dim lastNode = compilation.ChildNodes().LastOrDefault()
If lastNode Is Nothing Then
Dim lastTopLevelNode = compilationUnit.ChildNodes().LastOrDefault()
If lastTopLevelNode Is Nothing Then
' Invalid submission. The compilation does not have any children.
Return True
End If
Dim lastToken = lastNode.GetLastToken(includeZeroWidth:=True, includeSkipped:=True)
Dim lastToken = lastTopLevelNode.GetLastToken(includeZeroWidth:=True, includeSkipped:=True)
If IsPartOfUnterminatedLinqQuery(lastToken, lastNode, compilation.EndOfFileToken) OrElse
If IsPartOfLinqQueryNotFollowedByNewLine(lastToken, lastTopLevelNode, compilationUnit.EndOfFileToken) OrElse
(lastToken.HasTrailingTrivia AndAlso lastToken.TrailingTrivia.Last().IsKind(SyntaxKind.LineContinuationTrivia)) Then
' Even if the compilation is correct but has a line continuation trivia return statement as incomplete.
' For example `Dim x = 12 _` has no compilation errors but should be treated as an incomplete statement.
Return False
ElseIf Not compilation.HasErrors Then
ElseIf Not compilationUnit.HasErrors Then
' No errors returned. This is a valid and complete submission.
Return True
ElseIf lastNode.IsKind(SyntaxKind.IncompleteMember) OrElse lastToken.IsMissing Then
ElseIf lastTopLevelNode.IsKind(SyntaxKind.IncompleteMember) OrElse lastToken.IsMissing Then
Return False
End If
For Each err In lastToken.GetDiagnostics()
Select Case DirectCast(err.Code, ERRID)
Case ERRID.ERR_UnterminatedStringLiteral
If languageVersion = LanguageVersion.VisualBasic14 Then
If Parser.CheckFeatureAvailability(languageVersion, Feature.MultilineStringLiterals) Then
Return False
End If
End Select
Next
' By default mark the submissions as invalid.
' By default mark submissions as invalid since there's at least one error.
Return True
End Function
End Class
......
......@@ -12144,6 +12144,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Syntax tree should be created from a submission..
'''</summary>
Friend ReadOnly Property SyntaxTreeIsNotASubmission() As String
Get
Return ResourceManager.GetString("SyntaxTreeIsNotASubmission", resourceCulture)
End Get
End Property
'''<summary>
''' Looks up a localized string similar to SyntaxTree &apos;{0}&apos; not found to remove.
'''</summary>
......
......@@ -5338,4 +5338,7 @@
<data name="ERR_InvalidPathMap" xml:space="preserve">
<value>The pathmap option was incorrectly formatted.</value>
</data>
</root>
<data name="SyntaxTreeIsNotASubmission" xml:space="preserve">
<value>Syntax tree should be created from a submission.</value>
</data>
</root>
\ No newline at end of file
......@@ -29,6 +29,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests
<Fact>
Public Sub TestCompleteSubmission()
Assert.Throws(Of ArgumentNullException)(Function() SyntaxFactory.IsCompleteSubmission(
Nothing))
Assert.Throws(Of ArgumentException)(Function() SyntaxFactory.IsCompleteSubmission(
SyntaxFactory.ParseSyntaxTree("Dim x = 12", options:=TestOptions.Regular)))
' Basic submissions
AssertValidCompleteSubmission("")
AssertValidCompleteSubmission("'comment")
......@@ -186,6 +191,9 @@ End If")
SELECT x + 1
")
' String interpolation
AssertValidCompleteSubmission("Dim s = $""{name}""")
End Sub
End Class
......
......@@ -1221,6 +1221,61 @@ End Class
</Text>.Value, "ToString")
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestGlobalScriptMembers() As Task
Await VerifyItemExistsAsync(
<Text>
$$
</Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestGlobalScriptMembersAfterStatement() As Task
Await VerifyItemExistsAsync(
<Text>
Dim x = 1: $$
</Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
Await VerifyItemExistsAsync(
<Text>
Dim x = 1
$$
</Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
Await VerifyItemIsAbsentAsync(
<Text>
Dim x = 1 $$
</Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestGlobalStatementMembersBeforeDirectives() As Task
Await VerifyItemIsAbsentAsync(
<Text>
$$
#If DEBUG
#End If
</Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestGlobalScriptMembersInsideDirectives() As Task
Await VerifyItemIsAbsentAsync(
<Text>
#If $$
</Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestGlobalScriptMembersAfterAnnotation() As Task
Await VerifyItemIsAbsentAsync(
<Text><![CDATA[
<Annotation>
$$
]]></Text>.Value, "Console", sourceCodeKind:=SourceCodeKind.Script)
End Function
<WpfFact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestNoSharedMembers() As Task
Dim test = <Text>
......
......@@ -19,7 +19,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context
var syntaxTree = context.SyntaxTree;
return
context.IsPreProcessorKeywordContext &&
syntaxTree.IsInteractiveOrScript() &&
syntaxTree.IsScript() &&
syntaxTree.IsBeforeFirstToken(position, cancellationToken);
}
}
......
......@@ -20,7 +20,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context
var syntaxTree = context.SyntaxTree;
// namespaces are illegal in interactive code:
if (syntaxTree.IsInteractiveOrScript())
if (syntaxTree.IsScript())
{
return false;
}
......
......@@ -19,7 +19,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context
var syntaxTree = context.SyntaxTree;
return
context.IsPreProcessorKeywordContext &&
syntaxTree.IsInteractiveOrScript() &&
syntaxTree.IsScript() &&
syntaxTree.IsBeforeFirstToken(position, cancellationToken);
}
}
......
......@@ -27,7 +27,7 @@ private static bool IsAttributeContext(CSharpSyntaxContext context, Cancellation
{
return
context.IsMemberAttributeContext(SyntaxKindSet.ClassInterfaceStructTypeDeclarations, cancellationToken) ||
(context.SyntaxTree.IsInteractiveOrScript() && context.IsTypeAttributeContext(cancellationToken));
(context.SyntaxTree.IsScript() && context.IsTypeAttributeContext(cancellationToken));
}
}
}
......@@ -68,7 +68,7 @@ public static bool IsAttributeNameContext(this SyntaxTree syntaxTree, int positi
ISet<SyntaxKind> validModifiers,
CancellationToken cancellationToken)
{
if (!syntaxTree.IsInteractiveOrScript())
if (!syntaxTree.IsScript())
{
return false;
}
......@@ -1375,7 +1375,7 @@ public static bool IsStatementContext(this SyntaxTree syntaxTree, int position,
public static bool IsGlobalStatementContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
{
if (!syntaxTree.IsInteractiveOrScript())
if (!syntaxTree.IsScript())
{
return false;
}
......
......@@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Shared.Extensions
{
internal static class SyntaxTreeExtensions
{
public static bool IsInteractiveOrScript(this SyntaxTree syntaxTree)
public static bool IsScript(this SyntaxTree syntaxTree)
{
return syntaxTree.Options.Kind != SourceCodeKind.Regular;
}
......
......@@ -56,31 +56,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Return TypeOf token.Parent Is SkippedTokensTriviaSyntax
End Function
<Extension()>
Public Function IsGlobalStatementContext(token As SyntaxToken, position As Integer) As Boolean
If Not token.IsLastTokenOfStatement() Then
Return False
End If
' NB: Checks whether the caret is placed after a colon or an end of line.
' Otherwise the typed expression would still be a part of the previous statement.
If Not token.HasTrailingTrivia Then
Return False
End If
For Each trivia In token.TrailingTrivia
If trivia.Span.Start > position Then
Return False
ElseIf trivia.IsKind(SyntaxKind.ColonTrivia) Then
Return True
ElseIf trivia.IsKind(SyntaxKind.EndOfLineTrivia) Then
Return True
End If
Next
Return False
End Function
<Extension()>
Public Function FirstAncestorOrSelf(token As SyntaxToken, predicate As Func(Of SyntaxNode, Boolean)) As SyntaxNode
Return token.Parent.FirstAncestorOrSelf(predicate)
......
......@@ -146,20 +146,45 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Return trivia.IsKind(SyntaxKind.SkippedTokensTrivia)
End Function
Private Function IsGlobalStatementContext(token As SyntaxToken, position As Integer) As Boolean
If Not token.IsLastTokenOfStatement() Then
Return False
End If
' NB: Checks whether the caret is placed after a colon or an end of line.
' Otherwise the typed expression would still be a part of the previous statement.
If Not token.HasTrailingTrivia OrElse token.HasAncestor(Of IncompleteMemberSyntax) Then
Return False
End If
For Each trivia In token.TrailingTrivia
If trivia.Span.Start > position Then
Return False
ElseIf trivia.IsKind(SyntaxKind.ColonTrivia) Then
Return True
ElseIf trivia.IsKind(SyntaxKind.EndOfLineTrivia) Then
Return True
End If
Next
Return False
End Function
<Extension()>
Public Function IsGlobalStatementContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
If Not syntaxTree.IsInteractiveOrScript() Then
If Not syntaxTree.IsScript() Then
Return False
End If
Dim token As SyntaxToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken).GetPreviousTokenIfTouchingWord(position)
Dim token As SyntaxToken = syntaxTree.FindTokenOnLeftOfPosition(
position, cancellationToken, includeDirectives:=True).GetPreviousTokenIfTouchingWord(position)
If token.IsKind(SyntaxKind.None) Then
Dim compilationUnit = TryCast(syntaxTree.GetRoot(cancellationToken), CompilationUnitSyntax)
Return compilationUnit Is Nothing OrElse compilationUnit.Imports.Count = 0
End If
Return token.IsGlobalStatementContext(position)
Return IsGlobalStatementContext(token, position)
End Function
''' <summary>
......
......@@ -77,8 +77,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Recommendations
context As VisualBasicSyntaxContext,
cancellationToken As CancellationToken
) As IEnumerable(Of ISymbol)
Dim symbols = context.SemanticModel.LookupSymbols(context.TargetToken.Span.End)
Return symbols
Return context.SemanticModel.LookupSymbols(context.TargetToken.Span.End)
End Function
Private Function GetUnqualifiedSymbolsForQueryIntoContext(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册