)]}'
{
  "commit": "36841b1d3eb8691d89a324de0a0bd10bcd25fc86",
  "tree": "6f15b6d0b79a8c6210be445af87a5b34df657353",
  "parents": [
    "66f539cf5096a878629ff0c2d3d5f8cf8001b021"
  ],
  "author": {
    "name": "Paul Irwin",
    "email": "paulirwin@gmail.com",
    "time": "Sat May 02 14:14:24 2026 -0600"
  },
  "committer": {
    "name": "GitHub",
    "email": "noreply@github.com",
    "time": "Sat May 02 14:14:24 2026 -0600"
  },
  "message": "Implement LuceneDev4000-4002 NoInlining Analyzers \u0026 CodeFix (#1097) (#30)\n\n* Implement LuceneDev4000-4002 NoInlining Analyzers \u0026 CodeFix (#1097)\n\nAdds three Roslyn analyzers under the new Performance category covering\n[MethodImpl(MethodImplOptions.NoInlining)] usage:\n\n- LuceneDev4000: Reports when NoInlining is applied to an interface or\n  abstract method. The MethodImpl attribute is not inherited, so the\n  attribute has no effect on the implementation.\n- LuceneDev4001: Reports when NoInlining is applied to an empty-bodied\n  method. An empty body cannot appear above any frame in a stack trace,\n  so preventing inlining provides no benefit.\n- LuceneDev4002: Reports when a method referenced by the 2-argument\n  StackTraceHelper.DoesStackTraceContainMethod(className, methodName)\n  overload is missing NoInlining. Without it, the JIT may inline the\n  method out of the stack trace, silently breaking the check.\n\nA code fix is provided for 4000 and 4001 (remove the attribute). 4002\nhas no automated fix because the diagnostic is reported on a method\ndeclaration triggered by an invocation in another scope, which Roslyn\ntreats as a non-local diagnostic and refuses to fix automatically.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Split LuceneDev4002 into its own analyzer class\n\nLuceneDev4002 (StackTraceHelper-driven NoInlining requirement) is a\ndistinct rule from 4000/4001 (NoInlining-as-no-op detection): it has a\ndifferent trigger (invocation vs. method declaration) and no code fix.\nExtract it into its own analyzer + test class. Shared logic for\nrecognising the [MethodImpl(MethodImplOptions.NoInlining)] attribute,\nempty bodies, and interface/abstract methods moves into\nNoInliningAttributeHelper.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Add Sample project entries for LuceneDev4000-4002\n\nDemonstrates each NoInlining diagnostic firing in the sample project:\n4000 on an interface and an abstract method, 4001 on an empty-bodied\nmethod, and 4002 on a method referenced by the 2-arg\nStackTraceHelper.DoesStackTraceContainMethod overload (with a local\nstub mirroring the real type so the sample compiles standalone).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Preserve leading comments and trim attribute indent in 4000/4001 fix\n\nThe previous remove-attribute logic used SyntaxRemoveOptions.KeepNoTrivia\nwhich discarded any comments or blank lines that preceded the attribute,\nor KeepLeadingTrivia which left a stray indent on the next line. Replace\nwith an approach that operates on the parent member declaration directly:\nstrip the attribute list while moving its leading comments (minus the\nfinal whitespace block, which was just the attribute\u0027s own indent) onto\nthe next member\u0027s leading trivia.\n\nAdds tests covering: leading comment preservation, removing one of\nseveral attributes inside a single attribute list, and removing one\nattribute list among multiple sibling lists.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Report LuceneDev4002 at the call site instead of the target method\n\nReporting on the target method declaration produced a non-local\ndiagnostic — the analyzer was visiting an InvocationExpressionSyntax\nbut raising the diagnostic on a syntax tree it had not visited. MSBuild\nran a full compilation and surfaced the warning, but the IDE\u0027s per-file\nlive analysis filtered it out, so the warning never appeared in the\neditor.\n\nMove the report to the DoesStackTraceContainMethod invocation. The\ndiagnostic is now local, shows up in the IDE, and opens the door to a\ncode fix at the call site. The message names the qualified target\n(e.g. \u0027Target.Merge\u0027) so it remains clear which method needs the\nattribute. Test span updated accordingly.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Add code fix for LuceneDev4002\n\nAdds [MethodImpl(MethodImplOptions.NoInlining)] to the target method\nreferenced by a DoesStackTraceContainMethod call. The fix resolves the\ntarget from the call\u0027s nameof() (or string-literal) arguments, locates\nits declaration in source, and edits that document — even when it lives\nin a different file from the call. Adds\n\u0027using System.Runtime.CompilerServices;\u0027 to the target\u0027s compilation\nunit if missing, and prepends the new attribute list ahead of any\nexisting attributes on the method.\n\nPromote NoInliningAttributeHelper from internal to public so the code\nfix project can reuse it. Adds tests covering: target with using\nalready present, target without the using, and target with an existing\nattribute.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Match source line-ending convention in 4002 code fix\n\nThe fix previously emitted \u0027\\n\u0027 line terminators unconditionally, which\nmatched local Mac checkouts (LF) but failed on Linux CI where the\nchecked-out source uses CRLF. Detect the existing line ending from the\ntarget tree\u0027s trivia and reuse it so the fixed output stays consistent\nwith the surrounding file.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Simplify 4002 code fix and rely on Formatter for layout\n\nReplaces hand-rolled trivia construction with a single Formatter pass.\nThe new attribute list and using directive are built without trivia and\nformatted via Formatter.FormatAsync, with the workspace NewLine option\nexplicitly set from the source file\u0027s existing line endings so the\noutput doesn\u0027t mix CRLF and LF on platforms where Environment.NewLine\ndisagrees with the file (e.g. CRLF source on Linux CI).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n* Address Copilot review on LuceneDev4002\n\nUse a symbol-based attribute check (IMethodSymbol.GetAttributes) instead\nof inspecting AttributeSyntax with a SemanticModel from the wrong tree.\nThe previous code threw \"Syntax node is not within syntax tree\" whenever\nthe target method lived in a different file from the call site and had\nany attribute. Symbol-based lookup also avoids Compilation.GetSemanticModel\nin the analyzer (which trips RS1030).\n\nAlso:\n- Drop the Contains(\"NoInlining\") string fallback in the syntax-based\n  helper; the constant-value path already resolves MethodImplOptions.NoInlining\n  and the fallback false-positives on identifiers like \"NotNoInlining\".\n- Replace the namespace walk in FindSourceTypeByName with\n  Compilation.GetSymbolsWithName to leverage Roslyn\u0027s symbol index.\n- Update the analyzer\u0027s XML doc — the PR adds a code fix; the previous\n  comment claimed there was none.\n- Add cross-document tests for both analyzer and code fix; the analyzer\n  test reproduces the SemanticModel bug above.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) \u003cnoreply@anthropic.com\u003e",
  "tree_diff": [
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "4072500d02dd3e5325b129473c3a83ed58e4cb5b",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev4xxx/LuceneDev4000_4001_NoInliningOnNoOpCodeFixProvider.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "68bb4582f62398a7cfdfdf3546521d5e49e48aae",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev4xxx/LuceneDev4002_StackTraceHelperNoInliningCodeFixProvider.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "88d47dce9355e3813c9a570ba56acad415e40018",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev4xxx/LuceneDev4000_4001_NoInliningOnNoOpSample.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "aef48402674cb3c9a98c5db361d277cc9af624b5",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev4xxx/LuceneDev4002_StackTraceHelperNoInliningSample.cs"
    },
    {
      "type": "modify",
      "old_id": "5b384e3becbdb2572f66cff4ac2e58fd839c06ee",
      "old_mode": 33188,
      "old_path": "src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md",
      "new_id": "80e78112d5fa4778a056c23a32a31890a89305c4",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Unshipped.md"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "ea50d8c3626f25463d34974552d57028179806bd",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev/LuceneDev4xxx/LuceneDev4000_4001_NoInliningOnNoOpAnalyzer.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "243cb296b2a0de0f10f853e76b2a68e366852735",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev/LuceneDev4xxx/LuceneDev4002_StackTraceHelperNoInliningAnalyzer.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "c0e0df53a302b211d27ca682b50a5c913b0d455f",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev/LuceneDev4xxx/NoInliningAttributeHelper.cs"
    },
    {
      "type": "modify",
      "old_id": "90216e6ad323f868e05f533d5e2d65e717d70ebc",
      "old_mode": 33188,
      "old_path": "src/Lucene.Net.CodeAnalysis.Dev/Resources.resx",
      "new_id": "2bf6cb74e769a67a0b8cff557705d9e2009d78b8",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev/Resources.resx"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "e29a93d5925e56db07cb4e02e3dc8a3586a350b9",
      "new_mode": 33188,
      "new_path": "src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev4xxx.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "a44196abc465a3ee42381e79a601929f8128b8c6",
      "new_mode": 33188,
      "new_path": "tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev4xxx/TestLuceneDev4000_4001_NoInliningOnNoOpCodeFixProvider.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "56b8d1a50341ebfd9d31e41651356e761babbe48",
      "new_mode": 33188,
      "new_path": "tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev4xxx/TestLuceneDev4002_StackTraceHelperNoInliningCodeFixProvider.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "ee0891742ed797d774bf711090a242df3ed085c9",
      "new_mode": 33188,
      "new_path": "tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev4xxx/TestLuceneDev4000_4001_NoInliningOnNoOpAnalyzer.cs"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "2ae3414ab65bf8b0f952d2b10a6a83e965fe1ce1",
      "new_mode": 33188,
      "new_path": "tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev4xxx/TestLuceneDev4002_StackTraceHelperNoInliningAnalyzer.cs"
    }
  ]
}
