Skip to content

Commit 6e1b646

Browse files
committed
[dotnet] Convert resource utility build step to C# source generator
1 parent a2b4269 commit 6e1b646

File tree

11 files changed

+266
-30
lines changed

11 files changed

+266
-30
lines changed

dotnet/Selenium.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Selenium.WebDriver.Safari.T
2323
EndProject
2424
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Selenium.WebDriver.Support.Tests", "test\support\Selenium.WebDriver.Support.Tests.csproj", "{2136C695-2526-45E0-AE1D-68FBBC6A9DE2}"
2525
EndProject
26+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Selenium.WebDriver.SourceGenerator", "private\Selenium.WebDriver.SourceGenerator\Selenium.WebDriver.SourceGenerator.csproj", "{4C46AED0-45A6-40FB-A300-BDACC54D1F25}"
27+
EndProject
2628
Global
2729
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2830
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +71,10 @@ Global
6971
{2136C695-2526-45E0-AE1D-68FBBC6A9DE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
7072
{2136C695-2526-45E0-AE1D-68FBBC6A9DE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
7173
{2136C695-2526-45E0-AE1D-68FBBC6A9DE2}.Release|Any CPU.Build.0 = Release|Any CPU
74+
{4C46AED0-45A6-40FB-A300-BDACC54D1F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
75+
{4C46AED0-45A6-40FB-A300-BDACC54D1F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
76+
{4C46AED0-45A6-40FB-A300-BDACC54D1F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
77+
{4C46AED0-45A6-40FB-A300-BDACC54D1F25}.Release|Any CPU.Build.0 = Release|Any CPU
7278
EndGlobalSection
7379
GlobalSection(SolutionProperties) = preSolution
7480
HideSolutionNode = FALSE

dotnet/defs.bzl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ load("//dotnet:selenium-dotnet-version.bzl", "SUPPORTED_DEVTOOLS_VERSIONS")
33
load("//dotnet/private:dotnet_nunit_test_suite.bzl", _dotnet_nunit_test_suite = "dotnet_nunit_test_suite")
44
load("//dotnet/private:framework.bzl", _framework = "framework")
55
load("//dotnet/private:generate_devtools.bzl", _generate_devtools = "generate_devtools")
6-
load("//dotnet/private:generate_resources.bzl", _generated_resource_utilities = "generated_resource_utilities")
76
load("//dotnet/private:generated_assembly_info.bzl", _generated_assembly_info = "generated_assembly_info")
87
load("//dotnet/private:nuget_pack.bzl", _nuget_pack = "nuget_pack")
98
load("//dotnet/private:nunit_test.bzl", _nunit_test = "nunit_test")
@@ -20,7 +19,6 @@ csharp_test = _csharp_test
2019
dotnet_nunit_test_suite = _dotnet_nunit_test_suite
2120
framework = _framework
2221
generate_devtools = _generate_devtools
23-
generated_resource_utilities = _generated_resource_utilities
2422
generated_assembly_info = _generated_assembly_info
2523
nuget_pack = _nuget_pack
2624
nunit_test = _nunit_test

dotnet/paket.dependencies

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ nuget NUnit 3.13.2
1515
nuget NUnitLite 3.13.2
1616
nuget System.Text.Json 8.0.5
1717
nuget Runfiles 0.14.0
18+
nuget Microsoft.CodeAnalysis.Analyzers 3.3.4
19+
nuget Microsoft.CodeAnalysis.CSharp 4.9.2
20+
nuget Microsoft.CodeAnalysis.Common 4.9.2

dotnet/paket.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,14 @@ NUGET
6262
System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0)
6363
System.Threading.Tasks.Extensions (4.5.4) - restriction: || (&& (== net8.0) (>= net462)) (&& (== net8.0) (< net6.0)) (== netstandard2.0)
6464
System.Runtime.CompilerServices.Unsafe (>= 4.5.3) - restriction: || (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netcoreapp2.1)) (&& (== net8.0) (< netstandard1.0)) (&& (== net8.0) (< netstandard2.0)) (&& (== net8.0) (>= wp8)) (== netstandard2.0)
65+
Microsoft.CodeAnalysis.Analyzers (3.3.4)
66+
Microsoft.CodeAnalysis.Common (4.9.2)
67+
Microsoft.CodeAnalysis.Analyzers (>= 3.3.4)
68+
System.Collections.Immutable (>= 8.0)
69+
System.Memory (>= 4.5.5) - restriction: || (&& (== net9.0) (< net6.0)) (== netstandard2.0)
70+
System.Reflection.Metadata (>= 8.0)
71+
System.Runtime.CompilerServices.Unsafe (>= 6.0)
72+
System.Text.Encoding.CodePages (>= 8.0) - restriction: || (&& (== net9.0) (< net6.0)) (== netstandard2.0)
73+
System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net9.0) (< net6.0)) (== netstandard2.0)
74+
Microsoft.CodeAnalysis.CSharp (4.9.2)
75+
Microsoft.CodeAnalysis.Common (4.9.2)

dotnet/paket.nuget.bzl

Lines changed: 8 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build_property.EnforceExtendedAnalyzerRules = true
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
load("@rules_dotnet//dotnet:defs.bzl", "csharp_library")
2+
load("//dotnet:defs.bzl", "framework")
3+
4+
csharp_library(
5+
name = "resource-utilities-generator",
6+
srcs = [
7+
"ResourceUtilitiesAtomGenerator.cs",
8+
],
9+
analyzer_configs = [
10+
".analyzerconfig",
11+
],
12+
langversion = "latest",
13+
is_analyzer = True,
14+
nullable = "enable",
15+
is_language_specific_analyzer = True,
16+
target_frameworks = ["netstandard2.0"],
17+
visibility = ["//dotnet:__subpackages__"],
18+
deps = [
19+
framework("nuget", "microsoft.codeanalysis.analyzers"),
20+
framework("nuget", "microsoft.codeanalysis.common"),
21+
framework("nuget", "microsoft.codeanalysis.csharp"),
22+
framework("nuget", "System.Collections.Immutable"),
23+
framework("nuget", "System.Memory"),
24+
],
25+
)
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Text;
3+
using System;
4+
using System.Collections.Immutable;
5+
using System.Data;
6+
using System.IO;
7+
using System.Text;
8+
using System.Threading;
9+
10+
namespace Selenium.WebDriver.SourceGenerator;
11+
12+
[Generator]
13+
public class ResourceUtilitiesGenerator : IIncrementalGenerator
14+
{
15+
public void Initialize(IncrementalGeneratorInitializationContext context)
16+
{
17+
var jsFiles = context.AdditionalTextsProvider
18+
.Where(static (text) => text.Path.EndsWith(".js") || text.Path.EndsWith(".json"))
19+
.WithTrackingName("AtomFiles")
20+
.Select(static (data, token) =>
21+
{
22+
var name = Path.GetFileName(data.Path);
23+
var code = GenerateAtom(data, token, out var diagnostics);
24+
25+
return (name, code, diagnostics);
26+
});
27+
28+
context.RegisterSourceOutput(jsFiles, static (context, pair) =>
29+
{
30+
foreach (var diagnostic in pair.diagnostics)
31+
{
32+
context.ReportDiagnostic(diagnostic);
33+
}
34+
if (pair.code is not null)
35+
{
36+
var propertyName = GetPropertyNameFromFilePath(pair.name, out _, out var diag);
37+
if (diag is not null)
38+
{
39+
context.ReportDiagnostic(diag);
40+
}
41+
42+
context.AddSource($"ResourceUtilities.{propertyName}.g.cs", SourceText.From(pair.code, Encoding.UTF8));
43+
}
44+
});
45+
}
46+
47+
private static string? GenerateAtom(AdditionalText additionalText, CancellationToken token, out ImmutableArray<Diagnostic> diagnostics)
48+
{
49+
diagnostics = [];
50+
var sourceText = additionalText.GetText(token);
51+
if (sourceText is null)
52+
{
53+
var d = Diagnostic.Create(new DiagnosticDescriptor("WRG1001", "Failed to read atom", "Atom '{0}' could not be read", "WebDriverResourceGenerator", DiagnosticSeverity.Error, true), Location.None, additionalText.Path);
54+
diagnostics = [d];
55+
return null;
56+
}
57+
58+
var propertyName = GetPropertyNameFromFilePath(additionalText.Path, out var lang, out var diag);
59+
if (diag is not null)
60+
{
61+
diagnostics = [diag];
62+
}
63+
64+
return $$"""""""
65+
// <auto-generated />
66+
67+
namespace OpenQA.Selenium.Internal;
68+
69+
internal static partial class ResourceUtilities
70+
{
71+
[global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("{{lang}}")]
72+
internal const string {{propertyName}} =
73+
"""""
74+
{{sourceText}}
75+
""""";
76+
}
77+
78+
""""""";
79+
}
80+
81+
private static string GetPropertyNameFromFilePath(string filePath, out string language, out Diagnostic? diagnostic)
82+
{
83+
diagnostic = null;
84+
if (filePath.EndsWith("webdriver.json"))
85+
{
86+
language = "json";
87+
return "WebDriverPrefsJson";
88+
}
89+
else if (filePath.EndsWith("is-displayed.js"))
90+
{
91+
language = "javascript";
92+
return "IsDisplayedAtom";
93+
}
94+
else if (filePath.EndsWith("mutation-listener.js"))
95+
{
96+
language = "javascript";
97+
return "MutationListenerAtom";
98+
}
99+
else if (filePath.EndsWith("get-attribute.js"))
100+
{
101+
language = "javascript";
102+
return "GetAttributeAtom";
103+
}
104+
else if (filePath.EndsWith("find-elements.js"))
105+
{
106+
language = "javascript";
107+
return "FindElementsAtom";
108+
}
109+
110+
diagnostic = Diagnostic.Create(new DiagnosticDescriptor("WRG1002", "Unknown resource file", "Unknown file in the resource generator '{0}'", "WebDriverResourceGenerator", DiagnosticSeverity.Warning, true), Location.None, filePath);
111+
112+
var suffix = filePath.EndsWith(".js") ? "Atom" : "Json";
113+
114+
language = string.Empty;
115+
return KebabCaseToPascalCase(Path.GetFileNameWithoutExtension(filePath)) + suffix;
116+
}
117+
118+
private static string KebabCaseToPascalCase(string v)
119+
{
120+
Span<char> newValues = new char[v.Length];
121+
int newValuesOffset = 0;
122+
for (int i = 0; i < v.Length; i++)
123+
{
124+
if (i == 0)
125+
{
126+
newValues[i - newValuesOffset] = char.ToUpperInvariant(v[i]);
127+
}
128+
else if (char.IsLetter(v[i]))
129+
{
130+
newValues[i - newValuesOffset] = v[i];
131+
}
132+
else
133+
{
134+
i++;
135+
newValuesOffset++;
136+
newValues[i - newValuesOffset] = char.ToUpperInvariant(v[i]);
137+
}
138+
}
139+
140+
return newValues.Slice(0, v.Length - newValuesOffset).ToString();
141+
}
142+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<LangVersion>preview</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
8+
9+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
10+
<IncludeBuildOutput>false</IncludeBuildOutput>
11+
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
16+
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
21+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
22+
</ItemGroup>
23+
24+
</Project>

0 commit comments

Comments
 (0)