diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e5eb2a2a..af2c322c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,5 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/dotnet/.devcontainer/base.Dockerfile -# [Choice] .NET version: 7.0, 6.0, 5.0 -ARG VARIANT="8.0" +# [Choice] .NET version: use "latest" or a specific version like "10.0" +ARG VARIANT="10.0" FROM mcr.microsoft.com/vscode/devcontainers/dotnet \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a20bb427..a63376fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v4 @@ -28,7 +28,7 @@ jobs: languages: csharp - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 id: installdotnet with: dotnet-version: 10.0.x diff --git a/.vscode/settings.json b/.vscode/settings.json index 86a81124..dad8c9c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,9 +27,9 @@ "dotnet-test-explorer.testProjectPath": "**/*Tests.csproj", "editor.formatOnType": true, "omnisharp.enableRoslynAnalyzers": true, - "omnisharp.organizeImportsOnFormat": true, "omnisharp.useModernNet": true, "omnisharp.enableMsBuildLoadProjectsOnDemand": true, "omnisharp.enableEditorConfigSupport": true, - "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "dotnet.formatting.organizeImportsOnFormat": true } \ No newline at end of file diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs index ec664f86..b99593fb 100644 --- a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs +++ b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs @@ -142,7 +142,9 @@ public IReadOnlyList Parse( Type type ) /// /// Attempts to parse an API version from the specified namespace identifier. /// - /// The namespace identifier to parse. + /// The namespace identifier to parse. The identifier must start with + /// 'v', 'V', or '_' followed by a digit. The '_' prefix supports folder names that start with + /// a number, which causes Visual Studio to prepend an underscore to the namespace. /// The parsed API version or null. /// True if parsing is successful; otherwise, false. protected virtual bool TryParse( Text identifier, out ApiVersion? apiVersion ) @@ -157,21 +159,36 @@ protected virtual bool TryParse( Text identifier, out ApiVersion? apiVersion ) return false; } - // 'v' | 'V' : [ : ['_'] : : ['_'] : ] : ['_'] : [ ['_' : ]] : ['_'] : [] + // 'v' | 'V' | '_' : [ : ['_'] : : ['_'] : ] : ['_'] : [ ['_' : ]] : ['_'] : [] // // - v1 // - v1_1 // - v2_0_Beta // - v20180401 // - v2018_04_01_1_1_Beta + // - _1 + // - _1_1 + // - _20180401 + // - _2018_04_01 var ch = identifier[0]; - if ( ch != 'v' && ch != 'V' ) + if ( ch != 'v' && ch != 'V' && ch != '_' ) { apiVersion = default; return false; } + if ( ch == '_' ) + { + // '_' prefix requires the next character to be a digit; + // otherwise, it's just a regular identifier + if ( identifier.Length < 2 || !char.IsDigit( identifier[1] ) ) + { + apiVersion = default; + return false; + } + } + #if NETSTANDARD identifier = identifier.Substring( 1 ); #else diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs index 186743b1..577d9483 100644 --- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs +++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/NamespaceParserTest.cs @@ -70,6 +70,16 @@ public void parse_should_return_no_versions() { "Contoso.Api.v2018_04_01_1_0_Beta.Controllers", "2018-04-01.1.0-Beta" }, { "MyRestaurant.Vegetarian.Food.v1_1.Controllers", "1.1" }, { "VersioningSample.V5.Controllers", "5.0" }, + { "_1", "1" }, + { "_1_1", "1.1" }, + { "_20180401", "2018-04-01" }, + { "_2018_04_01", "2018-04-01" }, + { "_2018_04_01_Beta", "2018-04-01-Beta" }, + { "_2018_04_01_1_0_Beta", "2018-04-01.1.0-Beta" }, + { "Contoso.Api._2018_04_01.Controllers", "2018-04-01" }, + { "Contoso.Api._1.Controllers", "1" }, + { "Contoso.Api._1_1.Controllers", "1.1" }, + { "Contoso.Api._0_9_Beta.Controllers", "0.9-Beta" }, }; public static TheoryData NamespaceWithMultipleVersions => new() diff --git a/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs b/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs index ab63edd5..aef22212 100644 --- a/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs +++ b/src/Common/test/Common.Mvc.Tests/Conventions/VersionByNamespaceConventionTest.cs @@ -24,6 +24,16 @@ public partial class VersionByNamespaceConventionTest { "Contoso.Api.v2018_04_01_1_0_Beta.Controllers", "2018-04-01.1.0-Beta" }, { "MyRestaurant.Vegetarian.Food.v1_1.Controllers", "1.1" }, { "VersioningSample.V5.Controllers", "5.0" }, + { "_1", "1.0" }, + { "_1_1", "1.1" }, + { "_20180401", "2018-04-01" }, + { "_2018_04_01", "2018-04-01" }, + { "_2018_04_01_Beta", "2018-04-01-Beta" }, + { "_2018_04_01_1_0_Beta", "2018-04-01.1.0-Beta" }, + { "Contoso.Api._2018_04_01.Controllers", "2018-04-01" }, + { "Contoso.Api._1.Controllers", "1.0" }, + { "Contoso.Api._1_1.Controllers", "1.1" }, + { "Contoso.Api._0_9_Beta.Controllers", "0.9-Beta" }, }; private sealed class TestType : TypeDelegator