Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - DO NOT MERGE - Spike for F# support #392

Open
wants to merge 2 commits into
base: 203
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ assertj_version=3.15.0

rd_version=0.203.161
rider_nuget_sdk_version=2020.3.0-eap07
rider_fsharp_plugin_version=2020.3.4646

rider_test_local_env_run=true
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Psi", "src\Azure.Psi\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Intellisense", "src\Azure.Intellisense\Azure.Intellisense.csproj", "{D6136977-43A4-49F4-92D9-72FD0CB30603}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.FSharp", "src\Azure.FSharp\Azure.FSharp.csproj", "{DD9BFB34-980E-4514-8FFA-79F9BE9BF756}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -40,12 +42,17 @@ Global
{D6136977-43A4-49F4-92D9-72FD0CB30603}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6136977-43A4-49F4-92D9-72FD0CB30603}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6136977-43A4-49F4-92D9-72FD0CB30603}.Release|Any CPU.Build.0 = Release|Any CPU
{DD9BFB34-980E-4514-8FFA-79F9BE9BF756}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD9BFB34-980E-4514-8FFA-79F9BE9BF756}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD9BFB34-980E-4514-8FFA-79F9BE9BF756}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD9BFB34-980E-4514-8FFA-79F9BE9BF756}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{31AE4101-68C2-407C-B780-5F9CAD5F6203} = {5E7A51CC-C345-433D-B91B-BE1B9BCAD6BB}
{68D0C7DB-242B-4981-9EC1-B99935906ACB} = {5E7A51CC-C345-433D-B91B-BE1B9BCAD6BB}
{ECB68A22-1792-4EAB-960B-DC421DFB17A3} = {5E7A51CC-C345-433D-B91B-BE1B9BCAD6BB}
{060E2258-BCFF-4616-AAE4-A9CA7E5FC744} = {5E7A51CC-C345-433D-B91B-BE1B9BCAD6BB}
{D6136977-43A4-49F4-92D9-72FD0CB30603} = {5E7A51CC-C345-433D-B91B-BE1B9BCAD6BB}
{DD9BFB34-980E-4514-8FFA-79F9BE9BF756} = {5E7A51CC-C345-433D-B91B-BE1B9BCAD6BB}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
-->
<Error staticGroup="AzureErrors"
name="TimerTriggerCronExpression"
configurableSeverity="Azure.FunctionApp.TimerTriggerCronExpression">
configurableSeverity="Azure.CSharp.FunctionApp.TimerTriggerCronExpression">

<Parameter type="ICSharpExpression" name="expression" />
<Parameter type="string" name="cronErrorMessage"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<RootNamespace>JetBrains.ReSharper.Azure.FSharp</RootNamespace>
<AssemblyName>JetBrains.ReSharper.Azure.FSharp</AssemblyName>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Rider.SDK" Version="$(RiderSDKVersion)" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net461" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Azure.Psi\Azure.Psi.csproj" />
<ProjectReference Include="..\Azure.Daemon\Azure.Daemon.csproj" />
</ItemGroup>

<ItemGroup>
<Reference Include="FSharp.Compiler.Service, Version=2020.3.0.0, Culture=neutral, PublicKeyToken=3099b8d9d20e74bf">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mfilippov / @auduchinok Right now, I have referenced assemblies from the F# plugin manually. Support for "full plugins" (back-end and front-end) seem hard to do.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true. We don't have auto-reference for bundled plugins :( Maybe we should think about it.

