diff --git a/MonoDevelop.MSBuild/Language/Typesystem/CustomTypeInfo.cs b/MonoDevelop.MSBuild/Language/Typesystem/CustomTypeInfo.cs index 82529631..61c056dd 100644 --- a/MonoDevelop.MSBuild/Language/Typesystem/CustomTypeInfo.cs +++ b/MonoDevelop.MSBuild/Language/Typesystem/CustomTypeInfo.cs @@ -34,6 +34,8 @@ public CustomTypeInfo ( } } + public bool IsAnonymous => Name is null || Name.Length == 0 || Name[0] == '#'; + public string? Name { get; } public DisplayText Description { get; } public bool AllowUnknownValues { get; } diff --git a/MonoDevelop.MSBuild/Schema/BuiltInSchema.cs b/MonoDevelop.MSBuild/Schema/BuiltInSchema.cs index ab6bf684..32d31fd1 100644 --- a/MonoDevelop.MSBuild/Schema/BuiltInSchema.cs +++ b/MonoDevelop.MSBuild/Schema/BuiltInSchema.cs @@ -36,6 +36,10 @@ static MSBuildSchema Load (BuiltInSchemaId[] schemaIds, out IList filenameToIdMap = new (BuiltInSchemaId[] resourceId, string? sdkId, string filename)[] { ([ BuiltInSchemaId.Android ], null, "Xamarin.Android.Common.targets"), ([ BuiltInSchemaId.Appx ], null, "Microsoft.DesktopBridge.targets"), + ([ BuiltInSchemaId.AspireAppHost ], null, "Aspire.Hosting.AppHost.targets" ), + ([ BuiltInSchemaId.AspireHostingOrchestration ], "Aspire.Hosting.Orchestration", sdkTargets ), + ([ BuiltInSchemaId.AspireHostingSdk ], "Aspire.Hosting.Sdk", sdkTargets ), + ([ BuiltInSchemaId.AspireDashboardSdk ], "Aspire.Dashboard.Sdk", sdkTargets ), ([ BuiltInSchemaId.AspNetCore ], "Microsoft.NET.Sdk.Web", sdkTargets), ([ BuiltInSchemaId.CodeAnalysis ], null, "Microsoft.CodeAnalysis.targets"), ([ BuiltInSchemaId.CommonTargets ], null, "Microsoft.Common.targets"), diff --git a/MonoDevelop.MSBuild/Schema/BuiltInSchemaId.cs b/MonoDevelop.MSBuild/Schema/BuiltInSchemaId.cs index 3a234d4c..c6d3d06a 100644 --- a/MonoDevelop.MSBuild/Schema/BuiltInSchemaId.cs +++ b/MonoDevelop.MSBuild/Schema/BuiltInSchemaId.cs @@ -28,5 +28,9 @@ enum BuiltInSchemaId ProjectSystemMps, ProjectSystemCps, GenerateAssemblyInfo, - ValidatePackage + ValidatePackage, + AspireAppHost, + AspireHostingOrchestration, + AspireHostingSdk, + AspireDashboardSdk } diff --git a/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs b/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs index 24a1083b..ec94c9b8 100644 --- a/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs +++ b/MonoDevelop.MSBuild/Schema/DescriptionFormatter.cs @@ -270,7 +270,7 @@ static string FormatKind (MSBuildValueKind kind, CustomTypeInfo customTypeInfo) case MSBuildValueKind.NuGetVersion: return "nuget-version"; case MSBuildValueKind.CustomType: - if (customTypeInfo != null && customTypeInfo.Name != null) { + if (customTypeInfo != null && !customTypeInfo.IsAnonymous) { return customTypeInfo.Name; } // derived types inherit the name from the base type diff --git a/MonoDevelop.MSBuild/Schema/MSBuildSchema.SchemaLoadState.cs b/MonoDevelop.MSBuild/Schema/MSBuildSchema.SchemaLoadState.cs index 3a94d07f..b1baafff 100644 --- a/MonoDevelop.MSBuild/Schema/MSBuildSchema.SchemaLoadState.cs +++ b/MonoDevelop.MSBuild/Schema/MSBuildSchema.SchemaLoadState.cs @@ -428,7 +428,7 @@ bool GetValueString ([NotNullWhen (true)] out string? value) } if (token is JArray conciseDef) { - return (MSBuildValueKind.CustomType, ReadConciseCustomTypeDefinition (conciseDef)); + return (MSBuildValueKind.CustomType, ReadConciseCustomTypeDefinition (conciseDef, null)); } if (token is not JObject typeDefObj) { @@ -436,7 +436,7 @@ bool GetValueString ([NotNullWhen (true)] out string? value) return (MSBuildValueKind.Unknown, null); } - (var definition, var reference) = ReadCustomTypeDefinitionOrReference (typeDefObj); + (var definition, var reference) = ReadCustomTypeDefinitionOrReference (typeDefObj, null); if (reference != null) { if (CustomTypes is not null && CustomTypes.TryGetValue (reference, out var resolved)) { @@ -456,7 +456,7 @@ bool GetValueString ([NotNullWhen (true)] out string? value) CustomTypeInfo? ReadCustomTypeDefinition (JObject customTypeContainer, string? customTypeId, JToken? customTypeDef) { if (customTypeDef is JArray conciseDef) { - return ReadConciseCustomTypeDefinition (conciseDef); + return ReadConciseCustomTypeDefinition (conciseDef, customTypeId); } if (customTypeDef is not JObject customTypeDefObj) { AddError (customTypeDef ?? customTypeContainer, customTypeId is not null @@ -465,7 +465,7 @@ bool GetValueString ([NotNullWhen (true)] out string? value) return null; } - (var definition, var reference) = ReadCustomTypeDefinitionOrReference (customTypeDefObj); + (var definition, var reference) = ReadCustomTypeDefinitionOrReference (customTypeDefObj, customTypeId); if (reference != null) { AddWarning (customTypeDefObj, customTypeId is not null ? $"Custom type definition '{customTypeId}' cannot be a type reference" @@ -476,7 +476,9 @@ bool GetValueString ([NotNullWhen (true)] out string? value) return definition; } - CustomTypeInfo ReadConciseCustomTypeDefinition (JArray conciseCustomTypeDef) + static string? MakeAnonymousTypeName (string? customTypeId) => customTypeId is null? null : $"#{customTypeId}"; + + CustomTypeInfo ReadConciseCustomTypeDefinition (JArray conciseCustomTypeDef, string? customTypeId) { var values = new List (conciseCustomTypeDef.Count); foreach (var defVal in conciseCustomTypeDef) { @@ -486,10 +488,10 @@ CustomTypeInfo ReadConciseCustomTypeDefinition (JArray conciseCustomTypeDef) } values.Add (new CustomTypeValue (customTypeValue, null)); } - return new (values); + return new (values, MakeAnonymousTypeName (customTypeId)); } - (CustomTypeInfo? definition, string? reference) ReadCustomTypeDefinitionOrReference (JObject customTypeObj) + (CustomTypeInfo? definition, string? reference) ReadCustomTypeDefinitionOrReference (JObject customTypeObj, string? customTypeId) { var enumerator = customTypeObj.GetEnumerator (); if (!enumerator.MoveNext ()) { @@ -609,7 +611,7 @@ bool GetValueBool ([NotNullWhen (true)] out bool? value) allowUnknownValues = true; } - return (new CustomTypeInfo (values, name, description, allowUnknownValues ?? false, baseValueKind ?? MSBuildValueKind.Unknown, caseSensitive ?? false, analyzerHints, helpUrl), null); + return (new CustomTypeInfo (values, name ?? MakeAnonymousTypeName (customTypeId), description, allowUnknownValues ?? false, baseValueKind ?? MSBuildValueKind.Unknown, caseSensitive ?? false, analyzerHints, helpUrl), null); } CustomTypeValue ReadCustomTypeValue (JObject customTypeValueCollection, string customTypeValueName, JToken? customTypeValueToken) diff --git a/MonoDevelop.MSBuild/Schema/MSBuildSchema.cs b/MonoDevelop.MSBuild/Schema/MSBuildSchema.cs index f4c6c260..f4d6426d 100644 --- a/MonoDevelop.MSBuild/Schema/MSBuildSchema.cs +++ b/MonoDevelop.MSBuild/Schema/MSBuildSchema.cs @@ -119,9 +119,10 @@ void LoadInternal (TextReader reader, out IList loadErro // all custom types are resolvable state.LoadCustomTypes (customTypes); - // only named custom types are surfaced directly on the schema + // only named custom types are surfaced directly on the schema, + // as well as anonymous types deriving from `warning-code` as those are needed for completion foreach (var ct in state.CustomTypes.Values) { - if (!string.IsNullOrEmpty (ct.Name)) { + if (ct.Name is not null && (!ct.IsAnonymous || ct.BaseKind == MSBuildValueKind.WarningCode)) { Types.Add (ct.Name, ct); } } diff --git a/MonoDevelop.MSBuild/Schemas/AspNetCore.buildschema.json b/MonoDevelop.MSBuild/Schemas/AspNetCore.buildschema.json index 2bb0eb56..3c36ac3c 100644 --- a/MonoDevelop.MSBuild/Schemas/AspNetCore.buildschema.json +++ b/MonoDevelop.MSBuild/Schemas/AspNetCore.buildschema.json @@ -20,11 +20,6 @@ "AspNetCoreModuleV2" ] }, - "UserSecretsId": { - "description": "The ID that will be used to locate the file storing secret configuration values for this project at development time. Although by default this is a GUID value, arbitrary strings may be used.", - "type": "string", - "helpUrl": "https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-8.0&tabs=windows#enable-secret-storage" - }, "BlazorEnableTimeZoneSupport": { "description": "Indicates whether Blazor WebAssembly should include a data file to make timezone information correct. Setting this to `false` will reduce your app size.", "defaultValue": "true", diff --git a/MonoDevelop.MSBuild/Schemas/AspireAppHost.buildschema.json b/MonoDevelop.MSBuild/Schemas/AspireAppHost.buildschema.json new file mode 100644 index 00000000..e424a009 --- /dev/null +++ b/MonoDevelop.MSBuild/Schemas/AspireAppHost.buildschema.json @@ -0,0 +1,40 @@ +{ + "license": "Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.", + "properties": { + "AspireGeneratedClassesVisibility": { + "description": "Controls the visibility of generated .NET Aspire classes, as a C# keyword", + "type": { + "values": { + "public": "The generated class will have `public` visibility", + "internal": "The generated class will have `internal` visibility" + } + }, + "defaultValue": "public" + }, + "SkipValidateAspireHostProjectResources": { + "description": "Skip validation of .NET Aspire AppHost project references", + "type": "bool", + "defaultValue": "false" + }, + "AspirePublisher": { + "description": "Sets the publisher to be invoked by the `GenerateAspireManifest` target", + "type": "string", + "defaultValue": "manifest" + }, + "AspireManifestPublishOutputPath": { + "description": "The path to the output directory for the .NET Aspire manifest publish", + "type": "folder-with-slash" + } + }, + "targets": { + "GenerateAspireManifest": "Generates a .NET Aspire manifest file for the project at the path indicated by the `AspireManifestPublishOutputPath` property " + }, + "types": { + "aspire-apphost-warning": { + "baseType": "warning-code", + "values": { + "ASPIRE004": "Project referenced by Aspire AppHost is not an executable" + } + } + }, +} \ No newline at end of file diff --git a/MonoDevelop.MSBuild/Schemas/AspireDashboardSdk.buildschema.json b/MonoDevelop.MSBuild/Schemas/AspireDashboardSdk.buildschema.json new file mode 100644 index 00000000..22ff7d86 --- /dev/null +++ b/MonoDevelop.MSBuild/Schemas/AspireDashboardSdk.buildschema.json @@ -0,0 +1,13 @@ +{ + "license": "Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.", + "properties": { + "AspireDashboardDir": { + "description": "The directory where the .NET Aspire Dashboard is located", + "type": "folder-with-slash" + }, + "AspireDashboardPath": { + "description": "The path to the .NET Aspire Dashboard binary", + "type": "file" + } + } +} \ No newline at end of file diff --git a/MonoDevelop.MSBuild/Schemas/AspireHostingOrchestration.buildschema.json b/MonoDevelop.MSBuild/Schemas/AspireHostingOrchestration.buildschema.json new file mode 100644 index 00000000..ed030666 --- /dev/null +++ b/MonoDevelop.MSBuild/Schemas/AspireHostingOrchestration.buildschema.json @@ -0,0 +1,21 @@ +{ + "license": "Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.", + "properties": { + "DcpDir": { + "description": "The path to the directory containing the DCP tools.", + "type": "folder-with-slash" + }, + "DcpExtensionsDir": { + "description": "The path to the directory containing the DCP tool extensions.", + "type": "folder-with-slash" + }, + "DcpBinDir": { + "description": "The path to the directory containing the DCP tool extension binaries.", + "type": "folder-with-slash" + }, + "DcpCliPath": { + "description": "The path to the DCP CLI executable.", + "type": "file" + } + } +} \ No newline at end of file diff --git a/MonoDevelop.MSBuild/Schemas/AspireHostingSdk.buildschema.json b/MonoDevelop.MSBuild/Schemas/AspireHostingSdk.buildschema.json new file mode 100644 index 00000000..d3f996b3 --- /dev/null +++ b/MonoDevelop.MSBuild/Schemas/AspireHostingSdk.buildschema.json @@ -0,0 +1,22 @@ +{ + "license": "Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.", + "types": { + "aspire-hosting-sdk-warning": { + "baseType": "warning-code", + "values": { + "ASPIRE002": "Aspire AppHost project has missing dependencies", + "ASPIRE003": "Visual Studio version is too old for Aspire AppHost project" + } + } + }, + "metadata": [ + { + "$appliesTo": [ "ProjectReference" ], + "IsAspireProjectResource":{ + "type": "bool", + "description": "Indicates that the referenced project should be orchestrated by the .NET Aspire AppHost, instead of referencing its output assembly.", + "defaultValue": "true" + } + } + ] +} \ No newline at end of file diff --git a/MonoDevelop.MSBuild/Schemas/NetSdk.buildschema.json b/MonoDevelop.MSBuild/Schemas/NetSdk.buildschema.json index 489bf9fe..f42fd93f 100644 --- a/MonoDevelop.MSBuild/Schemas/NetSdk.buildschema.json +++ b/MonoDevelop.MSBuild/Schemas/NetSdk.buildschema.json @@ -346,6 +346,24 @@ "type": "version", "helpUrl": "https://learn.microsoft.com/dotnet/core/versions/selection#self-contained-deployments-include-the-selected-runtime" }, + "IsAspireSharedProject": { + "description": "Indicates that the project is an .NET Aspire shared project, causing Visual Studio to treat it as such.", + "type": "bool" + }, + "IsAspireHost": { + "description": "Indicates that the project is an .NET Aspire Host project, causing the Aspire workload targets to be referenced.", + "type": "bool" + }, + "SkipAspireWorkloadManifest": { + "description": "Indicates that the project should not reference the .NET Aspire workload targets despite setting `IsAspireHost` to `true`.", + "type": "bool" + }, + // this is used to generate an attribute in in Microsoft.NET.Sdk.FrameworkReferenceResolution.targets + "UserSecretsId": { + "description": "The ID that will be used to locate the file storing secret configuration values for this project at development time. Although by default this is a GUID value, arbitrary strings may be used.", + "type": "string", + "helpUrl": "https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-8.0&tabs=windows#enable-secret-storage" + } }, "items": {