Skip to content

Commit 68b01be

Browse files
authored
Language injections: support IDEA language injections (#482)
1 parent 493711a commit 68b01be

File tree

57 files changed

+1177
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1177
-89
lines changed

ReSharper.FSharp/src/FSharp.Psi.Features/FSharp.Psi.Features.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<Compile Include="src\Injected\FSharpLiteralInjectionTarget.fs" />
9090
<Compile Include="src\Injected\FSharpRegexProviders.fs" />
9191
<Compile Include="src\Injected\FSharpRegexNodeProvider.fs" />
92+
<Compile Include="src\Injected\FSharpInjectionTargetsFinderFactory.fs" />
9293
<Compile Include="src\FSharpTypingAssist.fs" />
9394
<Compile Include="src\ZoneMarker.fs" />
9495
</ItemGroup>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Injected
2+
3+
open System
4+
open JetBrains.Application
5+
open JetBrains.ReSharper.Plugins.FSharp.Psi.Injections
6+
open JetBrains.ReSharper.Plugins.FSharp.Util
7+
open JetBrains.ReSharper.Plugins.FSharp.Psi
8+
open JetBrains.ReSharper.Psi
9+
open JetBrains.ReSharper.Psi.CodeAnnotations
10+
open JetBrains.ReSharper.Psi.Tree
11+
open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl
12+
open JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
13+
open JetBrains.ReSharper.Psi.impl.Shared.InjectedPsi
14+
open JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Daemon.Highlightings.FSharpErrorUtil
15+
open JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Injected.FSharpInjectionAnnotationUtil
16+
17+
type FSharpInjectionTargetsFinder() =
18+
let possibleInjectorFunctionNames = [|"html"; "css"; "sql"; "javascript"; "json"; "jsx"|]
19+
let normalizeLanguage = function
20+
| "js" | "jsx" -> "javascript"
21+
| language -> language
22+
23+
let checkForAttributes (expr: IFSharpExpression) =
24+
match getAttributesOwner expr with
25+
| ValueNone -> ValueNone
26+
| ValueSome attributesOwner ->
27+
28+
let info = getAnnotationInfo<StringSyntaxAnnotationProvider, string>(attributesOwner)
29+
if isNotNull info then ValueSome(info, "", "") else
30+
let info = getAnnotationInfo<LanguageInjectionAnnotationProvider, InjectionAnnotationInfo>(attributesOwner)
31+
if isNotNull info then ValueSome(info.Language, info.Prefix, info.Suffix) else ValueNone
32+
33+
static member val Instance = FSharpInjectionTargetsFinder()
34+
35+
interface ILanguageInjectionTargetsFinder with
36+
member this.Find(searchRoot, consumer) =
37+
let mutable descendants = searchRoot.CompositeDescendants()
38+
while descendants.MoveNext() do
39+
Interruption.Current.CheckAndThrow()
40+
41+
match descendants.Current with
42+
| :? IInjectionHostNode as expr when expr.IsValidHost ->
43+
match checkForAttributes expr with
44+
| ValueSome(language, prefix, suffix) when not (equalsIgnoreCase language "Regex") ->
45+
consumer.Consume(expr, normalizeLanguage language, prefix, suffix)
46+
| _ ->
47+
48+
let prefixApp = PrefixAppExprNavigator.GetByArgumentExpression(expr.IgnoreParentParens())
49+
if isNotNull prefixApp then
50+
// support injection functions
51+
// https://github.com/alfonsogarciacaro/vscode-template-fsharp-highlight
52+
match prefixApp.FunctionExpression.IgnoreInnerParens() with
53+
| :? IReferenceExpr as ref when isSimpleQualifiedName ref ->
54+
let language = normalizeLanguage ref.ShortName
55+
if Array.contains language possibleInjectorFunctionNames then
56+
consumer.Consume(expr, language, "", "")
57+
| _ -> ()
58+
else match tryGetTypeProviderName (expr.As<IConstExpr>()) with
59+
| ValueSome "SqlCommandProvider" ->
60+
consumer.Consume(expr, "sql", "", "")
61+
| ValueSome "JsonProvider" when expr.GetText().Contains("{") ->
62+
consumer.Consume(expr, "json", "", "")
63+
| ValueSome "XmlProvider" when expr.GetText().Contains("<") ->
64+
consumer.Consume(expr, "xml", "", "")
65+
| _ -> ()
66+
67+
| :? IChameleonNode as c when not c.IsOpened -> descendants.SkipThisNode()
68+
| _ -> ()
69+
70+
71+
[<Language(typeof<FSharpLanguage>)>]
72+
type FSharInjectionTargetsFinderFactory() =
73+
interface ILanguageInjectionTargetsFinderFactory with
74+
member this.CreateAnnotationTargetsFinder() = FSharpInjectionTargetsFinder.Instance

ReSharper.FSharp/src/FSharp.Psi.Features/src/Injected/FSharpInjectionUtil.fs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ open JetBrains.ReSharper.Psi
55
open JetBrains.ReSharper.Psi.CodeAnnotations
66
open JetBrains.ReSharper.Psi.Tree
77
open JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Util.FSharpMethodInvocationUtil
8+
open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl
89

910
let getAnnotationInfo<'AnnotationProvider, 'TAnnotationInfo
1011
when 'AnnotationProvider :> CodeAnnotationInfoProvider<IAttributesOwner, 'TAnnotationInfo>>
@@ -37,3 +38,12 @@ let getAttributesOwner (expr: IFSharpExpression) =
3738

3839
if isNull declaration then ValueNone else
3940
declaration.DeclaredElement.As<IAttributesOwner>() |> ValueOption.ofObj
41+
42+
let tryGetTypeProviderName (expr: IConstExpr) =
43+
let providedTypeName =
44+
ExprStaticConstantTypeUsageNavigator.GetByExpression(expr)
45+
|> PrefixAppTypeArgumentListNavigator.GetByTypeUsage
46+
|> TypeReferenceNameNavigator.GetByTypeArgumentList
47+
48+
if isNotNull providedTypeName then ValueSome (providedTypeName.Identifier.GetSourceName())
49+
else ValueNone

ReSharper.FSharp/src/FSharp.Psi.Features/src/Injected/FSharpRegexNodeProvider.fs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,6 @@ type FSharpRegexNodeProvider() =
7676
let isRegexActivePat = isNotNull parametersOwnerPat && parametersOwnerPat.Identifier.GetSourceName() = "Regex"
7777
if isRegexActivePat then ValueSome RegexOptions.None else ValueNone
7878

79-
let checkForRegexTypeProvider (expr: IConstExpr) =
80-
let providedTypeName =
81-
ExprStaticConstantTypeUsageNavigator.GetByExpression(expr)
82-
|> PrefixAppTypeArgumentListNavigator.GetByTypeUsage
83-
|> TypeReferenceNameNavigator.GetByTypeArgumentList
84-
85-
let isRegexProvider = isNotNull providedTypeName && providedTypeName.Identifier.GetSourceName() = "Regex"
86-
if isRegexProvider then ValueSome RegexOptions.None else ValueNone
87-
8879
interface IInjectionNodeProvider with
8980
override _.Check(node, _, data) =
9081
data <- null
@@ -96,7 +87,10 @@ type FSharpRegexNodeProvider() =
9687
| :? ILiteralExpr as expr ->
9788
let checkAttributesResult = checkForAttributes expr
9889
if checkAttributesResult.IsSome then checkAttributesResult else
99-
checkForRegexTypeProvider expr
90+
91+
match tryGetTypeProviderName expr with
92+
| ValueSome "Regex" -> ValueSome RegexOptions.None
93+
| _ -> ValueNone
10094

10195
| :? ILiteralPat as pat -> checkForRegexActivePattern pat
10296
| _ -> ValueNone

ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/InterpolatedStringExpr.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using JetBrains.DocumentModel;
2+
using JetBrains.ReSharper.Plugins.FSharp.Psi.Injections;
23
using JetBrains.ReSharper.Plugins.FSharp.Psi.Parsing;
34
using JetBrains.ReSharper.Psi.Tree;
45

@@ -27,5 +28,7 @@ public DocumentRange GetDollarSignRange()
2728
? startOffset.ExtendRight(+1)
2829
: startOffset.Shift(+1).ExtendRight(+1);
2930
}
31+
32+
bool IInjectionHostNode.IsValidHost => true;
3033
}
3134
}

ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/LiteralExpr.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using JetBrains.ReSharper.Plugins.FSharp.Psi.Injections;
23
using JetBrains.ReSharper.Plugins.FSharp.Psi.Parsing;
34
using JetBrains.ReSharper.Psi;
45
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
@@ -110,7 +111,7 @@ public override ConstantValue ConstantValue
110111