<HintPath>..\..\..\build\dependencies\dotnet\FSharp.Compiler.Service.dll</HintPath>
</Reference>
<Reference Include="JetBrains.ReSharper.Plugins.FSharp.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=333a98252ac829ae">
<HintPath>..\..\..\build\dependencies\dotnet\JetBrains.ReSharper.Plugins.FSharp.Common.dll</HintPath>
</Reference>
<Reference Include="JetBrains.ReSharper.Plugins.FSharp.ProjectModelBase, Version=1.0.0.0, Culture=neutral, PublicKeyToken=333a98252ac829ae">
<HintPath>..\..\..\build\dependencies\dotnet\JetBrains.ReSharper.Plugins.FSharp.ProjectModelBase.dll</HintPath>
</Reference>
<Reference Include="JetBrains.ReSharper.Plugins.FSharp.Psi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=333a98252ac829ae">
<HintPath>..\..\..\build\dependencies\dotnet\JetBrains.ReSharper.Plugins.FSharp.Psi.dll</HintPath>
</Reference>
<Reference Include="JetBrains.ReSharper.Plugins.FSharp.Psi.Features, Version=1.0.0.0, Culture=neutral, PublicKeyToken=333a98252ac829ae">
<HintPath>..\..\..\build\dependencies\dotnet\JetBrains.ReSharper.Plugins.FSharp.Psi.Features.dll</HintPath>
</Reference>
</ItemGroup>

<ItemGroup Label="F#">
<ErrorsGen Include="Errors\FunctionAppErrors.xml">
<Namespace>JetBrains.ReSharper.Azure.FSharp.Errors.FunctionAppErrors</Namespace>
<OutputFile>Errors\FunctionAppErrors.Generated.cs</OutputFile>
<Mode>ERRORS</Mode>
</ErrorsGen>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Errors language="C#" configurableSeverityImplementationLanguage="FSHARP">

<Usings>
JetBrains.ReSharper.Azure.Daemon.Highlighters;
JetBrains.ReSharper.Azure.FSharp.Errors;
JetBrains.ReSharper.Azure.FSharp.FunctionApp.Stages.Analysis;
JetBrains.ReSharper.Plugins.FSharp.Psi;
JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
</Usings>

<!-- Register the static severity groups. This is mostly used in grouping items in SWEA results.
The groups should be different to any configurable severity groups -->
<StaticSeverityGroups>
<Group name="Azure Errors" key="AzureErrors" />
<Group name="Azure Warnings" key="AzureWarnings" />
<Group name="Azure Gutter Marks" key="AzureGutterMarks" />
</StaticSeverityGroups>

<!-- Register the configurable severities
Can take a child Tag or Group element
* Tag is configurable severity without a group
* Group[@name] specifies the name of the group, expects child Tag elements.
Note that the group name/title should be registered with
[assembly: RegisterConfigurableHighlightingsGroup(name, "Title")]
* Tag[@name] is the HIGHLIGHTING_ID of a warning/error. It is treated as a string
literal and quoted. It is referenced in Warning[@configurableSeverity]
* Tag[@default] is the default severity. Just the enum value, no leading "Severity."
* Tag[@type] is optional. Default is "simple". Other options include "global",
"localAndGlobal"
* Tag[@externalName] is also the HIGHLIGHTING_ID, but is treated verbatim, rather
than as a string literal. This allows for using the fully qualified name of the
HIGHLIGHTING_ID
* Tag[@alternatives] the value of RegisterConfigurableSeverityAttribute.AlternativeIDs
* Tag/Title is the short title of the severity, usually matching the highlight's MESSAGE
* Tag/Description is a longer description, shown in the options pages
* Tag/CompoundItemName only used if Tag[@type] is missing or "simple". Sets the compound
item name
-->
<SeverityConfiguration>
<Group name="AzureHighlightingGroupIds.FunctionApp">

<Tag externalName="TimerTriggerCronExpressionError.HIGHLIGHTING_ID" default="ERROR">
<Title>Invalid Function App Timer Trigger Cron expression</Title>
<Description>Function App Timer Trigger Cron expresion is not valid and can not be used.</Description>
</Tag>

</Group>
</SeverityConfiguration>

