Skip to content

Commit bd72042

Browse files
paulirwinclaude
andcommitted
Fix code fix to preserve license headers and comments
The code fix was stripping leading trivia (license headers, comments) when changing public to internal. The fix now preserves both leading trivia from the first modifier and trailing trivia from the accessibility modifier being replaced. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ca536ff commit bd72042

2 files changed

Lines changed: 203 additions & 1 deletion

File tree

src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1xxx/LuceneDev1005_LuceneNetSupportPublicTypesCSCodeFixProvider.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,27 @@ private static async Task<Solution> MakeDeclarationInternal(Document document,
106106
var syntaxRoot = await declarationDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
107107
if (syntaxRoot == null) continue;
108108

109+
// Get leading trivia from the first modifier (which contains license headers/comments)
110+
var leadingTrivia = declaration.Modifiers.Count > 0
111+
? declaration.Modifiers[0].LeadingTrivia
112+
: SyntaxTriviaList.Empty;
113+
114+
// Get trailing trivia from the accessibility modifier we're removing (typically whitespace)
115+
var accessibilityModifier = declaration.Modifiers
116+
.FirstOrDefault(m => m.IsKind(SyntaxKind.PublicKeyword) ||
117+
m.IsKind(SyntaxKind.InternalKeyword) ||
118+
m.IsKind(SyntaxKind.ProtectedKeyword) ||
119+
m.IsKind(SyntaxKind.PrivateKeyword));
120+
var trailingTrivia = accessibilityModifier.TrailingTrivia;
121+
109122
// Remove existing accessibility modifiers
110123
var newModifiers = SyntaxFactory.TokenList(
111124
declaration.Modifiers
112125
.Where(modifier => !modifier.IsKind(SyntaxKind.PrivateKeyword) &&
113126
!modifier.IsKind(SyntaxKind.ProtectedKeyword) &&
114127
!modifier.IsKind(SyntaxKind.InternalKeyword) &&
115128
!modifier.IsKind(SyntaxKind.PublicKeyword))
116-
).Insert(0, SyntaxFactory.Token(SyntaxKind.InternalKeyword)); // Ensure 'internal' is the first modifier
129+
).Insert(0, SyntaxFactory.Token(leadingTrivia, SyntaxKind.InternalKeyword, trailingTrivia)); // Ensure 'internal' is the first modifier with preserved trivia
117130

118131
var newDeclaration = declaration.WithModifiers(newModifiers);
119132
var newRoot = syntaxRoot.ReplaceNode(declaration, newDeclaration);

tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev1xxx/TestLuceneDev1005_LuceneNetSupportPublicTypesCSCodeFixProvider.cs

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,193 @@ public void Method2() { }
252252

