diff --git a/.github/workflows/os-matrix.json b/.github/workflows/os-matrix.json
index 2d5c9b00..adbf7228 100644
--- a/.github/workflows/os-matrix.json
+++ b/.github/workflows/os-matrix.json
@@ -1 +1 @@
-["windows-latest", "ubuntu-latest", "macOS-latest"]
\ No newline at end of file
+[ "ubuntu-latest", "macOS-latest", "windows-latest" ]
\ No newline at end of file
diff --git a/.netconfig b/.netconfig
index aa97b32f..147d9bcc 100644
--- a/.netconfig
+++ b/.netconfig
@@ -40,24 +40,19 @@
etag = 2c6335b37e4ae05eea7c01f5d0c9d82b49c488f868a8b5ba7bff7c6ff01f3994
weak
sha = 0683ee777d7d878d4bf013d7deea352685135a05
-[file "SponsorLink.sln"]
- url = https://github.com/devlooped/oss/blob/main/SponsorLink.sln
- skip
[file "src/Directory.Build.props"]
url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props
- etag = f177eb767aaa6a347da43ff7ff419c9a0736c562cb171e17ded8007a1945a8b0
+ etag = c8b56f3860cc7ccb8773b7bd6189f5c7a6e3a2c27e9104c1ee201fbdc5af9873
weak
- sha = 14deaea5cecc64df51781d29891a2f67caf8be16
+ sha = b76de49afb376aa48eb172963ed70663b59b31d3
[file "src/Directory.Build.targets"]
url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets
- etag = 7cb1421f00d9f6f4c00f0ca98e485dcadb927cfa6b3f0b5d4fb212525d2ce9c0
+ etag = 1a3a0151b5771ee97ed8351254ff4c18a0ff568e0df5c33c6830f069bfbb067b
weak
- sha = 1bf1eacc7ac3920d52c8e7045bfa34abc7c05302
+ sha = 33a20db26e47589769284817b271ce67ea9ccfd8
[file "src/kzu.snk"]
url = https://github.com/devlooped/oss/blob/main/src/kzu.snk
- etag = b8d789b5b6bea017cdcc8badcea888ad78de3e34298efca922054e9fb0e7b6b9
- weak
- sha = 0683ee777d7d878d4bf013d7deea352685135a05
+ skip
[file ".netconfig"]
url = https://github.com/devlooped/oss/blob/main/.netconfig
etag = d5e93b15ea9c23293b985ec6e2bd0a225d002f9a5820b6b972c105a89eba12bb
@@ -134,198 +129,235 @@
sha = 1afd173fe8f81b510c597737b0d271218e81fa73
etag = 482dc2c892fc7ce0cb3a01eb5d9401bee50ddfb067d8cb85873555ce63cf5438
weak
+[file "src/SponsorLink"]
+ url = https://github.com/devlooped/SponsorLink/tree/main/samples/dotnet/
[file "src/SponsorLink/Analyzer/Analyzer.csproj"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/Analyzer.csproj
- sha = 93df7c7ec34f83ae58efbf213624d5ea31fe3c41
- etag = f76e33fde812244a275b95c8815101f6f87d144a5305a2c1f0f631f770d91920
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/Analyzer.csproj
+ sha = 200107908a6aafac1957ac905f47d144b5bf8939
+ etag = c4832b00fdb1a6111d686c95badebb01ab5eb24ffb9ecbe45325c82f704ccf54
weak
[file "src/SponsorLink/Analyzer/Properties/launchSettings.json"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/Properties/launchSettings.json
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/Properties/launchSettings.json
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 6c59ab4d008e3221e316c9e3b6e0da155b892680d48cdc400a39d53cb9a12aac
weak
[file "src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs
- sha = 5009784040109cb405cddc8404d00c0d9290f40d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/StatusReportingAnalyzer.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 27d85baa35ecd8be15105ec370a44a68d6ba611eb2616c070e0375b706a04a17
weak
[file "src/SponsorLink/Analyzer/StatusReportingGenerator.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/StatusReportingGenerator.cs
- sha = 5009784040109cb405cddc8404d00c0d9290f40d
- etag = 61c31eb675aada69a0506dba4d32b472fbfbe715bbcbe14cd8e7424a88eb7479
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/StatusReportingGenerator.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 44bf8df4db4a6a5d6f6c82568b57a044dfe2d2116e5a2493ef8fe929d9d3da41
weak
[file "src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
- sha = 717ddb136f83f1d9f55723d7155949ce808e9990
- etag = 2f679d203aa27b2de0b5b6bcb04490bc2251aea5fd1310cf238fcaeaa82d646b
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/buildTransitive/SponsorableLib.targets
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 3ab7309d005cfac33bf52d139692911a4e494db749a8c401b692ce28fa2ae45e
weak
[file "src/SponsorLink/Directory.Build.props"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Directory.Build.props
- sha = 3b943f5aa59f33141d1c0fffcb215446d594ad53
- etag = 0c7737411744012078642dbfc174af3f2ac7dc9f7b8ea4423981ae38753a5be4
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Directory.Build.props
+ sha = 7b5109b5b5a53a2cc16759b776c4a092aec5ca57
+ etag = 5d4e433c71291ea953d328aa26b2d93cdf4708271f0eb024138ba2e0db93ab15
weak
[file "src/SponsorLink/Directory.Build.targets"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Directory.Build.targets
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Directory.Build.targets
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 9938f29c3573bf8bdb9686e1d9884dee177256b1d5dd7ee41472dd64bfbdd92d
weak
[file "src/SponsorLink/Library/Library.csproj"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/Library.csproj
- sha = 4b7f922d42ce50db5d740de80450a761b374a48d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/Library.csproj
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 06a4ffe0d8f24f795a484b1e86bcbad538437d819c41dd1ae0e5184a1e4a7d31
weak
[file "src/SponsorLink/Library/MyClass.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/MyClass.cs
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/MyClass.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = b5b3ccd6cd14bb90dd9702b9d7e52cc22c11e601c039617738d688f9fd45d49b
weak
[file "src/SponsorLink/Library/Resources.resx"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/Resources.resx
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/Resources.resx
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = aff6051733d22982e761f2b414173aafeab40e0a76a142e2b33025dced213eb2
weak
[file "src/SponsorLink/Library/readme.md"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/readme.md
- sha = 55124bc610b2dcad9efb343bdffc79c959170593
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/readme.md
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 5002ac8c5bbeee60c13937a32c1b6c1a5dbf0065617c8f2550e6eca6fded256d
weak
[file "src/SponsorLink/SponsorLink.Tests.targets"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink.Tests.targets
- sha = ba1310c46580419960da85aa4db1196449606a10
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink.Tests.targets
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 5be3b99c0049cbe98c305f5af2fd482a3a995960c3a1fc6dd2bffcbf3b7e4d52
weak
[file "src/SponsorLink/SponsorLink.targets"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink.targets
- sha = 5813f21a2de409b5b56d059034b1617e29fbe2fd
- etag = 1aa2218536333b7ed887f276a5e380fa91c833607134d6c8939feb36cbc07c1c
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink.targets
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = bba2a5e8b8e3ddba2460b83a2169bcc32597bcc5f0840e48cbb074022663a487
weak
[file "src/SponsorLink/SponsorLink/AppDomainDictionary.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/AppDomainDictionary.cs
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/AppDomainDictionary.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 4a70f86e73f951bca95618c221d821e38a31ef9092af4ac61447eab845671a28
weak
[file "src/SponsorLink/SponsorLink/DiagnosticsManager.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
- sha = 4b7f922d42ce50db5d740de80450a761b374a48d
- etag = d2bc6f7165f44852ae1f6ae5796f7a1aa490df4e7f5c152e9c0a50c2f2998503
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/DiagnosticsManager.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 0f2bb6afa53513c0837dc7621fdf86f66cbd8b5eaa1978047dc50b96e3a87c99
weak
[file "src/SponsorLink/SponsorLink/ManifestStatus.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/ManifestStatus.cs
- sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/ManifestStatus.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = e46848f83c0436ba33a1c09a4060ad627a74db41bab66bb37ca40fce8a6532a7
weak
[file "src/SponsorLink/SponsorLink/Resources.es.resx"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/Resources.es.resx
- sha = c879f25bf483086725c8a29f104555644e6ee542
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es.resx
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = c0a05bb5efedf8e30a73ab96678579ad33832e4a4aec75d3b596b47f248c23f5
weak
[file "src/SponsorLink/SponsorLink/Resources.resx"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/Resources.resx
- sha = c879f25bf483086725c8a29f104555644e6ee542
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.resx
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = fcb46a86511cb7996e8dcd1f4e283cea9cd51610b094ac49a7396301730814b0
weak
[file "src/SponsorLink/SponsorLink/SponsorLink.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.cs
- sha = 5009784040109cb405cddc8404d00c0d9290f40d
- etag = bd5a52b6e24f7a6893292b3ee9a5179f9355f1a608ae072113e29af09f833518
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = a5b16dc3c8022add752db5c3b0055fbfd7df9b14dad2ff1a6360a6ecafda1ce1
weak
[file "src/SponsorLink/SponsorLink/SponsorLink.csproj"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.csproj
- sha = 068140b6855afb9cf9f4ab02002d80ca11389314
- etag = e48d567ebf51fa1877d010ce3f0b51ab5415848939bc356979f2f575f67a75eb
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.csproj
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = dee8fab315723f4d82c5b0106f3f15ecc4d663fbba68e324289bdaada50c14e5
weak
[file "src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
- sha = 4b7f922d42ce50db5d740de80450a761b374a48d
- etag = 793ced2e53b39cba2965b6aa5d8e295c8466c9e34a42ddd1d88888427811190d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLinkAnalyzer.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 9c4604895aafcbc6bfe859f1b9fb0313a94e315b336556b0b78ea553d4eac1e1
weak
[file "src/SponsorLink/SponsorLink/SponsorStatus.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorStatus.cs
- sha = 4fca946c3201d90d30e2183f699c850dcc1bf8d5
- etag = 9a5f6f35c38c34b77796925d80addc998e204bc112fcd5fc124030060390e7c2
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorStatus.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = a29e71737e91725deb450384379ff955dfab67bb0846e77b2c234dfb036d3f7a
weak
[file "src/SponsorLink/SponsorLink/SponsorableLib.targets"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorableLib.targets
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorableLib.targets
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 2f923a97081481a6a264d63c8ff70ce5ba65c3dbaf7ea078cbe1388fb0868e1c
weak
[file "src/SponsorLink/SponsorLink/Tracing.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/Tracing.cs
- sha = 08a8488036f0a8e42a62af400f37b54165ad771a
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Tracing.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 29d6c0362f4c47eedfebea5018d563adb04a8f7b30da87495c5c8a4561e2c4ed
weak
[file "src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
- sha = 717ddb136f83f1d9f55723d7155949ce808e9990
- etag = b27f6cbb67b8e12541b2c3fe914d071ddd051bac2e895c73495667b65829385e
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 2d1bdeba897ce160ebb2988e75bf89ca9034699b736b1e24ed621a5e8d47e2d7
weak
[file "src/SponsorLink/SponsorLink/sponsorable.md"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/sponsorable.md
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/sponsorable.md
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 9c275d50705a2e661f0f86f1ae5e555c0033a05e86e12f936283a5b5ef47ae77
weak
[file "src/SponsorLink/SponsorLinkAnalyzer.sln"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLinkAnalyzer.sln
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLinkAnalyzer.sln
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = fc2928c9b303d81ff23891ee791a859b794d9f2d4b9f4e81b9ed15e5b74db487
weak
[file "src/SponsorLink/Tests/.netconfig"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/.netconfig
- sha = 068140b6855afb9cf9f4ab02002d80ca11389314
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/.netconfig
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 0323e19eb4582113dd409853ba83e9845069bf35733ed84a0bdc9fb6990502a9
weak
+[file "src/SponsorLink/Tests/AnalyzerTests.cs"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/AnalyzerTests.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 39227dc53df109e4d450cfe075ded530a8d23f9ea7b293dca58a6fdd3746875b
+ weak
[file "src/SponsorLink/Tests/Attributes.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Attributes.cs
- sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Attributes.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 1d7c17a2c9424db73746112c338a39e0000134ac878b398e2aa88f7ea5c0c488
weak
[file "src/SponsorLink/Tests/Extensions.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Extensions.cs
- sha = 068140b6855afb9cf9f4ab02002d80ca11389314
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Extensions.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 9e51b7e6540fae140490a5283b1e67ce071bd18a267bc2ae0b35c7248261aed1
weak
[file "src/SponsorLink/Tests/JsonOptions.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/JsonOptions.cs
- sha = 068140b6855afb9cf9f4ab02002d80ca11389314
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/JsonOptions.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 17799725ad9b24eb5998365962c30b9a487bddadca37c616e35b76b8c9eb161a
weak
-[file "src/SponsorLink/Tests/Resources.Designer.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Resources.Designer.cs
- sha = c879f25bf483086725c8a29f104555644e6ee542
- etag = 69404ac09238930893fdbc225ae7839b14957e129b4c05f1ef0e7afcc4c91d63
- weak
[file "src/SponsorLink/Tests/Resources.resx"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Resources.resx
- sha = c879f25bf483086725c8a29f104555644e6ee542
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Resources.resx
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 13d1bb8b0de32a8c9b5dbdc806a036ed89d423cd7c0be187b8c56055c9bf7783
weak
[file "src/SponsorLink/Tests/Sample.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Sample.cs
- sha = 5009784040109cb405cddc8404d00c0d9290f40d
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Sample.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 8d32d6b062061672f37acd9360cdeb0d5fc87e15f2f65f355c2a1ee98e8da21c
weak
[file "src/SponsorLink/Tests/SponsorLinkTests.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/SponsorLinkTests.cs
- sha = d74f5111504a0fae6e5a1e68ca92bf7afddb3254
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorLinkTests.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = 1fa41250bd984e8aa840a966d34ce0e94f2111d1422d7f50b864c38364fcf4a4
weak
[file "src/SponsorLink/Tests/SponsorableManifest.cs"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/SponsorableManifest.cs
- sha = 7febebc3ad82adcd6de9a0f151a36473d458d61b
- etag = 310cf1b2245e7f4c2863e019f48087f6a0b57644407ab38447804d8ada2647a9
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorableManifest.cs
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = eb2292c6d7bf53a56acbb73d7c89ccc78fd8bec2e2198d70e36da93c01d36374
weak
[file "src/SponsorLink/Tests/Tests.csproj"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Tests.csproj
- sha = 5009784040109cb405cddc8404d00c0d9290f40d
- etag = be8bfd0877cd974a2f940eba4a9e697c4061db57e97efca24f9092a73a644d9e
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Tests.csproj
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 2be5d70bc8f03845aa4fe84209628fc5d32ea1fd6db89ce32bf72813a4138347
+ weak
+[file "src/SponsorLink/Tests/keys/kzu.key"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = bd8f5b16d248829e9cf4d8695677b2b7c09607d2b50b1cda05dbaa48c2a3fe04
+ weak
+[file "src/SponsorLink/Tests/keys/kzu.key.jwk"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key.jwk
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = dca60d636ab866adf211662a5aa597e4d1f477a280f6ee82cd7f7b390535a458
+ weak
+[file "src/SponsorLink/Tests/keys/kzu.key.txt"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key.txt
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 7553487806f6dbd219b4dbda5d6fb097b8047a1d1856255a339e049c7496da43
+ weak
+[file "src/SponsorLink/Tests/keys/kzu.pub"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.pub
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 75c544bb911372c909a58d6d07e89abe776ef618861f6d580915b0e79c6bb2fe
+ weak
+[file "src/SponsorLink/Tests/keys/kzu.pub.jwk"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.pub.jwk
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 9a2829bf01fe53089c0f4ff46f5bca60955338bbfc7a2354482cde05dc750806
+ weak
+[file "src/SponsorLink/Tests/keys/kzu.pub.txt"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.pub.txt
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 6308869899eb7efeee34dc4daa71ee04a06f21cc09199beb74a78af8e213f576
+ weak
+[file "src/SponsorLink/Tests/keys/sponsorlink.jwt"]
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/sponsorlink.jwt
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = af05cc803434a0e22b67521be8bb66676c5c0ca0795afb4430bd26751ce307e1
weak
[file "src/SponsorLink/jwk.ps1"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/jwk.ps1
- sha = c4830fc3b1aa78ec98d1d2ea4fed86ef0b7b803c
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/jwk.ps1
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
etag = f399e05ecb56adaf41d2545171f299a319142b17dd09fc38e452ca8c5d13bd0d
weak
[file "src/SponsorLink/readme.md"]
- url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/readme.md
- sha = 827a1d18bf0245978d81bcd3d52e9e6f1584d1ef
- etag = 079b4aedba2aa9851e609b569f25c55db8d5922e3dbb1adc22611ce4d6cfe465
+ url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/readme.md
+ sha = f47528874a6d9192b5546f84b455f5ccc474a707
+ etag = 89f9984ca933c7647eff583e36374ba1c21c94342649d07cd369ef0ea4f056b9
weak
diff --git a/src/Directory.props b/src/Directory.props
index 2cdb13a9..989986aa 100644
--- a/src/Directory.props
+++ b/src/Directory.props
@@ -4,6 +4,7 @@
dotnet roslyn
true
+ false
true
4.0
analyzers/dotnet/roslyn$(ThisAssemblyMinimumRoslynVersion)/cs
diff --git a/src/SponsorLink/Analyzer/Analyzer.csproj b/src/SponsorLink/Analyzer/Analyzer.csproj
index f65390a7..7bb62ab3 100644
--- a/src/SponsorLink/Analyzer/Analyzer.csproj
+++ b/src/SponsorLink/Analyzer/Analyzer.csproj
@@ -6,9 +6,11 @@
true
analyzers/dotnet/roslyn4.0
true
+ false
+ true
$(MSBuildThisFileDirectory)..\SponsorLink.targets
- true
disable
+ SponsorableLib
@@ -22,16 +24,23 @@
-
-
-
-
-
+
+
+
+
+
+ $([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\Tests\keys\kzu.pub.jwk'))
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/SponsorLink/Analyzer/StatusReportingGenerator.cs b/src/SponsorLink/Analyzer/StatusReportingGenerator.cs
index 0a13b1cb..a1437f9d 100644
--- a/src/SponsorLink/Analyzer/StatusReportingGenerator.cs
+++ b/src/SponsorLink/Analyzer/StatusReportingGenerator.cs
@@ -10,10 +10,13 @@ public class StatusReportingGenerator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterSourceOutput(
- context.GetSponsorManifests(),
+ // this is required to ensure status is registered properly independently
+ // of analyzer runs.
+ context.GetSponsorAdditionalFiles().Combine(context.AnalyzerConfigOptionsProvider),
(spc, source) =>
{
- var status = Diagnostics.GetOrSetStatus(source);
+ var (manifests, options) = source;
+ var status = Diagnostics.GetOrSetStatus(manifests, options);
spc.AddSource("StatusReporting.cs", $"// Status: {status}");
});
}
diff --git a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets b/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
index 37585e86..8c0f8efd 100644
--- a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
+++ b/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
@@ -2,6 +2,6 @@
-
+
\ No newline at end of file
diff --git a/src/SponsorLink/Directory.Build.props b/src/SponsorLink/Directory.Build.props
index 8afa0611..191107d6 100644
--- a/src/SponsorLink/Directory.Build.props
+++ b/src/SponsorLink/Directory.Build.props
@@ -24,24 +24,4 @@
SponsorableLib
-
-
-
-
-
-
-
-
-
diff --git a/src/SponsorLink/SponsorLink.targets b/src/SponsorLink/SponsorLink.targets
index 4678d5d0..83dffe17 100644
--- a/src/SponsorLink/SponsorLink.targets
+++ b/src/SponsorLink/SponsorLink.targets
@@ -6,7 +6,8 @@
true
- true
+ true
+ false
true
@@ -14,6 +15,7 @@
$(Product)
+ $(PackageId)
$([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))
@@ -68,28 +70,38 @@
+
+ true
+ false
+
+
-
+
-
+
-
+
-
+
+
+
+ $(FundingProduct)
namespace Devlooped.Sponsors%3B
partial class SponsorLink
{
public partial class Funding
{
+ public const string PackageId = "$(FundingPackageId)"%3B
public const string Product = "$(FundingProduct)"%3B
public const string Prefix = "$(FundingPrefix)"%3B
public const int Grace = $(FundingGrace)%3B
@@ -141,7 +153,7 @@ partial class SponsorLink
-
+
diff --git a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs b/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
index 96e7e14c..9320ad21 100644
--- a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
+++ b/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
@@ -9,6 +9,7 @@
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading;
using Humanizer;
using Humanizer.Localisation;
@@ -50,18 +51,19 @@ ConcurrentDictionary Diagnostics
/// The removed diagnostic, or if none was previously pushed.
public void ReportOnce(Action report, string product = Funding.Product)
{
- if (Diagnostics.TryRemove(product, out var diagnostic))
+ if (Diagnostics.TryRemove(product, out var diagnostic) &&
+ GetStatus(diagnostic) != SponsorStatus.Grace)
{
// Ensure only one such diagnostic is reported per product for the entire process,
// so that we can avoid polluting the error list with duplicates across multiple projects.
var id = string.Concat(Process.GetCurrentProcess().Id, product, diagnostic.Id);
using var mutex = new Mutex(false, "mutex" + id);
mutex.WaitOne();
- using var mmf = MemoryMappedFile.CreateOrOpen(id, 1);
+ using var mmf = CreateOrOpenMemoryMappedFile(id, 1);
using var accessor = mmf.CreateViewAccessor();
if (accessor.ReadByte(0) == 0)
{
- accessor.Write(0, 1);
+ accessor.Write(0, (byte)1);
report(diagnostic);
Tracing.Trace($"👈{diagnostic.Severity.ToString().ToLowerInvariant()}:{Process.GetCurrentProcess().Id}:{Process.GetCurrentProcess().ProcessName}:{product}:{diagnostic.Id}");
}
@@ -75,52 +77,61 @@ public void ReportOnce(Action report, string product = Funding.Produ
/// https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md under Ordering of actions).
///
/// Optional that was reported, if any.
+ ///
+ /// The SponsorLinkAnalyzer.GetOrSetStatus uses diagnostic properties to store the
+ /// kind of diagnostic as a simple string instead of the enum. We do this so that
+ /// multiple analyzers or versions even across multiple products, which all would
+ /// have their own enum, can still share the same diagnostic kind.
+ ///
public SponsorStatus? GetStatus()
- {
- // NOTE: the SponsorLinkAnalyzer.SetStatus uses diagnostic properties to store the
- // kind of diagnostic as a simple string instead of the enum. We do this so that
- // multiple analyzers or versions even across multiple products, which all would
- // have their own enum, can still share the same diagnostic kind.
- if (Diagnostics.TryGetValue(Funding.Product, out var diagnostic) &&
- diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value))
- {
- // Switch on value matching DiagnosticKind names
- return value switch
- {
- nameof(SponsorStatus.Unknown) => SponsorStatus.Unknown,
- nameof(SponsorStatus.Sponsor) => SponsorStatus.Sponsor,
- nameof(SponsorStatus.Expiring) => SponsorStatus.Expiring,
- nameof(SponsorStatus.Expired) => SponsorStatus.Expired,
- _ => null,
- };
- }
-
- return null;
- }
+ => Diagnostics.TryGetValue(Funding.Product, out var diagnostic) ? GetStatus(diagnostic) : null;
///
/// Gets the status of the , or sets it from
/// the given set of if not already set.
///
- public SponsorStatus GetOrSetStatus(ImmutableArray manifests)
- => GetOrSetStatus(() => manifests);
+ public SponsorStatus GetOrSetStatus(ImmutableArray manifests, AnalyzerConfigOptionsProvider options)
+ => GetOrSetStatus(() => manifests, () => options.GlobalOptions);
///
/// Gets the status of the , or sets it from
/// the given analyzer if not already set.
///
public SponsorStatus GetOrSetStatus(Func options)
- => GetOrSetStatus(() => options().GetSponsorManifests());
+ => GetOrSetStatus(() => options().GetSponsorAdditionalFiles(), () => options()?.AnalyzerConfigOptionsProvider.GlobalOptions);
- SponsorStatus GetOrSetStatus(Func> getManifests)
+ SponsorStatus GetOrSetStatus(Func> getAdditionalFiles, Func getGlobalOptions)
{
if (GetStatus() is { } status)
return status;
- if (!SponsorLink.TryRead(out var claims, getManifests().Select(text =>
+ if (!SponsorLink.TryRead(out var claims, getAdditionalFiles().Where(x => x.Path.EndsWith(".jwt")).Select(text =>
(text.GetText()?.ToString() ?? "", Sponsorables[Path.GetFileNameWithoutExtension(text.Path)]))) ||
claims.GetExpiration() is not DateTime exp)
{
+ var noGrace = getGlobalOptions() is { } globalOptions &&
+ globalOptions.TryGetValue("build_property.SponsorLinkNoInstallGrace", out var value) &&
+ bool.TryParse(value, out var skipCheck) && skipCheck;
+
+ if (noGrace != true)
+ {
+ // Consider grace period if we can find the install time.
+ var installed = getAdditionalFiles()
+ .Where(x => x.Path.EndsWith(".dll"))
+ .Select(x => File.GetLastWriteTime(x.Path))
+ .OrderByDescending(x => x)
+ .FirstOrDefault();
+
+ if (installed != default && ((DateTime.Now - installed).TotalDays <= Funding.Grace))
+ {
+ // report unknown, either unparsed manifest or one with no expiration (which we never emit).
+ Push(Diagnostic.Create(KnownDescriptors[SponsorStatus.Unknown], null,
+ properties: ImmutableDictionary.Create().Add(nameof(SponsorStatus), nameof(SponsorStatus.Grace)),
+ Funding.Product, Sponsorables.Keys.Humanize(Resources.Or)));
+ return SponsorStatus.Grace;
+ }
+ }
+
// report unknown, either unparsed manifest or one with no expiration (which we never emit).
Push(Diagnostic.Create(KnownDescriptors[SponsorStatus.Unknown], null,
properties: ImmutableDictionary.Create().Add(nameof(SponsorStatus), nameof(SponsorStatus.Unknown)),
@@ -169,25 +180,54 @@ Diagnostic Push(Diagnostic diagnostic, string product = Funding.Product)
var id = string.Concat(Process.GetCurrentProcess().Id, product, diagnostic.Id);
using var mutex = new Mutex(false, "mutex" + id);
mutex.WaitOne();
- using var mmf = MemoryMappedFile.CreateOrOpen(id, 1);
+ using var mmf = CreateOrOpenMemoryMappedFile(id, 1);
using var accessor = mmf.CreateViewAccessor();
- accessor.Write(0, 0);
+ accessor.Write(0, (byte)0);
Tracing.Trace($"👉{diagnostic.Severity.ToString().ToLowerInvariant()}:{Process.GetCurrentProcess().Id}:{Process.GetCurrentProcess().ProcessName}:{product}:{diagnostic.Id}");
}
return diagnostic;
}
+ SponsorStatus? GetStatus(Diagnostic? diagnostic) => diagnostic?.Properties.TryGetValue(nameof(SponsorStatus), out var value) == true
+ ? value switch
+ {
+ nameof(SponsorStatus.Grace) => SponsorStatus.Grace,
+ nameof(SponsorStatus.Unknown) => SponsorStatus.Unknown,
+ nameof(SponsorStatus.Sponsor) => SponsorStatus.Sponsor,
+ nameof(SponsorStatus.Expiring) => SponsorStatus.Expiring,
+ nameof(SponsorStatus.Expired) => SponsorStatus.Expired,
+ _ => null,
+ }
+ : null;
+
+ static MemoryMappedFile CreateOrOpenMemoryMappedFile(string mapName, int capacity)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return MemoryMappedFile.CreateOrOpen(mapName, capacity);
+ }
+ else
+ {
+ // On Linux, use a file-based memory-mapped file
+ string filePath = $"/tmp/{mapName}";
+ using (var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
+ fs.Write(new byte[capacity], 0, capacity);
+
+ return MemoryMappedFile.CreateFromFile(filePath, FileMode.OpenOrCreate);
+ }
+ }
+
internal static DiagnosticDescriptor CreateSponsor(string[] sponsorable, string prefix) => new(
- $"{prefix}100",
- Resources.Sponsor_Title,
- Resources.Sponsor_Message,
- "SponsorLink",
- DiagnosticSeverity.Info,
- isEnabledByDefault: true,
- description: Resources.Sponsor_Description,
- helpLinkUri: "https://github.com/devlooped#sponsorlink",
- "DoesNotSupportF1Help");
+ $"{prefix}100",
+ Resources.Sponsor_Title,
+ Resources.Sponsor_Message,
+ "SponsorLink",
+ DiagnosticSeverity.Info,
+ isEnabledByDefault: true,
+ description: Resources.Sponsor_Description,
+ helpLinkUri: "https://github.com/devlooped#sponsorlink",
+ "DoesNotSupportF1Help");
internal static DiagnosticDescriptor CreateUnknown(string[] sponsorable, string product, string prefix) => new(
$"{prefix}101",
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.cs b/src/SponsorLink/SponsorLink/SponsorLink.cs
index b3b1cf3d..1efc4876 100644
--- a/src/SponsorLink/SponsorLink/SponsorLink.cs
+++ b/src/SponsorLink/SponsorLink/SponsorLink.cs
@@ -64,31 +64,38 @@ static partial class SponsorLink
.Max().DateTime is var exp && exp == DateTime.MinValue ? null : exp;
///
- /// Gets all sponsor manifests from the provided analyzer options.
+ /// Gets all necessary additional files to determine status.
///
- public static ImmutableArray GetSponsorManifests(this AnalyzerOptions? options)
+ public static ImmutableArray GetSponsorAdditionalFiles(this AnalyzerOptions? options)
=> options == null ? ImmutableArray.Create() : options.AdditionalFiles
- .Where(x =>
- options.AnalyzerConfigOptionsProvider.GetOptions(x).TryGetValue("build_metadata.SponsorManifest.ItemType", out var itemType) &&
- itemType == "SponsorManifest" &&
- Sponsorables.ContainsKey(Path.GetFileNameWithoutExtension(x.Path)))
+ .Where(x => x.IsSponsorManifest(options.AnalyzerConfigOptionsProvider) || x.IsSponsorableAnalyzer(options.AnalyzerConfigOptionsProvider))
.ToImmutableArray();
///
/// Gets all sponsor manifests from the provided analyzer options.
///
- public static IncrementalValueProvider> GetSponsorManifests(this IncrementalGeneratorInitializationContext context)
+ public static IncrementalValueProvider> GetSponsorAdditionalFiles(this IncrementalGeneratorInitializationContext context)
=> context.AdditionalTextsProvider.Combine(context.AnalyzerConfigOptionsProvider)
.Where(source =>
{
- var (text, options) = source;
- return options.GetOptions(text).TryGetValue("build_metadata.SponsorManifest.ItemType", out var itemType) &&
- itemType == "SponsorManifest" &&
- Sponsorables.ContainsKey(Path.GetFileNameWithoutExtension(text.Path));
+ var (text, provider) = source;
+ return text.IsSponsorManifest(provider) || text.IsSponsorableAnalyzer(provider);
})
.Select((source, c) => source.Left)
.Collect();
+ static bool IsSponsorManifest(this AdditionalText text, AnalyzerConfigOptionsProvider provider)
+ => provider.GetOptions(text).TryGetValue("build_metadata.SponsorManifest.ItemType", out var itemType) &&
+ itemType == "SponsorManifest" &&
+ Sponsorables.ContainsKey(Path.GetFileNameWithoutExtension(text.Path));
+
+ static bool IsSponsorableAnalyzer(this AdditionalText text, AnalyzerConfigOptionsProvider provider)
+ => provider.GetOptions(text) is { } options &&
+ options.TryGetValue("build_metadata.Analyzer.ItemType", out var itemType) &&
+ options.TryGetValue("build_metadata.Analyzer.NuGetPackageId", out var packageId) &&
+ itemType == "Analyzer" &&
+ packageId == Funding.PackageId;
+
///
/// Reads all manifests, validating their signatures.
///
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.csproj b/src/SponsorLink/SponsorLink/SponsorLink.csproj
index 740b146c..64797921 100644
--- a/src/SponsorLink/SponsorLink/SponsorLink.csproj
+++ b/src/SponsorLink/SponsorLink/SponsorLink.csproj
@@ -6,11 +6,13 @@
disable
false
CoreResGen;$(CoreCompileDependsOn)
+ SponsorLink
$(Product)
+ $(PackageId)
$([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))
@@ -37,13 +39,18 @@
+
+
+ $(FundingProduct)
namespace Devlooped.Sponsors%3B
partial class SponsorLink
{
public partial class Funding
{
+ public const string PackageId = "$(FundingPackageId)"%3B
public const string Product = "$(FundingProduct)"%3B
public const string Prefix = "$(FundingPrefix)"%3B
public const int Grace = $(FundingGrace)%3B
diff --git a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs b/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
index 0cf507fa..dcdf2e91 100644
--- a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
+++ b/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
@@ -1,9 +1,6 @@
//
#nullable enable
-using System;
using System.Collections.Immutable;
-using System.IO;
-using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using static Devlooped.Sponsors.SponsorLink;
@@ -40,48 +37,11 @@ public override void Initialize(AnalysisContext context)
// Never report any diagnostic unless we're in an editor.
if (IsEditor)
{
+ // NOTE: for multiple projects with the same product name, we only report one diagnostic,
+ // so it's expected to NOT get a diagnostic back. Also, we don't want to report
+ // multiple diagnostics for each project in a solution that uses the same product.
ctx.RegisterCompilationEndAction(ctx =>
- {
- // NOTE: for multiple projects with the same product name, we only report one diagnostic,
- // so it's expected to NOT get a diagnostic back. Also, we don't want to report
- // multiple diagnostics for each project in a solution that uses the same product.
- Diagnostics.ReportOnce(diagnostic =>
- {
- // For unknown (never sync'ed), only report if install grace period is over
- if (status == SponsorStatus.Unknown)
- {
- var noGrace = ctx.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.SponsorLinkNoInstallGrace", out var value) &&
- bool.TryParse(value, out var skipCheck) && skipCheck;
-
- // NOTE: we'll always report if noGrace is set to true, regardless of install time, for
- // testing purposes. This can be achieved via MSBuild with:
- //
- // true
- //
- //
- //
- //
- if (noGrace == false)
- {
- var installed = ctx.Options.AdditionalFiles.Where(x =>
- {
- var options = ctx.Options.AnalyzerConfigOptionsProvider.GetOptions(x);
- // In release builds, we'll have a single such item, since we IL-merge the analyzer.
- return options.TryGetValue("build_metadata.Analyzer.ItemType", out var itemType) &&
- options.TryGetValue("build_metadata.Analyzer.NuGetPackageId", out var packageId) &&
- itemType == "Analyzer" &&
- packageId == Funding.Product;
- }).Select(x => File.GetLastWriteTime(x.Path)).OrderByDescending(x => x).FirstOrDefault();
-
- // NOTE: if we can't determine install time, we'll always report.
- if (installed != default && installed.AddDays(Funding.Grace) > DateTime.Now)
- return;
- }
- }
-
- ctx.ReportDiagnostic(diagnostic);
- });
- });
+ Diagnostics.ReportOnce(diagnostic => ctx.ReportDiagnostic(diagnostic)));
}
});
#pragma warning restore RS1013 // Start action has no registered non-end actions
diff --git a/src/SponsorLink/SponsorLink/SponsorStatus.cs b/src/SponsorLink/SponsorLink/SponsorStatus.cs
index 6cdbc901..97b344e4 100644
--- a/src/SponsorLink/SponsorLink/SponsorStatus.cs
+++ b/src/SponsorLink/SponsorLink/SponsorStatus.cs
@@ -11,6 +11,10 @@ public enum SponsorStatus
///
Unknown,
///
+ /// Sponsorship status is unknown, but within the grace period.
+ ///
+ Grace,
+ ///
/// The sponsors manifest is expired but within the grace period.
///
Expiring,
diff --git a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets b/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
index 9f843e25..6e4492ae 100644
--- a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
+++ b/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
@@ -51,15 +51,15 @@
-
+
- %(SponsorablePackageId.Identity)
+ %(FundingPackageId.Identity)
-
+
diff --git a/src/SponsorLink/Tests/AnalyzerTests.cs b/src/SponsorLink/Tests/AnalyzerTests.cs
new file mode 100644
index 00000000..daed0fb2
--- /dev/null
+++ b/src/SponsorLink/Tests/AnalyzerTests.cs
@@ -0,0 +1,223 @@
+extern alias Analyzer;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using Analyzer::Devlooped.Sponsors;
+using Devlooped.Sponsors;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using Xunit;
+
+namespace Tests;
+
+public class AnalyzerTests : IDisposable
+{
+ static readonly SponsorableManifest sponsorable = new(
+ new Uri("https://sponsorlink.devlooped.com"),
+ [new Uri("https://github.com/sponsors/devlooped"), new Uri("https://github.com/sponsors/kzu")],
+ "a82350fb2bae407b3021",
+ new JsonWebKey(ThisAssembly.Resources.keys.kzu_key.Text));
+
+ public AnalyzerTests()
+ {
+ // Simulate being a VS IDE for analyzers to actually run.
+ if (Environment.GetEnvironmentVariable("VSAPPIDNAME") == null)
+ Environment.SetEnvironmentVariable("VSAPPIDNAME", "test");
+ }
+
+ void IDisposable.Dispose()
+ {
+ if (Environment.GetEnvironmentVariable("VSAPPIDNAME") == "test")
+ Environment.SetEnvironmentVariable("VSAPPIDNAME", null);
+ }
+
+ [Fact]
+ public async Task WhenNoAdditionalFiles_ThenReportsUnknown()
+ {
+ var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
+ .WithAnalyzers([new SponsorLinkAnalyzer()]);
+
+ var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
+
+ Assert.NotEmpty(diagnostics);
+
+ var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+
+ Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+ var status = Enum.Parse(value);
+
+ Assert.Equal(SponsorStatus.Unknown, status);
+ }
+
+ [Fact]
+ public async Task WhenUnknownAndGrace_ThenDoesNotReport()
+ {
+ // simulate an analyzer file with the right metadata, which is recent and therefore
+ // within the grace period
+ var dll = Path.Combine(GetTempPath(), "FakeAnalyzer.dll");
+ File.WriteAllText(dll, "");
+
+ var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
+ .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(dll)], new TestAnalyzerConfigOptionsProvider(new())
+ {
+ { "build_metadata.Analyzer.ItemType", "Analyzer" },
+ { "build_metadata.Analyzer.NuGetPackageId", "SponsorableLib" }
+ }));
+
+ var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
+
+ Assert.Empty(diagnostics);
+ }
+
+ [Fact]
+ public async Task WhenUnknownAndNoGraceOption_ThenReportsUnknown()
+ {
+ // simulate an analyzer file with the right metadata, which is recent and therefore
+ // within the grace period
+ var dll = Path.Combine(GetTempPath(), "FakeAnalyzer.dll");
+ File.WriteAllText(dll, "");
+
+ var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
+ .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(dll)], new TestAnalyzerConfigOptionsProvider(new())
+ {
+ { "build_property.SponsorLinkNoInstallGrace", "true" },
+ { "build_metadata.Analyzer.ItemType", "Analyzer" },
+ { "build_metadata.Analyzer.NuGetPackageId", "SponsorableLib" }
+ }));
+
+ var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
+
+ Assert.NotEmpty(diagnostics);
+
+ var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+
+ Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+ var status = Enum.Parse(value);
+
+ Assert.Equal(SponsorStatus.Unknown, status);
+ }
+
+ [Fact]
+ public async Task WhenUnknownAndGraceExpired_ThenReportsUnknown()
+ {
+ // simulate an analyzer file with the right metadata, which is recent and therefore
+ // within the grace period
+ var dll = Path.Combine(GetTempPath(), "FakeAnalyzer.dll");
+ File.WriteAllText(dll, "");
+ File.SetLastWriteTimeUtc(dll, DateTime.UtcNow - TimeSpan.FromDays(30));
+
+ var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
+ .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(dll)], new TestAnalyzerConfigOptionsProvider(new())
+ {
+ { "build_metadata.Analyzer.ItemType", "Analyzer" },
+ { "build_metadata.Analyzer.NuGetPackageId", "SponsorableLib" }
+ }));
+
+ var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
+
+ Assert.NotEmpty(diagnostics);
+
+ var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+
+ Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+ var status = Enum.Parse(value);
+
+ Assert.Equal(SponsorStatus.Unknown, status);
+ }
+
+ [Fact]
+ public async Task WhenSponsoring_ThenReportsSponsor()
+ {
+ var sponsor = sponsorable.Sign([], expiration: TimeSpan.FromMinutes(5));
+ var jwt = Path.Combine(GetTempPath(), "kzu.jwt");
+ File.WriteAllText(jwt, sponsor, Encoding.UTF8);
+
+ var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
+ .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(jwt)], new TestAnalyzerConfigOptionsProvider(new())
+ {
+ { "build_metadata.SponsorManifest.ItemType", "SponsorManifest" }
+ }));
+
+ var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
+
+ Assert.NotEmpty(diagnostics);
+
+ var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+
+ Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+ var status = Enum.Parse(value);
+
+ Assert.Equal(SponsorStatus.Sponsor, status);
+ }
+
+ [Fact]
+ public async Task WhenMultipleAnalyzers_ThenReportsOnce()
+ {
+ var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
+ .WithAnalyzers([new SponsorLinkAnalyzer(), new SponsorLinkAnalyzer()]);
+
+ var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
+
+ Assert.NotEmpty(diagnostics);
+
+ var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+
+ Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
+ var status = Enum.Parse(value);
+
+ Assert.Equal(SponsorStatus.Unknown, status);
+ }
+
+
+ string GetTempPath([CallerMemberName] string? test = default)
+ {
+ var path = Path.Combine(Path.GetTempPath(), test ?? nameof(AnalyzerTests));
+ Directory.CreateDirectory(path);
+ return path;
+ }
+
+ class AdditionalTextFile(string path) : AdditionalText
+ {
+ public override string Path => path;
+ public override SourceText GetText(CancellationToken cancellationToken) => SourceText.From(File.ReadAllText(Path), Encoding.UTF8);
+ }
+
+ class TestAnalyzerConfigOptionsProvider(Dictionary options) : AnalyzerConfigOptionsProvider, IDictionary
+ {
+ AnalyzerConfigOptions analyzerOptions = new TestAnalyzerConfigOptions(options);
+ public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => analyzerOptions;
+
+ public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => analyzerOptions;
+ public void Add(string key, string value) => options.Add(key, value);
+ public bool ContainsKey(string key) => options.ContainsKey(key);
+ public bool Remove(string key) => options.Remove(key);
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) => options.TryGetValue(key, out value);
+ public void Add(KeyValuePair item) => ((ICollection>)options).Add(item);
+ public void Clear() => ((ICollection>)options).Clear();
+ public bool Contains(KeyValuePair item) => ((ICollection>)options).Contains(item);
+ public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)options).CopyTo(array, arrayIndex);
+ public bool Remove(KeyValuePair item) => ((ICollection>)options).Remove(item);
+ public IEnumerator> GetEnumerator() => ((IEnumerable>)options).GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)options).GetEnumerator();
+ public override AnalyzerConfigOptions GlobalOptions => analyzerOptions;
+ public ICollection Keys => options.Keys;
+ public ICollection Values => options.Values;
+ public int Count => ((ICollection>)options).Count;
+ public bool IsReadOnly => ((ICollection>)options).IsReadOnly;
+ public string this[string key] { get => options[key]; set => options[key] = value; }
+
+ class TestAnalyzerConfigOptions(Dictionary options) : AnalyzerConfigOptions
+ {
+ public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value);
+ }
+ }
+}
diff --git a/src/SponsorLink/Tests/Resources.Designer.cs b/src/SponsorLink/Tests/Resources.Designer.cs
deleted file mode 100644
index 7824a607..00000000
--- a/src/SponsorLink/Tests/Resources.Designer.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace Tests {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tests.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/src/SponsorLink/Tests/SponsorableManifest.cs b/src/SponsorLink/Tests/SponsorableManifest.cs
index d65d0fb7..907fc103 100644
--- a/src/SponsorLink/Tests/SponsorableManifest.cs
+++ b/src/SponsorLink/Tests/SponsorableManifest.cs
@@ -2,6 +2,7 @@
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
+using System.Text.Json.Serialization;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
@@ -186,15 +187,25 @@ public string ToJwt(SigningCredentials? signing = default)
/// Sign the JWT claims with the provided RSA key.
///
public string Sign(IEnumerable claims, RSA rsa, TimeSpan? expiration = default)
- => Sign(claims, new RsaSecurityKey(rsa), expiration);
-
- public string Sign(IEnumerable claims, RsaSecurityKey? key = default, TimeSpan? expiration = default)
{
- var rsa = key ?? SecurityKey as RsaSecurityKey;
- if (rsa?.PrivateKeyStatus != PrivateKeyStatus.Exists)
+ var key = new RsaSecurityKey(rsa);
+ if (key.PrivateKeyStatus != PrivateKeyStatus.Exists)
throw new NotSupportedException("No private key found or specified to sign the manifest.");
- var signing = new SigningCredentials(rsa, SecurityAlgorithms.RsaSha256);
+ // Don't allow mismatches of public manifest key and the one used to sign, to avoid
+ // weird run-time errors verifiying manifests that were signed with a different key.
+ if (!rsa.ThumbprintEquals(SecurityKey))
+ throw new ArgumentException($"Cannot sign with a private key that does not match the manifest public key.");
+
+ return Sign(claims, key, expiration);
+ }
+
+ ///
+ /// Sign the JWT claims, optionally overriding the used for signing.
+ ///
+ public string Sign(IEnumerable claims, SecurityKey? key = default, TimeSpan? expiration = default)
+ {
+ var credentials = new SigningCredentials(key ?? SecurityKey, SecurityAlgorithms.RsaSha256);
var expirationDate = expiration != null ?
DateTime.UtcNow.Add(expiration.Value) :
@@ -240,11 +251,6 @@ public string Sign(IEnumerable claims, RsaSecurityKey? key = default, Tim
tokenClaims.Insert(1, new(JwtRegisteredClaimNames.Aud, audience));
}
- // Don't allow mismatches of public manifest key and the one used to sign, to avoid
- // weird run-time errors verifiying manifests that were signed with a different key.
- if (!rsa.ThumbprintEquals(SecurityKey))
- throw new ArgumentException($"Cannot sign with a private key that does not match the manifest public key.");
-
return new JsonWebTokenHandler
{
MapInboundClaims = false,
@@ -254,7 +260,7 @@ public string Sign(IEnumerable claims, RsaSecurityKey? key = default, Tim
Subject = new ClaimsIdentity(tokenClaims),
IssuedAt = DateTime.UtcNow,
Expires = expirationDate,
- SigningCredentials = signing,
+ SigningCredentials = credentials,
});
}
@@ -333,7 +339,7 @@ public string ClientId
internal set
{
clientId = value;
- var thumb = JsonWebKeyConverter.ConvertFromSecurityKey(SecurityKey).ComputeJwkThumbprint();
+ var thumb = SecurityKey.ComputeJwkThumbprint();
hashcode = new HashCode().Add(Issuer, ClientId, Convert.ToBase64String(thumb)).AddRange(Audience).ToHashCode();
}
}
diff --git a/src/SponsorLink/Tests/Tests.csproj b/src/SponsorLink/Tests/Tests.csproj
index 5082c97a..fd98b131 100644
--- a/src/SponsorLink/Tests/Tests.csproj
+++ b/src/SponsorLink/Tests/Tests.csproj
@@ -3,6 +3,7 @@
net8.0
true
+ CS8981;$(NoWarn)
@@ -11,32 +12,35 @@
+
+
-
-
- True
- True
- Resources.resx
-
+
+
+
+
+
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
+
+
+
+
+
+
+
+
@@ -56,7 +60,7 @@
-
+
@@ -66,4 +70,5 @@
+
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.key b/src/SponsorLink/Tests/keys/kzu.key
new file mode 100644
index 00000000..cddc6c6e
Binary files /dev/null and b/src/SponsorLink/Tests/keys/kzu.key differ
diff --git a/src/SponsorLink/Tests/keys/kzu.key.jwk b/src/SponsorLink/Tests/keys/kzu.key.jwk
new file mode 100644
index 00000000..3589e3db
--- /dev/null
+++ b/src/SponsorLink/Tests/keys/kzu.key.jwk
@@ -0,0 +1,11 @@
+{
+ "d": "OmDrKyd0ap7Az2V09C8E6aASK0nnXHGUdTIymmU1tGoxaWpkZ4gLGaYDp4L5fKc-AaqqD3PjvfJvEWfXLqJtEWfUl4gahWrgUkmuzPyAVFFioIzeGIvTGELsR6lTRke0IB2kvfvS7hRgX8Py8ohCAGHiidmoec2SyKkEg0aPdxrIKV8hx5ybC_D_4zRKn0GuVwATIeVZzPpTcyJX_sn4NHDOqut0Xg02iHhMKpF850BSoC97xGMlcSjLocFSTwI63msz6jWQ-6LRVXsfRr2mqakAvsPpqEQ3Ytk9Ud9xW0ctuAWyo6UXev5w2XEL8cSXm33-57fi3ekC_jGCqW0KfAU4Cr2UbuTC0cv8Vv0F4Xm5FizolmuSBFOvf55-eqsjmpwf9hftYAiIlFF-49-P0DpeJejSeoL06BE3e3_IVu3g3HNnSWVUOLJ5Uk5FQ-ieHhf-r2Tq5qZ8_-losHekQbCxCMY2isc-r6V6BMnVL_9kWPxpXwhjKrYxNFZEXUJ1",
+ "dp": "HjCs_QF1Hn1SGS2OqZzYhGhNk4PTw9Hs97E7g3pb4liY0uECYOYp1RNoMyzvNBZVwlxhpeTTS299yPoXeYmseXfLtcjfIVi6mSWS_u27Hd0zfSdaPDOXyyK-mZfIV7Q76RTost0QY3LA0ciJbj3gJqpl38dhuNQ8h9Yqt-TFyb3CUaM3A_JUNKOTce8qnkLrasytPEuSroOBT8bgCWJIjw_mXWMGcoqRFWHw9Nyp9mIyvtPjUQ9ig3bGSP_-3LZf",
+ "dq": "IP6EsAZ_6psFdlQrvnugYFs91fEP5QfBzNHmbfmsPRVX4QM5B3L6klQyJsLqvPfF1Xu17ZffLFkNBKuiphIcLPo0yZTJG9Y7S8gLuPAmrH-ndfxG-bQ8Yt0ZB1pA77ILIS8bUTKrMqAWS-VcaxcSCIyhSusLEWYYDi3PEzB375OUw4aXIk3ob8bePG7UqFSL6qmDPgkGLTxkY9m5dEiOshHygtVY-H_jjOIawliEPgmgAr2M-zlXiphovDyAT0PV",
+ "e": "AQAB",
+ "kty": "RSA",
+ "n": "yP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9-oBw5zIVZekgMP55XxmKvkJd1k-bYWSv-QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y_7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe_bXDkR74yG3mmq_Ne0qhNk6wXuX-NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb-YKry-h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr-OPrxEdZzL9BcI4fMJMz7YdiIu-qbIp_vqatbalfNasumf8RgtPOkR2vgc59",
+ "p": "6JTf8Qb0iHRL6MIIs7MlkEKBNpnAu_Nie4HTqhxy2wfE4cBr6QZ98iJniXffDIjq_GxVpw9K-Bv2gTcNrlzOiBaLf3X2Itfice_Qd-luhNbnXVfiA5sg6dZ2wbBuue5ann5iJ_TIbxO4CLUiqQp0PCReUPzTQhzesHxM2-dBC9AYDl7P6p1FF53Hh_Knx9UywhoPvNtoCJy35-5rj0ghgPYz289dbOBccZnvabRueOr_wpHGMKaznqiDMrcFSZ07",
+ "q": "3TvrN8R9imw6E6JkVQ4PtveE0vkvkSWHUpn9KwKFIJJiwL_HSS4z_8IYR1_0Q1OgK5-z-QcXhq9P7jTjz02I2uwWhP3RZQf99RZACfMaeIs8O2V-I89WdlJYOerzAelW4nYw7zyeVoT5c5osicGWfSmWslLRjA1yx7x1KA_MCU_KIEBlpe1RgEUYPET3OtvPKFIVQYoJfQC5PFlmrC-kgHZMSpdHjWgWi5gPn0fIBCKFsXcPrt2n_lKKGc4lFOen",
+ "qi": "m-tgdFqO1Ax3C00oe7kdkYLHMD56wkGARdqPCqS5IGhFVKCOA8U6O_s5bSL4r0TzPE0KrJ4A5QJEwjbH4bXssPaaAlv1ZdWjn8YMQCYFolg_pgUWYYI5vNxG1gIsLGXPTfE8a6SObkJ2Q9VC5ZZp14r4lPvJhwFICIGSRBKcvS-gO_gqB3LKuG9TQBi-CE4DHDLJwsCbEBR8Ber45oTqvG7hphpOhBHsFZ8_6f3Reg_sK1BCz9HFCx8hhi8rBfUp"
+}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.key.txt b/src/SponsorLink/Tests/keys/kzu.key.txt
new file mode 100644
index 00000000..5fe87582
--- /dev/null
+++ b/src/SponsorLink/Tests/keys/kzu.key.txt
@@ -0,0 +1 @@
+MIIG4wIBAAKCAYEAyP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9+oBw5zIVZekgMP55XxmKvkJd1k+bYWSv+QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y/7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe/bXDkR74yG3mmq/Ne0qhNk6wXuX+NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb+YKry+h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr+OPrxEdZzL9BcI4fMJMz7YdiIu+qbIp/vqatbalfNasumf8RgtPOkR2vgc59AgMBAAECggGAOmDrKyd0ap7Az2V09C8E6aASK0nnXHGUdTIymmU1tGoxaWpkZ4gLGaYDp4L5fKc+AaqqD3PjvfJvEWfXLqJtEWfUl4gahWrgUkmuzPyAVFFioIzeGIvTGELsR6lTRke0IB2kvfvS7hRgX8Py8ohCAGHiidmoec2SyKkEg0aPdxrIKV8hx5ybC/D/4zRKn0GuVwATIeVZzPpTcyJX/sn4NHDOqut0Xg02iHhMKpF850BSoC97xGMlcSjLocFSTwI63msz6jWQ+6LRVXsfRr2mqakAvsPpqEQ3Ytk9Ud9xW0ctuAWyo6UXev5w2XEL8cSXm33+57fi3ekC/jGCqW0KfAU4Cr2UbuTC0cv8Vv0F4Xm5FizolmuSBFOvf55+eqsjmpwf9hftYAiIlFF+49+P0DpeJejSeoL06BE3e3/IVu3g3HNnSWVUOLJ5Uk5FQ+ieHhf+r2Tq5qZ8/+losHekQbCxCMY2isc+r6V6BMnVL/9kWPxpXwhjKrYxNFZEXUJ1AoHBAOiU3/EG9Ih0S+jCCLOzJZBCgTaZwLvzYnuB06occtsHxOHAa+kGffIiZ4l33wyI6vxsVacPSvgb9oE3Da5czogWi3919iLX4nHv0HfpboTW511X4gObIOnWdsGwbrnuWp5+Yif0yG8TuAi1IqkKdDwkXlD800Ic3rB8TNvnQQvQGA5ez+qdRRedx4fyp8fVMsIaD7zbaAict+fua49IIYD2M9vPXWzgXHGZ72m0bnjq/8KRxjCms56ogzK3BUmdOwKBwQDdO+s3xH2KbDoTomRVDg+294TS+S+RJYdSmf0rAoUgkmLAv8dJLjP/whhHX/RDU6Arn7P5BxeGr0/uNOPPTYja7BaE/dFlB/31FkAJ8xp4izw7ZX4jz1Z2Ulg56vMB6VbidjDvPJ5WhPlzmiyJwZZ9KZayUtGMDXLHvHUoD8wJT8ogQGWl7VGARRg8RPc6288oUhVBigl9ALk8WWasL6SAdkxKl0eNaBaLmA+fR8gEIoWxdw+u3af+UooZziUU56cCgcAeMKz9AXUefVIZLY6pnNiEaE2Tg9PD0ez3sTuDelviWJjS4QJg5inVE2gzLO80FlXCXGGl5NNLb33I+hd5iax5d8u1yN8hWLqZJZL+7bsd3TN9J1o8M5fLIr6Zl8hXtDvpFOiy3RBjcsDRyIluPeAmqmXfx2G41DyH1iq35MXJvcJRozcD8lQ0o5Nx7yqeQutqzK08S5Kug4FPxuAJYkiPD+ZdYwZyipEVYfD03Kn2YjK+0+NRD2KDdsZI//7ctl8CgcAg/oSwBn/qmwV2VCu+e6BgWz3V8Q/lB8HM0eZt+aw9FVfhAzkHcvqSVDImwuq898XVe7Xtl98sWQ0Eq6KmEhws+jTJlMkb1jtLyAu48Casf6d1/Eb5tDxi3RkHWkDvsgshLxtRMqsyoBZL5VxrFxIIjKFK6wsRZhgOLc8TMHfvk5TDhpciTehvxt48btSoVIvqqYM+CQYtPGRj2bl0SI6yEfKC1Vj4f+OM4hrCWIQ+CaACvYz7OVeKmGi8PIBPQ9UCgcEAm+tgdFqO1Ax3C00oe7kdkYLHMD56wkGARdqPCqS5IGhFVKCOA8U6O/s5bSL4r0TzPE0KrJ4A5QJEwjbH4bXssPaaAlv1ZdWjn8YMQCYFolg/pgUWYYI5vNxG1gIsLGXPTfE8a6SObkJ2Q9VC5ZZp14r4lPvJhwFICIGSRBKcvS+gO/gqB3LKuG9TQBi+CE4DHDLJwsCbEBR8Ber45oTqvG7hphpOhBHsFZ8/6f3Reg/sK1BCz9HFCx8hhi8rBfUp
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.pub b/src/SponsorLink/Tests/keys/kzu.pub
new file mode 100644
index 00000000..55947976
Binary files /dev/null and b/src/SponsorLink/Tests/keys/kzu.pub differ
diff --git a/src/SponsorLink/Tests/keys/kzu.pub.jwk b/src/SponsorLink/Tests/keys/kzu.pub.jwk
new file mode 100644
index 00000000..b4bfb314
--- /dev/null
+++ b/src/SponsorLink/Tests/keys/kzu.pub.jwk
@@ -0,0 +1,5 @@
+{
+ "e": "AQAB",
+ "kty": "RSA",
+ "n": "yP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9-oBw5zIVZekgMP55XxmKvkJd1k-bYWSv-QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y_7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe_bXDkR74yG3mmq_Ne0qhNk6wXuX-NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb-YKry-h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr-OPrxEdZzL9BcI4fMJMz7YdiIu-qbIp_vqatbalfNasumf8RgtPOkR2vgc59"
+}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.pub.txt b/src/SponsorLink/Tests/keys/kzu.pub.txt
new file mode 100644
index 00000000..729ecd5f
--- /dev/null
+++ b/src/SponsorLink/Tests/keys/kzu.pub.txt
@@ -0,0 +1 @@
+MIIBigKCAYEAyP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9+oBw5zIVZekgMP55XxmKvkJd1k+bYWSv+QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y/7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe/bXDkR74yG3mmq/Ne0qhNk6wXuX+NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb+YKry+h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr+OPrxEdZzL9BcI4fMJMz7YdiIu+qbIp/vqatbalfNasumf8RgtPOkR2vgc59AgMBAAE=
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/sponsorlink.jwt b/src/SponsorLink/Tests/keys/sponsorlink.jwt
new file mode 100644
index 00000000..b53fe627
--- /dev/null
+++ b/src/SponsorLink/Tests/keys/sponsorlink.jwt
@@ -0,0 +1 @@
+eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MTk4NjgyMzAsImlzcyI6Imh0dHBzOi8vc3BvbnNvcmxpbmsuZGV2bG9vcGVkLmNvbS8iLCJhdWQiOlsiaHR0cHM6Ly9naXRodWIuY29tL3Nwb25zb3JzL2t6dSIsImh0dHBzOi8vZ2l0aHViLmNvbS9zcG9uc29ycy9kZXZsb29wZWQiXSwiY2xpZW50X2lkIjoiYTgyMzUwZmIyYmFlNDA3YjMwMjEiLCJzdWJfandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InlQNzFWZ09nSER0R2J4ZHlOMzFtSUZGSVRtR1lFazJjd2VwS2J5cUtUYlRZWEYxT1hhTW9QNW4zbWZ3cXd6VVFtRUFzcmNsQWlnUGNLNEdJeTVXV2xjNVl1akl4S2F1SmpzS2UwRkJ4TW5GcDlvMVVjQlVIZmdESmphQUtpZVF4YjQ0NzE3YjFNd0NjZmxFR25DR1RYa250ZHI0NXk5R2kxRDktb0J3NXpJVlpla2dNUDU1WHhtS3ZrSmQxay1iWVdTdi1RRkcySkp3UklHd3IyOUpyNjJqdUNzTEI3VGc4M1pHS0NhMjJZXzdsUWNlenhSUkQ1T3JHV2hmM2dUWUFyYnJFemJZeTY1M3piSGZiT0NKZVZCZV9iWERrUjc0eUczbW1xX05lMHFoTms2d1h1WC1OcktFdmRQeFJTUkJGN0M0NjVmY1ZZOVBNNmVUcUVQUXdLRGlhckhwVTFOVHdVZXR6Yi1ZS3J5LWg2NzhSSldNaEM3STlsenpXVm9iYkMwWVZLRzdYcGVWcUJCNHU3UTZjR281WGtmMTlWbGRrSXhRTXU5c0ZldUhHRFNvaUNMcW1SbXdObjlHc01WNzdvWldyLU9QcnhFZFp6TDlCY0k0Zk1KTXo3WWRpSXUtcWJJcF92cWF0YmFsZk5hc3VtZjhSZ3RQT2tSMnZnYzU5In19.er4apYbEjHVKlQ_aMXoRhHYeR8N-3uIrCk3HX8UuZO7mb0CaS94-422EI3z5O9vRvckcGkNVoiSIX0ykZqUMHTZxBae-QZc1u_rhdBOChoaxWqpUiPXLZ5-yi7mcRwqg2DOUb2eHTNfRjwJ-0tjL1R1TqZw9d8Bgku1zw2ZTuJl_WsBRHKHTD_s5KyCP5yhSOUumrsf3nXYrc20fJ7ql0FsL0MP66utJk7TFYHGhQV3cfcXYqFEpv-k6tqB9k3Syc0UnepmQT0Y3dtcBzQzCOzfKQ8bdaAXVHjfp4VvXBluHmh9lP6TeZmpvlmQDFvyk0kp1diTbo9pqmX_llNDWNxBdvaSZGa7RZMG_dE2WJGtQNu0C_sbEZDPZsKncxdtm-j-6Y7GRqx7uxe4Py8tAZ7SxjiPgD64jf9KF2OT6f6drVtzohVzYCs6-vhcXzC2sQvd_gQ-SoFNTa1MEcMgGbL-fFWUC7-7bQV1DlSg2YFwrxEIwbM-gHpLZHyyJLvYD
\ No newline at end of file
diff --git a/src/SponsorLink/readme.md b/src/SponsorLink/readme.md
index cb651a1d..e2335273 100644
--- a/src/SponsorLink/readme.md
+++ b/src/SponsorLink/readme.md
@@ -1,4 +1,4 @@
-# SponsorLink .NET Analyzer
+# SponsorLink .NET Analyzer Sample
This is one opinionated implementation of [SponsorLink](https://devlooped.com/SponsorLink)
for .NET projects leveraging Roslyn analyzers.
@@ -9,8 +9,12 @@ is out of scope though, since we just use GitHub sponsors for now.
## Usage
-A project initializing from this template repo via [dotnet-file](https://github.com/devlooped/dotnet-file)
-will have all the sources cloned under `src\SponsorLink`.
+A project can include all the necessary files by using the [dotnet-file](https://github.com/devlooped/dotnet-file)
+tool and sync all files to a folder, such as:
+
+```shell
+dotnet file add https://github.com/devlooped/SponsorLink/tree/main/samples/dotnet src/SponsorLink/
+```
Including the analyzer and targets in a project involves two steps.
diff --git a/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets b/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets
index 87b96d86..acaa1d99 100644
--- a/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets
+++ b/src/ThisAssembly.AssemblyInfo/ThisAssembly.AssemblyInfo.targets
@@ -1,5 +1,5 @@
-
+
-
+
\ No newline at end of file
diff --git a/src/ThisAssembly.Constants/ConstantsGenerator.cs b/src/ThisAssembly.Constants/ConstantsGenerator.cs
index 622c6bec..2efc6121 100644
--- a/src/ThisAssembly.Constants/ConstantsGenerator.cs
+++ b/src/ThisAssembly.Constants/ConstantsGenerator.cs
@@ -17,7 +17,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var files = context.AdditionalTextsProvider
.Combine(context.AnalyzerConfigOptionsProvider)
.Where(x =>
- x.Right.GetOptions(x.Left).TryGetValue("build_metadata.AdditionalFiles.SourceItemType", out var itemType)
+ x.Right.GetOptions(x.Left).TryGetValue("build_metadata.Constant.ItemType", out var itemType)
&& itemType == "Constant")
.Select((x, ct) =>
{
diff --git a/src/ThisAssembly.Constants/ThisAssembly.Constants.targets b/src/ThisAssembly.Constants/ThisAssembly.Constants.targets
index 2ff4dbcc..046a8f76 100644
--- a/src/ThisAssembly.Constants/ThisAssembly.Constants.targets
+++ b/src/ThisAssembly.Constants/ThisAssembly.Constants.targets
@@ -1,8 +1,8 @@
-
+
-
+
@@ -10,6 +10,9 @@
+
+
+
@@ -50,7 +53,7 @@
|$([MSBuild]::ValueOrdefault('%(Constant.Value)', '').Replace(';', '|'))|
-
+
diff --git a/src/ThisAssembly.Project/ThisAssembly.Project.targets b/src/ThisAssembly.Project/ThisAssembly.Project.targets
index b93c23ec..483e80f7 100644
--- a/src/ThisAssembly.Project/ThisAssembly.Project.targets
+++ b/src/ThisAssembly.Project/ThisAssembly.Project.targets
@@ -1,5 +1,5 @@
-
+
-
+
diff --git a/src/ThisAssembly.Resources/ThisAssembly.Resources.targets b/src/ThisAssembly.Resources/ThisAssembly.Resources.targets
index 0f2567b5..ead0d830 100644
--- a/src/ThisAssembly.Resources/ThisAssembly.Resources.targets
+++ b/src/ThisAssembly.Resources/ThisAssembly.Resources.targets
@@ -1,5 +1,5 @@
-
+
-
+
diff --git a/src/ThisAssembly.Strings/ThisAssembly.Strings.targets b/src/ThisAssembly.Strings/ThisAssembly.Strings.targets
index 69fe44ed..bf7bdd3e 100644
--- a/src/ThisAssembly.Strings/ThisAssembly.Strings.targets
+++ b/src/ThisAssembly.Strings/ThisAssembly.Strings.targets
@@ -1,5 +1,5 @@
-
+
-
+
net472
ThisAssemblyTests
true
+ CS8981;$(NoWarn)
@@ -79,7 +80,16 @@
+
+
+
+ true
+
+
+
+
+
diff --git a/src/kzu.snk b/src/kzu.snk
deleted file mode 100644
index 8e181aea..00000000
Binary files a/src/kzu.snk and /dev/null differ