<!-- Warning, Error, Info or LocalAndGlobal
* @name - name of the highlighting. Suffix based on parent element is automatically added
* @configurableSeverity - the value of HIGHLIGHTING_ID. Treated as a string literal and quoted
* @externalConfigurableSeverity - the value of HIGHLIGHTING_ID. Treated verbatim to refer to code
* @staticGroup - define the static group this highlight belongs to. Must have been registered
in /Errors/StaticSeverityGroups
* ./Parameter - multiple elements for constructor parameters. Requires the @name and @type attributes.
* ./Parameter[@name] is capitalised and turned into a public property
* ./Message[@value] the string format template for the message
* ./Message/Argument multiple code snippets to be used as the arguments to string.Format
* ./Range code snippt to be the return value of CalculateRange()
* ./Behavour - NOTE THE SPELLING!
* ./Behavour[@attributeID] (optional) defines ConfigurableSeverityHighlightingAttribute.AttributeId
* ./Behavour[@overlapResolvePolicy] (optional) defines ConfigurableSeverityHighlightingAttribute.OverlapResolve
* ./Behavour/QuickFix multiple elements listing the types that will implement a quick fix
for this highlight. Only used when the file is processed in QUICKFIX mode (default is
to use ERROR mode)
-->
<Error staticGroup="AzureErrors"
name="TimerTriggerCronExpression"
configurableSeverity="Azure.FSharp.FunctionApp.TimerTriggerCronExpression">

<Parameter type="IExpression" name="expression" />
<Parameter type="string" name="cronErrorMessage"/>
<Message value="{0}">
<Argument>cronErrorMessage</Argument>
</Message>
<Range>Expression.GetHighlightingRange()</Range>
<Behavour overlapResolvePolicy="NONE"/>
</Error>

</Errors>
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) 2020 JetBrains s.r.o.
// <p/>
// All rights reserved.
// <p/>
// MIT License
// <p/>
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// <p/>
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of
// the Software.
// <p/>
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using JetBrains.Annotations;
using JetBrains.DocumentModel;
using JetBrains.ReSharper.Azure.Psi.FunctionApp;
using JetBrains.ReSharper.Feature.Services.CodeCompletion;
using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure;
using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.LookupItems;
using JetBrains.ReSharper.Features.Intellisense.CodeCompletion.CSharp;
using JetBrains.ReSharper.Plugins.FSharp.Psi;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Features.CodeCompletion;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Impl;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;