253253
await test.RunAsync();
254254
}
255+
256+
[Test]
257+
public async Task PublicTypeInSupport_WithLicenseHeader_PreservesHeader()
258+
{
259+
const string testCode =
260+
"""
261+
/*
262+
* Licensed to the Apache Software Foundation (ASF) under one or more
263+
* contributor license agreements. See the NOTICE file distributed with
264+
* this work for additional information regarding copyright ownership.
265+
* The ASF licenses this file to You under the Apache License, Version 2.0
266+
* (the "License"); you may not use this file except in compliance with
267+
* the License. You may obtain a copy of the License at
268+
*
269+
* http://www.apache.org/licenses/LICENSE-2.0
270+
*
271+
* Unless required by applicable law or agreed to in writing, software
272+
* distributed under the License is distributed on an "AS IS" BASIS,
273+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
274+
* See the License for the specific language governing permissions and
275+
* limitations under the License.
276+
*/
277+
278+
namespace Lucene.Net.Support;
279+
280+
public class MyClass
281+
{
282+
}
283+
""";
284+
285+
const string fixedCode =
286+
"""
287+
/*
288+
* Licensed to the Apache Software Foundation (ASF) under one or more
289+
* contributor license agreements. See the NOTICE file distributed with
290+
* this work for additional information regarding copyright ownership.
291+
* The ASF licenses this file to You under the Apache License, Version 2.0
292+
* (the "License"); you may not use this file except in compliance with
293+
* the License. You may obtain a copy of the License at
294+
*
295+
* http://www.apache.org/licenses/LICENSE-2.0
296+
*
297+
* Unless required by applicable law or agreed to in writing, software
298+
* distributed under the License is distributed on an "AS IS" BASIS,
299+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
300+
* See the License for the specific language governing permissions and
301+
* limitations under the License.
302+
*/
303+
304+
namespace Lucene.Net.Support;
305+
306+
internal class MyClass
307+
{
308+
}
309+
""";
310+
311+
var expected = new DiagnosticResult(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes)
312+
.WithSeverity(DiagnosticSeverity.Warning)
313+
.WithMessageFormat(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes.MessageFormat)
314+
.WithArguments("Class", "MyClass")
315+
.WithLocation(20, 1);
316+
317+
var test = new InjectableCodeFixTest(
318+
() => new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer(),
319+
() => new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeFixProvider())
320+
{
321+
TestCode = testCode.ReplaceLineEndings(),
322+
FixedCode = fixedCode.ReplaceLineEndings(),
323+
ExpectedDiagnostics = { expected }
324+
};
325+
326+
await test.RunAsync();
327+
}
328+
329+
[Test]
330+
public async Task PublicTypeInSupport_WithLicenseHeaderInsideNamespace_PreservesHeader()
331+
{
332+
const string testCode =
333+
"""
334+
namespace Lucene.Net.Support
335+
{
336+
/*
337+
* Licensed to the Apache Software Foundation (ASF) under one or more
338+
* contributor license agreements. See the NOTICE file distributed with
339+
* this work for additional information regarding copyright ownership.
340+
* The ASF licenses this file to You under the Apache License, Version 2.0
341+
* (the "License"); you may not use this file except in compliance with
342+
* the License. You may obtain a copy of the License at
343+
*
344+
* http://www.apache.org/licenses/LICENSE-2.0
345+
*
346+
* Unless required by applicable law or agreed to in writing, software
347+
* distributed under the License is distributed on an "AS IS" BASIS,
348+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
349+
* See the License for the specific language governing permissions and
350+
* limitations under the License.
351+
*/
352+
353+
public class MyClass
354+
{
355+
}
356+
}
357+
""";
358+
359+
const string fixedCode =
360+
"""
361+
namespace Lucene.Net.Support
362+
{
363+
/*
364+
* Licensed to the Apache Software Foundation (ASF) under one or more
365+
* contributor license agreements. See the NOTICE file distributed with
366+
* this work for additional information regarding copyright ownership.
367+
* The ASF licenses this file to You under the Apache License, Version 2.0
368+
* (the "License"); you may not use this file except in compliance with
369+
* the License. You may obtain a copy of the License at
370+
*
371+
* http://www.apache.org/licenses/LICENSE-2.0
372+
*
373+
* Unless required by applicable law or agreed to in writing, software
374+
* distributed under the License is distributed on an "AS IS" BASIS,
375+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
376+
* See the License for the specific language governing permissions and
377+
* limitations under the License.
378+
*/
379+
380+
internal class MyClass
381+
{
382+
}
383+
}
384+
""";
385+
386+
var expected = new DiagnosticResult(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes)
387+
.WithSeverity(DiagnosticSeverity.Warning)
388+
.WithMessageFormat(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes.MessageFormat)
389+
.WithArguments("Class", "MyClass")
390+
.WithLocation(20, 5);
391+
392+
var test = new InjectableCodeFixTest(
393+
() => new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer(),
394+
() => new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeFixProvider())
395+
{
396+
TestCode = testCode.ReplaceLineEndings(),
397+
FixedCode = fixedCode.ReplaceLineEndings(),
398+
ExpectedDiagnostics = { expected }
399+
};
400+
401+
await test.RunAsync();
402+
}
403+
404+
[Test]
405+
public async Task PublicTypeInSupport_WithTrailingTrivia_PreservesTrailingTrivia()
406+
{
407+
const string testCode =
408+
"""
409+
namespace Lucene.Net.Support
410+
{
411+
public class MyClass
412+
{
413+
} // Important trailing comment
414+
}
415+
""";
416+
417+
const string fixedCode =
418+
"""
419+
namespace Lucene.Net.Support
420+
{
421+
internal class MyClass
422+
{
423+
} // Important trailing comment
424+
}
425+
""";
426+
427+
var expected = new DiagnosticResult(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes)
428+
.WithSeverity(DiagnosticSeverity.Warning)
429+
.WithMessageFormat(Descriptors.LuceneDev1005_LuceneNetSupportPublicTypes.MessageFormat)
430+
.WithArguments("Class", "MyClass")
431+
.WithLocation(3, 5);
432+
433+
var test = new InjectableCodeFixTest(
434+
() => new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeAnalyzer(),
435+
() => new LuceneDev1005_LuceneNetSupportPublicTypesCSCodeFixProvider())
436+
{
437+
TestCode = testCode.ReplaceLineEndings(),
438+
FixedCode = fixedCode.ReplaceLineEndings(),
439+
ExpectedDiagnostics = { expected }
440+
};
441+
442+
await test.RunAsync();
443+
}
255444
}

0 commit comments

Comments
 (0)