111112
try
112113
{
113-
var result = Convert.ToInt32(literalText, (int) literalBase);
114+
var result = Convert.ToInt32(literalText, (int)literalBase);
114115
return ConstantValue.Create(result, GetPsiModule().GetPredefinedType().Int);
115116
}
116117
catch (Exception)
@@ -147,5 +148,11 @@ private enum IntBase
147148
Decimal = 10,
148149
Hexadecimal = 16
149150
}
151+
152+
bool IInjectionHostNode.IsValidHost =>
153+
Literal?.GetTokenType() is { } tokenType &&
154+
(tokenType == FSharpTokenType.STRING ||
155+
tokenType == FSharpTokenType.VERBATIM_STRING ||
156+
tokenType == FSharpTokenType.TRIPLE_QUOTED_STRING);
150157
}
151158
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
2+
3+
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Injections
4+
{
5+
public interface IInjectionHostNode: IFSharpExpression
6+
{
7+
bool IsValidHost { get; }
8+
}
9+
}

ReSharper.FSharp/src/FSharp.Psi/src/Tree/IInterpolatedStringExpr.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using JetBrains.DocumentModel;
2+
using JetBrains.ReSharper.Plugins.FSharp.Psi.Injections;
23

34
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
45
{
5-
public partial interface IInterpolatedStringExpr
6+
public partial interface IInterpolatedStringExpr : IInjectionHostNode
67
{
78
public bool IsTrivial();
89
public DocumentRange GetDollarSignRange();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using JetBrains.ReSharper.Plugins.FSharp.Psi.Injections;
2+
3+
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
4+
{
5+
public partial interface ILiteralExpr : IInjectionHostNode
6+
{
7+
}
8+
}

rider-fsharp/build.gradle.kts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,15 @@ intellij {
6868

6969
// rider-plugins-appender: workaround for https://youtrack.jetbrains.com/issue/IDEA-179607
7070
// org.intellij.intelliLang needed for tests with language injection marks
71-
plugins.set(listOf("rider-plugins-appender", "org.intellij.intelliLang"))
71+
plugins.set(
72+
listOf(
73+
"rider-plugins-appender",
74+
"org.intellij.intelliLang",
75+
"DatabaseTools",
76+
"css-impl",
77+
"javascript-impl"
78+
)
79+
)
7280
}
7381

7482
val repoRoot = projectDir.parentFile!!

0 commit comments

Comments
 (0)