namespace JetBrains.ReSharper.Azure.FSharp.FunctionApp.CodeCompletion.Rules
{
[Language(typeof(FSharpLanguage))]
public class FSharpTimerTriggerCronArgumentsProvider : ItemsProviderOfSpecificContext<FSharpCodeCompletionContext>
{
// TODO: Refactor so C# and F# share this
private readonly struct CronSuggestion
{
public readonly string Expression;
public readonly string Description;

public CronSuggestion(string expression, string description)
{
Expression = expression;
Description = description;
}
}

// TODO: Refactor so C# and F# share this
private readonly CronSuggestion[] CronSuggestions =
{
new CronSuggestion("* * * * * *", "Every second"),
new CronSuggestion("0 * * * * *", "Every minute"),
new CronSuggestion("0 */5 * * * *", "Every 5 minutes"),
new CronSuggestion("0 0 * * * *", "Every hour"),
new CronSuggestion("0 0 */6 * * *", "Every 6 hours at minute 0"),
new CronSuggestion("0 0 8-18 * * *", "Every hour between 08:00 AM and 06:59 PM"),
new CronSuggestion("0 0 0 * * *", "At 12:00 AM"),
new CronSuggestion("0 0 10 * * *", "At 10:00 AM"),
new CronSuggestion("0 0 * * * 1-5", "Every hour, Monday through Friday"),
new CronSuggestion("0 0 0 * * 0", "At 12:00 AM, only on Sunday"),
new CronSuggestion("0 0 9 * * Mon", "At 09:00 AM, only on Monday"),
new CronSuggestion("0 0 0 1 * *", "At 12:00 AM, on day 1 of the month"),
new CronSuggestion("0 0 0 1 1 *", "At 12:00 AM, on day 1 of the month, only in January"),
new CronSuggestion("0 0 * * * Sun", "Every hour, only on Sunday"),
new CronSuggestion("0 0 0 * * Sat,Sun", "At 12:00 AM, only on Saturday and Sunday"),
new CronSuggestion("0 0 0 * * 6,0", "At 12:00 AM, only on Saturday and Sunday"),
new CronSuggestion("0 0 0 1-7 * Sun", "At 12:00 AM, between day 1 and 7 of the month, only on Sunday"),
new CronSuggestion("11 5 23 * * *", "At 11:05:11 PM"),
new CronSuggestion("*/15 * * * * *", "Every 15 seconds"),
new CronSuggestion("0 30 9 * Jan Mon", "At 09:30 AM, only on Monday, only in January")
};

protected override bool IsAvailable(FSharpCodeCompletionContext context)
{
return IsStringLiteral(context) &&
IsTimerTriggerAnnotation(context) &&
context.BasicContext.CodeCompletionType == CodeCompletionType.BasicCompletion;
}

protected override bool AddLookupItems(FSharpCodeCompletionContext context, IItemsCollector collector)
{
foreach (var cronSuggestion in CronSuggestions)
{
var token = context.TokenAtCaret as ITokenNode;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@auduchinok Seems completion can only be triggered right before a string, so {caret}"....." triggers completion, "...{caret}...." does not. This completion provider now assumes the quotes are always included. This means that on L98 below, I have to provide completion items with " ?

(I could be misunderstanding the PSI as well)

Copy link

@auduchinok auduchinok Nov 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have weird completion contexts in places where it wasn't previously possible to complete things. There're chances I'll need to fix it up a bit for completion inside strings to work as expected.

if (token == null) return false;

var literalExpression = token.Parent as ILiteralExpr;
if (literalExpression == null) return false;

var ranges = GetRangeFor(literalExpression);

var lookupItem =
CSharpLookupItemFactory.Instance.CreateTextLookupItem(new TextLookupRanges(ranges, ranges),
"\"" + cronSuggestion.Expression + "\"");

lookupItem.Presentation.DisplayName.Text = cronSuggestion.Expression;
lookupItem.Presentation.DisplayTypeName.Text = cronSuggestion.Description;

// Replace spaces prefixes with nbsp to make sure <space> terminates the lookup.
// It is required since we need to support case when a user choose to complete by <space>.
// TODO: Disable until RIDER-45943 is fixed.
// lookupItem.WithMatcher(item =>
// new TextualMatcher<TextualInfo>(item.Info.Text.Replace(" ", "·"), item.Info));

collector.Add(lookupItem);
}

return true;
}

private static DocumentRange GetRangeFor([NotNull] ILiteralExpr expression)
{
var underQuotesRange = expression.GetTreeTextRange();
var containingFile = expression.GetContainingFile();
return containingFile?.GetDocumentRange(underQuotesRange) ?? DocumentRange.InvalidRange;
}

private bool IsStringLiteral([NotNull] FSharpCodeCompletionContext context)
{
return context.TokenAtCaret is ITokenNode token &&
token.GetTokenType().IsStringLiteral;
}

private bool IsTimerTriggerAnnotation([NotNull] FSharpCodeCompletionContext context)
{
var token = context.TokenAtCaret as ITokenNode;
if (token == null) return false;

var literalExpression = token.Parent as ILiteralExpr;
var attribute = AttributeNavigator.GetByExpression(literalExpression.IgnoreParentParens());

if (attribute == null) return false;
if (attribute.Arguments.Count != 1) return false;

var resolveResult = attribute.ReferenceName.Reference.Resolve();
return resolveResult.DeclaredElement is ITypeElement typeElement &&
FunctionAppFinder.IsTimerTriggerAttribute(typeElement);
}
}
}
Loading