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