Skip to content

Commit

Permalink
After grace period, emit code with warnings
Browse files Browse the repository at this point in the history
It was made quite clear that (some?) folks would rather just disable our friendly warning rather than sponsor. So we'll instead just make sponsorship mandatory.

See #352 (comment)

I worked hard to accomodate very flexible options for sponsoring, and this project ain't maintaining itself. It's fine if folks use something else. I made this initially just for myself, and I'm glad it's been useful for others.

TODO: pending Strings to use the same approach (those don't leverage Constants like the others).
  • Loading branch information
kzu committed Sep 15, 2024
1 parent 313e6da commit b0a5220
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 30 deletions.
5 changes: 4 additions & 1 deletion src/Directory.targets
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@

> This project uses SponsorLink and may issue IDE-only warnings if no active sponsorship is detected. Learn more at https://github.com/devlooped#sponsorlink.
</Description>
<IsAnalyzer>$(PackFolder.StartsWith('analyzers/'))</IsAnalyzer>
</PropertyGroup>

<ItemGroup>
<NoneWithoutCopyToOutputDirectory Include="@(None)" Exclude="@(None -> HasMetadata('CopyToOutputDirectory'))" />
<None Update="@(NoneWithoutCopyToOutputDirectory)" CopyToOutputDirectory="PreserveNewest" />
<EmbeddedResource Include="@(None -> WithMetadataValue('Extension', '.sbntxt'))" />
<None Update="@(None -> WithMetadataValue('Extension', '.sbntxt'))" Pack="true" />
</ItemGroup>

<ItemGroup Condition="'$(IsAnalyzer)' == 'true'">
<PackageFile Include="$(MSBuildThisFileDirectory)_._" PackagePath="lib/netstandard2.0/_._" />
<PackageFile Include="*.props;*.targets" PackagePath="build\$(TargetFramework)\%(Filename)%(Extension)" Visible="true" />
</ItemGroup>

<Target Name="PackCopyLocalLockFileAssemblies" Condition="'$(MergeAnalyzerAssemblies)' != 'true'" BeforeTargets="GetPackageContents" DependsOnTargets="ReferenceCopyLocalPathsOutputGroup">
<Target Name="PackCopyLocalLockFileAssemblies" Condition="'$(IsAnalyzer)' == 'true' and '$(MergeAnalyzerAssemblies)' != 'true'" BeforeTargets="GetPackageContents" DependsOnTargets="ReferenceCopyLocalPathsOutputGroup">
<ItemGroup>
<ReferenceCopyLocalAssemblies Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'
And !$([MSBuild]::ValueOrDefault('%(FileName)', '').EndsWith('.resources', StringComparison.OrdinalIgnoreCase))
Expand Down
15 changes: 14 additions & 1 deletion src/Shared/EmbeddedResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@
using System.Linq;
using System.Reflection;

static class EmbeddedResource
/// <summary>
/// Allows easy access to embedded resources.
/// </summary>
public static class EmbeddedResource
{
#if DEBUG
static readonly string baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "";
#endif

/// <summary>
/// Gets the content of the embedded resource at the specified relative path.
/// </summary>
public static string GetContent(string relativePath)
{
using var stream = GetStream(relativePath);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}

/// <summary>
/// Gets the bytes of the embedded resource at the specified relative path.
/// </summary>
public static byte[] GetBytes(string relativePath)
{
using var stream = GetStream(relativePath);
Expand All @@ -25,6 +34,10 @@ public static byte[] GetBytes(string relativePath)
return bytes;
}

/// <summary>
/// Gets the stream of the embedded resource at the specified relative path.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public static Stream GetStream(string relativePath)
{
#if DEBUG
Expand Down
9 changes: 8 additions & 1 deletion src/Shared/PathSanitizer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
using System.Text.RegularExpressions;

static class PathSanitizer
/// <summary>
/// Sanitizes paths for use as identifiers.
/// </summary>
public static class PathSanitizer
{
static readonly Regex invalidCharsRegex = new(@"\W");

/// <summary>
/// Sanitizes the specified path for use as an identifier.
/// </summary>
public static string Sanitize(string path, string parent)
{
var partStr = invalidCharsRegex.Replace(path, "_");
Expand Down
22 changes: 18 additions & 4 deletions src/ThisAssembly.AssemblyInfo/AssemblyInfoGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Scriban;
using static Devlooped.Sponsors.SponsorLink;
using Resources = Devlooped.Sponsors.Resources;

namespace ThisAssembly;

Expand Down Expand Up @@ -41,7 +45,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// Read the ThisAssemblyNamespace property or default to null
var right = context.AnalyzerConfigOptionsProvider
.Select((c, t) => c.GlobalOptions.TryGetValue("build_property.ThisAssemblyNamespace", out var ns) && !string.IsNullOrEmpty(ns) ? ns : null)
.Combine(context.ParseOptionsProvider);
.Combine(context.ParseOptionsProvider.Combine(context.GetStatusOptions()));

context.RegisterSourceOutput(
metadata.Combine(right),
Expand Down Expand Up @@ -74,11 +78,21 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}

static void GenerateSource(SourceProductionContext spc,
(ImmutableArray<KeyValuePair<string, string>> attributes, (string? ns, ParseOptions parse)) arg)
(ImmutableArray<KeyValuePair<string, string>> attributes, (string? ns, (ParseOptions parse, StatusOptions options))) arg)
{
var (attributes, (ns, parse)) = arg;

var (attributes, (ns, (parse, options))) = arg;
var model = new Model(attributes.ToList(), ns);

var status = Diagnostics.GetOrSetStatus(options);
var warn = IsEditor &&
(status == Devlooped.Sponsors.SponsorStatus.Unknown || status == Devlooped.Sponsors.SponsorStatus.Expired);

if (warn)
{
model.Warn = string.Format(CultureInfo.CurrentCulture, Resources.Editor_Disabled, Funding.Product, Funding.HelpUrl);
model.WarnSummary = Resources.Editor_DisabledRemarks;
}

if (parse is CSharpParseOptions cs && (int)cs.LanguageVersion >= 1100)
model.RawStrings = true;

Expand Down
11 changes: 11 additions & 0 deletions src/ThisAssembly.AssemblyInfo/CSharp.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
{{ func obsolete }}
{{~ if Warn ~}}
{{ WarnSummary }}
[Obsolete("{{ Warn }}", false
#if NET6_0_OR_GREATER
, UrlFormat = "{{ Url }}"
#endif
)]
{{~ end ~}}
{{ end }}

using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
Expand All @@ -26,6 +36,7 @@ partial class ThisAssembly
public static partial class Info
{
{{~ for prop in Properties ~}}
{{- obsolete -}}
{{~ if RawStrings ~}}
public const string {{ prop.Key }} =
"""
Expand Down
3 changes: 3 additions & 0 deletions src/ThisAssembly.AssemblyInfo/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public Model(IEnumerable<KeyValuePair<string, string>> properties, string? ns)
public string? Namespace { get; }
public bool RawStrings { get; set; } = false;
public string Version => Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
public string Url => Devlooped.Sponsors.SponsorLink.Funding.HelpUrl;
public string? Warn { get; set; }
public string? WarnSummary { get; set; }

public List<KeyValuePair<string, string>> Properties { get; }
}
16 changes: 14 additions & 2 deletions src/ThisAssembly.Constants/CSharp.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,32 @@
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
{{ func summary }}
{{- func summary -}}
/// <summary>
{{~ if $0.Comment ~}}
/// {{ $0.Comment }}
{{~ else ~}}
/// => @"{{ $0.Value }}"
{{~ end ~}}
/// </summary>
{{- end -}}
{{ func obsolete }}
{{ WarnSummary }}
[Obsolete("{{ Warn }}", false
#if NET6_0_OR_GREATER
, UrlFormat = "{{ Url }}"
#endif
)]

{{ end }}
{{ func render }}
public static partial class {{ $0.Name | string.replace "-" "_" | string.replace " " "_" }}
{
{{~ for value in $0.Values ~}}
{{- summary value ~}}
{{- summary value -}}
{{~ if Warn ~}}
{{ obsolete }}
{{~ end }}
{{~ if RawStrings ~}}
public const string {{ value.Name | string.replace "-" "_" | string.replace " " "_" }} =
"""
Expand Down
29 changes: 16 additions & 13 deletions src/ThisAssembly.Constants/ConstantsGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -58,13 +58,23 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Select((c, t) => c.GlobalOptions.TryGetValue("build_property.ThisAssemblyNamespace", out var ns) && !string.IsNullOrEmpty(ns) ? ns : null)
.Combine(context.ParseOptionsProvider);

context.RegisterSourceOutput(
files.Combine(right),
GenerateConstant);
var inputs = files.Combine(right);
// this is required to ensure status is registered properly independently of analyzer runs.
var options = context.GetStatusOptions();

context.RegisterSourceOutput(inputs.Combine(options),
(spc, source) =>
{
var status = Diagnostics.GetOrSetStatus(source.Right);
var warn = IsEditor &&
(status == Devlooped.Sponsors.SponsorStatus.Unknown || status == Devlooped.Sponsors.SponsorStatus.Expired);

GenerateConstant(spc, source.Left, warn ? string.Format(
CultureInfo.CurrentCulture, Resources.Editor_Disabled, Funding.Product, Funding.HelpUrl) : null);
});
}

void GenerateConstant(SourceProductionContext spc, ((string name, string value, string? comment, string root), (string? ns, ParseOptions parse)) args)
void GenerateConstant(SourceProductionContext spc, ((string name, string value, string? comment, string root), (string? ns, ParseOptions parse)) args, string? warn = default)
{
var ((name, value, comment, root), (ns, parse)) = args;
var cs = (CSharpParseOptions)parse;
Expand All @@ -83,7 +93,7 @@ void GenerateConstant(SourceProductionContext spc, ((string name, string value,
// For now, we only support C# though
var file = parse.Language.Replace("#", "Sharp") + ".sbntxt";
var template = Template.Parse(EmbeddedResource.GetContent(file), file);
var model = new Model(rootArea, ns);
var model = new Model(rootArea, ns, warn, Resources.Editor_DisabledRemarks, Funding.HelpUrl);
if ((int)cs.LanguageVersion >= 1100)
model.RawStrings = true;

Expand All @@ -98,13 +108,6 @@ void GenerateConstant(SourceProductionContext spc, ((string name, string value,
.GetText()
.ToString();
}
//else if (language == LanguageNames.VisualBasic)
//{
// output = Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.ParseCompilationUnit(output)
// .NormalizeWhitespace()
// .GetText()
// .ToString();
//}

spc.AddSource($"{root}.{name}.g.cs", SourceText.From(output, Encoding.UTF8));
}
Expand Down
2 changes: 1 addition & 1 deletion src/ThisAssembly.Constants/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace ThisAssembly;

[DebuggerDisplay("Values = {RootArea.Values.Count}")]
record Model(Area RootArea, string? Namespace)
record Model(Area RootArea, string? Namespace, string? Warn = default, string? WarnSummary = default, string Url = Devlooped.Sponsors.SponsorLink.Funding.HelpUrl)
{
public bool RawStrings { get; set; } = false;
public string Version => Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
Expand Down
6 changes: 3 additions & 3 deletions src/ThisAssembly.Tests/Tests.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using System.Diagnostics.CodeAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Devlooped;
using Xunit;
using Xunit.Abstractions;
//using ThisAssembly = ThisAssemblyTests

[assembly: SuppressMessage("SponsorLink", "SL04")]

namespace ThisAssemblyTests;

public record class Tests(ITestOutputHelper Output)
{
DateTime dxt = DateTime.Now;
[Fact]
public void CanReadResourceFile()
=> Assert.NotNull(ResourceFile.Load("Resources.resx", "Strings"));
Expand Down
16 changes: 12 additions & 4 deletions src/ThisAssembly.Tests/ThisAssembly.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<IsPackable>false</IsPackable>
<TargetFramework>net8.0</TargetFramework>
<ThisAssemblyNamespace>Devlooped</ThisAssemblyNamespace>
<ThisAssemblyNamespace>ThisAssemblyTests</ThisAssemblyNamespace>
<Multiline>
A Description
with a newline and
Expand All @@ -17,10 +17,10 @@
<TargetFramework Condition="'$(BuildingInsideVisualStudio)' == 'true'">net472</TargetFramework>
<RootNamespace>ThisAssemblyTests</RootNamespace>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<NoWarn>CS8981;$(NoWarn)</NoWarn>
<NoWarn>CS0618;CS8981;TA100;$(NoWarn)</NoWarn>
</PropertyGroup>

<Import Project="..\*\*.props" />
<Import Project="..\*\ThisAssembly*.props" />

<ItemGroup>
<Compile Remove="..\*.cs" />
Expand Down Expand Up @@ -87,10 +87,18 @@

<!-- Force immediate reporting of status, no install-time grace period -->
<PropertyGroup>
<SponsorLinkNoInstallGrace>true</SponsorLinkNoInstallGrace>
<!--<SponsorLinkNoInstallGrace>true</SponsorLinkNoInstallGrace>-->
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="SponsorLinkNoInstallGrace" />
</ItemGroup>

<!-- Simulate SL_CollectDependencies -->
<PropertyGroup>
<ThisAssembly>$(Version)</ThisAssembly>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="ThisAssembly" />
</ItemGroup>

</Project>

0 comments on commit b0a5220

Please sign in to comment.