Skip to content

Commit 3c918ef

Browse files
Revert "fix(http-client-csharp): strip [Experimental] attribute when internalizing types" (#10972)
Reverts #10958
1 parent 9e923c0 commit 3c918ef

7 files changed

Lines changed: 0 additions & 240 deletions

File tree

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ internal class PostProcessor
2020
private readonly HashSet<string> _typesToKeep;
2121
private INamedTypeSymbol? _modelFactorySymbol;
2222

23-
private static readonly string[] _experimentalAttributeNames = ["Experimental", "ExperimentalAttribute"];
24-
2523
public PostProcessor(
2624
HashSet<string> typesToKeep,
2725
string? modelFactoryFullName = null,
@@ -158,12 +156,6 @@ public async Task<Project> InternalizeAsync(Project project)
158156
project = MarkInternal(project, model, documentId);
159157
}
160158

161-
if (nodesToInternalize.Count > 0)
162-
{
163-
CodeModelGenerator.Instance.Emitter.Info(
164-
$"Internalized {nodesToInternalize.Count} unreferenced public type(s).");
165-
}
166-
167159
var modelNamesToRemove =
168160
nodesToInternalize.Keys.Select(item => item.Identifier.Text);
169161
project = await RemoveMethodsFromModelFactoryAsync(project, definitions, modelNamesToRemove.ToHashSet());
@@ -344,13 +336,7 @@ private static IEnumerable<T> GetReferencedTypes<T>(T definition,
344336

345337
private Project MarkInternal(Project project, BaseTypeDeclarationSyntax declarationNode, DocumentId documentId)
346338
{
347-
CodeModelGenerator.Instance.Emitter.Debug(
348-
$"Internalizing unreferenced public type '{declarationNode.Identifier.Text}'.");
349-
350339
var newNode = ChangeModifier(declarationNode, SyntaxKind.PublicKeyword, SyntaxKind.InternalKeyword);
351-
// The [Experimental] attribute is a public-API stability signal that is meaningless on a type that is
352-
// being internalized, so strip it to avoid emitting it (and its use-site diagnostics) on internal types.
353-
newNode = RemoveExperimentalAttribute(newNode, declarationNode.Identifier.Text);
354340
var tree = declarationNode.SyntaxTree;
355341
var document = project.GetDocument(documentId)!;
356342
var newRoot = tree.GetRoot().ReplaceNode(declarationNode, newNode)
@@ -359,78 +345,6 @@ private Project MarkInternal(Project project, BaseTypeDeclarationSyntax declarat
359345
return document.Project;
360346
}
361347

362-
/// <summary>
363-
/// Removes any <c>[Experimental]</c> (<see cref="System.Diagnostics.CodeAnalysis.ExperimentalAttribute"/>)
364-
/// attribute from <paramref name="declarationNode"/>. The declaration's leading trivia (such as documentation
365-
/// comments and indentation) is re-attached to the resulting first token so that the surrounding formatting and
366-
/// any documentation comment are preserved even when the attribute that carried them is removed.
367-
/// </summary>
368-
private static BaseTypeDeclarationSyntax RemoveExperimentalAttribute(
369-
BaseTypeDeclarationSyntax declarationNode,
370-
string typeName)
371-
{
372-
if (declarationNode.AttributeLists.Count == 0)
373-
{
374-
return declarationNode;
375-
}
376-
377-
var newAttributeLists = new List<AttributeListSyntax>();
378-
bool removed = false;
379-
380-
foreach (var attributeList in declarationNode.AttributeLists)
381-
{
382-
var keptAttributes = attributeList.Attributes
383-
.Where(attribute => !IsExperimentalAttribute(attribute))
384-
.ToList();
385-
386-
if (keptAttributes.Count == attributeList.Attributes.Count)
387-
{
388-
// Nothing removed from this list.
389-
newAttributeLists.Add(attributeList);
390-
}
391-
else if (keptAttributes.Count > 0)
392-
{
393-
// Keep the remaining (non-experimental) attributes in this list.
394-
removed = true;
395-
newAttributeLists.Add(attributeList.WithAttributes(SyntaxFactory.SeparatedList(keptAttributes)));
396-
}
397-
else
398-
{
399-
// The entire list only contained experimental attributes and is dropped.
400-
removed = true;
401-
}
402-
}
403-
404-
if (!removed)
405-
{
406-
return declarationNode;
407-
}
408-
409-
CodeModelGenerator.Instance.Emitter.Debug(
410-
$"Removed [Experimental] attribute from '{typeName}' while internalizing it.");
411-
412-
// Preserve the original leading trivia (e.g. documentation comments and indentation) by re-attaching it to
413-
// whatever token now leads the declaration. This keeps the doc comment even when it was attached to the
414-
// attribute list that was removed.
415-
var originalLeadingTrivia = declarationNode.GetLeadingTrivia();
416-
var newNode = declarationNode.WithAttributeLists(SyntaxFactory.List(newAttributeLists));
417-
var firstToken = newNode.GetFirstToken();
418-
419-
return newNode.ReplaceToken(firstToken, firstToken.WithLeadingTrivia(originalLeadingTrivia));
420-
}
421-
422-
private static bool IsExperimentalAttribute(AttributeSyntax attribute)
423-
{
424-
var name = attribute.Name switch
425-
{
426-
QualifiedNameSyntax qualified => qualified.Right.Identifier.Text,
427-
IdentifierNameSyntax identifier => identifier.Identifier.Text,
428-
_ => attribute.Name.ToString()
429-
};
430-
431-
return Array.IndexOf(_experimentalAttributeNames, name) >= 0;
432-
}
433-
434348
private async Task<Project> RemoveModelsAsync(Project project,
435349
IEnumerable<BaseTypeDeclarationSyntax> unusedModels)
436350
{

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33

44
using System.ClientModel.Primitives;
55
using System.Collections.Generic;
6-
using System.Diagnostics.CodeAnalysis;
76
using System.IO;
87
using System.Linq;
98
using System.Threading.Tasks;
109
using Microsoft.CodeAnalysis;
11-
using Microsoft.CodeAnalysis.CSharp;
1210
using Microsoft.CodeAnalysis.CSharp.Syntax;
1311
using Microsoft.TypeSpec.Generator.Tests.Common;
1412
using NUnit.Framework;
@@ -291,100 +289,6 @@ public async Task DoesNotRemoveValidAttributes()
291289
Assert.AreEqual(Helpers.GetExpectedFromFile().TrimEnd(), output, "The output should match the expected content.");
292290
}
293291

294-
[Test]
295-
public async Task RemovesExperimentalAttributeWhenInternalizing()
296-
{
297-
MockHelpers.LoadMockGenerator();
298-
var workspace = new AdhocWorkspace();
299-
var projectInfo = ProjectInfo.Create(
300-
ProjectId.CreateNewId(),
301-
VersionStamp.Create(),
302-
name: "TestProj",
303-
assemblyName: "TestProj",
304-
language: LanguageNames.CSharp)
305-
.WithMetadataReferences(new[]
306-
{
307-
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
308-
MetadataReference.CreateFromFile(typeof(ExperimentalAttribute).Assembly.Location)
309-
});
310-
311-
var project = workspace.AddProject(projectInfo);
312-
var folder = Helpers.GetAssetFileOrDirectoryPath(false);
313-
const string rootFileName = "ExperimentalInternalizeRoot.cs";
314-
string[] modelFileNames =
315-
[
316-
"ReferencedModel.cs",
317-
"UnreferencedModel.cs",
318-
"UnreferencedWithOtherAttribute.cs",
319-
"UnreferencedWithCombinedAttributes.cs"
320-
];
321-
foreach (var fileName in modelFileNames)
322-
{
323-
project = project.AddDocument(
324-
fileName,
325-
File.ReadAllText(Path.Join(folder, fileName))).Project;
326-
}
327-
project = project.AddDocument(
328-
rootFileName,
329-
File.ReadAllText(Path.Join(folder, rootFileName))).Project;
330-
var postProcessor = new TestPostProcessor(rootFileName);
331-
332-
var resultProject = await postProcessor.InternalizeAsync(project);
333-
334-
var referencedModel = await GetSingleClassAsync(resultProject, "ReferencedModel.cs", "ReferencedModel");
335-
var unreferencedModel = await GetSingleClassAsync(resultProject, "UnreferencedModel.cs", "UnreferencedModel");
336-
var unreferencedWithOther = await GetSingleClassAsync(resultProject, "UnreferencedWithOtherAttribute.cs", "UnreferencedWithOtherAttribute");
337-
var unreferencedWithCombined = await GetSingleClassAsync(resultProject, "UnreferencedWithCombinedAttributes.cs", "UnreferencedWithCombinedAttributes");
338-
339-
// The referenced model stays public and keeps its [Experimental] attribute.
340-
Assert.IsTrue(referencedModel.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));
341-
Assert.IsTrue(HasExperimentalAttribute(referencedModel), "Referenced (public) model should keep [Experimental].");
342-
343-
// The unreferenced model is internalized and loses its [Experimental] attribute.
344-
Assert.IsTrue(unreferencedModel.Modifiers.Any(m => m.IsKind(SyntaxKind.InternalKeyword)));
345-
Assert.IsFalse(unreferencedModel.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));
346-
Assert.IsFalse(HasExperimentalAttribute(unreferencedModel), "Internalized model should not keep [Experimental].");
347-
348-
// The documentation comment on the internalized type is preserved.
349-
Assert.IsTrue(
350-
unreferencedModel.GetLeadingTrivia().ToFullString().Contains("not referenced"),
351-
"Doc comment of the internalized type should be preserved.");
352-
353-
// An internalized type with another attribute in a separate list keeps the other attribute.
354-
Assert.IsTrue(unreferencedWithOther.Modifiers.Any(m => m.IsKind(SyntaxKind.InternalKeyword)));
355-
Assert.IsFalse(HasExperimentalAttribute(unreferencedWithOther), "Internalized model should not keep [Experimental].");
356-
Assert.IsTrue(HasAttribute(unreferencedWithOther, "Serializable"), "Other attributes should be preserved.");
357-
Assert.IsTrue(
358-
unreferencedWithOther.GetLeadingTrivia().ToFullString().Contains("must be preserved"),
359-
"Doc comment should be preserved when only one of several attribute lists is removed.");
360-
361-
// An internalized type with the experimental attribute combined in a single list keeps the others.
362-
Assert.IsTrue(unreferencedWithCombined.Modifiers.Any(m => m.IsKind(SyntaxKind.InternalKeyword)));
363-
Assert.IsFalse(HasExperimentalAttribute(unreferencedWithCombined), "Internalized model should not keep [Experimental].");
364-
Assert.IsTrue(HasAttribute(unreferencedWithCombined, "Serializable"), "Other attributes in the same list should be preserved.");
365-
}
366-
367-
private static async Task<ClassDeclarationSyntax> GetSingleClassAsync(Project project, string fileName, string className)
368-
{
369-
var doc = project.Documents.Single(d => d.Name == fileName);
370-
var root = await doc.GetSyntaxRootAsync();
371-
return ((CompilationUnitSyntax)root!)
372-
.DescendantNodes()
373-
.OfType<ClassDeclarationSyntax>()
374-
.Single(t => t.Identifier.Text == className);
375-
}
376-
377-
private static bool HasAttribute(BaseTypeDeclarationSyntax type, string attributeName)
378-
=> type.AttributeLists
379-
.SelectMany(list => list.Attributes)
380-
.Any(attr => attr.Name.ToString() == attributeName);
381-
382-
383-
private static bool HasExperimentalAttribute(BaseTypeDeclarationSyntax type)
384-
=> type.AttributeLists
385-
.SelectMany(list => list.Attributes)
386-
.Any(attr => attr.Name.ToString() is "Experimental" or "ExperimentalAttribute");
387-
388292
private class TestPostProcessor : PostProcessor
389293
{
390294
private readonly string _rootFile;

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/TestData/PostProcessorTests/RemovesExperimentalAttributeWhenInternalizing/ExperimentalInternalizeRoot.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/TestData/PostProcessorTests/RemovesExperimentalAttributeWhenInternalizing/ReferencedModel.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/TestData/PostProcessorTests/RemovesExperimentalAttributeWhenInternalizing/UnreferencedModel.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/TestData/PostProcessorTests/RemovesExperimentalAttributeWhenInternalizing/UnreferencedWithCombinedAttributes.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/TestData/PostProcessorTests/RemovesExperimentalAttributeWhenInternalizing/UnreferencedWithOtherAttribute.cs

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)