From 1c46cd734e296da1e03900608643f65b5fc54759 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 17:32:57 +0200 Subject: [PATCH 1/8] [Tools] Add Opc.Ua.CodeFixers Roslyn analyzer package Introduces Tools/Opc.Ua.CodeFixers, a new Roslyn analyzer + code-fixer package that helps consumers migrate from OPC UA .NET Standard 1.5.378 to 2.0 by mechanizing 17 of the breaking-change patterns documented in Docs/MigrationGuide.md. Rules (UA0001-UA0020): UA0001 Utils.Trace/LogX to ILogger (diagnostic only); UA0002 removed Type Collections to List/ArrayOf; UA0003 == null on now-struct built-ins to .IsNull; UA0004 ?. on now-struct built-ins; UA0005 byte[] to ByteString at API boundaries; UA0006 obsolete Variant(object|DateTime|Guid|byte[]) ctors to Variant.From; UA0007 new NodeId(string) to NodeId.Parse; UA0008 Session.Call params object[] to Variant.From wrapping; UA0009 [DataContract]/[DataMember] to [DataType]/[DataTypeField]; UA0010 remove using/Dispose on CertificateIdentifier/UserIdentity/IUserIdentityTokenHandler (diagnostic only); UA0011 IUserIdentityTokenHandler sync to Async (diagnostic only); UA0012 CertificateFactory static to DefaultCertificateFactory.Instance; UA0014 DataValue.IsGood static helper to instance property; UA0015 GDS/LDS client sync/APM to Async (diagnostic only); UA0018 CertificateIdentifier.Certificate getter to ResolveAsync (diagnostic only); UA0019 obsolete new DataValue(StatusCode) to DataValue.FromStatusCode; UA0020 EncodeableFactory.GlobalFactory/Create to ServiceMessageContext.Factory/Fork. Includes Tools/Opc.Ua.CodeFixers (analyzer project, netstandard2.0, EnforceExtendedAnalyzerRules, centralized DiagnosticDescriptors and DiagnosticIds, UaSymbols + SymbolExtensions helpers, AnalyzerReleases tracking) and Tests/Opc.Ua.CodeFixers.Tests (87 NUnit tests with a custom AnalyzerHarness and OpcUaStubs). Adds Microsoft.CodeAnalysis.CSharp.Workspaces 5.3.0 and Microsoft.CodeAnalysis.Workspaces.Common 5.3.0 to Directory.Packages.props (used PrivateAssets=all by the analyzer; required by the test harness). Registers the projects in UA.slnx and Tools/SourceGeneration.slnx under a new 'CodeFixers' folder. Tests pass on net10.0 (87/87). --- Directory.Packages.props | 2 + .../AnalyzerHarness.cs | 189 ++++++++ .../Analyzers/UA0001Tests.cs | 146 ++++++ .../Analyzers/UA0002Tests.cs | 137 ++++++ .../Analyzers/UA0003Tests.cs | 191 ++++++++ .../Analyzers/UA0004Tests.cs | 147 ++++++ .../Analyzers/UA0005Tests.cs | 153 ++++++ .../Analyzers/UA0006Tests.cs | 220 +++++++++ .../Analyzers/UA0007Tests.cs | 148 ++++++ .../Analyzers/UA0008Tests.cs | 167 +++++++ .../Analyzers/UA0009Tests.cs | 213 +++++++++ .../Analyzers/UA0010Tests.cs | 133 ++++++ .../Analyzers/UA0011Tests.cs | 143 ++++++ .../Analyzers/UA0012Tests.cs | 127 +++++ .../Analyzers/UA0014Tests.cs | 146 ++++++ .../Analyzers/UA0015Tests.cs | 168 +++++++ .../Analyzers/UA0018Tests.cs | 140 ++++++ .../Analyzers/UA0019Tests.cs | 153 ++++++ .../Analyzers/UA0020Tests.cs | 215 +++++++++ .../Opc.Ua.CodeFixers.Tests.csproj | 31 ++ .../Properties/AssemblyInfo.cs | 32 ++ .../Stubs/OpcUaStubs.cs | 441 ++++++++++++++++++ .../AnalyzerReleases.Shipped.md | 2 + .../AnalyzerReleases.Unshipped.md | 24 + .../UA0001UtilsTraceToILoggerAnalyzer.cs | 102 ++++ .../UA0002RemovedCollectionTypeAnalyzer.cs | 96 ++++ .../UA0003NullCheckOnStructTypeAnalyzer.cs | 129 +++++ ...UA0004ConditionalAccessOnStructAnalyzer.cs | 98 ++++ .../UA0005ByteArrayToByteStringAnalyzer.cs | 118 +++++ .../UA0006ObsoleteVariantCtorAnalyzer.cs | 115 +++++ .../UA0007ObsoleteNodeIdStringCtorAnalyzer.cs | 86 ++++ .../UA0008SessionCallParamsObjectAnalyzer.cs | 134 ++++++ .../UA0009DataContractToDataTypeAnalyzer.cs | 131 ++++++ .../UA0010RemoveDisposableAnalyzer.cs | 204 ++++++++ .../UA0011TokenHandlerSyncToAsyncAnalyzer.cs | 129 +++++ ...tificateFactoryStaticToInstanceAnalyzer.cs | 94 ++++ .../UA0014DataValueIsGoodAnalyzer.cs | 108 +++++ .../Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs | 115 +++++ ...ertificateIdentifierCertificateAnalyzer.cs | 94 ++++ .../UA0019DataValueStatusCodeCtorAnalyzer.cs | 97 ++++ .../UA0020EncodeableFactoryRenameAnalyzer.cs | 114 +++++ .../UA0002RemovedCollectionTypeCodeFix.cs | 117 +++++ .../UA0003NullCheckOnStructTypeCodeFix.cs | 134 ++++++ .../UA0004ConditionalAccessOnStructCodeFix.cs | 119 +++++ .../UA0005ByteArrayToByteStringCodeFix.cs | 101 ++++ .../UA0006ObsoleteVariantCtorCodeFix.cs | 146 ++++++ .../UA0007ObsoleteNodeIdStringCtorCodeFix.cs | 120 +++++ .../UA0008SessionCallParamsObjectCodeFix.cs | 144 ++++++ .../UA0009DataContractToDataTypeCodeFix.cs | 218 +++++++++ .../UA0010RemoveDisposableCodeFix.cs | 59 +++ ...rtificateFactoryStaticToInstanceCodeFix.cs | 106 +++++ .../CodeFixes/UA0014DataValueIsGoodCodeFix.cs | 122 +++++ .../UA0019DataValueStatusCodeCtorCodeFix.cs | 100 ++++ .../UA0020EncodeableFactoryRenameCodeFix.cs | 107 +++++ .../Diagnostics/DiagnosticDescriptors.cs | 179 +++++++ .../Diagnostics/DiagnosticIds.cs | 71 +++ .../Helpers/SymbolExtensions.cs | 172 +++++++ Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs | 165 +++++++ Tools/Opc.Ua.CodeFixers/NugetREADME.md | 59 +++ .../OPCFoundation.Opc.Ua.CodeFixers.props | 10 + .../Opc.Ua.CodeFixers.csproj | 49 ++ .../Properties/AssemblyInfo.cs | 32 ++ Tools/SourceGeneration.slnx | 4 + UA.slnx | 4 + 64 files changed, 7770 insertions(+) create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Properties/AssemblyInfo.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs create mode 100644 Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Shipped.md create mode 100644 Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0010RemoveDisposableCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0014DataValueIsGoodCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs create mode 100644 Tools/Opc.Ua.CodeFixers/NugetREADME.md create mode 100644 Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props create mode 100644 Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj create mode 100644 Tools/Opc.Ua.CodeFixers/Properties/AssemblyInfo.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 2f4a477322..fd1e813434 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,6 +21,8 @@ + + diff --git a/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs b/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs new file mode 100644 index 0000000000..ab4e4877fb --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs @@ -0,0 +1,189 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Opc.Ua.CodeFixers.Tests +{ + /// + /// Lightweight, no-extra-package analyzer + code-fix test harness. Lifts the + /// useful bits of Microsoft.CodeAnalysis.Testing without taking the dependency. + /// + /// Each test feeds a small C# source snippet (concatenated with + /// ) into the harness; the harness compiles + /// the snippet, runs the given , asserts + /// the diagnostics, optionally applies the matching , + /// and verifies the fixed source matches an expected string. + /// + public static class AnalyzerHarness + { + private static readonly ImmutableArray s_baseReferences = BuildBaseReferences(); + + private static ImmutableArray BuildBaseReferences() + { + string trustedAssemblies = (string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? string.Empty; + return [.. trustedAssemblies + .Split(Path.PathSeparator) + .Where(p => !string.IsNullOrEmpty(p)) + .Select(p => (MetadataReference)MetadataReference.CreateFromFile(p))]; + } + + /// + /// Compile together with the OPC UA stub surface + /// and return the resulting . + /// + public static CSharpCompilation Compile(string userSource, string assemblyName = "TestAssembly") + { + CSharpParseOptions parseOptions = new CSharpParseOptions(LanguageVersion.CSharp13); + SyntaxTree[] trees = + [ + CSharpSyntaxTree.ParseText(OpcUaStubs.Source, parseOptions, "OpcUaStubs.cs"), + CSharpSyntaxTree.ParseText(userSource, parseOptions, "Test.cs"), + ]; + CSharpCompilationOptions options = new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary, + allowUnsafe: false, + nullableContextOptions: NullableContextOptions.Enable); + return CSharpCompilation.Create(assemblyName, trees, s_baseReferences, options); + } + + /// + /// Run against and + /// return only the analyzer's diagnostics (compiler diagnostics are filtered out). + /// + public static async Task> GetAnalyzerDiagnosticsAsync( + DiagnosticAnalyzer analyzer, + string userSource) + { + CSharpCompilation compilation = Compile(userSource); + CompilationWithAnalyzers withAnalyzers = compilation.WithAnalyzers( + ImmutableArray.Create(analyzer), + new CompilationWithAnalyzersOptions( + options: null!, + onAnalyzerException: null, + concurrentAnalysis: true, + logAnalyzerExecutionTime: true, + reportSuppressedDiagnostics: true)); + ImmutableArray all = await withAnalyzers.GetAnalyzerDiagnosticsAsync() + .ConfigureAwait(false); + return all; + } + + /// + /// Apply to every diagnostic raised by + /// against and + /// return the fixed source string for "Test.cs". + /// + public static async Task ApplyFixAsync( + DiagnosticAnalyzer analyzer, + CodeFixProvider codeFix, + string userSource) + { + CSharpCompilation compilation = Compile(userSource); + CompilationWithAnalyzers withAnalyzers = compilation.WithAnalyzers( + ImmutableArray.Create(analyzer), + new CompilationWithAnalyzersOptions( + options: null!, + onAnalyzerException: null, + concurrentAnalysis: true, + logAnalyzerExecutionTime: true, + reportSuppressedDiagnostics: true)); + ImmutableArray diags = await withAnalyzers.GetAnalyzerDiagnosticsAsync() + .ConfigureAwait(false); + + SyntaxTree testTree = compilation.SyntaxTrees.First(t => t.FilePath == "Test.cs"); + Document document = CreateDocument(testTree, codeFix); + + foreach (Diagnostic diag in diags) + { + if (diag.Location.SourceTree?.FilePath != "Test.cs") + { + continue; + } + List actions = []; + CodeFixContext ctx = new CodeFixContext( + document, + diag, + (action, _) => actions.Add(action), + CancellationToken.None); + await codeFix.RegisterCodeFixesAsync(ctx).ConfigureAwait(false); + if (actions.Count == 0) + { + continue; + } + CodeAction first = actions[0]; + ImmutableArray ops = await first + .GetOperationsAsync(CancellationToken.None) + .ConfigureAwait(false); + Solution? newSolution = null; + foreach (CodeActionOperation op in ops) + { + if (op is ApplyChangesOperation applyOp) + { + newSolution = applyOp.ChangedSolution; + break; + } + } + if (newSolution != null) + { + document = newSolution.GetDocument(document.Id)!; + } + } + + SourceText resultText = await document.GetTextAsync().ConfigureAwait(false); + return resultText.ToString(); + } + + private static Document CreateDocument(SyntaxTree tree, CodeFixProvider _) + { + AdhocWorkspace workspace = new AdhocWorkspace(); + ProjectId projectId = ProjectId.CreateNewId(); + DocumentId documentId = DocumentId.CreateNewId(projectId); + Solution solution = workspace.CurrentSolution + .AddProject(projectId, "TestProject", "TestProject", LanguageNames.CSharp) + .AddMetadataReferences(projectId, s_baseReferences) + .AddDocument(documentId, "Test.cs", tree.GetText()); + return solution.GetDocument(documentId)!; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs new file mode 100644 index 0000000000..30970eab72 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs @@ -0,0 +1,146 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0001 (Utils.Trace / Utils.LogX -> ILogger). Diagnostic-only; + /// no code fix is shipped — the replacement requires an ILogger instance. + /// + [TestFixture] + public class UA0001Tests + { + [Test] + public async Task ReportsDiagnosticOnUtilsTraceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M() => Utils.Trace("hello"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0001UtilsTraceToILoggerAnalyzer(), source); + + Diagnostic? ua0001 = diags.SingleOrDefault(d => d.Id == "UA0001"); + Assert.That(ua0001, Is.Not.Null, "Expected UA0001 to fire on Utils.Trace(...)."); + Assert.That( + ua0001!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("Utils.Trace")); + } + + [Test] + public async Task ReportsDiagnosticOnUtilsLogErrorAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M() => Utils.LogError("error: {0}", 42); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0001UtilsTraceToILoggerAnalyzer(), source); + + Diagnostic? ua0001 = diags.SingleOrDefault(d => d.Id == "UA0001"); + Assert.That(ua0001, Is.Not.Null, "Expected UA0001 to fire on Utils.LogError(...)."); + Assert.That( + ua0001!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("Utils.LogError")); + } + + [Test] + public async Task ReportsDiagnosticOnUtilsLogInformationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M() => Utils.LogInformation("info"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0001UtilsTraceToILoggerAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0001"), Is.True, + "Expected UA0001 to fire on Utils.LogInformation(...)."); + } + + [Test] + public async Task DoesNotReportOnInstanceILoggerCallAsync() + { + const string source = """ + using Microsoft.Extensions.Logging; + class C + { + static void M(ILogger logger) => logger.LogInformation("info"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0001UtilsTraceToILoggerAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0001"), Is.False, + "Instance ILogger.LogInformation must not trigger UA0001."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedStaticUtilsTraceAsync() + { + const string source = """ + static class MyUtils + { + public static void Trace(string s) { } + } + class C + { + static void M() => MyUtils.Trace("x"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0001UtilsTraceToILoggerAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0001"), Is.False, + "A user-defined static Trace on an unrelated class must not trigger UA0001."); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs new file mode 100644 index 0000000000..2493bd8985 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs @@ -0,0 +1,137 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0002 (removed <Type>Collection wrappers). + /// + [TestFixture] + public class UA0002Tests + { + private static bool IsUserDiagnostic(Diagnostic d) + { + return d.Id == "UA0002" && d.Location.SourceTree?.FilePath == "Test.cs"; + } + + [Test] + public async Task ReportsDiagnosticOnInt32CollectionVariableDeclarationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M() { Int32Collection x = new Int32Collection(); } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0002RemovedCollectionTypeAnalyzer(), source); + + Diagnostic? ua0002 = diags.FirstOrDefault(IsUserDiagnostic); + Assert.That(ua0002, Is.Not.Null, + "Expected UA0002 to fire on the Int32Collection variable declaration."); + Assert.That( + ua0002!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("Int32Collection")); + } + + [Test] + public async Task ReportsDiagnosticOnNodeIdCollectionParameterAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(NodeIdCollection ids) { _ = ids; } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0002RemovedCollectionTypeAnalyzer(), source); + + Assert.That(diags.Any(IsUserDiagnostic), Is.True, + "Expected UA0002 to fire on the NodeIdCollection parameter type."); + } + + [Test] + public async Task DoesNotReportOnListOfIntAsync() + { + const string source = """ + using System.Collections.Generic; + class C + { + static void M() { List x = new List(); _ = x; } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0002RemovedCollectionTypeAnalyzer(), source); + + Assert.That(diags.Any(IsUserDiagnostic), Is.False, + "List must not trigger UA0002."); + } + + [Test] + public async Task FixRewritesInt32CollectionDeclarationAndCreationAsync() + { + const string source = """ + using System.Collections.Generic; + using Opc.Ua; + class C + { + static void M() { Int32Collection x = new Int32Collection(); _ = x; } + } + """; + const string expected = """ + using System.Collections.Generic; + using Opc.Ua; + class C + { + static void M() { List x = new List(); _ = x; } + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0002RemovedCollectionTypeAnalyzer(), + new UA0002RemovedCollectionTypeCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs new file mode 100644 index 0000000000..81999b6641 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs @@ -0,0 +1,191 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0003 (null comparison on now-struct built-in type). + /// + [TestFixture] + public class UA0003Tests + { + [Test] + public async Task ReportsOnNodeIdEqualsNullAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(NodeId n) => n == null; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0003NullCheckOnStructTypeAnalyzer(), source); + + Diagnostic? ua0003 = diags.SingleOrDefault(d => d.Id == "UA0003"); + Assert.That(ua0003, Is.Not.Null); + Assert.That( + ua0003!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("NodeId")); + } + + [Test] + public async Task ReportsOnNullEqualsNodeIdAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(NodeId n) => null == n; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0003NullCheckOnStructTypeAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0003"), Is.True); + } + + [Test] + public async Task ReportsOnLocalizedTextNotEqualsNullAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(LocalizedText lt) => lt != null; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0003NullCheckOnStructTypeAnalyzer(), source); + + Diagnostic? ua0003 = diags.SingleOrDefault(d => d.Id == "UA0003"); + Assert.That(ua0003, Is.Not.Null); + Assert.That( + ua0003!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("LocalizedText")); + } + + [Test] + public async Task DoesNotReportOnStringEqualsNullAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(string s) => s == null; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0003NullCheckOnStructTypeAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0003"), Is.False); + } + + [Test] + public async Task DoesNotReportOnEqualsMethodCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(NodeId n) => n.Equals(null); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0003NullCheckOnStructTypeAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0003"), Is.False); + } + + [Test] + public async Task FixRewritesNodeIdEqualsNullToIsNullAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(NodeId n) => n == null; + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static bool M(NodeId n) => n.IsNull; + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0003NullCheckOnStructTypeAnalyzer(), + new UA0003NullCheckOnStructTypeCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + + [Test] + public async Task FixRewritesLocalizedTextNotEqualsNullToNotIsNullOrEmptyAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(LocalizedText lt) => lt != null; + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static bool M(LocalizedText lt) => !lt.IsNullOrEmpty; + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0003NullCheckOnStructTypeAnalyzer(), + new UA0003NullCheckOnStructTypeCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs new file mode 100644 index 0000000000..15b3dc2e42 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs @@ -0,0 +1,147 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0004 (null-conditional access on now-struct built-in type). + /// + [TestFixture] + public class UA0004Tests + { + [Test] + public async Task ReportsOnNodeIdConditionalAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M(NodeId nodeId) => nodeId?.NamespaceIndex; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0004ConditionalAccessOnStructAnalyzer(), source); + + Diagnostic? ua0004 = diags.SingleOrDefault(d => d.Id == "UA0004"); + Assert.That(ua0004, Is.Not.Null, "Expected UA0004 to fire on nodeId?.NamespaceIndex."); + Assert.That( + ua0004!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("NodeId")); + } + + [Test] + public async Task ReportsOnDataValueConditionalAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M(DataValue dv) => dv?.IsGood; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0004ConditionalAccessOnStructAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0004"), Is.True, + "Expected UA0004 to fire on dv?.IsGood."); + } + + [Test] + public async Task DoesNotReportOnStringConditionalAccessAsync() + { + const string source = """ + class C + { + static object M(string s) => s?.Length; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0004ConditionalAccessOnStructAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0004"), Is.False, + "Conditional access on string must not trigger UA0004."); + } + + [Test] + public async Task DoesNotReportOnPlainMemberAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M(NodeId nodeId) => nodeId.NamespaceIndex; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0004ConditionalAccessOnStructAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0004"), Is.False, + "Plain member access without '?.' must not trigger UA0004."); + } + + [Test] + public async Task FixRewritesConditionalAccessToDirectAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M(NodeId nodeId) => nodeId?.NamespaceIndex; + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static object M(NodeId nodeId) => nodeId.NamespaceIndex; + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0004ConditionalAccessOnStructAnalyzer(), + new UA0004ConditionalAccessOnStructCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs new file mode 100644 index 0000000000..5da291ccf2 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs @@ -0,0 +1,153 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0005 (byte[] passed where ByteString is now expected). + /// + [TestFixture] + public class UA0005Tests + { + [Test] + public async Task ReportsOnByteArrayArgumentWhereByteStringIsExpectedAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(ByteString b) { } + static void Caller(byte[] arr) => M(arr); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0005ByteArrayToByteStringAnalyzer(), source); + + Diagnostic? ua0005 = diags.SingleOrDefault(d => d.Id == "UA0005"); + Assert.That(ua0005, Is.Not.Null, "Expected UA0005 to fire when byte[] is passed to a ByteString parameter."); + Assert.That( + ua0005!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("M")); + } + + [Test] + public async Task DoesNotReportWhenArgumentAlreadyToByteStringAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(ByteString b) { } + static void Caller(byte[] arr) => M(arr.ToByteString()); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0005ByteArrayToByteStringAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0005"), Is.False, + "Calling .ToByteString() at the call site must not trigger UA0005."); + } + + [Test] + public async Task DoesNotReportWhenByteArrayOverloadBindsAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void Caller(byte[] arr) => ByteStringApi.Process(arr); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0005ByteArrayToByteStringAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0005"), Is.False, + "When the byte[] overload binds the rule must not fire."); + } + + [Test] + public async Task DoesNotReportOnDefaultLiteralAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(ByteString b) { } + static void Caller() => M(default); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0005ByteArrayToByteStringAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0005"), Is.False, + "A 'default' literal has type ByteString — UA0005 must not fire."); + } + + [Test] + public async Task FixAppendsToByteStringAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(ByteString b) { } + static void Caller(byte[] arr) => M(arr); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static void M(ByteString b) { } + static void Caller(byte[] arr) => M(arr.ToByteString()); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0005ByteArrayToByteStringAnalyzer(), + new UA0005ByteArrayToByteStringCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs new file mode 100644 index 0000000000..b349ccc961 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs @@ -0,0 +1,220 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0006 (obsolete Variant(object|DateTime|Guid|byte[]) constructors). + /// + [TestFixture] + public class UA0006Tests + { + [Test] + public async Task ReportsOnNewVariantObjectAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static Variant M() => new Variant((object)42); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0006ObsoleteVariantCtorAnalyzer(), source); + + Diagnostic? ua0006 = diags.SingleOrDefault(d => d.Id == "UA0006"); + Assert.That(ua0006, Is.Not.Null); + Assert.That( + ua0006!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("object")); + } + + [Test] + public async Task ReportsOnNewVariantDateTimeAsync() + { + const string source = """ + using System; + using Opc.Ua; + class C + { + static Variant M() => new Variant(DateTime.UtcNow); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0006ObsoleteVariantCtorAnalyzer(), source); + + Diagnostic? ua0006 = diags.SingleOrDefault(d => d.Id == "UA0006"); + Assert.That(ua0006, Is.Not.Null); + Assert.That( + ua0006!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("DateTime")); + } + + [Test] + public async Task ReportsOnNewVariantGuidAsync() + { + const string source = """ + using System; + using Opc.Ua; + class C + { + static Variant M() => new Variant(Guid.NewGuid()); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0006ObsoleteVariantCtorAnalyzer(), source); + + Diagnostic? ua0006 = diags.SingleOrDefault(d => d.Id == "UA0006"); + Assert.That(ua0006, Is.Not.Null); + Assert.That( + ua0006!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("Guid")); + } + + [Test] + public async Task ReportsOnNewVariantByteArrayAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static Variant M() => new Variant(new byte[] { 1, 2, 3 }); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0006ObsoleteVariantCtorAnalyzer(), source); + + Diagnostic? ua0006 = diags.SingleOrDefault(d => d.Id == "UA0006"); + Assert.That(ua0006, Is.Not.Null); + Assert.That( + ua0006!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("byte[]")); + } + + [Test] + public async Task DoesNotReportOnNewVariantIntAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static Variant M() => new Variant(42); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0006ObsoleteVariantCtorAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0006"), Is.False); + } + + [Test] + public async Task DoesNotReportOnNewVariantStringAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static Variant M() => new Variant("hello"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0006ObsoleteVariantCtorAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0006"), Is.False); + } + + [Test] + public async Task FixRewritesDateTimeCtorToVariantFromDateTimeUtcAsync() + { + const string source = """ + using System; + using Opc.Ua; + class C + { + static Variant M() => new Variant(DateTime.UtcNow); + } + """; + const string expected = """ + using System; + using Opc.Ua; + class C + { + static Variant M() => Variant.From(new DateTimeUtc(DateTime.UtcNow)); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0006ObsoleteVariantCtorAnalyzer(), + new UA0006ObsoleteVariantCtorCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + + [Test] + public async Task FixRewritesByteArrayCtorToVariantFromToByteStringAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static Variant M(byte[] arr) => new Variant(arr); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static Variant M(byte[] arr) => Variant.From(arr.ToByteString()); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0006ObsoleteVariantCtorAnalyzer(), + new UA0006ObsoleteVariantCtorCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs new file mode 100644 index 0000000000..a9693de270 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs @@ -0,0 +1,148 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0007 (obsolete NodeId/ExpandedNodeId string constructor). + /// + [TestFixture] + public class UA0007Tests + { + [Test] + public async Task ReportsDiagnosticOnNewNodeIdStringAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static NodeId M() => new NodeId("ns=1;i=42"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0007ObsoleteNodeIdStringCtorAnalyzer(), source); + + Diagnostic? ua0007 = diags.SingleOrDefault(d => d.Id == "UA0007"); + Assert.That(ua0007, Is.Not.Null); + Assert.That( + ua0007!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("NodeId")); + } + + [Test] + public async Task ReportsDiagnosticOnNewExpandedNodeIdStringAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static ExpandedNodeId M() => new ExpandedNodeId("ns=1;i=42"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0007ObsoleteNodeIdStringCtorAnalyzer(), source); + + Diagnostic? ua0007 = diags.SingleOrDefault(d => d.Id == "UA0007"); + Assert.That(ua0007, Is.Not.Null); + Assert.That( + ua0007!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("ExpandedNodeId")); + } + + [Test] + public async Task DoesNotReportOnNewNodeIdUintAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static NodeId M() => new NodeId(42u); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0007ObsoleteNodeIdStringCtorAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0007"), Is.False); + } + + [Test] + public async Task DoesNotReportOnNewNodeIdUintNamespaceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static NodeId M() => new NodeId(42u, (ushort)0); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0007ObsoleteNodeIdStringCtorAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0007"), Is.False); + } + + [Test] + public async Task FixRewritesStringCtorToParseAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static NodeId M(string s) => new NodeId(s); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static NodeId M(string s) => NodeId.Parse(s); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0007ObsoleteNodeIdStringCtorAnalyzer(), + new UA0007ObsoleteNodeIdStringCtorCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs new file mode 100644 index 0000000000..4ce5fc1dea --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs @@ -0,0 +1,167 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0008 (Session.Call params object[] → params Variant[]). + /// + [TestFixture] + public class UA0008Tests + { + [Test] + public async Task ReportsDiagnosticOnSessionCallWithRawArgsAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(Session session, NodeId objId, NodeId methodId) + { + session.Call(objId, methodId, 1, "two"); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0008SessionCallParamsObjectAnalyzer(), source); + + Diagnostic? ua0008 = diags.SingleOrDefault(d => d.Id == "UA0008"); + Assert.That(ua0008, Is.Not.Null, + "Expected UA0008 to fire on Session.Call with raw int / string args."); + Assert.That( + ua0008!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("Call")); + } + + [Test] + public async Task ReportsDiagnosticOnSessionCallAsyncWithRawArgsAsync() + { + const string source = """ + using System.Threading; + using Opc.Ua; + class C + { + static void M(Session session, NodeId objId, NodeId methodId, CancellationToken ct) + { + _ = session.CallAsync(objId, methodId, ct, 42); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0008SessionCallParamsObjectAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0008"), Is.True, + "Expected UA0008 to fire on Session.CallAsync with a raw int arg."); + } + + [Test] + public async Task DoesNotReportWhenAllArgsAreVariantAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(Session session, NodeId objId, NodeId methodId) + { + session.Call(objId, methodId, Variant.From(1), Variant.From("two")); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0008SessionCallParamsObjectAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0008"), Is.False, + "All-Variant arguments must not trigger UA0008."); + } + + [Test] + public async Task DoesNotReportWhenNoVariadicArgsAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(Session session, NodeId objId, NodeId methodId) + { + session.Call(objId, methodId); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0008SessionCallParamsObjectAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0008"), Is.False, + "Session.Call with no variadic args must not trigger UA0008."); + } + + [Test] + public async Task FixWrapsRawArgsWithVariantFromAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(Session session, NodeId o, NodeId m) + { + session.Call(o, m, 1, "two"); + } + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static void M(Session session, NodeId o, NodeId m) + { + session.Call(o, m, Variant.From(1), Variant.From("two")); + } + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0008SessionCallParamsObjectAnalyzer(), + new UA0008SessionCallParamsObjectCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs new file mode 100644 index 0000000000..7451103929 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs @@ -0,0 +1,213 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0009 ([DataContract]/[DataMember] -> [DataType]/[DataTypeField]). + /// + [TestFixture] + public class UA0009Tests + { + [Test] + public async Task ReportsOnDataContractClassWithDataMemberPropertyAsync() + { + const string source = """ + using System.Runtime.Serialization; + namespace Test + { + [DataContract] + public class Foo + { + [DataMember] + public int X { get; set; } + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0009DataContractToDataTypeAnalyzer(), source); + + Diagnostic? ua0009 = diags.SingleOrDefault(d => d.Id == "UA0009"); + Assert.That(ua0009, Is.Not.Null, "Expected UA0009 on [DataContract] + [DataMember] class."); + Assert.That( + ua0009!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("Foo")); + } + + [Test] + public async Task ReportsOnDataContractClassWithoutParseExtensionUseAsync() + { + // The simplified detection flags any candidate class regardless of whether + // ApplicationConfiguration.ParseExtension/UpdateExtension is invoked in the + // same compilation. This test pins that behaviour. + const string source = """ + using System.Runtime.Serialization; + namespace Test + { + [DataContract(Name = "Foo")] + public class Foo + { + [DataMember(Order = 1)] + public int X { get; set; } + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0009DataContractToDataTypeAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0009"), Is.True, + "Expected UA0009 even when no ParseExtension call is present."); + } + + [Test] + public async Task DoesNotReportWhenDataMemberIsOnFieldOnlyAsync() + { + const string source = """ + using System.Runtime.Serialization; + namespace Test + { + [DataContract] + public class Foo + { + [DataMember] + public int X; + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0009DataContractToDataTypeAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0009"), Is.False, + "Field-only [DataMember] must not trigger UA0009 under simplified detection."); + } + + [Test] + public async Task DoesNotReportWhenNoDataMemberPresentAsync() + { + const string source = """ + using System.Runtime.Serialization; + namespace Test + { + [DataContract] + public class Foo + { + public int X { get; set; } + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0009DataContractToDataTypeAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0009"), Is.False, + "[DataContract] alone (no [DataMember]) must not trigger UA0009."); + } + + [Test] + public async Task FixReplacesDataContractAndDataMemberAttributesAsync() + { + const string source = """ + using System.Runtime.Serialization; + using Opc.Ua; + namespace Test + { + [DataContract] + public partial class Foo + { + [DataMember] + public int X { get; set; } + } + } + """; + const string expected = """ + using System.Runtime.Serialization; + using Opc.Ua; + namespace Test + { + [DataType] + public partial class Foo + { + [DataTypeField] + public int X { get; set; } + } + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0009DataContractToDataTypeAnalyzer(), + new UA0009DataContractToDataTypeCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + + [Test] + public async Task FixAddsPartialModifierAndUsingOpcUaWhenMissingAsync() + { + const string source = """ + using System.Runtime.Serialization; + namespace Test + { + [DataContract] + public class Foo + { + [DataMember] + public int X { get; set; } + } + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0009DataContractToDataTypeAnalyzer(), + new UA0009DataContractToDataTypeCodeFix(), + source); + + Assert.That(fixedSource, Does.Contain("using Opc.Ua;"), + "Fix must add 'using Opc.Ua;' when missing."); + Assert.That(fixedSource, Does.Contain("partial class Foo"), + "Fix must add the 'partial' modifier to the class."); + Assert.That(fixedSource, Does.Contain("[DataType]"), + "Fix must rewrite [DataContract] to [DataType]."); + Assert.That(fixedSource, Does.Contain("[DataTypeField]"), + "Fix must rewrite [DataMember] to [DataTypeField]."); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs new file mode 100644 index 0000000000..a7f2b2c2fd --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs @@ -0,0 +1,133 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0010 (using/Dispose on identity types that are no longer + /// IDisposable in 2.0). The analyzer is diagnostic-only: no code fix is + /// registered. + /// + [TestFixture] + public class UA0010Tests + { + [Test] + public async Task ReportsOnUsingDeclarationOfCertificateIdentifierAsync() + { + const string source = """ + #pragma warning disable CS1674 + using Opc.Ua; + class C + { + static void M() + { + using var ci = new CertificateIdentifier(); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0010RemoveDisposableAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0010"), Is.True, + "Expected UA0010 to fire on 'using var ci = new CertificateIdentifier();'."); + } + + [Test] + public async Task ReportsOnUsingStatementOfUserIdentityAsync() + { + const string source = """ + #pragma warning disable CS1674 + using Opc.Ua; + class C + { + static void M() + { + using (var ui = new UserIdentity()) { } + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0010RemoveDisposableAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0010"), Is.True, + "Expected UA0010 to fire on 'using (var ui = new UserIdentity()) { }'."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedDisposableAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M() + { + using var ms = new System.IO.MemoryStream(); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0010RemoveDisposableAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0010"), Is.False, + "Unrelated IDisposable types must not trigger UA0010."); + } + + [Test] + public async Task DoesNotReportOnPlainDeclarationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M() + { + var ci = new CertificateIdentifier(); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0010RemoveDisposableAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0010"), Is.False, + "A plain variable declaration without 'using' must not trigger UA0010."); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs new file mode 100644 index 0000000000..3faee8f4df --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs @@ -0,0 +1,143 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0011 (IUserIdentityTokenHandler synchronous Encrypt/Decrypt/Sign/Verify + /// replaced by the *Async counterparts). + /// + [TestFixture] + public class UA0011Tests + { + [Test] + public async Task ReportsDiagnosticOnEncryptCallOnInterfaceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static byte[] M(IUserIdentityTokenHandler handler, byte[] bytes) + { + #pragma warning disable CS0618 + return handler.Encrypt(bytes); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0011TokenHandlerSyncToAsyncAnalyzer(), source); + + Diagnostic? ua0011 = diags.SingleOrDefault(d => d.Id == "UA0011"); + Assert.That(ua0011, Is.Not.Null, "Expected UA0011 to fire on handler.Encrypt(bytes)."); + Assert.That( + ua0011!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("Encrypt")); + } + + [Test] + public async Task ReportsDiagnosticOnSignCallOnInterfaceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static byte[] M(IUserIdentityTokenHandler handler, byte[] bytes) + { + #pragma warning disable CS0618 + return handler.Sign(bytes); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0011TokenHandlerSyncToAsyncAnalyzer(), source); + + Diagnostic? ua0011 = diags.SingleOrDefault(d => d.Id == "UA0011"); + Assert.That(ua0011, Is.Not.Null, "Expected UA0011 to fire on handler.Sign(bytes)."); + Assert.That( + ua0011!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("Sign")); + } + + [Test] + public async Task DoesNotReportOnEncryptAsyncCallAsync() + { + const string source = """ + using System.Threading; + using System.Threading.Tasks; + using Opc.Ua; + class C + { + static async Task M(IUserIdentityTokenHandler handler, byte[] bytes, CancellationToken ct) + { + return await handler.EncryptAsync(bytes, ct); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0011TokenHandlerSyncToAsyncAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0011"), Is.False, + "EncryptAsync must not trigger UA0011."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedTypeEncryptCallAsync() + { + const string source = """ + class Other + { + public byte[] Encrypt(byte[] data) => data; + } + class C + { + static byte[] M(Other o, byte[] bytes) => o.Encrypt(bytes); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0011TokenHandlerSyncToAsyncAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0011"), Is.False, + "Encrypt on an unrelated type must not trigger UA0011."); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs new file mode 100644 index 0000000000..066b258573 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs @@ -0,0 +1,127 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0012 (obsolete static CertificateFactory members). + /// + [TestFixture] + public class UA0012Tests + { + [Test] + public async Task ReportsDiagnosticOnCertificateFactoryCreateAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M() => CertificateFactory.Create("CN=Test"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0012CertificateFactoryStaticToInstanceAnalyzer(), source); + + Diagnostic? ua0012 = diags.SingleOrDefault(d => d.Id == "UA0012"); + Assert.That(ua0012, Is.Not.Null); + Assert.That( + ua0012!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("Create")); + } + + [Test] + public async Task ReportsDiagnosticOnCertificateFactoryCreateSigningRequestAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M() => CertificateFactory.CreateSigningRequest("CN=Test"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0012CertificateFactoryStaticToInstanceAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0012"), Is.True); + } + + [Test] + public async Task DoesNotReportOnDefaultCertificateFactoryInstanceCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M() => DefaultCertificateFactory.Instance.Create("CN=Test"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0012CertificateFactoryStaticToInstanceAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0012"), Is.False); + } + + [Test] + public async Task FixRewritesStaticCallToInstanceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object M(string s) => CertificateFactory.Create(s); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static object M(string s) => DefaultCertificateFactory.Instance.Create(s); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0012CertificateFactoryStaticToInstanceAnalyzer(), + new UA0012CertificateFactoryStaticToInstanceCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs new file mode 100644 index 0000000000..ee1446c669 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs @@ -0,0 +1,146 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0014 (DataValue.IsGood static helper -> instance property). + /// + [TestFixture] + public class UA0014Tests + { + [Test] + public async Task ReportsDiagnosticOnStaticDataValueIsGoodCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(DataValue dv) => DataValue.IsGood(dv); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0014DataValueIsGoodAnalyzer(), source); + + Diagnostic? ua0014 = diags.SingleOrDefault(d => d.Id == "UA0014"); + Assert.That(ua0014, Is.Not.Null, "Expected UA0014 to fire on DataValue.IsGood(dv)."); + Assert.That(ua0014!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), Does.Contain("IsGood")); + } + + [Test] + public async Task ReportsDiagnosticOnDataValueExtensionsIsBadCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(DataValue dv) => DataValueExtensions.IsBad(dv); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0014DataValueIsGoodAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0014"), + "Expected UA0014 to fire on DataValueExtensions.IsBad(dv)."); + } + + [Test] + public async Task DoesNotReportOnInstancePropertyAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(DataValue dv) => dv.IsGood; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0014DataValueIsGoodAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0014"), Is.False, + "Instance property access dv.IsGood must not trigger UA0014."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedSingleArgStaticCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool IsGood(DataValue dv) => false; + static bool M(DataValue dv) => IsGood(dv); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0014DataValueIsGoodAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0014"), Is.False, + "A user-defined static IsGood on an unrelated class must not trigger UA0014."); + } + + [Test] + public async Task FixRewritesStaticCallToInstancePropertyAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(DataValue dv) => DataValue.IsGood(dv); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static bool M(DataValue dv) => dv.IsGood; + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0014DataValueIsGoodAnalyzer(), + new UA0014DataValueIsGoodCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs new file mode 100644 index 0000000000..5e4bcc11b4 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs @@ -0,0 +1,168 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0015 (obsolete sync/APM members on GDS/LDS discovery clients). + /// + [TestFixture] + public class UA0015Tests + { + [Test] + public async Task ReportsDiagnosticOnGdsRegisterApplicationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(GlobalDiscoveryServerClient gdsClient) + { + #pragma warning disable CS0618 + gdsClient.RegisterApplication("urn:foo"); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0015GdsSyncToAsyncAnalyzer(), source); + + Diagnostic? ua0015 = diags.SingleOrDefault(d => d.Id == "UA0015"); + Assert.That(ua0015, Is.Not.Null, "Expected UA0015 to fire on RegisterApplication."); + Assert.That( + ua0015!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("RegisterApplication")); + } + + [Test] + public async Task ReportsDiagnosticOnServerPushApplyChangesAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(ServerPushConfigurationClient pushClient) + { + #pragma warning disable CS0618 + pushClient.ApplyChanges(); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0015GdsSyncToAsyncAnalyzer(), source); + + Diagnostic? ua0015 = diags.SingleOrDefault(d => d.Id == "UA0015"); + Assert.That(ua0015, Is.Not.Null, "Expected UA0015 to fire on ApplyChanges."); + Assert.That( + ua0015!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("ApplyChanges")); + } + + [Test] + public async Task ReportsDiagnosticOnLdsBeginFindServersAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(LocalDiscoveryServerClient ldsClient, string endpoint) + { + #pragma warning disable CS0618 + ldsClient.BeginFindServers(endpoint, null, null); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0015GdsSyncToAsyncAnalyzer(), source); + + Diagnostic? ua0015 = diags.SingleOrDefault(d => d.Id == "UA0015"); + Assert.That(ua0015, Is.Not.Null, "Expected UA0015 to fire on BeginFindServers."); + Assert.That( + ua0015!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("BeginFindServers")); + } + + [Test] + public async Task DoesNotReportOnRegisterApplicationAsyncAsync() + { + const string source = """ + using System.Threading; + using System.Threading.Tasks; + using Opc.Ua; + class C + { + static async Task M(GlobalDiscoveryServerClient gdsClient, CancellationToken ct) + { + await gdsClient.RegisterApplicationAsync("urn:foo", ct); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0015GdsSyncToAsyncAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0015"), Is.False, + "RegisterApplicationAsync must not trigger UA0015."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedRegisterApplicationCallAsync() + { + const string source = """ + class Other + { + public void RegisterApplication(string uri) { } + } + class C + { + static void M(Other o) => o.RegisterApplication("urn:foo"); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0015GdsSyncToAsyncAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0015"), Is.False, + "RegisterApplication on an unrelated type must not trigger UA0015."); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs new file mode 100644 index 0000000000..23050120a8 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs @@ -0,0 +1,140 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0018 (obsolete CertificateIdentifier.Certificate getter). + /// + [TestFixture] + public class UA0018Tests + { + [Test] + public async Task ReportsDiagnosticOnCertificateGetterReadAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object? M(CertificateIdentifierWithObsoleteCertificate id) + { + #pragma warning disable CS0618 + var c = id.Certificate; + #pragma warning restore CS0618 + return c; + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync( + new UA0018CertificateIdentifierCertificateAnalyzer(), source); + + Diagnostic? ua0018 = diags.SingleOrDefault(d => d.Id == "UA0018"); + Assert.That(ua0018, Is.Not.Null, "Expected UA0018 to fire on id.Certificate read."); + Assert.That( + ua0018!.GetMessage(CultureInfo.InvariantCulture), + Does.Contain("CertificateIdentifier")); + } + + [Test] + public async Task ReportsDiagnosticOnCertificateGetterInConditionAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static bool M(CertificateIdentifierWithObsoleteCertificate id) + { + #pragma warning disable CS0618 + if (id.Certificate != null) { return true; } + #pragma warning restore CS0618 + return false; + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync( + new UA0018CertificateIdentifierCertificateAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0018"), Is.True, + "Expected UA0018 to fire on id.Certificate in a null comparison."); + } + + [Test] + public async Task DoesNotReportOnSubjectNamePropertyAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static string? M(CertificateIdentifier id) => id.SubjectName; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync( + new UA0018CertificateIdentifierCertificateAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0018"), Is.False, + "id.SubjectName must not trigger UA0018."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedTypeCertificatePropertyAsync() + { + const string source = """ + class Other + { + public object? Certificate => null; + } + class C + { + static object? M(Other o) => o.Certificate; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync( + new UA0018CertificateIdentifierCertificateAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0018"), Is.False, + "Certificate property on an unrelated type must not trigger UA0018."); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs new file mode 100644 index 0000000000..1efbc8d6af --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs @@ -0,0 +1,153 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0019 (obsolete DataValue(StatusCode[,DateTimeUtc]) constructor). + /// + [TestFixture] + public class UA0019Tests + { + [Test] + public async Task ReportsDiagnosticOnNewDataValueStatusCodeAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static DataValue M(StatusCode sc) => new DataValue(sc); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0019DataValueStatusCodeCtorAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0019"), Is.True); + } + + [Test] + public async Task ReportsDiagnosticOnNewDataValueStatusCodeDateTimeUtcAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static DataValue M(StatusCode sc, DateTimeUtc ts) => new DataValue(sc, ts); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0019DataValueStatusCodeCtorAnalyzer(), source); + + Diagnostic? ua0019 = diags.SingleOrDefault(d => d.Id == "UA0019"); + Assert.That(ua0019, Is.Not.Null); + Assert.That( + ua0019!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("DateTimeUtc")); + } + + [Test] + public async Task DoesNotReportOnNewDataValueVariantAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static DataValue M(Variant v) => new DataValue(v); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0019DataValueStatusCodeCtorAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0019"), Is.False); + } + + [Test] + public async Task FixRewritesStatusCodeCtorToFromStatusCodeAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static DataValue M(StatusCode sc) => new DataValue(sc); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static DataValue M(StatusCode sc) => DataValue.FromStatusCode(sc); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0019DataValueStatusCodeCtorAnalyzer(), + new UA0019DataValueStatusCodeCtorCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + + [Test] + public async Task FixRewritesStatusCodeDateTimeUtcCtorToFromStatusCodeAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static DataValue M(StatusCode sc, DateTimeUtc ts) => new DataValue(sc, ts); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static DataValue M(StatusCode sc, DateTimeUtc ts) => DataValue.FromStatusCode(sc, ts); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0019DataValueStatusCodeCtorAnalyzer(), + new UA0019DataValueStatusCodeCtorCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs new file mode 100644 index 0000000000..0f9fc07782 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs @@ -0,0 +1,215 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0020 (EncodeableFactory renames: GlobalFactory and Create). + /// + [TestFixture] + public class UA0020Tests + { + [Test] + public async Task ReportsDiagnosticOnGlobalFactoryAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M() { var f = EncodeableFactory.GlobalFactory; return f; } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0020EncodeableFactoryRenameAnalyzer(), source); + + Diagnostic? ua0020 = diags.SingleOrDefault(d => d.Id == "UA0020"); + Assert.That(ua0020, Is.Not.Null, + "Expected UA0020 to fire on EncodeableFactory.GlobalFactory access."); + Assert.That( + ua0020!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("GlobalFactory")); + } + + [Test] + public async Task ReportsDiagnosticOnEncodeableFactoryCreateInvocationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M(EncodeableFactory factory) => factory.Create(); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0020EncodeableFactoryRenameAnalyzer(), source); + + Diagnostic? ua0020 = diags.SingleOrDefault(d => d.Id == "UA0020"); + Assert.That(ua0020, Is.Not.Null, + "Expected UA0020 to fire on EncodeableFactory.Create() invocation."); + Assert.That( + ua0020!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("Create")); + } + + [Test] + public async Task DoesNotReportOnForkInvocationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M(EncodeableFactory factory) => factory.Fork(); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0020EncodeableFactoryRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0020"), Is.False, + "factory.Fork() must not trigger UA0020."); + } + + [Test] + public async Task DoesNotReportOnServiceMessageContextFactoryAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M(ServiceMessageContext ctx) => ctx.Factory; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0020EncodeableFactoryRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0020"), Is.False, + "ServiceMessageContext.Factory access must not trigger UA0020."); + } + + [Test] + public async Task FixRewritesCreateInvocationToForkAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M(EncodeableFactory factory) => factory.Create(); + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static EncodeableFactory M(EncodeableFactory factory) => factory.Fork(); + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0020EncodeableFactoryRenameAnalyzer(), + new UA0020EncodeableFactoryRenameCodeFix(), + source); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + + [Test] + public async Task FixDoesNotRegisterActionForGlobalFactoryFormAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M() => EncodeableFactory.GlobalFactory; + } + """; + + CodeAction[] actions = await CollectFixActionsAsync( + new UA0020EncodeableFactoryRenameAnalyzer(), + new UA0020EncodeableFactoryRenameCodeFix(), + source); + + Assert.That(actions, Is.Empty, + "Form A (GlobalFactory) must not register any code-fix actions."); + } + + private static async Task CollectFixActionsAsync( + Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer analyzer, + CodeFixProvider codeFix, + string source) + { + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(analyzer, source); + Diagnostic[] userDiags = diags + .Where(d => d.Location.SourceTree?.FilePath == "Test.cs") + .ToArray(); + Assert.That(userDiags, Is.Not.Empty, + "Expected at least one diagnostic on Test.cs for the fix-actions probe."); + + Microsoft.CodeAnalysis.CSharp.CSharpCompilation compilation = + AnalyzerHarness.Compile(source); + Microsoft.CodeAnalysis.SyntaxTree testTree = compilation.SyntaxTrees + .First(t => t.FilePath == "Test.cs"); + + AdhocWorkspace workspace = new AdhocWorkspace(); + ProjectId projectId = ProjectId.CreateNewId(); + DocumentId documentId = DocumentId.CreateNewId(projectId); + Solution solution = workspace.CurrentSolution + .AddProject(projectId, "TestProject", "TestProject", LanguageNames.CSharp) + .AddDocument(documentId, "Test.cs", testTree.GetText()); + Document document = solution.GetDocument(documentId)!; + + List actions = new List(); + foreach (Diagnostic diag in userDiags) + { + CodeFixContext ctx = new CodeFixContext( + document, + diag, + (action, _) => actions.Add(action), + CancellationToken.None); + await codeFix.RegisterCodeFixesAsync(ctx).ConfigureAwait(false); + } + return actions.ToArray(); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj b/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj new file mode 100644 index 0000000000..62ce4998b1 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj @@ -0,0 +1,31 @@ + + + $(TestsTargetFrameworks) + Opc.Ua.CodeFixers.Tests + true + true + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Properties/AssemblyInfo.cs b/Tests/Opc.Ua.CodeFixers.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2b9848014c --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +[assembly: CLSCompliant(false)] diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs new file mode 100644 index 0000000000..6022277967 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs @@ -0,0 +1,441 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +namespace Opc.Ua.CodeFixers.Tests +{ + /// + /// Minimal hand-written OPC UA 2.0 stubs used by the analyzer tests. + /// Stubs are kept narrow on purpose - they reproduce just the public + /// surface that the analyzers key off (struct vs class, [Obsolete], + /// member signatures). Anything beyond that risks teaching tests to + /// pass against the wrong shape. + /// + public static class OpcUaStubs + { + public const string Source = +""" +#nullable enable +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Opc.Ua +{ + public readonly struct NodeId + { + public NodeId(uint id) { Identifier = id; NamespaceIndex = 0; } + public NodeId(uint id, ushort ns) { Identifier = id; NamespaceIndex = ns; } + [Obsolete("Use NodeId.Parse(string) instead.")] + public NodeId(string s) { Identifier = s; NamespaceIndex = 0; } + public object Identifier { get; } + public ushort NamespaceIndex { get; } + public bool IsNull => Identifier is null; + public static NodeId Parse(string s) => default; + public static bool TryParse(string s, out NodeId id) { id = default; return true; } + public override string ToString() => Identifier?.ToString() ?? ""; + public override bool Equals(object? obj) => obj is NodeId n && Equals(Identifier, n.Identifier); + public override int GetHashCode() => Identifier?.GetHashCode() ?? 0; + public static bool operator ==(NodeId left, object? right) => right is null ? left.IsNull : left.Equals(right); + public static bool operator !=(NodeId left, object? right) => !(left == right); + public static bool operator ==(object? left, NodeId right) => right == left; + public static bool operator !=(object? left, NodeId right) => right != left; + } + + public readonly struct ExpandedNodeId + { + public ExpandedNodeId(uint id) { Identifier = id; } + [Obsolete("Use ExpandedNodeId.Parse(string) instead.")] + public ExpandedNodeId(string s) { Identifier = s; } + public object Identifier { get; } + public bool IsNull => Identifier is null; + public static ExpandedNodeId Parse(string s) => default; + } + + public readonly struct QualifiedName + { + public QualifiedName(string name) { Name = name; } + public string Name { get; } + public bool IsNull => string.IsNullOrEmpty(Name); + } + + public readonly struct LocalizedText + { + public LocalizedText(string text) { Text = text; } + public string Text { get; } + public bool IsNullOrEmpty => string.IsNullOrEmpty(Text); + public override bool Equals(object? obj) => obj is LocalizedText lt && Text == lt.Text; + public override int GetHashCode() => Text?.GetHashCode() ?? 0; + public static bool operator ==(LocalizedText left, object? right) => right is null ? left.IsNullOrEmpty : left.Equals(right); + public static bool operator !=(LocalizedText left, object? right) => !(left == right); + public static bool operator ==(object? left, LocalizedText right) => right == left; + public static bool operator !=(object? left, LocalizedText right) => right != left; + } + + public readonly struct ByteString + { + public ByteString(byte[] data) { Data = data; } + public byte[]? Data { get; } + public bool IsNull => Data is null; + public Span Span => Data; + } + + public static class ByteStringExtensions + { + public static ByteString ToByteString(this byte[] data) => new ByteString(data); + } + + public readonly struct StatusCode + { + public StatusCode(uint code) { Code = code; } + public uint Code { get; } + } + + public readonly struct DateTimeUtc + { + public DateTimeUtc(DateTime dt) { UtcDateTime = dt; } + public DateTime UtcDateTime { get; } + } + + public readonly struct Uuid + { + public Uuid(Guid g) { Value = g; } + public Guid Value { get; } + } + + public readonly struct Variant + { + public Variant(int i) { _value = i; } + public Variant(uint i) { _value = i; } + public Variant(string s) { _value = s; } + public Variant(NodeId n) { _value = n; } + [Obsolete("Use Variant.From(object) instead.")] + public Variant(object o) { _value = o; } + [Obsolete("Use Variant.From(new DateTimeUtc(value)) instead.")] + public Variant(DateTime dt) { _value = dt; } + [Obsolete("Use Variant.From(new Uuid(value)) instead.")] + public Variant(Guid g) { _value = g; } + [Obsolete("Use Variant.From(value.ToByteString()) instead.")] + public Variant(byte[] b) { _value = b; } + private readonly object? _value; + public bool IsNull => _value is null; + public static Variant Null => default; + public static Variant From(T value) => default; + } + + public readonly struct ExtensionObject + { + public ExtensionObject(object body) { Body = body; } + public object Body { get; } + public bool IsNull => Body is null; + } + + public readonly struct DiagnosticInfo + { + } + + public readonly struct DataValue + { + public DataValue(Variant v) { Value = v; StatusCode = default; SourceTimestamp = default; } + [Obsolete("Use DataValue.FromStatusCode(StatusCode) instead.")] + public DataValue(StatusCode sc) { Value = default; StatusCode = sc; SourceTimestamp = default; } + [Obsolete("Use DataValue.FromStatusCode(StatusCode, DateTimeUtc) instead.")] + public DataValue(StatusCode sc, DateTimeUtc ts) { Value = default; StatusCode = sc; SourceTimestamp = ts; } + public Variant Value { get; } + public StatusCode StatusCode { get; } + public DateTimeUtc SourceTimestamp { get; } + public bool IsGood => StatusCode.Code == 0; + public bool IsBad => (StatusCode.Code & 0x80000000) != 0; + public bool IsUncertain => (StatusCode.Code & 0x40000000) != 0; + public bool IsNull => false; + public static DataValue Null => default; + public static DataValue FromStatusCode(StatusCode sc) => default; + public static DataValue FromStatusCode(StatusCode sc, DateTimeUtc ts) => default; + [Obsolete("Use the dv.IsGood instance property.")] + public static bool IsGood(DataValue dv) => dv.IsGood; + [Obsolete("Use the dv.IsBad instance property.")] + public static bool IsBad(DataValue dv) => dv.IsBad; + [Obsolete("Use the dv.IsUncertain instance property.")] + public static bool IsUncertain(DataValue dv) => dv.IsUncertain; + [Obsolete("Use the !dv.IsGood instance property.")] + public static bool IsNotGood(DataValue dv) => !dv.IsGood; + [Obsolete("Use the !dv.IsBad instance property.")] + public static bool IsNotBad(DataValue dv) => !dv.IsBad; + [Obsolete("Use the !dv.IsUncertain instance property.")] + public static bool IsNotUncertain(DataValue dv) => !dv.IsUncertain; + } + + public static class DataValueExtensions + { + [Obsolete("Use the dv.IsGood instance property.")] + public static bool IsGood(DataValue dv) => dv.IsGood; + [Obsolete("Use the dv.IsBad instance property.")] + public static bool IsBad(DataValue dv) => dv.IsBad; + [Obsolete("Use the dv.IsUncertain instance property.")] + public static bool IsUncertain(DataValue dv) => dv.IsUncertain; + [Obsolete("Use the !dv.IsGood instance property.")] + public static bool IsNotGood(DataValue dv) => !dv.IsGood; + [Obsolete("Use the !dv.IsBad instance property.")] + public static bool IsNotBad(DataValue dv) => !dv.IsBad; + [Obsolete("Use the !dv.IsUncertain instance property.")] + public static bool IsNotUncertain(DataValue dv) => !dv.IsUncertain; + } + + public static class CertificateFactory + { + [Obsolete("Use DefaultCertificateFactory.Instance.Create.")] + public static object Create(string subject) => null!; + [Obsolete("Use DefaultCertificateFactory.Instance.CreateCertificate.")] + public static object CreateCertificate(string subject) => null!; + [Obsolete("Use DefaultCertificateFactory.Instance.CreateSigningRequest.")] + public static object CreateSigningRequest(string subject) => null!; + [Obsolete("Use DefaultCertificateFactory.Instance.RevokeCertificate.")] + public static object RevokeCertificate(string subject) => null!; + [Obsolete("Use DefaultCertificateFactory.Instance.CreateCertificateWithPEMPrivateKey.")] + public static object CreateCertificateWithPEMPrivateKey(string subject) => null!; + [Obsolete("Use DefaultCertificateFactory.Instance.CreateCertificateWithPrivateKey.")] + public static object CreateCertificateWithPrivateKey(string subject) => null!; + } + + public sealed class DefaultCertificateFactory + { + public static DefaultCertificateFactory Instance { get; } = new(); + public object Create(string subject) => null!; + public object CreateCertificate(string subject) => null!; + public object CreateSigningRequest(string subject) => null!; + public object RevokeCertificate(string subject) => null!; + public object CreateCertificateWithPEMPrivateKey(string subject) => null!; + public object CreateCertificateWithPrivateKey(string subject) => null!; + } + + // ─── Stubs for UA0001 (Utils.Trace/LogX → ILogger) ─── + public interface ITelemetryContext + { + Microsoft.Extensions.Logging.ILogger CreateLogger(); + } + public static partial class Utils + { + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void Trace(string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void Trace(string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void Trace(Exception e, string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void Trace(int traceMask, string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogError(string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogError(string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogError(Exception e, string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogWarning(string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogWarning(string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogInformation(string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogInformation(string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogDebug(string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogDebug(string format, params object[] args) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogTrace(string message) { } + [Obsolete("Use a customized ITelemetryContext.LoggerFactory instead.")] + public static void LogCritical(string message) { } + } + public static class TraceMasks + { + public const int Error = 1; + public const int Information = 2; + } + + // ─── Stubs for UA0002 (Collection types removed) ─── + public class Int32Collection : List { } + public class UInt32Collection : List { } + public class StringCollection : List { } + public class NodeIdCollection : List { } + public class VariantCollection : List { } + public class DataValueCollection : List { } + public class ByteStringCollection : List { } + public class ArgumentCollection : List { } + public class ServerSecurityPolicyCollection : List { } + public class TransportConfigurationCollection : List { } + public class ReverseConnectClientCollection : List { } + public class Argument { } + public class ServerSecurityPolicy { } + public class TransportConfiguration { } + public class ReverseConnectClient { } + public readonly struct ArrayOf + { + public ArrayOf(T[] data) { Data = data; } + public T[]? Data { get; } + } + + // ─── Stubs for UA0005 (byte[] → ByteString) ─── + // ByteString is already defined above; expose an API surface that takes ByteString. + public static class ByteStringApi + { + public static void Process(ByteString data) { } + public static void Process(byte[] data) { } + } + + // ─── Stubs for UA0008 (Session.Call params object[] → params Variant[]) ─── + public interface ISession + { + object Call(NodeId objectId, NodeId methodId, params Variant[] args); + Task CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct, params Variant[] args); + } + public class Session : ISession + { + public object Call(NodeId objectId, NodeId methodId, params Variant[] args) => null!; + public Task CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct, params Variant[] args) => Task.FromResult(null!); + } + + // ─── Stubs for UA0009 ([DataContract]/[DataMember] → [DataType]/[DataTypeField]) ─── + [AttributeUsage(AttributeTargets.Class)] + public sealed class DataTypeAttribute : Attribute + { + public string? TypeId { get; set; } + } + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public sealed class DataTypeFieldAttribute : Attribute + { + public int Order { get; set; } + } + public class ApplicationConfiguration + { + public static T ParseExtension() where T : new() => new(); + public static void UpdateExtension(T value) { } + } + + // ─── Stubs for UA0010 (Remove IDisposable on cert/identity types) ─── + // CertificateIdentifier intentionally does NOT implement IDisposable in 2.0. + public class CertificateIdentifier + { + public string? SubjectName { get; set; } + } + public class UserIdentity + { + public string? Name { get; set; } + } + + // ─── Stubs for UA0011 (TokenHandler sync → async) ─── + public interface IUserIdentityTokenHandler + { + [Obsolete("Use EncryptAsync instead.")] + byte[] Encrypt(byte[] data); + [Obsolete("Use DecryptAsync instead.")] + byte[] Decrypt(byte[] data); + [Obsolete("Use SignAsync instead.")] + byte[] Sign(byte[] data); + [Obsolete("Use VerifyAsync instead.")] + bool Verify(byte[] data, byte[] signature); + Task EncryptAsync(byte[] data, CancellationToken ct); + Task DecryptAsync(byte[] data, CancellationToken ct); + Task SignAsync(byte[] data, CancellationToken ct); + Task VerifyAsync(byte[] data, byte[] signature, CancellationToken ct); + } + + // ─── Stubs for UA0015 (GDS sync → async) ─── + public class GlobalDiscoveryServerClient + { + [Obsolete("Use RegisterApplicationAsync instead.")] + public void RegisterApplication(string applicationUri) { } + [Obsolete("Use UnregisterApplicationAsync instead.")] + public void UnregisterApplication(string applicationUri) { } + public Task RegisterApplicationAsync(string applicationUri, CancellationToken ct) => Task.CompletedTask; + public Task UnregisterApplicationAsync(string applicationUri, CancellationToken ct) => Task.CompletedTask; + } + public class ServerPushConfigurationClient + { + [Obsolete("Use ApplyChangesAsync instead.")] + public void ApplyChanges() { } + public Task ApplyChangesAsync(CancellationToken ct) => Task.CompletedTask; + } + public class LocalDiscoveryServerClient + { + [Obsolete("Use FindServersAsync instead.")] + public IAsyncResult BeginFindServers(string endpoint, AsyncCallback? callback, object? state) => null!; + [Obsolete("Use FindServersAsync instead.")] + public string[] EndFindServers(IAsyncResult result) => System.Array.Empty(); + public Task FindServersAsync(string endpoint, CancellationToken ct) => Task.FromResult(System.Array.Empty()); + } + + // ─── Stubs for UA0018 (CertificateIdentifier.Certificate → ResolveAsync) ─── + // Add a getter that's marked obsolete on the CertificateIdentifier defined above + // (we provide a separate type with the obsolete getter for test isolation). + public class CertificateIdentifierWithObsoleteCertificate + { + [Obsolete("Use CertificateIdentifierResolver.ResolveAsync instead.")] + public object? Certificate => null; + } + public static class CertificateIdentifierResolver + { + public static Task ResolveAsync( + CertificateIdentifier id, + object registry, + bool needPrivateKey, + string applicationUri, + ITelemetryContext telemetry, + CancellationToken ct) => Task.FromResult(null!); + } + + // ─── Stubs for UA0020 (EncodeableFactory renames) ─── + public class EncodeableFactory + { + [Obsolete("Use ServiceMessageContext.Factory instead.")] + public static EncodeableFactory GlobalFactory { get; } = new EncodeableFactory(); + [Obsolete("Use Fork() instead.")] + public EncodeableFactory Create() => new EncodeableFactory(); + public EncodeableFactory Fork() => new EncodeableFactory(); + } + public class ServiceMessageContext + { + public EncodeableFactory Factory { get; } = new EncodeableFactory(); + } +} + +namespace Microsoft.Extensions.Logging +{ + public interface ILogger + { + void LogError(string message); + void LogWarning(string message); + void LogInformation(string message); + void LogDebug(string message); + void LogTrace(string message); + void LogCritical(string message); + } +} +"""; + } +} diff --git a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Shipped.md b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000000..5ccc9f037f --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Shipped.md @@ -0,0 +1,2 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md diff --git a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000000..50dfdb0b95 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md @@ -0,0 +1,24 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +--------|-----------|----------|------------------------------------------------------------------------------------------------------ +UA0001 | Migration | Info | Replace Utils.Trace/Utils.LogX calls with ILogger obtained from ITelemetryContext. +UA0002 | Migration | Warning | Replace removed `Collection` types with `List` or `ArrayOf`. +UA0003 | Migration | Warning | Replace `== null` / `!= null` on now-struct built-in types with the `.IsNull` property. +UA0004 | Migration | Warning | Remove null-conditional `?.` on now-struct built-in types (NodeId, Variant, DataValue, ...). +UA0005 | Migration | Warning | Convert `byte[]` to `ByteString` at API boundaries that now require `ByteString`. +UA0006 | Migration | Warning | Replace obsoleted non-generic Variant constructors with Variant.From. +UA0007 | Migration | Warning | Replace `new NodeId(string)` / `new ExpandedNodeId(string)` with `NodeId.Parse(s)` / `ExpandedNodeId.Parse(s)`. +UA0008 | Migration | Warning | Wrap `params object[]` arguments to `Session.Call`/`CallAsync` with `Variant.From(...)`. +UA0009 | Migration | Warning | Replace `[DataContract]`/`[DataMember]` on configuration extensions with `[DataType]`/`[DataTypeField]`. +UA0010 | Migration | Warning | Remove `using`/`Dispose()` on `CertificateIdentifier`, `UserIdentity`, `IUserIdentityTokenHandler` (no longer IDisposable). +UA0011 | Migration | Info | Replace sync `IUserIdentityTokenHandler.Encrypt/Decrypt/Sign/Verify` with `…Async`. +UA0012 | Migration | Warning | Replace obsolete static `CertificateFactory.*` helpers with `DefaultCertificateFactory.Instance.*`. +UA0014 | Migration | Warning | Replace `DataValue.IsGood(dv)`/`IsBad`/`IsUncertain` static helpers with `dv.IsGood`/`IsBad`/`IsUncertain` instance properties. +UA0015 | Migration | Info | Replace sync/APM members on GDS/LDS clients with their `…Async` counterparts. +UA0018 | Migration | Info | Replace `CertificateIdentifier.Certificate` getter with `CertificateIdentifierResolver.ResolveAsync(...)`. +UA0019 | Migration | Warning | Replace `new DataValue(StatusCode[, ts])` with `DataValue.FromStatusCode(...)`. +UA0020 | Migration | Warning | Replace `EncodeableFactory.GlobalFactory` / `EncodeableFactory.Create()` with `ServiceMessageContext.Factory` / `Fork()`. diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs new file mode 100644 index 0000000000..20288b221b --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs @@ -0,0 +1,102 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +// UA0001 is diagnostic-only. The replacement requires an ILogger instance +// obtained from ITelemetryContext.CreateLogger() which the analyzer cannot +// synthesize automatically. A code fix would require the host type to expose +// an ILogger field or an ITelemetryContext from which to derive one. + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0001: Flag calls to the obsolete static Opc.Ua.Utils.Trace and + /// Opc.Ua.Utils.LogX helpers. Replacement requires an + /// ILogger from an ITelemetryContext which the analyzer + /// cannot synthesize, so this rule ships diagnostic-only (no code fix). + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0001UtilsTraceToILoggerAnalyzer : DiagnosticAnalyzer + { + private static readonly HashSet s_targetNames = + [ + "Trace", + "LogError", + "LogWarning", + "LogInformation", + "LogDebug", + "LogTrace", + "LogCritical", + ]; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0001_UtilsTraceToILogger); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + } + + private static void AnalyzeInvocation(OperationAnalysisContext context) + { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + IMethodSymbol method = invocation.TargetMethod; + + if (!method.IsStatic || !s_targetNames.Contains(method.Name)) + { + return; + } + + INamedTypeSymbol containing = method.ContainingType; + if (containing is null || containing.ToDisplayString() != "Opc.Ua.Utils") + { + return; + } + + if (!method.IsObsolete()) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0001_UtilsTraceToILogger, + invocation.Syntax.GetLocation(), + "Utils." + method.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs new file mode 100644 index 0000000000..4c486cae06 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs @@ -0,0 +1,96 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0002: Detect references to removed <Type>Collection + /// wrappers (Int32Collection, NodeIdCollection, ...) and recommend + /// List<T> or ArrayOf<T>. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0002RemovedCollectionTypeAnalyzer : DiagnosticAnalyzer + { + public const string ElementTypeProperty = "ElementType"; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0002_RemovedCollectionType); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeIdentifier, SyntaxKind.IdentifierName); + } + + private static void AnalyzeIdentifier(SyntaxNodeAnalysisContext context) + { + IdentifierNameSyntax id = (IdentifierNameSyntax)context.Node; + + SymbolInfo info = context.SemanticModel.GetSymbolInfo(id, context.CancellationToken); + if (info.Symbol is not INamedTypeSymbol named) + { + return; + } + + if (!named.TryGetRemovedCollectionElement(out string elementName)) + { + return; + } + + ImmutableDictionary properties = ImmutableDictionary.Empty + .Add(ElementTypeProperty, TrimOpcUaPrefix(elementName)); + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0002_RemovedCollectionType, + id.GetLocation(), + properties, + named.ToDisplayString(), + TrimOpcUaPrefix(elementName))); + } + + internal static string TrimOpcUaPrefix(string name) + { + const string prefix = "Opc.Ua."; + if (name != null && name.StartsWith(prefix, System.StringComparison.Ordinal)) + { + return name.Substring(prefix.Length); + } + return name; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs new file mode 100644 index 0000000000..a8b39327c4 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs @@ -0,0 +1,129 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0003: Detect x == null / x != null comparisons on + /// built-in OPC UA types that became readonly structs in 2.0. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0003NullCheckOnStructTypeAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0003_NullCheckOnStructType); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + Dictionary cache = []; + UaSymbols symbols = UaSymbols.For(context.Compilation, cache); + if (!symbols.ReferencesOpcUa) + { + return; + } + context.RegisterOperationAction(c => AnalyzeBinary(c, symbols), OperationKind.Binary); + } + + private static void AnalyzeBinary(OperationAnalysisContext context, UaSymbols symbols) + { + IBinaryOperation op = (IBinaryOperation)context.Operation; + if (op.OperatorKind != BinaryOperatorKind.Equals && + op.OperatorKind != BinaryOperatorKind.NotEquals) + { + return; + } + + IOperation valueOperand = GetValueOperandOrNull(op.LeftOperand, op.RightOperand); + if (valueOperand is null) + { + return; + } + + ITypeSymbol valueType = valueOperand.Type; + if (valueType is null) + { + return; + } + if (valueType.OriginalDefinition?.SpecialType == SpecialType.System_Nullable_T && + valueType is INamedTypeSymbol named && named.TypeArguments.Length == 1) + { + valueType = named.TypeArguments[0]; + } + + if (!symbols.IsBuiltInStructType(valueType)) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0003_NullCheckOnStructType, + op.Syntax.GetLocation(), + valueType.Name)); + } + + private static IOperation GetValueOperandOrNull(IOperation left, IOperation right) + { + if (IsNullLiteral(left)) + { + return right; + } + if (IsNullLiteral(right)) + { + return left; + } + return null; + } + + private static bool IsNullLiteral(IOperation op) + { + while (op is IConversionOperation conv) + { + op = conv.Operand; + } + return op is ILiteralOperation lit && + lit.ConstantValue.HasValue && + lit.ConstantValue.Value is null; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs new file mode 100644 index 0000000000..ed28327a61 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs @@ -0,0 +1,98 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0004: Detect null-conditional access (?.) whose receiver is + /// one of the now-struct OPC UA built-in types. Since the receiver can + /// never be null, the ?. is misleading. + /// + /// + /// Implemented as a syntax-node action so the analyzer still fires on + /// migration code that no longer compiles (e.g. nodeId?.X where + /// NodeId is now a non-nullable struct). + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0004ConditionalAccessOnStructAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0004_ConditionalAccessOnStructType); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + Dictionary cache = []; + UaSymbols symbols = UaSymbols.For(context.Compilation, cache); + if (!symbols.ReferencesOpcUa) + { + return; + } + context.RegisterSyntaxNodeAction( + c => AnalyzeConditionalAccess(c, symbols), + SyntaxKind.ConditionalAccessExpression); + } + + private static void AnalyzeConditionalAccess(SyntaxNodeAnalysisContext context, UaSymbols symbols) + { + ConditionalAccessExpressionSyntax node = (ConditionalAccessExpressionSyntax)context.Node; + ITypeSymbol receiverType = context.SemanticModel + .GetTypeInfo(node.Expression, context.CancellationToken).Type; + if (receiverType is null) + { + return; + } + + if (!symbols.IsBuiltInStructType(receiverType)) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0004_ConditionalAccessOnStructType, + node.GetLocation(), + receiverType.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs new file mode 100644 index 0000000000..9c114438fc --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs @@ -0,0 +1,118 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0005: Detect call sites passing a byte[] argument where the + /// resolved parameter type is now Opc.Ua.ByteString. + /// + /// + /// Implemented as a syntax-node action so the analyzer fires on migration + /// code that no longer compiles (the byte[]-to-ByteString + /// conversion is gone). + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0005ByteArrayToByteStringAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0005_ByteArrayWhereByteStringExpected); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + Dictionary cache = []; + UaSymbols symbols = UaSymbols.For(context.Compilation, cache); + if (!symbols.ReferencesOpcUa || symbols.ByteStringType is null) + { + return; + } + context.RegisterSyntaxNodeAction( + c => AnalyzeInvocation(c, symbols), + SyntaxKind.InvocationExpression); + } + + private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context, UaSymbols symbols) + { + InvocationExpressionSyntax invocation = (InvocationExpressionSyntax)context.Node; + SymbolInfo info = context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken); + IMethodSymbol method = info.Symbol as IMethodSymbol + ?? info.CandidateSymbols.OfType().FirstOrDefault(); + if (method is null) + { + return; + } + + INamedTypeSymbol byteStringType = symbols.ByteStringType; + SeparatedSyntaxList arguments = invocation.ArgumentList.Arguments; + for (int i = 0; i < arguments.Count; i++) + { + ArgumentSyntax arg = arguments[i]; + IParameterSymbol parameter = i < method.Parameters.Length ? method.Parameters[i] : null; + if (parameter is null) + { + continue; + } + if (!SymbolEqualityComparer.Default.Equals(parameter.Type, byteStringType)) + { + continue; + } + + ITypeSymbol valueType = context.SemanticModel + .GetTypeInfo(arg.Expression, context.CancellationToken).Type; + if (valueType is not IArrayTypeSymbol array || + array.ElementType.SpecialType != SpecialType.System_Byte) + { + continue; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0005_ByteArrayWhereByteStringExpected, + arg.GetLocation(), + method.Name)); + } + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs new file mode 100644 index 0000000000..4fb306deed --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs @@ -0,0 +1,115 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0006: Detect new Variant(object|DateTime|Guid|byte[]) obsolete + /// constructor calls and recommend the generic Variant.From<T> + /// factory together with the appropriate wrapper type. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0006ObsoleteVariantCtorAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0006_ObsoleteVariantCtor); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation); + } + + private static void AnalyzeObjectCreation(OperationAnalysisContext context) + { + IObjectCreationOperation creation = (IObjectCreationOperation)context.Operation; + IMethodSymbol ctor = creation.Constructor; + if (ctor is null || ctor.Parameters.Length != 1) + { + return; + } + + INamedTypeSymbol containing = ctor.ContainingType; + if (containing is null || containing.ToDisplayString() != "Opc.Ua.Variant") + { + return; + } + + if (!ctor.IsObsolete()) + { + return; + } + + ITypeSymbol argType = ctor.Parameters[0].Type; + string label = GetLabel(argType); + if (label is null) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0006_ObsoleteVariantCtor, + creation.Syntax.GetLocation(), + label)); + } + + private static string GetLabel(ITypeSymbol type) + { + if (type is null) + { + return null; + } + switch (type.SpecialType) + { + case SpecialType.System_Object: + return "object"; + case SpecialType.System_DateTime: + return "DateTime"; + } + if (type.ToDisplayString() == "System.Guid") + { + return "Guid"; + } + if (type is IArrayTypeSymbol arr && + arr.ElementType?.SpecialType == SpecialType.System_Byte) + { + return "byte[]"; + } + return null; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs new file mode 100644 index 0000000000..83c3bdeac4 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs @@ -0,0 +1,86 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0007: Detect new NodeId(string) / new ExpandedNodeId(string) + /// constructor calls and recommend the explicit Parse factory. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0007ObsoleteNodeIdStringCtorAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0007_ObsoleteNodeIdStringCtor); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation); + } + + private static void AnalyzeObjectCreation(OperationAnalysisContext context) + { + IObjectCreationOperation creation = (IObjectCreationOperation)context.Operation; + IMethodSymbol ctor = creation.Constructor; + if (ctor is null || ctor.Parameters.Length != 1) + { + return; + } + + INamedTypeSymbol containing = ctor.ContainingType; + if (containing is null) + { + return; + } + string containingName = containing.ToDisplayString(); + if (containingName != "Opc.Ua.NodeId" && containingName != "Opc.Ua.ExpandedNodeId") + { + return; + } + + if (ctor.Parameters[0].Type?.SpecialType != SpecialType.System_String) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0007_ObsoleteNodeIdStringCtor, + creation.Syntax.GetLocation(), + containing.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs new file mode 100644 index 0000000000..b1fde10301 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs @@ -0,0 +1,134 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0008: Detect ISession.Call / ISession.CallAsync + /// invocations whose variadic arguments are raw values instead of + /// Variant instances, and recommend wrapping with + /// Variant.From(...). + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0008SessionCallParamsObjectAnalyzer : DiagnosticAnalyzer + { + public const string MethodNameProperty = "MethodName"; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0008_SessionCallParamsObject); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + INamedTypeSymbol sessionInterface = context.Compilation.GetTypeByMetadataName("Opc.Ua.ISession"); + INamedTypeSymbol variantType = context.Compilation.GetTypeByMetadataName("Opc.Ua.Variant"); + if (sessionInterface is null || variantType is null) + { + return; + } + context.RegisterSyntaxNodeAction( + c => AnalyzeInvocation(c, sessionInterface, variantType), + SyntaxKind.InvocationExpression); + } + + private static void AnalyzeInvocation( + SyntaxNodeAnalysisContext context, + INamedTypeSymbol sessionInterface, + INamedTypeSymbol variantType) + { + InvocationExpressionSyntax invocation = (InvocationExpressionSyntax)context.Node; + if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) + { + return; + } + + string methodName = memberAccess.Name.Identifier.ValueText; + if (methodName != "Call" && methodName != "CallAsync") + { + return; + } + + ITypeSymbol receiverType = context.SemanticModel + .GetTypeInfo(memberAccess.Expression, context.CancellationToken).Type; + if (receiverType is null || !receiverType.IsAssignableTo(sessionInterface)) + { + return; + } + + int firstVariadicIndex = methodName == "Call" ? 2 : 3; + IReadOnlyList args = invocation.ArgumentList.Arguments; + if (args.Count <= firstVariadicIndex) + { + return; + } + + bool anyNonVariant = false; + for (int i = firstVariadicIndex; i < args.Count; i++) + { + ITypeSymbol argType = context.SemanticModel + .GetTypeInfo(args[i].Expression, context.CancellationToken).Type; + if (argType is null || !SymbolEqualityComparer.Default.Equals(argType, variantType)) + { + anyNonVariant = true; + break; + } + } + + if (!anyNonVariant) + { + return; + } + + ImmutableDictionary properties = ImmutableDictionary.Empty + .Add(MethodNameProperty, methodName); + + string display = receiverType.Name + "." + methodName; + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0008_SessionCallParamsObject, + invocation.GetLocation(), + properties, + display)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs new file mode 100644 index 0000000000..878cdb627f --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs @@ -0,0 +1,131 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0009: Flag classes annotated with + /// System.Runtime.Serialization.DataContractAttribute that also have + /// at least one DataMember property — they are candidates for + /// migration to [Opc.Ua.DataType] / [Opc.Ua.DataTypeField]. + /// + /// + /// Simplified detection: the analyzer does NOT verify that the class is + /// actually consumed by ApplicationConfiguration.ParseExtension<T> + /// or UpdateExtension<T>. Cross-compilation usage scanning would + /// require a CompilationEndAction and full symbol walk. Trade-off: + /// more false positives but a much simpler analyzer. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0009DataContractToDataTypeAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0009_DataContractToDataType); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + Dictionary cache = []; + UaSymbols symbols = UaSymbols.For(context.Compilation, cache); + if (symbols.DataContractType is null || symbols.DataMemberType is null) + { + return; + } + context.RegisterSymbolAction(c => AnalyzeNamedType(c, symbols), SymbolKind.NamedType); + } + + private static void AnalyzeNamedType(SymbolAnalysisContext context, UaSymbols symbols) + { + INamedTypeSymbol type = (INamedTypeSymbol)context.Symbol; + if (type.TypeKind != TypeKind.Class) + { + return; + } + + if (!HasAttribute(type, symbols.DataContractType)) + { + return; + } + + if (!HasDataMemberProperty(type, symbols.DataMemberType)) + { + return; + } + + foreach (Location location in type.Locations) + { + if (location.IsInSource) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0009_DataContractToDataType, + location, + type.Name)); + } + } + } + + private static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attributeType) + { + SymbolEqualityComparer eq = SymbolEqualityComparer.Default; + foreach (AttributeData attr in symbol.GetAttributes()) + { + if (attr.AttributeClass != null && eq.Equals(attr.AttributeClass, attributeType)) + { + return true; + } + } + return false; + } + + private static bool HasDataMemberProperty(INamedTypeSymbol type, INamedTypeSymbol dataMemberType) + { + foreach (ISymbol member in type.GetMembers()) + { + if (member is IPropertySymbol property && HasAttribute(property, dataMemberType)) + { + return true; + } + } + return false; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs new file mode 100644 index 0000000000..d5a2d8b221 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs @@ -0,0 +1,204 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0010: Detect using declarations / statements whose variable + /// type is one of the OPC UA identity types that are no longer + /// in 2.0 + /// (CertificateIdentifier, UserIdentity, or any + /// implementation of IUserIdentityTokenHandler). + /// + /// + /// Detection is purely syntactic: the analyzer looks at + /// and + /// nodes that carry the using keyword and resolves the declared + /// variable's type through the . + /// + /// Form B (direct Dispose() invocation) is intentionally not + /// implemented here: the stub OPC UA types are not IDisposable, + /// so any explicit Dispose() call would not compile and there is + /// no operation to bind against in the analyzer test surface. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0010RemoveDisposableAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0010_RemoveDisposable); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + Dictionary cache = []; + UaSymbols symbols = UaSymbols.For(context.Compilation, cache); + if (!symbols.ReferencesOpcUa) + { + return; + } + if (symbols.CertificateIdentifierType is null && + symbols.UserIdentityType is null && + symbols.UserIdentityTokenHandlerType is null) + { + return; + } + + context.RegisterSyntaxNodeAction( + c => AnalyzeUsingStatement(c, symbols), + SyntaxKind.UsingStatement); + context.RegisterSyntaxNodeAction( + c => AnalyzeLocalDeclaration(c, symbols), + SyntaxKind.LocalDeclarationStatement); + } + + private static void AnalyzeUsingStatement(SyntaxNodeAnalysisContext context, UaSymbols symbols) + { + UsingStatementSyntax usingStmt = (UsingStatementSyntax)context.Node; + if (usingStmt.Declaration is { } declaration) + { + ReportIfMatch(context, symbols, declaration, usingStmt.GetLocation()); + } + else if (usingStmt.Expression is { } expression) + { + ITypeSymbol type = context.SemanticModel.GetTypeInfo(expression, context.CancellationToken).Type; + Report(context, symbols, type, usingStmt.GetLocation()); + } + } + + private static void AnalyzeLocalDeclaration(SyntaxNodeAnalysisContext context, UaSymbols symbols) + { + LocalDeclarationStatementSyntax local = (LocalDeclarationStatementSyntax)context.Node; + if (local.UsingKeyword.IsKind(SyntaxKind.None)) + { + return; + } + ReportIfMatch(context, symbols, local.Declaration, local.GetLocation()); + } + + private static void ReportIfMatch( + SyntaxNodeAnalysisContext context, + UaSymbols symbols, + VariableDeclarationSyntax declaration, + Location location) + { + ITypeSymbol declaredType = context.SemanticModel + .GetTypeInfo(declaration.Type, context.CancellationToken).Type; + + // var: resolve from a variable initializer if available. + if (declaredType is null || declaredType.TypeKind == TypeKind.Error) + { + foreach (VariableDeclaratorSyntax variable in declaration.Variables) + { + if (variable.Initializer?.Value is { } init) + { + ITypeSymbol initType = context.SemanticModel + .GetTypeInfo(init, context.CancellationToken).Type; + if (initType != null) + { + declaredType = initType; + break; + } + } + } + } + + Report(context, symbols, declaredType, location); + } + + private static void Report( + SyntaxNodeAnalysisContext context, + UaSymbols symbols, + ITypeSymbol type, + Location location) + { + if (type is null) + { + return; + } + if (IsTargetType(type, symbols, out string typeName)) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0010_RemoveDisposable, + location, + typeName)); + } + } + + private static bool IsTargetType(ITypeSymbol type, UaSymbols symbols, out string name) + { + name = null; + if (symbols.CertificateIdentifierType != null && + SymbolEqualityComparer.Default.Equals(type, symbols.CertificateIdentifierType)) + { + name = symbols.CertificateIdentifierType.Name; + return true; + } + if (symbols.UserIdentityType != null && + SymbolEqualityComparer.Default.Equals(type, symbols.UserIdentityType)) + { + name = symbols.UserIdentityType.Name; + return true; + } + INamedTypeSymbol tokenHandler = symbols.UserIdentityTokenHandlerType; + if (tokenHandler != null) + { + if (SymbolEqualityComparer.Default.Equals(type, tokenHandler)) + { + name = tokenHandler.Name; + return true; + } + foreach (INamedTypeSymbol iface in type.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(iface, tokenHandler)) + { + name = type.Name; + return true; + } + } + } + return false; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs new file mode 100644 index 0000000000..63a1a64b94 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs @@ -0,0 +1,129 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0011: Detect calls to the obsolete synchronous + /// Encrypt/Decrypt/Sign/Verify members on + /// Opc.Ua.IUserIdentityTokenHandler (or any implementing type) + /// and recommend their async counterparts. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0011TokenHandlerSyncToAsyncAnalyzer : DiagnosticAnalyzer + { + private const string TokenHandlerFullName = "Opc.Ua.IUserIdentityTokenHandler"; + + private static readonly HashSet s_targetNames = + [ + "Encrypt", + "Decrypt", + "Sign", + "Verify", + ]; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0011_TokenHandlerSyncToAsync); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + INamedTypeSymbol tokenHandler = context.Compilation.GetTypeByMetadataName(TokenHandlerFullName); + if (tokenHandler is null) + { + return; + } + + context.RegisterOperationAction( + ctx => AnalyzeInvocation(ctx, tokenHandler), + OperationKind.Invocation); + } + + private static void AnalyzeInvocation( + OperationAnalysisContext context, + INamedTypeSymbol tokenHandler) + { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + IMethodSymbol method = invocation.TargetMethod; + + if (method is null || !s_targetNames.Contains(method.Name)) + { + return; + } + + if (!method.IsObsolete()) + { + return; + } + + INamedTypeSymbol containing = method.ContainingType; + if (containing is null) + { + return; + } + + bool declaredOnHandler = SymbolEqualityComparer.Default.Equals(containing, tokenHandler); + if (!declaredOnHandler) + { + bool implementsHandler = false; + foreach (INamedTypeSymbol iface in containing.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(iface, tokenHandler)) + { + implementsHandler = true; + break; + } + } + if (!implementsHandler) + { + return; + } + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0011_TokenHandlerSyncToAsync, + invocation.Syntax.GetLocation(), + method.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs new file mode 100644 index 0000000000..8505894501 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs @@ -0,0 +1,94 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0012: Detect calls to obsolete static CertificateFactory members + /// and recommend the DefaultCertificateFactory.Instance singleton. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0012CertificateFactoryStaticToInstanceAnalyzer : DiagnosticAnalyzer + { + private static readonly HashSet s_targetNames = + [ + "Create", + "CreateCertificate", + "CreateSigningRequest", + "RevokeCertificate", + "CreateCertificateWithPEMPrivateKey", + "CreateCertificateWithPrivateKey", + ]; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0012_CertificateFactoryStaticToInstance); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + } + + private static void AnalyzeInvocation(OperationAnalysisContext context) + { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + IMethodSymbol method = invocation.TargetMethod; + + if (!method.IsStatic || !s_targetNames.Contains(method.Name)) + { + return; + } + + INamedTypeSymbol containing = method.ContainingType; + if (containing is null || containing.ToDisplayString() != "Opc.Ua.CertificateFactory") + { + return; + } + + if (!method.IsObsolete()) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0012_CertificateFactoryStaticToInstance, + invocation.Syntax.GetLocation(), + method.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs new file mode 100644 index 0000000000..369809d8a5 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs @@ -0,0 +1,108 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0014: Replace the obsolete static DataValue.IsGood(dv) / + /// IsBad / IsUncertain (+ IsNotXxx) helpers with the + /// matching instance property on DataValue. + /// + /// + /// Detection: any that targets a static + /// method named one of the six helpers on either Opc.Ua.DataValue or + /// Opc.Ua.DataValueExtensions, with a single DataValue argument. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0014DataValueIsGoodAnalyzer : DiagnosticAnalyzer + { + private static readonly HashSet s_targetNames = + [ + "IsGood", + "IsBad", + "IsUncertain", + "IsNotGood", + "IsNotBad", + "IsNotUncertain", + ]; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0014_DataValueIsGoodStaticToInstance); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + } + + private static void AnalyzeInvocation(OperationAnalysisContext context) + { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + IMethodSymbol method = invocation.TargetMethod; + + if (!method.IsStatic || + method.Parameters.Length != 1 || + !s_targetNames.Contains(method.Name)) + { + return; + } + + INamedTypeSymbol containing = method.ContainingType; + if (containing is null) + { + return; + } + string containingName = containing.ToDisplayString(); + if (containingName != "Opc.Ua.DataValue" && + containingName != "Opc.Ua.DataValueExtensions") + { + return; + } + + ITypeSymbol argType = method.Parameters[0].Type; + if (argType is null || argType.ToDisplayString() != "Opc.Ua.DataValue") + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0014_DataValueIsGoodStaticToInstance, + invocation.Syntax.GetLocation(), + method.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs new file mode 100644 index 0000000000..a60149eab1 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs @@ -0,0 +1,115 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0015: Detect calls to obsolete synchronous / APM members on the + /// OPC UA discovery clients (GlobalDiscoveryServerClient, + /// ServerPushConfigurationClient, LocalDiscoveryServerClient) + /// and recommend the async counterparts. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0015GdsSyncToAsyncAnalyzer : DiagnosticAnalyzer + { + private static readonly string[] s_targetTypeNames = + [ + "Opc.Ua.GlobalDiscoveryServerClient", + "Opc.Ua.ServerPushConfigurationClient", + "Opc.Ua.LocalDiscoveryServerClient", + ]; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0015_GdsSyncToAsync); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + HashSet targets = new(SymbolEqualityComparer.Default); + foreach (string name in s_targetTypeNames) + { + INamedTypeSymbol sym = context.Compilation.GetTypeByMetadataName(name); + if (sym != null) + { + targets.Add(sym); + } + } + if (targets.Count == 0) + { + return; + } + + context.RegisterOperationAction( + ctx => AnalyzeInvocation(ctx, targets), + OperationKind.Invocation); + } + + private static void AnalyzeInvocation( + OperationAnalysisContext context, + HashSet targets) + { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + IMethodSymbol method = invocation.TargetMethod; + if (method is null) + { + return; + } + + INamedTypeSymbol containing = method.ContainingType; + if (containing is null || !targets.Contains(containing)) + { + return; + } + + if (!method.IsObsolete()) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0015_GdsSyncToAsync, + invocation.Syntax.GetLocation(), + method.Name)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs new file mode 100644 index 0000000000..addb57f361 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs @@ -0,0 +1,94 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0018: Detect reads of the obsolete Certificate getter on + /// CertificateIdentifier-family types and recommend + /// CertificateIdentifierResolver.ResolveAsync. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0018CertificateIdentifierCertificateAnalyzer : DiagnosticAnalyzer + { + private const string CertificatePropertyName = "Certificate"; + private const string CertificateIdentifierTypeSuffix = "CertificateIdentifier"; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0018_CertificateIdentifierCertificateGetter); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzePropertyReference, OperationKind.PropertyReference); + } + + private static void AnalyzePropertyReference(OperationAnalysisContext context) + { + IPropertyReferenceOperation reference = (IPropertyReferenceOperation)context.Operation; + IPropertySymbol property = reference.Property; + if (property is null || property.Name != CertificatePropertyName) + { + return; + } + + if (!property.IsObsolete()) + { + return; + } + + INamedTypeSymbol containing = property.ContainingType; + if (containing is null) + { + return; + } + + string typeName = containing.Name; + if (typeName is null || + !(typeName == CertificateIdentifierTypeSuffix || + typeName.EndsWith(CertificateIdentifierTypeSuffix, System.StringComparison.Ordinal) || + typeName.Contains(CertificateIdentifierTypeSuffix))) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0018_CertificateIdentifierCertificateGetter, + reference.Syntax.GetLocation())); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs new file mode 100644 index 0000000000..410ae25032 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs @@ -0,0 +1,97 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0019: Detect new DataValue(StatusCode) and + /// new DataValue(StatusCode, DateTimeUtc) constructor calls and + /// recommend the explicit DataValue.FromStatusCode(...) factory. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0019DataValueStatusCodeCtorAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0019_ObsoleteDataValueStatusCodeCtor); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation); + } + + private static void AnalyzeObjectCreation(OperationAnalysisContext context) + { + IObjectCreationOperation creation = (IObjectCreationOperation)context.Operation; + IMethodSymbol ctor = creation.Constructor; + if (ctor is null) + { + return; + } + + INamedTypeSymbol containing = ctor.ContainingType; + if (containing is null || containing.ToDisplayString() != "Opc.Ua.DataValue") + { + return; + } + + if (ctor.Parameters.Length < 1 || ctor.Parameters.Length > 2) + { + return; + } + + if (ctor.Parameters[0].Type?.ToDisplayString() != "Opc.Ua.StatusCode") + { + return; + } + + string extra = string.Empty; + if (ctor.Parameters.Length == 2) + { + if (ctor.Parameters[1].Type?.ToDisplayString() != "Opc.Ua.DateTimeUtc") + { + return; + } + extra = ", DateTimeUtc"; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0019_ObsoleteDataValueStatusCodeCtor, + creation.Syntax.GetLocation(), + extra)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs new file mode 100644 index 0000000000..90c9501b4c --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs @@ -0,0 +1,114 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0020: Detect references to the obsolete + /// EncodeableFactory.GlobalFactory static property (Form A) and + /// instance EncodeableFactory.Create() calls (Form B) and + /// recommend the 2.0 replacements. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0020EncodeableFactoryRenameAnalyzer : DiagnosticAnalyzer + { + public const string FormProperty = "Form"; + public const string FormGlobalFactory = "A"; + public const string FormCreate = "B"; + + private const string EncodeableFactoryTypeName = "Opc.Ua.EncodeableFactory"; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0020_EncodeableFactoryRename); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzePropertyReference, OperationKind.PropertyReference); + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + } + + private static void AnalyzePropertyReference(OperationAnalysisContext context) + { + IPropertyReferenceOperation reference = (IPropertyReferenceOperation)context.Operation; + IPropertySymbol property = reference.Property; + if (property is null || property.Name != "GlobalFactory" || !property.IsStatic) + { + return; + } + INamedTypeSymbol containing = property.ContainingType; + if (containing is null || containing.ToDisplayString() != EncodeableFactoryTypeName) + { + return; + } + + ImmutableDictionary properties = ImmutableDictionary.Empty + .Add(FormProperty, FormGlobalFactory); + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0020_EncodeableFactoryRename, + reference.Syntax.GetLocation(), + properties, + "EncodeableFactory.GlobalFactory", + "ServiceMessageContext.Factory")); + } + + private static void AnalyzeInvocation(OperationAnalysisContext context) + { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + IMethodSymbol method = invocation.TargetMethod; + if (method is null || method.Name != "Create" || method.IsStatic || method.Parameters.Length != 0) + { + return; + } + INamedTypeSymbol containing = method.ContainingType; + if (containing is null || containing.ToDisplayString() != EncodeableFactoryTypeName) + { + return; + } + + ImmutableDictionary properties = ImmutableDictionary.Empty + .Add(FormProperty, FormCreate); + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0020_EncodeableFactoryRename, + invocation.Syntax.GetLocation(), + properties, + "EncodeableFactory.Create()", + "Fork()")); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs new file mode 100644 index 0000000000..ee232a17bc --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs @@ -0,0 +1,117 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0002 code fix: rewrite every reference to a removed + /// <Type>Collection wrapper as List<TElement>. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0002RemovedCollectionTypeCodeFix)), Shared] + public sealed class UA0002RemovedCollectionTypeCodeFix : CodeFixProvider + { + private static readonly Dictionary s_shortNameToElement = BuildShortNameMap(); + + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0002); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (Diagnostic diagnostic in context.Diagnostics) + { + context.RegisterCodeFix( + CodeAction.Create( + title: "Use 'List'", + createChangedDocument: ct => ApplyAsync(context.Document, ct), + equivalenceKey: DiagnosticIds.UA0002), + diagnostic); + } + return Task.CompletedTask; + } + + private static async Task ApplyAsync(Document document, CancellationToken cancellationToken) + { + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + CollectionRewriter rewriter = new CollectionRewriter(); + SyntaxNode newRoot = rewriter.Visit(root); + return document.WithSyntaxRoot(newRoot); + } + + private static Dictionary BuildShortNameMap() + { + Dictionary map = new Dictionary(); + foreach ((string collectionName, string elementName) in SymbolExtensions.RemovedCollectionTypes) + { + string shortCollection = StripNamespace(collectionName); + string shortElement = StripNamespace(elementName); + map[shortCollection] = shortElement; + } + return map; + } + + private static string StripNamespace(string name) + { + int lastDot = name.LastIndexOf('.'); + return lastDot < 0 ? name : name.Substring(lastDot + 1); + } + + private sealed class CollectionRewriter : CSharpSyntaxRewriter + { + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) + { + if (s_shortNameToElement.TryGetValue(node.Identifier.ValueText, out string elementName)) + { + GenericNameSyntax replacement = SyntaxFactory.GenericName( + SyntaxFactory.Identifier("List"), + SyntaxFactory.TypeArgumentList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.ParseTypeName(elementName)))); + return replacement.WithTriviaFrom(node); + } + return base.VisitIdentifierName(node); + } + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs new file mode 100644 index 0000000000..96bea749db --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs @@ -0,0 +1,134 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0003 code fix: rewrite x == null as x.IsNull (or + /// x.IsNullOrEmpty for LocalizedText) and x != null as the + /// negated form. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0003NullCheckOnStructTypeCodeFix)), Shared] + public sealed class UA0003NullCheckOnStructTypeCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0003); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + BinaryExpressionSyntax binary = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(b => b.IsKind(SyntaxKind.EqualsExpression) || b.IsKind(SyntaxKind.NotEqualsExpression)); + if (binary is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use '.IsNull' instead of null comparison", + createChangedDocument: ct => ApplyAsync(context.Document, binary, ct), + equivalenceKey: DiagnosticIds.UA0003), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + BinaryExpressionSyntax binary, + CancellationToken cancellationToken) + { + SemanticModel model = (await document.GetSemanticModelAsync(cancellationToken) + .ConfigureAwait(false))!; + + ExpressionSyntax valueExpr = GetValueExpression(binary); + if (valueExpr is null) + { + return document; + } + + ITypeSymbol valueType = model.GetTypeInfo(valueExpr, cancellationToken).Type; + if (valueType is INamedTypeSymbol nullable && + nullable.OriginalDefinition?.SpecialType == SpecialType.System_Nullable_T && + nullable.TypeArguments.Length == 1) + { + valueType = nullable.TypeArguments[0]; + } + string memberName = valueType?.Name == "LocalizedText" ? "IsNullOrEmpty" : "IsNull"; + + MemberAccessExpressionSyntax access = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + valueExpr.WithoutTrivia(), + SyntaxFactory.IdentifierName(memberName)); + + ExpressionSyntax replacement = binary.IsKind(SyntaxKind.EqualsExpression) + ? (ExpressionSyntax)access + : SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, access); + + replacement = replacement.WithTriviaFrom(binary); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(binary, replacement); + return document.WithSyntaxRoot(newRoot); + } + + private static ExpressionSyntax GetValueExpression(BinaryExpressionSyntax binary) + { + if (binary.Left.IsKind(SyntaxKind.NullLiteralExpression)) + { + return binary.Right; + } + if (binary.Right.IsKind(SyntaxKind.NullLiteralExpression)) + { + return binary.Left; + } + return null; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs new file mode 100644 index 0000000000..e6cb506289 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs @@ -0,0 +1,119 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0004 code fix: drop the leading ?. of a null-conditional chain + /// whose receiver is a now-struct type, leaving any deeper ?. intact. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0004ConditionalAccessOnStructCodeFix)), Shared] + public sealed class UA0004ConditionalAccessOnStructCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0004); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + ConditionalAccessExpressionSyntax condAccess = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (condAccess is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Drop '?.' on value-type receiver", + createChangedDocument: ct => ApplyAsync(context.Document, condAccess, ct), + equivalenceKey: DiagnosticIds.UA0004), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + ConditionalAccessExpressionSyntax condAccess, + CancellationToken cancellationToken) + { + ExpressionSyntax receiver = condAccess.Expression; + ExpressionSyntax whenNotNull = condAccess.WhenNotNull; + + MemberBindingExpressionSyntax firstBinding = whenNotNull + .DescendantNodesAndSelf() + .OfType() + .FirstOrDefault(); + if (firstBinding is null) + { + return document; + } + + MemberAccessExpressionSyntax memberAccess = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + receiver.WithoutTrivia(), + firstBinding.Name); + + ExpressionSyntax replacement; + if (firstBinding == whenNotNull) + { + replacement = memberAccess; + } + else + { + replacement = whenNotNull.ReplaceNode(firstBinding, memberAccess); + } + replacement = replacement.WithTriviaFrom(condAccess); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(condAccess, replacement); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs new file mode 100644 index 0000000000..5bc933853a --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs @@ -0,0 +1,101 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0005 code fix: append .ToByteString() to a byte[] + /// argument that needs to become a ByteString. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0005ByteArrayToByteStringCodeFix)), Shared] + public sealed class UA0005ByteArrayToByteStringCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0005); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + ArgumentSyntax argument = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (argument is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Append '.ToByteString()'", + createChangedDocument: ct => ApplyAsync(context.Document, argument, ct), + equivalenceKey: DiagnosticIds.UA0005), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + ArgumentSyntax argument, + CancellationToken cancellationToken) + { + ExpressionSyntax expr = argument.Expression; + + InvocationExpressionSyntax newInvocation = SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + expr.WithoutTrivia(), + SyntaxFactory.IdentifierName("ToByteString"))); + + ArgumentSyntax newArgument = argument.WithExpression(newInvocation); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(argument, newArgument); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs new file mode 100644 index 0000000000..7e2e793e84 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs @@ -0,0 +1,146 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0006 code fix: rewrite obsolete new Variant(...) as + /// Variant.From(...), wrapping DateTime/Guid/byte[] arguments with + /// the appropriate strongly-typed surface (DateTimeUtc, Uuid, ByteString). + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0006ObsoleteVariantCtorCodeFix)), Shared] + public sealed class UA0006ObsoleteVariantCtorCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0006); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + ObjectCreationExpressionSyntax creation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (creation is null || + creation.ArgumentList is null || + creation.ArgumentList.Arguments.Count != 1) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use 'Variant.From(...)'", + createChangedDocument: ct => ApplyAsync(context.Document, creation, ct), + equivalenceKey: DiagnosticIds.UA0006), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + ObjectCreationExpressionSyntax creation, + CancellationToken cancellationToken) + { + SemanticModel model = (await document.GetSemanticModelAsync(cancellationToken) + .ConfigureAwait(false))!; + + ArgumentSyntax arg = creation.ArgumentList!.Arguments[0]; + ITypeSymbol argType = model.GetTypeInfo(arg.Expression, cancellationToken).Type; + ExpressionSyntax inner = BuildInner(arg.Expression.WithoutTrivia(), argType); + + InvocationExpressionSyntax replacement = SyntaxFactory + .InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Variant"), + SyntaxFactory.IdentifierName("From")), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(inner)))) + .WithTriviaFrom(creation); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(creation, replacement); + return document.WithSyntaxRoot(newRoot); + } + + private static ExpressionSyntax BuildInner(ExpressionSyntax argExpr, ITypeSymbol argType) + { + if (argType is null) + { + return argExpr; + } + switch (argType.SpecialType) + { + case SpecialType.System_DateTime: + return SyntaxFactory.ObjectCreationExpression( + SyntaxFactory.IdentifierName("DateTimeUtc"), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(argExpr))), + initializer: null); + } + if (argType.ToDisplayString() == "System.Guid") + { + return SyntaxFactory.ObjectCreationExpression( + SyntaxFactory.IdentifierName("Uuid"), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(argExpr))), + initializer: null); + } + if (argType is IArrayTypeSymbol arr && + arr.ElementType?.SpecialType == SpecialType.System_Byte) + { + return SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + argExpr, + SyntaxFactory.IdentifierName("ToByteString"))); + } + return argExpr; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs new file mode 100644 index 0000000000..80657c3a2e --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs @@ -0,0 +1,120 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0007 code fix: rewrite new NodeId(s) / new ExpandedNodeId(s) + /// as the corresponding Parse(s) call. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0007ObsoleteNodeIdStringCtorCodeFix)), Shared] + public sealed class UA0007ObsoleteNodeIdStringCtorCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0007); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + ObjectCreationExpressionSyntax creation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (creation is null || creation.ArgumentList is null) + { + continue; + } + + string typeName = GetTypeName(creation); + if (typeName is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: $"Use '{typeName}.Parse(s)'", + createChangedDocument: ct => ApplyAsync(context.Document, creation, typeName, ct), + equivalenceKey: $"{DiagnosticIds.UA0007}:{typeName}"), + diagnostic); + } + } + + private static string GetTypeName(ObjectCreationExpressionSyntax creation) + { + switch (creation.Type) + { + case IdentifierNameSyntax id: + return id.Identifier.ValueText; + case QualifiedNameSyntax qn: + return qn.Right.Identifier.ValueText; + default: + return null; + } + } + + private static async Task ApplyAsync( + Document document, + ObjectCreationExpressionSyntax creation, + string typeName, + CancellationToken cancellationToken) + { + InvocationExpressionSyntax replacement = SyntaxFactory + .InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(typeName), + SyntaxFactory.IdentifierName("Parse")), + creation.ArgumentList!) + .WithTriviaFrom(creation); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(creation, replacement); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs new file mode 100644 index 0000000000..663ff57c1e --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs @@ -0,0 +1,144 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0008 code fix: wrap each variadic argument of ISession.Call / + /// ISession.CallAsync with Variant.From(...) (replacing + /// null literals with Variant.Null). + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0008SessionCallParamsObjectCodeFix)), Shared] + public sealed class UA0008SessionCallParamsObjectCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0008); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + InvocationExpressionSyntax invocation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (invocation is null || invocation.ArgumentList is null) + { + continue; + } + + if (!diagnostic.Properties.TryGetValue( + UA0008SessionCallParamsObjectAnalyzer.MethodNameProperty, + out string methodName)) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Wrap arguments with 'Variant.From(...)'", + createChangedDocument: ct => ApplyAsync(context.Document, invocation, methodName, ct), + equivalenceKey: DiagnosticIds.UA0008), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + InvocationExpressionSyntax invocation, + string methodName, + CancellationToken cancellationToken) + { + int firstVariadicIndex = methodName == "Call" ? 2 : 3; + SeparatedSyntaxList originalArgs = invocation.ArgumentList.Arguments; + + List newArgs = new List(originalArgs.Count); + for (int i = 0; i < originalArgs.Count; i++) + { + ArgumentSyntax arg = originalArgs[i]; + if (i < firstVariadicIndex) + { + newArgs.Add(arg); + continue; + } + newArgs.Add(arg.WithExpression(Wrap(arg.Expression))); + } + + InvocationExpressionSyntax newInvocation = invocation + .WithArgumentList(invocation.ArgumentList.WithArguments( + SyntaxFactory.SeparatedList(newArgs))); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(invocation, newInvocation); + return document.WithSyntaxRoot(newRoot); + } + + private static ExpressionSyntax Wrap(ExpressionSyntax expression) + { + if (expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.NullLiteralExpression)) + { + return SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Variant"), + SyntaxFactory.IdentifierName("Null")) + .WithTriviaFrom(expression); + } + + ExpressionSyntax stripped = expression.WithoutTrivia(); + InvocationExpressionSyntax call = SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Variant"), + SyntaxFactory.IdentifierName("From")), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Argument(stripped)))); + return call.WithTriviaFrom(expression); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs new file mode 100644 index 0000000000..987a2814cd --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs @@ -0,0 +1,218 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0009 code fix: rewrite + /// [DataContract]/[DataMember] attributes to the OPC UA 2.0 + /// [DataType]/[DataTypeField] equivalents, mark the class + /// partial, and add using Opc.Ua; when missing. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0009DataContractToDataTypeCodeFix)), Shared] + public sealed class UA0009DataContractToDataTypeCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0009); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + ClassDeclarationSyntax classDecl = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (classDecl is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Migrate to [DataType]/[DataTypeField] and add 'partial'", + createChangedDocument: ct => ApplyAsync(context.Document, classDecl, ct), + equivalenceKey: DiagnosticIds.UA0009), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + ClassDeclarationSyntax classDecl, + CancellationToken cancellationToken) + { + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + + ClassDeclarationSyntax rewritten = RewriteClass(classDecl); + SyntaxNode newRoot = root.ReplaceNode(classDecl, rewritten); + + if (newRoot is CompilationUnitSyntax compilationUnit) + { + newRoot = EnsureUsing(compilationUnit, "Opc.Ua"); + } + + return document.WithSyntaxRoot(newRoot); + } + + private static ClassDeclarationSyntax RewriteClass(ClassDeclarationSyntax classDecl) + { + SyntaxList newAttributeLists = RewriteAttributeLists(classDecl.AttributeLists); + ClassDeclarationSyntax result = classDecl.WithAttributeLists(newAttributeLists); + + if (!result.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + SyntaxToken partialToken = SyntaxFactory.Token(SyntaxKind.PartialKeyword) + .WithTrailingTrivia(SyntaxFactory.Space); + result = result.WithModifiers(result.Modifiers.Add(partialToken)); + } + + List newMembers = new List(result.Members.Count); + foreach (MemberDeclarationSyntax member in result.Members) + { + if (member is PropertyDeclarationSyntax property) + { + SyntaxList rewritten = RewriteAttributeLists(property.AttributeLists); + newMembers.Add(property.WithAttributeLists(rewritten)); + } + else + { + newMembers.Add(member); + } + } + return result.WithMembers(SyntaxFactory.List(newMembers)); + } + + private static SyntaxList RewriteAttributeLists( + SyntaxList attributeLists) + { + List newLists = new List(attributeLists.Count); + foreach (AttributeListSyntax list in attributeLists) + { + List newAttrs = new List(list.Attributes.Count); + foreach (AttributeSyntax attr in list.Attributes) + { + newAttrs.Add(RewriteAttribute(attr)); + } + newLists.Add(list.WithAttributes(SyntaxFactory.SeparatedList(newAttrs))); + } + return SyntaxFactory.List(newLists); + } + + private static AttributeSyntax RewriteAttribute(AttributeSyntax attribute) + { + string simpleName = GetSimpleAttributeName(attribute.Name); + if (simpleName == "DataContract" || simpleName == "DataContractAttribute") + { + return SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("DataType")) + .WithTriviaFrom(attribute); + } + if (simpleName == "DataMember" || simpleName == "DataMemberAttribute") + { + AttributeArgumentListSyntax argList = FilterToOrderArgument(attribute.ArgumentList); + AttributeSyntax replacement = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("DataTypeField")); + if (argList != null) + { + replacement = replacement.WithArgumentList(argList); + } + return replacement.WithTriviaFrom(attribute); + } + return attribute; + } + + private static string GetSimpleAttributeName(NameSyntax name) + { + switch (name) + { + case IdentifierNameSyntax id: + return id.Identifier.ValueText; + case QualifiedNameSyntax qn: + return qn.Right.Identifier.ValueText; + case AliasQualifiedNameSyntax aq: + return aq.Name.Identifier.ValueText; + default: + return name?.ToString(); + } + } + + private static AttributeArgumentListSyntax FilterToOrderArgument(AttributeArgumentListSyntax argumentList) + { + if (argumentList is null) + { + return null; + } + List kept = new List(); + foreach (AttributeArgumentSyntax arg in argumentList.Arguments) + { + if (arg.NameEquals?.Name.Identifier.ValueText == "Order") + { + kept.Add(arg.WithoutTrivia()); + } + } + if (kept.Count == 0) + { + return null; + } + return SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(kept)); + } + + private static CompilationUnitSyntax EnsureUsing(CompilationUnitSyntax compilationUnit, string namespaceName) + { + foreach (UsingDirectiveSyntax existing in compilationUnit.Usings) + { + if (existing.Name?.ToString() == namespaceName) + { + return compilationUnit; + } + } + + UsingDirectiveSyntax newUsing = SyntaxFactory.UsingDirective( + SyntaxFactory.ParseName(namespaceName)); + return compilationUnit.AddUsings(newUsing); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0010RemoveDisposableCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0010RemoveDisposableCodeFix.cs new file mode 100644 index 0000000000..9bba413216 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0010RemoveDisposableCodeFix.cs @@ -0,0 +1,59 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0010 code fix: intentionally diagnostic-only — removing the + /// using may change variable scope, so we surface the warning and + /// let the developer rework the lifecycle by hand. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0010RemoveDisposableCodeFix)), Shared] + public sealed class UA0010RemoveDisposableCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0010); + + public override FixAllProvider GetFixAllProvider() => null; + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + // No automatic code action: removing the 'using' may change the + // variable's scope/lifetime, which is not safe to do mechanically. + return Task.CompletedTask; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs new file mode 100644 index 0000000000..a0b937ad7d --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs @@ -0,0 +1,106 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0012 code fix: rewrite CertificateFactory.X(args) as + /// DefaultCertificateFactory.Instance.X(args). + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0012CertificateFactoryStaticToInstanceCodeFix)), Shared] + public sealed class UA0012CertificateFactoryStaticToInstanceCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0012); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + InvocationExpressionSyntax invocation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (invocation is null || invocation.Expression is not MemberAccessExpressionSyntax member) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use 'DefaultCertificateFactory.Instance'", + createChangedDocument: ct => ApplyAsync(context.Document, invocation, member, ct), + equivalenceKey: DiagnosticIds.UA0012), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + InvocationExpressionSyntax invocation, + MemberAccessExpressionSyntax member, + CancellationToken cancellationToken) + { + MemberAccessExpressionSyntax newReceiver = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("DefaultCertificateFactory"), + SyntaxFactory.IdentifierName("Instance")); + + MemberAccessExpressionSyntax newMember = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + newReceiver, + member.Name); + + InvocationExpressionSyntax replacement = invocation + .WithExpression(newMember) + .WithTriviaFrom(invocation); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(invocation, replacement); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0014DataValueIsGoodCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0014DataValueIsGoodCodeFix.cs new file mode 100644 index 0000000000..34c14ded5d --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0014DataValueIsGoodCodeFix.cs @@ -0,0 +1,122 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0014 code fix: rewrite DataValue.IsGood(dv) (or the + /// DataValueExtensions extension form) as dv.IsGood. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0014DataValueIsGoodCodeFix)), Shared] + public sealed class UA0014DataValueIsGoodCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0014); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + InvocationExpressionSyntax invocation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (invocation is null || invocation.ArgumentList.Arguments.Count != 1) + { + continue; + } + + string memberName = GetMemberName(invocation); + if (memberName is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: $"Use '{memberName}' instance property", + createChangedDocument: ct => ApplyAsync(context.Document, invocation, memberName, ct), + equivalenceKey: $"{DiagnosticIds.UA0014}:{memberName}"), + diagnostic); + } + } + + private static string GetMemberName(InvocationExpressionSyntax invocation) + { + switch (invocation.Expression) + { + case MemberAccessExpressionSyntax member: + return member.Name.Identifier.ValueText; + case IdentifierNameSyntax id: + return id.Identifier.ValueText; + default: + return null; + } + } + + private static async Task ApplyAsync( + Document document, + InvocationExpressionSyntax invocation, + string memberName, + CancellationToken cancellationToken) + { + ArgumentSyntax arg = invocation.ArgumentList.Arguments[0]; + ExpressionSyntax receiver = arg.Expression.WithoutTrivia(); + + MemberAccessExpressionSyntax replacement = SyntaxFactory + .MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + receiver, + SyntaxFactory.IdentifierName(memberName)) + .WithTriviaFrom(invocation); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(invocation, replacement); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs new file mode 100644 index 0000000000..4cb80c5954 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs @@ -0,0 +1,100 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0019 code fix: rewrite new DataValue(sc) / new DataValue(sc, ts) + /// as DataValue.FromStatusCode(sc) / DataValue.FromStatusCode(sc, ts). + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0019DataValueStatusCodeCtorCodeFix)), Shared] + public sealed class UA0019DataValueStatusCodeCtorCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0019); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + ObjectCreationExpressionSyntax creation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (creation is null || creation.ArgumentList is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use 'DataValue.FromStatusCode(...)'", + createChangedDocument: ct => ApplyAsync(context.Document, creation, ct), + equivalenceKey: DiagnosticIds.UA0019), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + ObjectCreationExpressionSyntax creation, + CancellationToken cancellationToken) + { + InvocationExpressionSyntax replacement = SyntaxFactory + .InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("DataValue"), + SyntaxFactory.IdentifierName("FromStatusCode")), + creation.ArgumentList!) + .WithTriviaFrom(creation); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(creation, replacement); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs new file mode 100644 index 0000000000..318d047da8 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs @@ -0,0 +1,107 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0020 code fix: rewrite instance factory.Create() as + /// factory.Fork(). The GlobalFactory form has no + /// automatic fix because the replacement + /// (ServiceMessageContext.Factory) requires a context instance + /// that the analyzer cannot conjure. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0020EncodeableFactoryRenameCodeFix)), Shared] + public sealed class UA0020EncodeableFactoryRenameCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0020); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + if (!diagnostic.Properties.TryGetValue( + UA0020EncodeableFactoryRenameAnalyzer.FormProperty, + out string form) || + form != UA0020EncodeableFactoryRenameAnalyzer.FormCreate) + { + continue; + } + + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + InvocationExpressionSyntax invocation = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (invocation is null || invocation.Expression is not MemberAccessExpressionSyntax memberAccess) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use 'Fork()'", + createChangedDocument: ct => ApplyAsync(context.Document, invocation, memberAccess, ct), + equivalenceKey: DiagnosticIds.UA0020), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + InvocationExpressionSyntax invocation, + MemberAccessExpressionSyntax memberAccess, + CancellationToken cancellationToken) + { + MemberAccessExpressionSyntax newMemberAccess = memberAccess + .WithName(Microsoft.CodeAnalysis.CSharp.SyntaxFactory.IdentifierName("Fork")); + InvocationExpressionSyntax newInvocation = invocation.WithExpression(newMemberAccess); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(invocation, newInvocation); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs new file mode 100644 index 0000000000..16de94b1e5 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs @@ -0,0 +1,179 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using Microsoft.CodeAnalysis; + +namespace Opc.Ua.CodeFixers.Diagnostics +{ + /// + /// Centralised registry of every shipped + /// by the OPC UA migration analyzer package. Descriptors are created once + /// and shared by analyzers and tests, so message text and severity stay + /// consistent across the codebase. + /// + internal static class DiagnosticDescriptors + { + private static DiagnosticDescriptor Create( + string id, + string title, + string messageFormat, + DiagnosticSeverity defaultSeverity, + string description) + { + return new DiagnosticDescriptor( + id: id, + title: title, + messageFormat: messageFormat, + category: DiagnosticIds.Category, + defaultSeverity: defaultSeverity, + isEnabledByDefault: true, + description: description, + helpLinkUri: DiagnosticIds.HelpLinkFor(id)); + } + + public static readonly DiagnosticDescriptor UA0001_UtilsTraceToILogger = Create( + DiagnosticIds.UA0001, + "Replace Utils.Trace / Utils.LogX with ILogger", + "'{0}' is deprecated. Use an ILogger obtained from ITelemetryContext.CreateLogger() instead.", + DiagnosticSeverity.Info, + "Opc.Ua.Utils.Trace and the Utils.LogX helpers are obsolete in 2.0. Logging is now performed through ILogger instances created from an ITelemetryContext that flows through the constructor."); + + public static readonly DiagnosticDescriptor UA0002_RemovedCollectionType = Create( + DiagnosticIds.UA0002, + "Removed collection wrapper type", + "'{0}' was removed in 2.0. Use 'List<{1}>' for mutable storage or 'ArrayOf<{1}>' for read-only consumers.", + DiagnosticSeverity.Warning, + "Generated Collection wrappers (Int32Collection, VariantCollection, NodeIdCollection, ...) were removed in 2.0 in favour of List and ArrayOf."); + + public static readonly DiagnosticDescriptor UA0003_NullCheckOnStructType = Create( + DiagnosticIds.UA0003, + "Null comparison on now-struct built-in type", + "'{0}' is now a value type; use IsNull instead of comparing with null", + DiagnosticSeverity.Warning, + "NodeId, ExpandedNodeId, QualifiedName, LocalizedText, ExtensionObject, DataValue, Variant and ByteString are readonly structs in 2.0. Comparing them with null is misleading. Use the .IsNull property (or LocalizedText.IsNullOrEmpty) instead."); + + public static readonly DiagnosticDescriptor UA0004_ConditionalAccessOnStructType = Create( + DiagnosticIds.UA0004, + "Null-conditional access on now-struct built-in type", + "'?.' on '{0}' is unnecessary because '{0}' is now a value type. Use a direct access or guard with '.IsNull'.", + DiagnosticSeverity.Warning, + "NodeId, Variant, DataValue and the other built-in types became structs in 2.0 — the null-conditional operator is no longer meaningful on them."); + + public static readonly DiagnosticDescriptor UA0005_ByteArrayWhereByteStringExpected = Create( + DiagnosticIds.UA0005, + "Pass ByteString where required", + "A 'byte[]' is being passed where 'ByteString' is now expected by '{0}'. Call '.ToByteString()' on the array.", + DiagnosticSeverity.Warning, + "2.0 APIs that previously took byte[] now require Opc.Ua.ByteString. Convert with the .ToByteString() extension."); + + public static readonly DiagnosticDescriptor UA0006_ObsoleteVariantCtor = Create( + DiagnosticIds.UA0006, + "Obsolete Variant constructor", + "'new Variant({0})' is obsolete. Use 'Variant.From({0})' (and the matching Uuid/DateTimeUtc/ByteString wrapper if needed).", + DiagnosticSeverity.Warning, + "The non-generic Variant constructors accepting object/DateTime/Guid/byte[] were obsoleted in 2.0. Variant.From(T) preserves the value's type information correctly."); + + public static readonly DiagnosticDescriptor UA0007_ObsoleteNodeIdStringCtor = Create( + DiagnosticIds.UA0007, + "Obsolete NodeId(string) constructor", + "'new {0}(string)' is obsolete. Use '{0}.Parse(s)' (or 'TryParse' for untrusted input).", + DiagnosticSeverity.Warning, + "new NodeId(string) and new ExpandedNodeId(string) were obsoleted in 2.0 in favour of explicit Parse / TryParse."); + + public static readonly DiagnosticDescriptor UA0008_SessionCallParamsObject = Create( + DiagnosticIds.UA0008, + "Wrap Session.Call arguments with Variant.From", + "'{0}' now takes 'params Variant[]'. Wrap each argument with 'Variant.From(...)' (or 'Variant.Null' for null).", + DiagnosticSeverity.Warning, + "Session.Call / Session.CallAsync changed from params object[] to params Variant[] in 2.0."); + + public static readonly DiagnosticDescriptor UA0009_DataContractToDataType = Create( + DiagnosticIds.UA0009, + "Replace [DataContract]/[DataMember] on configuration extensions", + "'{0}' is consumed by ParseExtension or UpdateExtension; use [DataType]/[DataTypeField] from Opc.Ua and mark the class partial", + DiagnosticSeverity.Warning, + "Configuration extension classes serialised through ParseExtension/UpdateExtension use the source-generator-driven [DataType]/[DataTypeField] attributes in 2.0."); + + public static readonly DiagnosticDescriptor UA0010_RemoveDisposable = Create( + DiagnosticIds.UA0010, + "Remove using/Dispose on non-IDisposable identity", + "'{0}' is no longer IDisposable in 2.0 — remove the 'using' (or the explicit Dispose call). Lifecycle is owned by CertificateManager.", + DiagnosticSeverity.Warning, + "CertificateIdentifier, UserIdentity and IUserIdentityTokenHandler are no longer IDisposable in 2.0."); + + public static readonly DiagnosticDescriptor UA0011_TokenHandlerSyncToAsync = Create( + DiagnosticIds.UA0011, + "User identity token handler — use async members", + "'{0}' is removed in 2.0; call the async counterpart and propagate the CancellationToken", + DiagnosticSeverity.Info, + "IUserIdentityTokenHandler synchronous Encrypt/Decrypt/Sign/Verify have been replaced by their *Async counterparts."); + + public static readonly DiagnosticDescriptor UA0012_CertificateFactoryStaticToInstance = Create( + DiagnosticIds.UA0012, + "Obsolete static CertificateFactory member", + "'CertificateFactory.{0}' is obsolete. Use 'DefaultCertificateFactory.Instance.{0}'.", + DiagnosticSeverity.Warning, + "Static CertificateFactory helpers (Create, CreateCertificate, CreateSigningRequest, RevokeCertificate, ...) were obsoleted in 2.0 in favour of the singleton DefaultCertificateFactory.Instance."); + + public static readonly DiagnosticDescriptor UA0014_DataValueIsGoodStaticToInstance = Create( + DiagnosticIds.UA0014, + "Use DataValue.IsGood instance property", + "'DataValue.{0}(dv)' is obsolete. Use 'dv.{0}'.", + DiagnosticSeverity.Warning, + "The static DataValue.IsGood/IsBad/IsUncertain helpers became instance properties in 2.0."); + + public static readonly DiagnosticDescriptor UA0015_GdsSyncToAsync = Create( + DiagnosticIds.UA0015, + "GDS/LDS client — use async members", + "'{0}' is removed in 2.0; call the async counterpart and propagate the CancellationToken", + DiagnosticSeverity.Info, + "Synchronous and APM members on GlobalDiscoveryServerClient / LocalDiscoveryServerClient / ServerPushConfigurationClient were removed in 2.0."); + + public static readonly DiagnosticDescriptor UA0018_CertificateIdentifierCertificateGetter = Create( + DiagnosticIds.UA0018, + "Use CertificateIdentifierResolver.ResolveAsync", + "'CertificateIdentifier.Certificate' is removed in 2.0; call CertificateIdentifierResolver.ResolveAsync", + DiagnosticSeverity.Info, + "The synchronous CertificateIdentifier.Certificate getter was removed in 2.0. Use the async resolver."); + + public static readonly DiagnosticDescriptor UA0019_ObsoleteDataValueStatusCodeCtor = Create( + DiagnosticIds.UA0019, + "Obsolete DataValue(StatusCode) constructor", + "'new DataValue(StatusCode{0})' silently lost the value semantics. Use 'DataValue.FromStatusCode(...)'.", + DiagnosticSeverity.Warning, + "new DataValue(StatusCode) / new DataValue(StatusCode, DateTimeUtc) were obsoleted in 2.0 because they silently resolved to the StatusCode overload and lost the value. DataValue.FromStatusCode is explicit."); + + public static readonly DiagnosticDescriptor UA0020_EncodeableFactoryRename = Create( + DiagnosticIds.UA0020, + "EncodeableFactory member renamed", + "'{0}' was replaced in 2.0. Use '{1}' instead.", + DiagnosticSeverity.Warning, + "EncodeableFactory.GlobalFactory was removed (consumers now obtain the factory from ServiceMessageContext.Factory) and EncodeableFactory.Create was renamed to Fork."); + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs new file mode 100644 index 0000000000..fe51b43a67 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs @@ -0,0 +1,71 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +namespace Opc.Ua.CodeFixers.Diagnostics +{ + /// + /// Stable identifiers for every diagnostic shipped by the + /// OPCFoundation.NetStandard.Opc.Ua.CodeFixers analyzer package. + /// Keep IDs immutable across releases — consumers may use them + /// in #pragma warning disable or .editorconfig. + /// + internal static class DiagnosticIds + { + public const string UA0001 = "UA0001"; + public const string UA0002 = "UA0002"; + public const string UA0003 = "UA0003"; + public const string UA0004 = "UA0004"; + public const string UA0005 = "UA0005"; + public const string UA0006 = "UA0006"; + public const string UA0007 = "UA0007"; + public const string UA0008 = "UA0008"; + public const string UA0009 = "UA0009"; + public const string UA0010 = "UA0010"; + public const string UA0011 = "UA0011"; + public const string UA0012 = "UA0012"; + public const string UA0014 = "UA0014"; + public const string UA0015 = "UA0015"; + public const string UA0018 = "UA0018"; + public const string UA0019 = "UA0019"; + public const string UA0020 = "UA0020"; + + /// The diagnostic category every UA00xx rule belongs to. + public const string Category = "Migration"; + + /// + /// Base URL for per-rule help. Each rule appends its own ID. + /// Points at the MigrationGuide.md "Automated migration" section. + /// + public const string HelpLinkUriBase = + "https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#"; + + /// Compose a per-rule help URL anchored at the rule ID. + public static string HelpLinkFor(string id) => HelpLinkUriBase + id.ToLowerInvariant(); + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs b/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs new file mode 100644 index 0000000000..69e124b39e --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs @@ -0,0 +1,172 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Opc.Ua.CodeFixers.Helpers +{ + /// + /// Shared helpers for symbol-shape queries used by multiple analyzers. + /// Lifted out of individual analyzers to keep detection patterns consistent. + /// + internal static class SymbolExtensions + { + /// + /// True iff carries an [Obsolete] attribute. + /// + public static bool IsObsolete(this ISymbol symbol) + { + if (symbol is null) + { + return false; + } + foreach (AttributeData attr in symbol.GetAttributes()) + { + INamedTypeSymbol cls = attr.AttributeClass; + if (cls != null && cls.ToDisplayString() == "System.ObsoleteAttribute") + { + return true; + } + } + return false; + } + + /// + /// True iff is declared (directly or via override) + /// on the named type referenced by . + /// + public static bool IsDeclaredOn(this ISymbol member, string declaringTypeFullName) + { + INamedTypeSymbol declaring = member?.ContainingType; + while (declaring != null) + { + if (declaring.ToDisplayString() == declaringTypeFullName) + { + return true; + } + declaring = declaring.BaseType; + } + return false; + } + + /// + /// True iff is assignable to the named target type + /// (walks and ). + /// + public static bool IsAssignableTo(this ITypeSymbol type, ITypeSymbol target) + { + if (type is null || target is null) + { + return false; + } + + SymbolEqualityComparer eq = SymbolEqualityComparer.Default; + ITypeSymbol current = type; + while (current != null) + { + if (eq.Equals(current, target)) + { + return true; + } + current = current.BaseType; + } + foreach (INamedTypeSymbol iface in type.AllInterfaces) + { + if (eq.Equals(iface, target)) + { + return true; + } + } + return false; + } + + /// + /// Closed list of removed {Type}Collection wrappers that 2.0 dropped + /// in favour of List<T> / ArrayOf<T>. The element + /// type name is the second tuple item. + /// + public static IReadOnlyList<(string CollectionName, string ElementName)> RemovedCollectionTypes { get; } = + new[] + { + ("Opc.Ua.BooleanCollection", "bool"), + ("Opc.Ua.SByteCollection", "sbyte"), + ("Opc.Ua.ByteCollection", "byte"), + ("Opc.Ua.Int16Collection", "short"), + ("Opc.Ua.UInt16Collection", "ushort"), + ("Opc.Ua.Int32Collection", "int"), + ("Opc.Ua.UInt32Collection", "uint"), + ("Opc.Ua.Int64Collection", "long"), + ("Opc.Ua.UInt64Collection", "ulong"), + ("Opc.Ua.FloatCollection", "float"), + ("Opc.Ua.DoubleCollection", "double"), + ("Opc.Ua.StringCollection", "string"), + ("Opc.Ua.DateTimeCollection", "Opc.Ua.DateTimeUtc"), + ("Opc.Ua.GuidCollection", "Opc.Ua.Uuid"), + ("Opc.Ua.ByteStringCollection", "Opc.Ua.ByteString"), + ("Opc.Ua.XmlElementCollection", "System.Xml.XmlElement"), + ("Opc.Ua.NodeIdCollection", "Opc.Ua.NodeId"), + ("Opc.Ua.ExpandedNodeIdCollection", "Opc.Ua.ExpandedNodeId"), + ("Opc.Ua.QualifiedNameCollection", "Opc.Ua.QualifiedName"), + ("Opc.Ua.LocalizedTextCollection", "Opc.Ua.LocalizedText"), + ("Opc.Ua.StatusCodeCollection", "Opc.Ua.StatusCode"), + ("Opc.Ua.VariantCollection", "Opc.Ua.Variant"), + ("Opc.Ua.DiagnosticInfoCollection", "Opc.Ua.DiagnosticInfo"), + ("Opc.Ua.DataValueCollection", "Opc.Ua.DataValue"), + ("Opc.Ua.ExtensionObjectCollection", "Opc.Ua.ExtensionObject"), + ("Opc.Ua.ArgumentCollection", "Opc.Ua.Argument"), + ("Opc.Ua.ServerSecurityPolicyCollection", "Opc.Ua.ServerSecurityPolicy"), + ("Opc.Ua.TransportConfigurationCollection", "Opc.Ua.TransportConfiguration"), + ("Opc.Ua.ReverseConnectClientCollection", "Opc.Ua.ReverseConnectClient"), + }; + + /// + /// Convenience: returns the element type metadata name if + /// is one of the removed collection wrappers. + /// + public static bool TryGetRemovedCollectionElement(this ITypeSymbol type, out string elementName) + { + elementName = null; + if (type is null) + { + return false; + } + string typeFullName = type.ToDisplayString(); + foreach ((string collection, string element) in RemovedCollectionTypes) + { + if (collection == typeFullName) + { + elementName = element; + return true; + } + } + return false; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs b/Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs new file mode 100644 index 0000000000..c4c07374b9 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs @@ -0,0 +1,165 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Opc.Ua.CodeFixers.Helpers +{ + /// + /// Cached lookup of well-known OPC UA s for a + /// single . Analyzers register a compilation-start + /// action that creates one instance per compilation so symbol resolution + /// happens at most once per build. + /// + internal sealed class UaSymbols + { + private UaSymbols(Compilation compilation) + { + Compilation = compilation; + + // Struct built-ins (NodeId, Variant, ...) that became readonly structs in 2.0. + BuiltInStructTypes = ImmutableArray.Create( + Get("Opc.Ua.NodeId"), + Get("Opc.Ua.ExpandedNodeId"), + Get("Opc.Ua.QualifiedName"), + Get("Opc.Ua.LocalizedText"), + Get("Opc.Ua.ExtensionObject"), + Get("Opc.Ua.DataValue"), + Get("Opc.Ua.Variant"), + Get("Opc.Ua.ByteString")); + + UtilsType = Get("Opc.Ua.Utils"); + VariantType = Get("Opc.Ua.Variant"); + VariantNullField = VariantType?.GetMembers("Null").Length > 0 ? "Null" : null; + DataValueType = Get("Opc.Ua.DataValue"); + NodeIdType = Get("Opc.Ua.NodeId"); + ExpandedNodeIdType = Get("Opc.Ua.ExpandedNodeId"); + ByteStringType = Get("Opc.Ua.ByteString"); + StatusCodeType = Get("Opc.Ua.StatusCode"); + DateTimeUtcType = Get("Opc.Ua.DateTimeUtc"); + UuidType = Get("Opc.Ua.Uuid"); + CertificateIdentifierType = Get("Opc.Ua.CertificateIdentifier"); + UserIdentityType = Get("Opc.Ua.UserIdentity"); + UserIdentityTokenHandlerType = Get("Opc.Ua.IUserIdentityTokenHandler"); + CertificateFactoryType = Get("Opc.Ua.CertificateFactory"); + SessionType = Get("Opc.Ua.Client.Session"); + SessionInterfaceType = Get("Opc.Ua.Client.ISession"); + EncodeableFactoryType = Get("Opc.Ua.EncodeableFactory"); + ServiceMessageContextType = Get("Opc.Ua.ServiceMessageContext"); + TelemetryContextType = Get("Opc.Ua.ITelemetryContext"); + LoggerType = Get("Microsoft.Extensions.Logging.ILogger"); + DataContractType = Get("System.Runtime.Serialization.DataContractAttribute"); + DataMemberType = Get("System.Runtime.Serialization.DataMemberAttribute"); + } + + public Compilation Compilation { get; } + + /// NodeId, Variant, DataValue, ... — all readonly structs in 2.0. + public ImmutableArray BuiltInStructTypes { get; } + + public INamedTypeSymbol UtilsType { get; } + public INamedTypeSymbol VariantType { get; } + + /// Name of the static "null" field on Variant (e.g. Variant.Null). + public string VariantNullField { get; } + + public INamedTypeSymbol DataValueType { get; } + public INamedTypeSymbol NodeIdType { get; } + public INamedTypeSymbol ExpandedNodeIdType { get; } + public INamedTypeSymbol ByteStringType { get; } + public INamedTypeSymbol StatusCodeType { get; } + public INamedTypeSymbol DateTimeUtcType { get; } + public INamedTypeSymbol UuidType { get; } + public INamedTypeSymbol CertificateIdentifierType { get; } + public INamedTypeSymbol UserIdentityType { get; } + public INamedTypeSymbol UserIdentityTokenHandlerType { get; } + public INamedTypeSymbol CertificateFactoryType { get; } + public INamedTypeSymbol SessionType { get; } + public INamedTypeSymbol SessionInterfaceType { get; } + public INamedTypeSymbol EncodeableFactoryType { get; } + public INamedTypeSymbol ServiceMessageContextType { get; } + public INamedTypeSymbol TelemetryContextType { get; } + public INamedTypeSymbol LoggerType { get; } + public INamedTypeSymbol DataContractType { get; } + public INamedTypeSymbol DataMemberType { get; } + + /// True iff the compilation references at least one OPC UA 2.0 surface. + public bool ReferencesOpcUa => + NodeIdType != null || VariantType != null || DataValueType != null; + + public bool IsBuiltInStructType(ITypeSymbol type) + { + if (type is null) + { + return false; + } + foreach (INamedTypeSymbol candidate in BuiltInStructTypes) + { + if (candidate != null && SymbolEqualityComparer.Default.Equals(candidate, type)) + { + return true; + } + } + return false; + } + + /// Resolve a well-known type by fully-qualified metadata name. + public INamedTypeSymbol Get(string fullyQualifiedName) + { + return Compilation.GetTypeByMetadataName(fullyQualifiedName); + } + + public static UaSymbols Create(Compilation compilation) + { + if (compilation is null) + { + throw new ArgumentNullException(nameof(compilation)); + } + return new UaSymbols(compilation); + } + + /// + /// Convenience: pull a cached instance keyed by + /// the Compilation. Analyzers should call this in their compilation-start + /// callback so symbols are resolved exactly once per build. + /// + public static UaSymbols For(Compilation compilation, Dictionary cache) + { + if (!cache.TryGetValue(compilation, out UaSymbols symbols)) + { + symbols = Create(compilation); + cache[compilation] = symbols; + } + return symbols; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/NugetREADME.md b/Tools/Opc.Ua.CodeFixers/NugetREADME.md new file mode 100644 index 0000000000..0b0f6d4656 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/NugetREADME.md @@ -0,0 +1,59 @@ +# OPC UA migration analyzers and code fixers + +This package ships Roslyn analyzers and code fixers that help consumers +migrate from OPC UA .NET Standard 1.5.378 to 2.0. + +Install in your consumer project: + +```xml + +``` + +Then rebuild — the analyzer will flag each of the 17 patterns covered by the +[`Docs/MigrationGuide.md`](../../Docs/MigrationGuide.md) and, where safe, +offer an automatic code fix. + +## Severity model + +| Severity | Meaning | +| -------- | --------------------------------------------------------------------------------------------- | +| Warning | Mechanical fix that is safe to apply across the whole solution in one go. | +| Info | Fix requires manual review (e.g. UA0001 telemetry plumbing, UA0011 async signature promotion). | + +## Rules + +| ID | Default | Replaces | +| ------ | -------- | ----------------------------------------------------------------------------------------| +| UA0001 | Info | `Utils.Trace` / `Utils.LogX` | +| UA0002 | Warning | Removed `Collection` wrappers | +| UA0003 | Warning | `x == null` on now-struct built-in types | +| UA0004 | Warning | `?.` on now-struct built-in types | +| UA0005 | Warning | `byte[]` where `ByteString` is now expected | +| UA0006 | Warning | `new Variant(object\|DateTime\|Guid\|byte[])` | +| UA0007 | Warning | `new NodeId(string)` / `new ExpandedNodeId(string)` | +| UA0008 | Warning | `Session.Call(..., params object[])` argument wrapping | +| UA0009 | Warning | `[DataContract]`/`[DataMember]` on configuration extensions | +| UA0010 | Warning | `using`/`Dispose` on `CertificateIdentifier`, `UserIdentity`, `IUserIdentityTokenHandler` | +| UA0011 | Info | Sync `IUserIdentityTokenHandler.Encrypt/Decrypt/Sign/Verify` | +| UA0012 | Warning | `CertificateFactory.*` static helpers | +| UA0014 | Warning | `DataValue.IsGood(dv)` static helper | +| UA0015 | Info | Sync / APM members on GDS / LDS clients | +| UA0018 | Info | `CertificateIdentifier.Certificate` getter | +| UA0019 | Warning | `new DataValue(StatusCode[, ts])` | +| UA0020 | Warning | `EncodeableFactory.GlobalFactory` / `Create()` | + +To suppress an individual rule for a single line: + +```csharp +#pragma warning disable UA0008 // Wrap Session.Call arguments with Variant.From +session.Call(objectId, methodId, "legacy"); +#pragma warning restore UA0008 +``` + +To set a project-wide severity, add to your `.editorconfig`: + +```ini +[*.cs] +dotnet_diagnostic.UA0001.severity = none # silence UA0001 entirely +dotnet_diagnostic.UA0008.severity = error # treat UA0008 as an error +``` diff --git a/Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props b/Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props new file mode 100644 index 0000000000..0e7f751e81 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props @@ -0,0 +1,10 @@ + + + diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj new file mode 100644 index 0000000000..80226864ea --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj @@ -0,0 +1,49 @@ + + + netstandard2.0 + false + true + $(AssemblyPrefix).CodeFixers + Opc.Ua.CodeFixers + OPC UA .NET Standard migration analyzers and code fixers (1.5.378 to 2.0). + + $(NoWarn);RS1007;RS1038 + + + + $(PackagePrefix).Opc.Ua.CodeFixers + true + false + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + $(TargetsForTfmSpecificContentInPackage);_AddAnalyzerToPackage + + diff --git a/Tools/Opc.Ua.CodeFixers/Properties/AssemblyInfo.cs b/Tools/Opc.Ua.CodeFixers/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2b9848014c --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +[assembly: CLSCompliant(false)] diff --git a/Tools/SourceGeneration.slnx b/Tools/SourceGeneration.slnx index 659a85f8dd..563b07a295 100644 --- a/Tools/SourceGeneration.slnx +++ b/Tools/SourceGeneration.slnx @@ -5,6 +5,10 @@ + + + + diff --git a/UA.slnx b/UA.slnx index 6301965ac9..04b510e641 100644 --- a/UA.slnx +++ b/UA.slnx @@ -121,6 +121,9 @@ + + + @@ -151,6 +154,7 @@ + From 270f8d8c73903c5291eba73316bb67ee61fc077b Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 10:25:51 +0200 Subject: [PATCH 2/8] [Tools] Add 1.5.378 to 1.6 compatibility shim assembly to CodeFixers package Introduces Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj which ships alongside the analyzer DLL in the OPCFoundation.NetStandard.Opc.Ua.CodeFixers NuGet. Consumer projects can drop the package in: 1.5.378-style code keeps compiling against 1.6 via the shim extension surface, and the matching UA00xx analyzer fires Info-level diagnostics so consumers can migrate off the shim incrementally. Shim contents organized under Shims// mirroring the source project layout: Shims/Types/, Shims/Core.Types/, Shims/Core/, Shims/Client/, Shims/Configuration/, Shims/Gds.Client.Common/. Marker [OpcUaShim(RuleId)] in Marker/OpcUaShimAttribute.cs lets the analyzer correlate shim calls back to the UA00xx migration rule. Phase 6.C moved obsolete extension surface from 8 *Obsolete*.cs files in Stack/Opc.Ua.Types, Stack/Opc.Ua.Core, Stack/Opc.Ua.Core.Types, Libraries/Opc.Ua.Client, Libraries/Opc.Ua.Configuration into the shim project. Phase 6.D added 6 NEW shims for genuinely-removed members (EncodeableFactory.GlobalFactory; CertificateIdentifier.Certificate (throwing); IUserIdentityTokenHandler.Encrypt/Decrypt/Sign/Verify sync wrappers; GlobalDiscoveryServerClient.RegisterApplication/UnregisterApplication; ServerPushConfigurationClient.ApplyChanges; LocalDiscoveryServerClient.BeginFindServers/EndFindServers APM). Phase 6.E extended the existing UA0008/UA0011/UA0015/UA0018/UA0020 analyzers to detect calls binding to shim extensions via [OpcUaShim] marker. Phase 6.F added Tests/Opc.Ua.CodeFixers.Shim.Tests with 11 runtime + meta tests (verifying every [OpcUaShim] member also carries [Obsolete] and a well-formed UA00xx id). Phase 6.G rewrote NugetREADME.md to cover both the analyzer rules and the shim. Three *Obsolete*.cs files left in-place (BLOCKED): Stack/Opc.Ua.Core/Types/Utils/UtilsObsolete.cs, Stack/Opc.Ua.Core.Types/Constants/Helpers.Obsolete.cs, Libraries/Opc.Ua.Client/CoreClientUtilsObsolete.cs. Reason: C# 14 extension(StaticClass) does NOT add real static members to a static class (per csharplang/proposals/extensions.md 131-138), so consumer call syntax like Utils.Trace(...) cannot be preserved by relocating the implementation to a separate assembly. Cross-project internal callers in Opc.Ua.Configuration/Opc.Ua.Server/ConsoleReferenceClient that called the moved IServerBase.Stop() and TraceConfiguration.ApplySettings() extensions were rewritten to inline the modern non-obsolete equivalents (Utils.SetTraceLog/SetTraceMask/SetTraceOutput, IServerBase.StopAsync). InternalsVisibleTo added on Opc.Ua.Core and Opc.Ua.Configuration so the shim can reach the internal helpers its moved factory members already depended on. Build: UA.slnx compiles 0 errors. Tests: Opc.Ua.CodeFixers.Tests 94/94 passing; Opc.Ua.CodeFixers.Shim.Tests 8 pass + 3 ignored placeholders (sealed GDS client types cannot be Moq-ed; tests deferred to integration). Tools/Opc.Ua.SourceGeneration.Core/Generators/ClientApiTemplates.cs adapted to emit modern async-with-GetAwaiter().GetResult() bodies instead of the now-moved obsolete sync/APM helpers. --- .../ConsoleReferenceClient/Program.cs | 12 +- .../ApplicationConfigurationBuilder.cs | 13 +- .../ApplicationInstance.cs | 12 +- .../Opc.Ua.Configuration.csproj | 1 + .../Opc.Ua.Server/Server/StandardServer.cs | 12 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 1 + .../Stack/Client/ChannelBaseObsolete.cs | 276 ---------------- .../Configuration/ApplicationConfiguration.cs | 10 +- .../CertificateIdentifierShimTests.cs | 62 ++++ .../EncodeableFactoryShimTests.cs | 57 ++++ .../GlobalDiscoveryServerClientShimTests.cs | 80 +++++ .../LocalDiscoveryServerClientShimTests.cs | 64 ++++ .../Opc.Ua.CodeFixers.Shim.Tests.csproj | 33 ++ .../OpcUaShimAttributeInventoryTests.cs | 124 +++++++ .../Properties/AssemblyInfo.cs | 32 ++ .../UserIdentityTokenHandlerShimTests.cs | 149 +++++++++ .../Analyzers/UA0008Tests.cs | 23 ++ .../Analyzers/UA0011Tests.cs | 47 +++ .../Analyzers/UA0015Tests.cs | 23 ++ .../Analyzers/UA0018Tests.cs | 24 ++ .../Analyzers/UA0020Tests.cs | 46 +++ .../Stubs/OpcUaStubs.cs | 73 +++++ .../Opc.Ua.Types.Tests.csproj | 4 + .../Marker/OpcUaShimAttribute.cs | 69 ++++ .../Opc.Ua.CodeFixers.Shim.csproj | 22 ++ .../Properties/AssemblyInfo.cs | 32 ++ .../Shims/Client/Session/Session.cs | 13 +- .../Subscription/Classic/Subscription.cs | 0 .../Configuration/ApplicationInstance.cs | 0 .../Certificates/CertificateIdentifier.cs | 63 ++++ .../Shims/Core/Stack/Client/ChannelBase.cs | 309 ++++++++++++++++++ .../Configuration/ApplicationConfiguration.cs | 0 .../Shims/Core/Stack/Server/ServerBase.cs | 0 .../Core/Stack/Transport/TransportChannel.cs | 0 .../Stack/Types/IUserIdentityTokenHandler.cs | 145 ++++++++ .../Core/Types/Encoders/EncodeableFactory.cs | 54 +++ .../GlobalDiscoveryServerClient.cs | 72 ++++ .../LocalDiscoveryServerClient.cs | 92 ++++++ .../ServerPushConfigurationClient.cs | 58 ++++ .../Shims/Types/BuiltIn/BuiltInType.cs | 0 Tools/Opc.Ua.CodeFixers.Shim/readme.md | 40 +++ .../UA0008SessionCallParamsObjectAnalyzer.cs | 26 +- .../UA0011TokenHandlerSyncToAsyncAnalyzer.cs | 38 ++- .../Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs | 22 +- ...ertificateIdentifierCertificateAnalyzer.cs | 34 +- .../UA0020EncodeableFactoryRenameAnalyzer.cs | 35 +- .../Helpers/SymbolExtensions.cs | 43 +++ Tools/Opc.Ua.CodeFixers/NugetREADME.md | 101 +++++- .../Generators/ClientApiTemplates.cs | 34 +- Tools/SourceGeneration.slnx | 2 + UA.slnx | 2 + 51 files changed, 2124 insertions(+), 360 deletions(-) create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/Properties/AssemblyInfo.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Properties/AssemblyInfo.cs rename Libraries/Opc.Ua.Client/Session/SessionObsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Session/Session.cs (98%) rename Libraries/Opc.Ua.Client/Subscription/Classic/SubscriptionObsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Subscription/Classic/Subscription.cs (100%) rename Libraries/Opc.Ua.Configuration/ApplicationInstance.Obsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Configuration/ApplicationInstance.cs (100%) create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Security/Certificates/CertificateIdentifier.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Client/ChannelBase.cs rename Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.Obsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs (100%) rename Stack/Opc.Ua.Core/Stack/Server/ServerBaseObsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Server/ServerBase.cs (100%) rename Stack/Opc.Ua.Core/Stack/Transport/TransportChannelObsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Transport/TransportChannel.cs (100%) create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Types/Encoders/EncodeableFactory.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs rename Stack/Opc.Ua.Types/BuiltIn/BuiltInType.Obsolete.cs => Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs (100%) create mode 100644 Tools/Opc.Ua.CodeFixers.Shim/readme.md diff --git a/Applications/ConsoleReferenceClient/Program.cs b/Applications/ConsoleReferenceClient/Program.cs index 1b2351b795..44991c3b31 100644 --- a/Applications/ConsoleReferenceClient/Program.cs +++ b/Applications/ConsoleReferenceClient/Program.cs @@ -295,7 +295,17 @@ public static Task Main(string[] args) ); config.TraceConfiguration.DeleteOnLoad = true; #pragma warning disable CS0618 // Type or member is obsolete - config.TraceConfiguration.ApplySettings(); + { + TraceConfiguration traceConfiguration = config.TraceConfiguration; + if (traceConfiguration.OutputFilePath != null) + { + Utils.SetTraceLog(traceConfiguration.OutputFilePath, traceConfiguration.DeleteOnLoad); + } + Utils.SetTraceMask(traceConfiguration.TraceMasks); + Utils.SetTraceOutput(traceConfiguration.TraceMasks == 0 + ? Utils.TraceOutput.Off + : Utils.TraceOutput.DebugAndFile); + } #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index 0d352e8044..d2a9a4a013 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -365,7 +365,18 @@ ApplicationType.Client or ApplicationType.ClientAndServer && } #pragma warning disable CS0618 // Type or member is obsolete - ApplicationConfiguration.TraceConfiguration?.ApplySettings(); + TraceConfiguration? traceConfiguration = ApplicationConfiguration.TraceConfiguration; + if (traceConfiguration != null) + { + if (traceConfiguration.OutputFilePath != null) + { + Utils.SetTraceLog(traceConfiguration.OutputFilePath, traceConfiguration.DeleteOnLoad); + } + Utils.SetTraceMask(traceConfiguration.TraceMasks); + Utils.SetTraceOutput(traceConfiguration.TraceMasks == 0 + ? Utils.TraceOutput.Off + : Utils.TraceOutput.DebugAndFile); + } #pragma warning restore CS0618 // Type or member is obsolete await ApplicationConfiguration.ValidateAsync(ApplicationInstance.ApplicationType, ct) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 17e65170a5..b1c3e960b2 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -171,7 +171,7 @@ public ValueTask StopAsync(CancellationToken ct = default) [Obsolete("Use StopAsync")] public void Stop() { - Server?.Stop(); + Server?.StopAsync().AsTask().GetAwaiter().GetResult(); } /// @@ -293,7 +293,15 @@ public IApplicationConfigurationBuilderTypes Build(string applicationUri, string // Trace off #pragma warning disable CS0618 // Type or member is obsolete - ApplicationConfiguration.TraceConfiguration.ApplySettings(); + TraceConfiguration traceConfiguration = ApplicationConfiguration.TraceConfiguration; + if (traceConfiguration.OutputFilePath != null) + { + Utils.SetTraceLog(traceConfiguration.OutputFilePath, traceConfiguration.DeleteOnLoad); + } + Utils.SetTraceMask(traceConfiguration.TraceMasks); + Utils.SetTraceOutput(traceConfiguration.TraceMasks == 0 + ? Utils.TraceOutput.Off + : Utils.TraceOutput.DebugAndFile); #pragma warning restore CS0618 // Type or member is obsolete return new ApplicationConfigurationBuilder(this); diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index 8cc14bf327..e41a672830 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -12,6 +12,7 @@ + $(PackageId).Debug diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index b668cef57e..0a68634c03 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -2759,7 +2759,17 @@ await CertificateManager.UpdateAsync( new TraceConfiguration(); #pragma warning disable CS0618 // Type or member is obsolete - currentConfiguration.TraceConfiguration.ApplySettings(); + { + TraceConfiguration traceConfiguration = currentConfiguration.TraceConfiguration; + if (traceConfiguration.OutputFilePath != null) + { + Utils.SetTraceLog(traceConfiguration.OutputFilePath, traceConfiguration.DeleteOnLoad); + } + Utils.SetTraceMask(traceConfiguration.TraceMasks); + Utils.SetTraceOutput(traceConfiguration.TraceMasks == 0 + ? Utils.TraceOutput.Off + : Utils.TraceOutput.DebugAndFile); + } #pragma warning restore CS0618 // Type or member is obsolete } catch (Exception e) diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index b2068996d5..8be7b7f832 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -17,6 +17,7 @@ + $(PackageId).Debug diff --git a/Stack/Opc.Ua.Core/Stack/Client/ChannelBaseObsolete.cs b/Stack/Opc.Ua.Core/Stack/Client/ChannelBaseObsolete.cs index 85004a7a7f..a073fa8395 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/ChannelBaseObsolete.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/ChannelBaseObsolete.cs @@ -29,7 +29,6 @@ using System; using Microsoft.Extensions.Logging; -using Opc.Ua.Security.Certificates; namespace Opc.Ua { @@ -45,183 +44,6 @@ public interface IChannelBase : IDisposable; [Obsolete("Use ITransportChannel instead.")] public interface ISessionChannel : IChannelBase; - /// - /// Obsolete, use transport channel instead - /// - [Obsolete("Use ITransportChannel instead.")] - public interface IDiscoveryChannel : IChannelBase; - - /// - /// Obsolete, use transport channel instead - /// - [Obsolete("Use ITransportChannel instead.")] - public interface IRegistrationChannel : IChannelBase; - - /// - /// Obsolete Session channel methods - /// - [Obsolete("Use UaChannelBase methods instead.")] - public static class SessionChannel - { - /// - /// Creates a new transport channel. - /// - [Obsolete("Use ClientChannelFactory.CreateChannelAsync method instead.")] - public static ITransportChannel Create( - ApplicationConfiguration configuration, - EndpointDescription description, - EndpointConfiguration endpointConfiguration, - Certificate clientCertificate, - IServiceMessageContext messageContext) - { - return ClientChannelManager.CreateUaBinaryChannelAsync( - configuration, - description, - endpointConfiguration, - clientCertificate, - null, - messageContext, - null).AsTask().GetAwaiter().GetResult(); - } - - /// - /// Creates a new transport channel. - /// - [Obsolete("Use ClientChannelFactory.CreateChannelAsync method instead.")] - public static ITransportChannel Create( - ApplicationConfiguration configuration, - EndpointDescription description, - EndpointConfiguration endpointConfiguration, - Certificate clientCertificate, - CertificateCollection clientCertificateChain, - IServiceMessageContext messageContext) - { - return ClientChannelManager.CreateUaBinaryChannelAsync( - configuration, - description, - endpointConfiguration, - clientCertificate, - clientCertificateChain, - messageContext, - null).AsTask().GetAwaiter().GetResult(); - } - - /// - /// Creates a new transport channel. - /// - [Obsolete("Use ClientChannelFactory.CreateChannelAsync method instead.")] - public static ITransportChannel Create( - ApplicationConfiguration configuration, - ITransportWaitingConnection connection, - EndpointDescription description, - EndpointConfiguration endpointConfiguration, - Certificate clientCertificate, - CertificateCollection clientCertificateChain, - IServiceMessageContext messageContext) - { - // create a UA binary channel. - return ClientChannelManager.CreateUaBinaryChannelAsync( - configuration, - connection, - description, - endpointConfiguration, - clientCertificate, - clientCertificateChain, - messageContext, - null).AsTask().GetAwaiter().GetResult(); - } - } - - /// - /// Obsolete discovery channel methods - /// - [Obsolete("Use DiscoveryClient.CreateAsync instead to create a discovery client.")] - public static class DiscoveryChannel - { - /// - /// Creates a new transport channel for discovery - /// - [Obsolete("Use DiscoveryClient.CreateAsync instead to create a discovery client.")] - public static ITransportChannel Create( - Uri discoveryUrl, - EndpointConfiguration endpointConfiguration, - IServiceMessageContext messageContext, - Certificate? clientCertificate = null) - { - return DiscoveryClient.CreateChannelAsync( - discoveryUrl, - endpointConfiguration, - messageContext, - clientCertificate).AsTask().GetAwaiter().GetResult(); - } - - /// - /// Creates a new transport channel for discovery - /// - [Obsolete("Use CreateAsync instead.")] - public static ITransportChannel Create( - ApplicationConfiguration configuration, - ITransportWaitingConnection connection, - EndpointConfiguration endpointConfiguration, - IServiceMessageContext messageContext, - Certificate? clientCertificate = null) - { - return DiscoveryClient.CreateChannelAsync( - configuration, - connection, - endpointConfiguration, - messageContext, - clientCertificate).AsTask().GetAwaiter().GetResult(); - } - - /// - /// Creates a new transport channel for discovery - /// - [Obsolete("Use CreateAsync instead.")] - public static ITransportChannel Create( - ApplicationConfiguration configuration, - Uri discoveryUrl, - EndpointConfiguration endpointConfiguration, - IServiceMessageContext messageContext, - Certificate? clientCertificate = null) - { - return DiscoveryClient.CreateChannelAsync( - configuration, - discoveryUrl, - endpointConfiguration, - messageContext, - clientCertificate).AsTask().GetAwaiter().GetResult(); - } - } - - /// - /// Obsolete Registration channel methods - /// - [Obsolete("Use RegistrationClient.CreateAsync instead to create a registrations client.")] - public static class RegistrationChannel - { - /// - /// Creates a new transport channel that supports registration - /// - [Obsolete("Use ClientChannelFactory.CreateChannelAsync instead.")] - public static ITransportChannel Create( - ApplicationConfiguration configuration, - EndpointDescription description, - EndpointConfiguration endpointConfiguration, - Certificate clientCertificate, - IServiceMessageContext messageContext) - { - return ClientChannelManager.CreateUaBinaryChannelAsync( - configuration, - description, - endpointConfiguration, - clientCertificate, - null, - messageContext, - null).AsTask().GetAwaiter().GetResult(); - } - } - /// /// A base class for UA channel objects used access UA interfaces /// @@ -290,62 +112,6 @@ protected virtual void Dispose(bool disposing) private readonly ITelemetryContext m_telemetry = null!; } - /// - /// Legacy api to be removed - /// - public static class ChannelBaseObsolete - { - /// - /// Schedules an outgoing request. - /// - /// - [Obsolete("WCF channels are not supported anymore.")] - public static void ScheduleOutgoingRequest( - this IChannelBase channel, - IChannelOutgoingRequest request) - { - throw new NotImplementedException(); - } - - /// - /// The client side implementation of the InvokeService service contract. - /// - /// - [Obsolete("WCF channels are not supported anymore.")] - public static InvokeServiceResponseMessage InvokeService( - this IChannelBase channel, - InvokeServiceMessage request) - { - throw new NotImplementedException(); - } - - /// - /// The operation contract for the InvokeService service. - /// - /// - [Obsolete("WCF channels are not supported anymore.")] - public static IAsyncResult BeginInvokeService( - this IChannelBase channel, - InvokeServiceMessage request, - AsyncCallback callback, - object asyncState) - { - throw new NotImplementedException(); - } - - /// - /// The method used to retrieve the results of a InvokeService service request. - /// - /// - [Obsolete("WCF channels are not supported anymore.")] - public static InvokeServiceResponseMessage EndInvokeService( - this IChannelBase channel, - IAsyncResult result) - { - throw new NotImplementedException(); - } - } - public partial class UaChannelBase : IChannelBase where TChannel : class, IChannelBase { @@ -430,46 +196,4 @@ public void OnOperationCompleted(IAsyncResult ar) } } } - - /// - /// An interface to an object that manages a request received from a client. - /// - [Obsolete("WCF channels are no more supported.")] - public interface IChannelOutgoingRequest - { - /// - /// Gets the request. - /// - /// The request. - IServiceRequest Request { get; } - - /// - /// Gets the handler that must be used to send the request. - /// - /// The send request handler. - ChannelSendRequestEventHandler Handler { get; } - - /// - /// Used to call the default synchronous handler. - /// - /// - /// This method may block the current thread so the caller must not call in the - /// thread that calls IServerBase.ScheduleIncomingRequest(). - /// This method always traps any exceptions and reports them to the client as a fault. - /// - void CallSynchronously(); - - /// - /// Used to indicate that the asynchronous operation has completed. - /// - /// The response. May be null if an error is provided. - /// An error to result as a fault. - void OperationCompleted(IServiceResponse? response, ServiceResult error); - } - - /// - /// A delegate used to dispatch outgoing service requests. - /// - [Obsolete("WCF channels are not supported anymore.")] - public delegate IServiceResponse ChannelSendRequestEventHandler(IServiceRequest request); } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs index 2ea5e7a354..f3b06e4ce1 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs @@ -553,7 +553,15 @@ public static async Task LoadAsync( if (applyTraceSettings && configuration.TraceConfiguration != null) { #pragma warning disable CS0618 // Type or member is obsolete - configuration.TraceConfiguration.ApplySettings(); + TraceConfiguration traceConfiguration = configuration.TraceConfiguration; + if (traceConfiguration.OutputFilePath != null) + { + Utils.SetTraceLog(traceConfiguration.OutputFilePath, traceConfiguration.DeleteOnLoad); + } + Utils.SetTraceMask(traceConfiguration.TraceMasks); + Utils.SetTraceOutput(traceConfiguration.TraceMasks == 0 + ? Utils.TraceOutput.Off + : Utils.TraceOutput.DebugAndFile); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs new file mode 100644 index 0000000000..835931f830 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs @@ -0,0 +1,62 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Runtime tests for . + /// + [TestFixture] + [Category("Shim")] + public class CertificateIdentifierShimTests + { + /// + /// Accessing the obsolete Certificate property must throw + /// with the migration-pointer + /// message that names the async resolver replacement. + /// + [Test] + public Task CertificateGetterThrowsNotSupportedAsync() + { + var id = new CertificateIdentifier(); + +#pragma warning disable CS0618 // CertificateIdentifier.Certificate is an intentional shim call. + NotSupportedException ex = Assert.Throws( + () => { _ = id.Certificate; })!; +#pragma warning restore CS0618 + + Assert.That(ex.Message, Does.Contain("CertificateIdentifierResolver.ResolveAsync")); + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs new file mode 100644 index 0000000000..2c8d0e3b0f --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs @@ -0,0 +1,57 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Runtime tests for . + /// + [TestFixture] + [Category("Shim")] + public class EncodeableFactoryShimTests + { + /// + /// The static GlobalFactory shim must return the same + /// instance as ServiceMessageContext.GlobalContext.Factory. + /// + [Test] + public Task GlobalFactoryReturnsServiceMessageContextGlobalFactoryAsync() + { +#pragma warning disable CS0618 // EncodeableFactory.GlobalFactory is an intentional shim call. + IEncodeableFactory shimFactory = EncodeableFactory.GlobalFactory; + Assert.That(shimFactory, Is.Not.Null); + Assert.That(shimFactory, Is.SameAs(ServiceMessageContext.GlobalContext.Factory)); +#pragma warning restore CS0618 + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs new file mode 100644 index 0000000000..e157ffac57 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs @@ -0,0 +1,80 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Threading.Tasks; +using NUnit.Framework; +using Opc.Ua.Gds.Client; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Runtime tests for . + /// + /// + /// is sealed and has + /// non-virtual RegisterApplicationAsync / UnregisterApplicationAsync + /// methods, so neither Moq nor a hand-rolled subclass can intercept the + /// shim's forwarded call. Exercising the shim end-to-end requires a + /// live GDS endpoint (full server + secure channel bootstrap), which + /// belongs to the integration test suite (Opc.Ua.Gds.Tests), not + /// a unit-level runtime check of the shim wiring. + /// + [TestFixture] + [Category("Shim")] + public class GlobalDiscoveryServerClientShimTests + { + /// + /// Placeholder for the shim invocation test. Requires a full GDS + /// server bootstrap to exercise. + /// + [Test] + [Ignore("Requires GDS server bootstrap: GlobalDiscoveryServerClient " + + "is sealed and RegisterApplicationAsync is non-virtual, so the " + + "shim cannot be exercised via Moq. Integration coverage lives " + + "in Opc.Ua.Gds.Tests.")] + public Task RegisterApplicationCallsRegisterApplicationAsyncAsync() + { + return Task.CompletedTask; + } + + /// + /// Placeholder for the unregister shim invocation test. Requires a + /// full GDS server bootstrap to exercise. + /// + [Test] + [Ignore("Requires GDS server bootstrap: GlobalDiscoveryServerClient " + + "is sealed and UnregisterApplicationAsync is non-virtual, so the " + + "shim cannot be exercised via Moq. Integration coverage lives " + + "in Opc.Ua.Gds.Tests.")] + public Task UnregisterApplicationCallsUnregisterApplicationAsyncAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs new file mode 100644 index 0000000000..b0b42a7f31 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs @@ -0,0 +1,64 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Threading.Tasks; +using NUnit.Framework; +using Opc.Ua.Gds.Client; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Runtime tests for . + /// + /// + /// is not + /// virtual, so the APM BeginFindServers/EndFindServers + /// adapter cannot be exercised against a Moq stand-in. End-to-end + /// validation requires a live LDS endpoint and lives in the discovery + /// integration suite. + /// + [TestFixture] + [Category("Shim")] + public class LocalDiscoveryServerClientShimTests + { + /// + /// Placeholder for the APM adapter test. Requires a live LDS + /// endpoint to drive FindServersAsync. + /// + [Test] + [Ignore("Requires Local Discovery Server endpoint: " + + "LocalDiscoveryServerClient.FindServersAsync is non-virtual, so " + + "the APM Begin/End shim cannot be intercepted via Moq. " + + "Integration coverage lives in Opc.Ua.Lds.Tests.")] + public Task BeginEndFindServersDeliverAsyncResultAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj b/Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj new file mode 100644 index 0000000000..96f4a15ea6 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj @@ -0,0 +1,33 @@ + + + $(TestsTargetFrameworks) + Opc.Ua.CodeFixers.Shim.Tests + true + true + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs new file mode 100644 index 0000000000..1635ff5309 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs @@ -0,0 +1,124 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Meta-tests that scan every -marked + /// member in the shim assembly. These guard the contract that the + /// analyzer relies on: each shim member must carry both an + /// and a valid UA00xx rule id. + /// + [TestFixture] + [Category("Shim")] + public partial class OpcUaShimAttributeInventoryTests + { + [GeneratedRegex(@"^UA\d{4}$", RegexOptions.CultureInvariant)] + private static partial Regex RuleIdRegex(); + + private static IEnumerable ShimMembers() + { + Assembly shimAssembly = typeof(OpcUaShimAttribute).Assembly; + foreach (Type type in shimAssembly.GetTypes()) + { + foreach (MemberInfo member in type.GetMembers( + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Static | + BindingFlags.Instance | + BindingFlags.DeclaredOnly)) + { + if (member.GetCustomAttribute() != null) + { + yield return member; + } + } + } + } + + /// + /// Sanity check: the shim assembly exposes at least one + /// [OpcUaShim]-attributed member. + /// + [Test] + public Task ShimAssemblyContainsAttributedMembersAsync() + { + MemberInfo[] members = ShimMembers().ToArray(); + Assert.That(members, Is.Not.Empty, + "Expected at least one [OpcUaShim] member in the shim assembly."); + return Task.CompletedTask; + } + + /// + /// Every [OpcUaShim] member must also carry + /// so callers see the migration + /// guidance at the call site. + /// + [Test] + public Task EveryShimMemberIsAlsoObsoleteAsync() + { + var missing = ShimMembers() + .Where(m => m.GetCustomAttribute() == null) + .Select(m => $"{m.DeclaringType?.FullName}.{m.Name}") + .ToArray(); + + Assert.That(missing, Is.Empty, + "These shim members are missing [Obsolete]: " + + string.Join(", ", missing)); + return Task.CompletedTask; + } + + /// + /// Rule ids must match the UA00xx convention so the analyzer + /// can correlate the shim with its diagnostic descriptor. + /// + [Test] + public Task EveryShimRuleIdMatchesUa00xxConventionAsync() + { + var malformed = ShimMembers() + .Select(m => (Member: m, Id: m.GetCustomAttribute()!.RuleId)) + .Where(t => !RuleIdRegex().IsMatch(t.Id)) + .Select(t => $"{t.Member.DeclaringType?.FullName}.{t.Member.Name}={t.Id}") + .ToArray(); + + Assert.That(malformed, Is.Empty, + "These shim members have non-UA00xx rule ids: " + + string.Join(", ", malformed)); + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/Properties/AssemblyInfo.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2b9848014c --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +[assembly: CLSCompliant(false)] diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs new file mode 100644 index 0000000000..6523687249 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs @@ -0,0 +1,149 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Threading; +using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using Opc.Ua.Security.Certificates; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Runtime tests for . + /// Verifies the synchronous shim methods forward to the async + /// counterparts on . + /// + [TestFixture] + [Category("Shim")] + public class UserIdentityTokenHandlerShimTests + { + /// + /// The sync Encrypt shim must invoke + /// on the + /// underlying handler. + /// + [Test] + public Task EncryptCallsEncryptAsyncAsync() + { + var mock = new Mock(MockBehavior.Strict); + mock.Setup(h => h.EncryptAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(new ValueTask()); + + byte[] nonce = [1, 2, 3, 4]; + const string policy = "http://opcfoundation.org/UA/SecurityPolicy#None"; +#pragma warning disable CS0618 // Sync Encrypt is an intentional shim call; GlobalContext is a stable test stand-in. + IServiceMessageContext ctx = ServiceMessageContext.GlobalContext; + + mock.Object.Encrypt(null!, nonce, policy, ctx); +#pragma warning restore CS0618 + + mock.Verify(h => h.EncryptAsync( + null!, + nonce, + policy, + ctx, + null, + null, + null, + false, + default), + Times.Once); + return Task.CompletedTask; + } + + /// + /// The sync Sign shim must return the same + /// the async path produced. + /// + [Test] + public Task SignReturnsAsyncResultAsync() + { + var expected = new SignatureData + { + Algorithm = "http://opcfoundation.org/UA/test-sign", + Signature = [9, 8, 7, 6] + }; + var mock = new Mock(MockBehavior.Strict); + mock.Setup(h => h.SignAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(new ValueTask(expected)); + + byte[] payload = [1, 2, 3]; + const string policy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + +#pragma warning disable CS0618 // Sync Sign is an intentional shim call. + SignatureData actual = mock.Object.Sign(payload, policy); +#pragma warning restore CS0618 + + Assert.That(actual, Is.SameAs(expected)); + mock.Verify(h => h.SignAsync(payload, policy, default), Times.Once); + return Task.CompletedTask; + } + + /// + /// The sync Verify shim must propagate the boolean result + /// from the async path. + /// + [Test] + public Task VerifyReturnsAsyncResultAsync() + { + var mock = new Mock(MockBehavior.Strict); + mock.Setup(h => h.VerifyAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(new ValueTask(true)); + + byte[] data = [1, 2, 3]; + var sig = new SignatureData { Algorithm = "alg", Signature = [9] }; + const string policy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + +#pragma warning disable CS0618 // Sync Verify is an intentional shim call. + bool ok = mock.Object.Verify(data, sig, policy); +#pragma warning restore CS0618 + + Assert.That(ok, Is.True); + mock.Verify(h => h.VerifyAsync(data, sig, policy, default), Times.Once); + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs index 4ce5fc1dea..dfe4482863 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs @@ -163,5 +163,28 @@ static void M(Session session, NodeId o, NodeId m) Assert.That(fixedSource, Is.EqualTo(expected)); } + + [Test] + public async Task ReportsDiagnosticOnShimExtensionCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(Session session, NodeId objId, NodeId methodId) + { + #pragma warning disable CS0618 + SessionShim.Call(session, objId, methodId, 1, "two"); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0008SessionCallParamsObjectAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0008"), Is.True, + "Expected UA0008 to fire on a call resolving to a [OpcUaShim(\"UA0008\")] member."); + } } } diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs index 3faee8f4df..8ba2840269 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs @@ -139,5 +139,52 @@ class C Assert.That(diags.Any(d => d.Id == "UA0011"), Is.False, "Encrypt on an unrelated type must not trigger UA0011."); } + + [Test] + public async Task ReportsDiagnosticOnShimExtensionCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static byte[] M(IUserIdentityTokenHandler handler, byte[] bytes) + { + #pragma warning disable CS0618 + return UserIdentityTokenHandlerShim.Encrypt(handler, bytes); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0011TokenHandlerSyncToAsyncAnalyzer(), source); + + Diagnostic? ua0011 = diags.SingleOrDefault(d => d.Id == "UA0011"); + Assert.That(ua0011, Is.Not.Null, + "Expected UA0011 to fire on shim extension call carrying [OpcUaShim(\"UA0011\")]."); + } + + [Test] + public async Task DoesNotReportOnShimWithDifferentRuleIdAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static byte[] M(IUserIdentityTokenHandler handler, byte[] bytes) + { + #pragma warning disable CS0618 + return UserIdentityTokenHandlerShim.EncryptUnrelated(handler, bytes); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0011TokenHandlerSyncToAsyncAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0011"), Is.False, + "A shim attribute with a different RuleId must not trigger UA0011."); + } } } diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs index 5e4bcc11b4..88d132c979 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs @@ -164,5 +164,28 @@ class C Assert.That(diags.Any(d => d.Id == "UA0015"), Is.False, "RegisterApplication on an unrelated type must not trigger UA0015."); } + + [Test] + public async Task ReportsDiagnosticOnShimExtensionCallAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(GlobalDiscoveryServerClient client) + { + #pragma warning disable CS0618 + client.RegisterApplicationLegacy("urn:foo"); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0015GdsSyncToAsyncAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0015"), Is.True, + "Expected UA0015 to fire on a call resolving to a [OpcUaShim(\"UA0015\")] extension."); + } } } diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs index 23050120a8..e2df1bd489 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs @@ -136,5 +136,29 @@ class C Assert.That(diags.Any(d => d.Id == "UA0018"), Is.False, "Certificate property on an unrelated type must not trigger UA0018."); } + + [Test] + public async Task ReportsDiagnosticOnShimPropertyAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object? M(CertificateIdentifierShimHost host) + { + #pragma warning disable CS0618 + return host.Certificate; + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync( + new UA0018CertificateIdentifierCertificateAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0018"), Is.True, + "Expected UA0018 to fire on a property carrying [OpcUaShim(\"UA0018\")]."); + } } } diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs index 0f9fc07782..11d24856b3 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs @@ -173,6 +173,52 @@ class C "Form A (GlobalFactory) must not register any code-fix actions."); } + [Test] + public async Task ReportsDiagnosticOnShimGlobalFactoryAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M() + { + #pragma warning disable CS0618 + return EncodeableFactoryShim.GlobalFactory; + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0020EncodeableFactoryRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0020"), Is.True, + "Expected UA0020 to fire on a property carrying [OpcUaShim(\"UA0020\")]."); + } + + [Test] + public async Task ReportsDiagnosticOnShimCreateInvocationAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static EncodeableFactory M(EncodeableFactory factory) + { + #pragma warning disable CS0618 + return EncodeableFactoryShim.Create(factory); + #pragma warning restore CS0618 + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0020EncodeableFactoryRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0020"), Is.True, + "Expected UA0020 to fire on a Create invocation carrying [OpcUaShim(\"UA0020\")]."); + } + private static async Task CollectFixActionsAsync( Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs index 6022277967..d96f9cd9d3 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs @@ -422,6 +422,79 @@ public class ServiceMessageContext { public EncodeableFactory Factory { get; } = new EncodeableFactory(); } + + // ─── OpcUaShim marker attribute and shim wrappers used by analyzer tests ─── + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public sealed class OpcUaShimAttribute : Attribute + { + public string RuleId { get; } + public OpcUaShimAttribute(string ruleId) { RuleId = ruleId; } + } + + // Shim for UA0008: Call/CallAsync with raw object args on ISession-like receiver. + public static class SessionShim + { + [Obsolete("Use ISession.Call(params Variant[]) instead.")] + [OpcUaShim("UA0008")] + public static object Call(this ISession session, NodeId objectId, NodeId methodId, params object[] args) + => null!; + + [Obsolete("Use ISession.CallAsync(params Variant[]) instead.")] + [OpcUaShim("UA0008")] + public static Task CallAsync( + this ISession session, NodeId objectId, NodeId methodId, CancellationToken ct, params object[] args) + => Task.FromResult(null!); + } + + // Shim for UA0011: synchronous Encrypt/Decrypt/Sign/Verify on token handler. + public static class UserIdentityTokenHandlerShim + { + [Obsolete("Use EncryptAsync instead.")] + [OpcUaShim("UA0011")] + public static byte[] Encrypt(this IUserIdentityTokenHandler handler, byte[] data) => null!; + + [Obsolete("Use DecryptAsync instead.")] + [OpcUaShim("UA0011")] + public static byte[] Decrypt(this IUserIdentityTokenHandler handler, byte[] data) => null!; + + // Same shape, different RuleId — used to verify rule-id filtering. + [Obsolete("Different-rule shim used for negative test.")] + [OpcUaShim("UA9999")] + public static byte[] EncryptUnrelated(this IUserIdentityTokenHandler handler, byte[] data) => null!; + } + + // Shim for UA0015: synchronous GDS/LDS members. + public static class GdsClientShim + { + [Obsolete("Use RegisterApplicationAsync instead.")] + [OpcUaShim("UA0015")] + public static void RegisterApplicationLegacy(this GlobalDiscoveryServerClient client, string applicationUri) { } + + [Obsolete("Use FindServersAsync instead.")] + [OpcUaShim("UA0015")] + public static string[] FindServersLegacy(this LocalDiscoveryServerClient client, string endpoint) + => System.Array.Empty(); + } + + // Shim for UA0018: CertificateIdentifier.Certificate getter relocated to shim. + public class CertificateIdentifierShimHost + { + [Obsolete("Use CertificateIdentifierResolver.ResolveAsync instead.")] + [OpcUaShim("UA0018")] + public object? Certificate => null; + } + + // Shim for UA0020: EncodeableFactory.GlobalFactory / Create relocated to shim. + public static class EncodeableFactoryShim + { + [Obsolete("Use ServiceMessageContext.Factory instead.")] + [OpcUaShim("UA0020")] + public static EncodeableFactory GlobalFactory => new EncodeableFactory(); + + [Obsolete("Use Fork() instead.")] + [OpcUaShim("UA0020")] + public static EncodeableFactory Create(this EncodeableFactory factory) => new EncodeableFactory(); + } } namespace Microsoft.Extensions.Logging diff --git a/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj b/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj index 42dc9df060..ba0f946b92 100644 --- a/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj +++ b/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj @@ -41,6 +41,10 @@ + + diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs b/Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs new file mode 100644 index 0000000000..52659f8eca --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs @@ -0,0 +1,69 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +namespace Opc.Ua +{ + /// + /// Marks an API member as a 1.5.378 → 1.6 migration shim. Used by the + /// Opc.Ua.CodeFixers analyzer to map calls that bind to a shim + /// extension back to the underlying UA00xx diagnostic rule, so + /// consumers get the same migration guidance whether they call the + /// shim directly or the legacy API in source. + /// + /// + /// Apply this in addition to . The shim + /// keeps the call compilable; the analyzer fires an Info + /// diagnostic that points at the matching migration-guide section. + /// + [AttributeUsage( + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Constructor | + AttributeTargets.Class, + AllowMultiple = false, + Inherited = false)] + public sealed class OpcUaShimAttribute : Attribute + { + /// + /// The diagnostic rule identifier the shim corresponds to, + /// e.g. "UA0008". + /// + public string RuleId { get; } + + /// + /// Creates a new . + /// + public OpcUaShimAttribute(string ruleId) + { + RuleId = ruleId ?? throw new ArgumentNullException(nameof(ruleId)); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj b/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj new file mode 100644 index 0000000000..2e13dcdace --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj @@ -0,0 +1,22 @@ + + + $(AssemblyPrefix).CodeFixers.Shim + $(LibTargetFrameworks) + Opc.Ua + OPC UA 1.5.378 → 1.6 compatibility shim. Provides the obsolete extension-method surface that 1.6 removed, marked [Obsolete] so the matching Opc.Ua.CodeFixers analyzer rules guide consumers off it. Ships in the OPCFoundation.NetStandard.Opc.Ua.CodeFixers NuGet alongside the analyzer DLL. + false + true + enable + + $(NoWarn);CS0618 + + + + + + + + + + + diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Properties/AssemblyInfo.cs b/Tools/Opc.Ua.CodeFixers.Shim/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2b9848014c --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +[assembly: CLSCompliant(false)] diff --git a/Libraries/Opc.Ua.Client/Session/SessionObsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Session/Session.cs similarity index 98% rename from Libraries/Opc.Ua.Client/Session/SessionObsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Session/Session.cs index f926cd6131..bea052bfe4 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionObsolete.cs +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Session/Session.cs @@ -792,10 +792,17 @@ public static bool ResendData( return (false, Array.Empty()); } } - } - public partial class Session - { + // ---------------------------------------------------------------- + // The following static factory helpers were originally declared as + // `public partial class Session` members on the Session type in + // Libraries/Opc.Ua.Client. Cross-assembly partial classes are not + // supported, so when migrated into this shim assembly they are + // hosted as static methods on SessionObsolete. Callers that + // previously invoked `Session.Create(...)` / `Session.Recreate(...)` + // must now call `SessionObsolete.Create(...)` / `SessionObsolete.Recreate(...)`. + // ---------------------------------------------------------------- + /// /// Creates a new communication session with a server by invoking the CreateSession service /// diff --git a/Libraries/Opc.Ua.Client/Subscription/Classic/SubscriptionObsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Subscription/Classic/Subscription.cs similarity index 100% rename from Libraries/Opc.Ua.Client/Subscription/Classic/SubscriptionObsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Subscription/Classic/Subscription.cs diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.Obsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Configuration/ApplicationInstance.cs similarity index 100% rename from Libraries/Opc.Ua.Configuration/ApplicationInstance.Obsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Configuration/ApplicationInstance.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Security/Certificates/CertificateIdentifier.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Security/Certificates/CertificateIdentifier.cs new file mode 100644 index 0000000000..1b29b52c4d --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Security/Certificates/CertificateIdentifier.cs @@ -0,0 +1,63 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Security.Cryptography.X509Certificates; + +namespace Opc.Ua +{ + /// + /// Migration shim — restores the CertificateIdentifier.Certificate + /// instance property that was removed in 1.6 because resolution now + /// requires a registry/telemetry context. Accessing this shim member + /// throws at runtime; the + /// [Obsolete] attribute and matching analyzer rule guide + /// consumers to the async resolver replacement. + /// + public static class CertificateIdentifierShim + { + extension(CertificateIdentifier id) + { + /// + /// Returns the resolved X.509 certificate. Removed in 1.6 because + /// resolution is now async and requires a registry. + /// + [Obsolete("CertificateIdentifier.Certificate was removed in 1.6 because " + + "resolution requires a registry/telemetry context. " + + "Use CertificateIdentifierResolver.ResolveAsync(id, registry, needPrivateKey, applicationUri, telemetry, ct). " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0018")] + [OpcUaShim("UA0018")] + public X509Certificate2? Certificate + => throw new NotSupportedException( + "CertificateIdentifier.Certificate was removed in 1.6 because resolution " + + "requires a registry/telemetry context. Use " + + "CertificateIdentifierResolver.ResolveAsync(id, registry, needPrivateKey, applicationUri, telemetry, ct)."); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Client/ChannelBase.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Client/ChannelBase.cs new file mode 100644 index 0000000000..e1bd1263ad --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Client/ChannelBase.cs @@ -0,0 +1,309 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using Opc.Ua.Security.Certificates; + +namespace Opc.Ua +{ + /// + /// Obsolete, use transport channel instead + /// + [Obsolete("Use ITransportChannel instead.")] + public interface IDiscoveryChannel : IChannelBase; + + /// + /// Obsolete, use transport channel instead + /// + [Obsolete("Use ITransportChannel instead.")] + public interface IRegistrationChannel : IChannelBase; + + /// + /// Obsolete Session channel methods + /// + [Obsolete("Use UaChannelBase methods instead.")] + public static class SessionChannel + { + /// + /// Creates a new transport channel. + /// + [Obsolete("Use ClientChannelFactory.CreateChannelAsync method instead.")] + public static ITransportChannel Create( + ApplicationConfiguration configuration, + EndpointDescription description, + EndpointConfiguration endpointConfiguration, + Certificate clientCertificate, + IServiceMessageContext messageContext) + { + return ClientChannelManager.CreateUaBinaryChannelAsync( + configuration, + description, + endpointConfiguration, + clientCertificate, + null, + messageContext, + null).AsTask().GetAwaiter().GetResult(); + } + + /// + /// Creates a new transport channel. + /// + [Obsolete("Use ClientChannelFactory.CreateChannelAsync method instead.")] + public static ITransportChannel Create( + ApplicationConfiguration configuration, + EndpointDescription description, + EndpointConfiguration endpointConfiguration, + Certificate clientCertificate, + CertificateCollection clientCertificateChain, + IServiceMessageContext messageContext) + { + return ClientChannelManager.CreateUaBinaryChannelAsync( + configuration, + description, + endpointConfiguration, + clientCertificate, + clientCertificateChain, + messageContext, + null).AsTask().GetAwaiter().GetResult(); + } + + /// + /// Creates a new transport channel. + /// + [Obsolete("Use ClientChannelFactory.CreateChannelAsync method instead.")] + public static ITransportChannel Create( + ApplicationConfiguration configuration, + ITransportWaitingConnection connection, + EndpointDescription description, + EndpointConfiguration endpointConfiguration, + Certificate clientCertificate, + CertificateCollection clientCertificateChain, + IServiceMessageContext messageContext) + { + // create a UA binary channel. + return ClientChannelManager.CreateUaBinaryChannelAsync( + configuration, + connection, + description, + endpointConfiguration, + clientCertificate, + clientCertificateChain, + messageContext, + null).AsTask().GetAwaiter().GetResult(); + } + } + + /// + /// Obsolete discovery channel methods + /// + [Obsolete("Use DiscoveryClient.CreateAsync instead to create a discovery client.")] + public static class DiscoveryChannel + { + /// + /// Creates a new transport channel for discovery + /// + [Obsolete("Use DiscoveryClient.CreateAsync instead to create a discovery client.")] + public static ITransportChannel Create( + Uri discoveryUrl, + EndpointConfiguration endpointConfiguration, + IServiceMessageContext messageContext, + Certificate? clientCertificate = null) + { + return DiscoveryClient.CreateChannelAsync( + discoveryUrl, + endpointConfiguration, + messageContext, + clientCertificate).AsTask().GetAwaiter().GetResult(); + } + + /// + /// Creates a new transport channel for discovery + /// + [Obsolete("Use CreateAsync instead.")] + public static ITransportChannel Create( + ApplicationConfiguration configuration, + ITransportWaitingConnection connection, + EndpointConfiguration endpointConfiguration, + IServiceMessageContext messageContext, + Certificate? clientCertificate = null) + { + return DiscoveryClient.CreateChannelAsync( + configuration, + connection, + endpointConfiguration, + messageContext, + clientCertificate).AsTask().GetAwaiter().GetResult(); + } + + /// + /// Creates a new transport channel for discovery + /// + [Obsolete("Use CreateAsync instead.")] + public static ITransportChannel Create( + ApplicationConfiguration configuration, + Uri discoveryUrl, + EndpointConfiguration endpointConfiguration, + IServiceMessageContext messageContext, + Certificate? clientCertificate = null) + { + return DiscoveryClient.CreateChannelAsync( + configuration, + discoveryUrl, + endpointConfiguration, + messageContext, + clientCertificate).AsTask().GetAwaiter().GetResult(); + } + } + + /// + /// Obsolete Registration channel methods + /// + [Obsolete("Use RegistrationClient.CreateAsync instead to create a registrations client.")] + public static class RegistrationChannel + { + /// + /// Creates a new transport channel that supports registration + /// + [Obsolete("Use ClientChannelFactory.CreateChannelAsync instead.")] + public static ITransportChannel Create( + ApplicationConfiguration configuration, + EndpointDescription description, + EndpointConfiguration endpointConfiguration, + Certificate clientCertificate, + IServiceMessageContext messageContext) + { + return ClientChannelManager.CreateUaBinaryChannelAsync( + configuration, + description, + endpointConfiguration, + clientCertificate, + null, + messageContext, + null).AsTask().GetAwaiter().GetResult(); + } + } + + /// + /// Legacy api to be removed + /// + public static class ChannelBaseObsolete + { + /// + /// Schedules an outgoing request. + /// + /// + [Obsolete("WCF channels are not supported anymore.")] + public static void ScheduleOutgoingRequest( + this IChannelBase channel, + IChannelOutgoingRequest request) + { + throw new NotImplementedException(); + } + + /// + /// The client side implementation of the InvokeService service contract. + /// + /// + [Obsolete("WCF channels are not supported anymore.")] + public static InvokeServiceResponseMessage InvokeService( + this IChannelBase channel, + InvokeServiceMessage request) + { + throw new NotImplementedException(); + } + + /// + /// The operation contract for the InvokeService service. + /// + /// + [Obsolete("WCF channels are not supported anymore.")] + public static IAsyncResult BeginInvokeService( + this IChannelBase channel, + InvokeServiceMessage request, + AsyncCallback callback, + object asyncState) + { + throw new NotImplementedException(); + } + + /// + /// The method used to retrieve the results of a InvokeService service request. + /// + /// + [Obsolete("WCF channels are not supported anymore.")] + public static InvokeServiceResponseMessage EndInvokeService( + this IChannelBase channel, + IAsyncResult result) + { + throw new NotImplementedException(); + } + } + + /// + /// An interface to an object that manages a request received from a client. + /// + [Obsolete("WCF channels are no more supported.")] + public interface IChannelOutgoingRequest + { + /// + /// Gets the request. + /// + /// The request. + IServiceRequest Request { get; } + + /// + /// Gets the handler that must be used to send the request. + /// + /// The send request handler. + ChannelSendRequestEventHandler Handler { get; } + + /// + /// Used to call the default synchronous handler. + /// + /// + /// This method may block the current thread so the caller must not call in the + /// thread that calls IServerBase.ScheduleIncomingRequest(). + /// This method always traps any exceptions and reports them to the client as a fault. + /// + void CallSynchronously(); + + /// + /// Used to indicate that the asynchronous operation has completed. + /// + /// The response. May be null if an error is provided. + /// An error to result as a fault. + void OperationCompleted(IServiceResponse? response, ServiceResult error); + } + + /// + /// A delegate used to dispatch outgoing service requests. + /// + [Obsolete("WCF channels are not supported anymore.")] + public delegate IServiceResponse ChannelSendRequestEventHandler(IServiceRequest request); +} diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.Obsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs similarity index 100% rename from Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.Obsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBaseObsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Server/ServerBase.cs similarity index 100% rename from Stack/Opc.Ua.Core/Stack/Server/ServerBaseObsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Server/ServerBase.cs diff --git a/Stack/Opc.Ua.Core/Stack/Transport/TransportChannelObsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Transport/TransportChannel.cs similarity index 100% rename from Stack/Opc.Ua.Core/Stack/Transport/TransportChannelObsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Transport/TransportChannel.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs new file mode 100644 index 0000000000..acd8b02c43 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs @@ -0,0 +1,145 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using Opc.Ua.Security.Certificates; + +namespace Opc.Ua +{ + /// + /// Migration shim — restores synchronous Encrypt, Decrypt, + /// Sign and Verify operations on the user identity token + /// surface. In 1.6 the legacy synchronous methods on + /// UserIdentityToken were removed in favour of asynchronous + /// counterparts on . These shims + /// block the calling thread on the async path so existing call sites + /// keep compiling; consumers should migrate to the *Async + /// variants. + /// + public static class UserIdentityTokenHandlerShim + { + extension(IUserIdentityTokenHandler handler) + { + /// + /// Encrypts the token. Synchronous shim for + /// . + /// + [Obsolete("Synchronous Encrypt was removed in 1.6. Use EncryptAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0011")] + [OpcUaShim("UA0011")] + public void Encrypt( + Certificate receiverCertificate, + byte[] receiverNonce, + string securityPolicyUri, + IServiceMessageContext context, + Nonce? receiverEphemeralKey = null, + Certificate? senderCertificate = null, + CertificateCollection? senderIssuerCertificates = null, + bool doNotEncodeSenderCertificate = false) + { + handler.EncryptAsync( + receiverCertificate, + receiverNonce, + securityPolicyUri, + context, + receiverEphemeralKey, + senderCertificate, + senderIssuerCertificates, + doNotEncodeSenderCertificate) + .AsTask() + .GetAwaiter() + .GetResult(); + } + + /// + /// Decrypts the token. Synchronous shim for + /// . + /// + [Obsolete("Synchronous Decrypt was removed in 1.6. Use DecryptAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0011")] + [OpcUaShim("UA0011")] + public void Decrypt( + Certificate certificate, + Nonce receiverNonce, + string securityPolicyUri, + IServiceMessageContext context, + Nonce? ephemeralKey = null, + Certificate? senderCertificate = null, + CertificateCollection? senderIssuerCertificates = null, + ICertificateValidatorEx? validator = null) + { + handler.DecryptAsync( + certificate, + receiverNonce, + securityPolicyUri, + context, + ephemeralKey, + senderCertificate, + senderIssuerCertificates, + validator) + .AsTask() + .GetAwaiter() + .GetResult(); + } + + /// + /// Creates a signature with the token. Synchronous shim for + /// . + /// + [Obsolete("Synchronous Sign was removed in 1.6. Use SignAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0011")] + [OpcUaShim("UA0011")] + public SignatureData Sign(byte[] dataToSign, string securityPolicyUri) + { + return handler.SignAsync(dataToSign, securityPolicyUri) + .AsTask() + .GetAwaiter() + .GetResult(); + } + + /// + /// Verifies a signature created with the token. Synchronous shim + /// for . + /// + [Obsolete("Synchronous Verify was removed in 1.6. Use VerifyAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0011")] + [OpcUaShim("UA0011")] + public bool Verify( + byte[] dataToVerify, + SignatureData signatureData, + string securityPolicyUri) + { + return handler.VerifyAsync(dataToVerify, signatureData, securityPolicyUri) + .AsTask() + .GetAwaiter() + .GetResult(); + } + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Types/Encoders/EncodeableFactory.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Types/Encoders/EncodeableFactory.cs new file mode 100644 index 0000000000..774ca1cd79 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Types/Encoders/EncodeableFactory.cs @@ -0,0 +1,54 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +namespace Opc.Ua +{ + /// + /// Migration shim — restores EncodeableFactory.GlobalFactory + /// which was removed in 1.6. Callers should migrate to + /// 's + /// . + /// + public static class EncodeableFactoryShim + { + extension(EncodeableFactory) + { + /// + /// The global encodeable factory shared across the process. + /// + [Obsolete("EncodeableFactory.GlobalFactory was removed in 1.6. " + + "Use ServiceMessageContext.GlobalContext.Factory instead. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0020")] + [OpcUaShim("UA0020")] + public static IEncodeableFactory GlobalFactory => ServiceMessageContext.GlobalContext.Factory; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs new file mode 100644 index 0000000000..fa5061852b --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs @@ -0,0 +1,72 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +namespace Opc.Ua.Gds.Client +{ + /// + /// Migration shim — restores the synchronous RegisterApplication + /// and UnregisterApplication methods on + /// that were removed in 1.6. + /// + public static class GlobalDiscoveryServerClientShim + { + extension(GlobalDiscoveryServerClient client) + { + /// + /// Registers the application synchronously. + /// + [Obsolete("Synchronous RegisterApplication was removed in 1.6. Use RegisterApplicationAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0015")] + [OpcUaShim("UA0015")] + public NodeId RegisterApplication(ApplicationRecordDataType application) + { + return client.RegisterApplicationAsync(application) + .AsTask() + .GetAwaiter() + .GetResult(); + } + + /// + /// Unregisters the application synchronously. + /// + [Obsolete("Synchronous UnregisterApplication was removed in 1.6. Use UnregisterApplicationAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0015")] + [OpcUaShim("UA0015")] + public void UnregisterApplication(NodeId applicationId) + { + client.UnregisterApplicationAsync(applicationId) + .AsTask() + .GetAwaiter() + .GetResult(); + } + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs new file mode 100644 index 0000000000..12e94330df --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs @@ -0,0 +1,92 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Threading.Tasks; + +namespace Opc.Ua.Gds.Client +{ + /// + /// Migration shim — restores the APM + /// BeginFindServers / EndFindServers pair on + /// that was removed in 1.6 in + /// favour of FindServersAsync. + /// + public static class LocalDiscoveryServerClientShim + { + extension(LocalDiscoveryServerClient client) + { + /// + /// Begins an asynchronous FindServers call using the + /// classic Begin/End APM pattern. + /// + [Obsolete("BeginFindServers/EndFindServers were removed in 1.6. Use FindServersAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0015")] + [OpcUaShim("UA0015")] + public IAsyncResult BeginFindServers(AsyncCallback? callback, object? state) + { + var tcs = new TaskCompletionSource>(state); + client.FindServersAsync() + .AsTask() + .ContinueWith( + t => + { + if (t.IsFaulted) + { + tcs.TrySetException(t.Exception!.InnerExceptions); + } + else if (t.IsCanceled) + { + tcs.TrySetCanceled(); + } + else + { + tcs.TrySetResult(t.Result); + } + callback?.Invoke(tcs.Task); + }, + TaskScheduler.Default); + return tcs.Task; + } + + /// + /// Completes the asynchronous FindServers call. + /// + [Obsolete("BeginFindServers/EndFindServers were removed in 1.6. Use FindServersAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0015")] + [OpcUaShim("UA0015")] + public ArrayOf EndFindServers(IAsyncResult result) + { + return ((Task>)result) + .GetAwaiter() + .GetResult(); + } + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs new file mode 100644 index 0000000000..f6c666c3e4 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs @@ -0,0 +1,58 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +namespace Opc.Ua.Gds.Client +{ + /// + /// Migration shim — restores the synchronous ApplyChanges + /// method on that was + /// removed in 1.6. + /// + public static class ServerPushConfigurationClientShim + { + extension(ServerPushConfigurationClient client) + { + /// + /// Applies the staged configuration changes synchronously. + /// + [Obsolete("Synchronous ApplyChanges was removed in 1.6. Use ApplyChangesAsync. " + + "See https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md#ua0015")] + [OpcUaShim("UA0015")] + public void ApplyChanges() + { + client.ApplyChangesAsync() + .AsTask() + .GetAwaiter() + .GetResult(); + } + } + } +} diff --git a/Stack/Opc.Ua.Types/BuiltIn/BuiltInType.Obsolete.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs similarity index 100% rename from Stack/Opc.Ua.Types/BuiltIn/BuiltInType.Obsolete.cs rename to Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/readme.md b/Tools/Opc.Ua.CodeFixers.Shim/readme.md new file mode 100644 index 0000000000..ab8d454035 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.Shim/readme.md @@ -0,0 +1,40 @@ +# OPC UA 1.5.378 → 1.6 compatibility shim + +This project provides extension-method shims for the obsolete API surface that +the 1.6 release line is moving away from. It ships in the +`OPCFoundation.NetStandard.Opc.Ua.CodeFixers` NuGet package alongside the +analyzer DLL. + +## Directory convention + +Source files live under `Shims//...` mirroring +the project layout in `Stack/` and `Libraries/`. For example: + +| Source project | Shim path | +| ------------------------- | ---------------- | +| `Opc.Ua.Types` | `Shims/Types/` | +| `Opc.Ua.Core.Types` | `Shims/Core.Types/` | +| `Opc.Ua.Core` | `Shims/Core/` | +| `Opc.Ua.Client` | `Shims/Client/` | +| `Opc.Ua.Configuration` | `Shims/Configuration/` | +| `Opc.Ua.Gds.Client.Common`| `Shims/Gds.Client.Common/` | + +Within each `Shims//` directory the file path mirrors the directory +layout of the source project — e.g. `Stack/Opc.Ua.Core/Stack/Server/ServerBaseObsolete.cs` +moves to `Shims/Core/Stack/Server/ServerBase.cs`. + +## Conventions + +- Every shim member carries **both** `[Obsolete]` and + `[OpcUaShim(RuleId = "UANNNN")]` so the analyzer can route calls back to + the corresponding migration-guide section. +- Sync-over-async shims use `Task.Run(() => XxxAsync()).GetAwaiter().GetResult()` + to avoid sync-context deadlocks (see Phase 6 in the plan). +- Removed types (e.g. the legacy `Collection` wrappers) are + **not** shimmed — consumers must run the UA0002 fixer to migrate + declarations to `List` or `ArrayOf`. + +## Status + +Phase 6.A scaffolding only. Move-from-libraries work happens in Phase 6.C; +new shims for genuinely-removed members in Phase 6.D. diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs index b1fde10301..7aeabf6e7c 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs @@ -63,7 +63,7 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context) { INamedTypeSymbol sessionInterface = context.Compilation.GetTypeByMetadataName("Opc.Ua.ISession"); INamedTypeSymbol variantType = context.Compilation.GetTypeByMetadataName("Opc.Ua.Variant"); - if (sessionInterface is null || variantType is null) + if (variantType is null) { return; } @@ -89,11 +89,22 @@ private static void AnalyzeInvocation( return; } - ITypeSymbol receiverType = context.SemanticModel - .GetTypeInfo(memberAccess.Expression, context.CancellationToken).Type; - if (receiverType is null || !receiverType.IsAssignableTo(sessionInterface)) + IMethodSymbol resolvedMethod = context.SemanticModel + .GetSymbolInfo(invocation, context.CancellationToken).Symbol as IMethodSymbol; + bool isShim = resolvedMethod.IsOpcUaShim("UA0008"); + + if (!isShim) { - return; + if (sessionInterface is null) + { + return; + } + ITypeSymbol receiverType = context.SemanticModel + .GetTypeInfo(memberAccess.Expression, context.CancellationToken).Type; + if (receiverType is null || !receiverType.IsAssignableTo(sessionInterface)) + { + return; + } } int firstVariadicIndex = methodName == "Call" ? 2 : 3; @@ -123,7 +134,10 @@ private static void AnalyzeInvocation( ImmutableDictionary properties = ImmutableDictionary.Empty .Add(MethodNameProperty, methodName); - string display = receiverType.Name + "." + methodName; + ITypeSymbol displayReceiver = context.SemanticModel + .GetTypeInfo(memberAccess.Expression, context.CancellationToken).Type; + string receiverName = displayReceiver?.Name ?? "ISession"; + string display = receiverName + "." + methodName; context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.UA0008_SessionCallParamsObject, invocation.GetLocation(), diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs index 63a1a64b94..bf98de8918 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs @@ -91,32 +91,36 @@ private static void AnalyzeInvocation( return; } - if (!method.IsObsolete()) + bool isShim = method.IsOpcUaShim("UA0011"); + if (!isShim && !method.IsObsolete()) { return; } - INamedTypeSymbol containing = method.ContainingType; - if (containing is null) + if (!isShim) { - return; - } + INamedTypeSymbol containing = method.ContainingType; + if (containing is null) + { + return; + } - bool declaredOnHandler = SymbolEqualityComparer.Default.Equals(containing, tokenHandler); - if (!declaredOnHandler) - { - bool implementsHandler = false; - foreach (INamedTypeSymbol iface in containing.AllInterfaces) + bool declaredOnHandler = SymbolEqualityComparer.Default.Equals(containing, tokenHandler); + if (!declaredOnHandler) { - if (SymbolEqualityComparer.Default.Equals(iface, tokenHandler)) + bool implementsHandler = false; + foreach (INamedTypeSymbol iface in containing.AllInterfaces) { - implementsHandler = true; - break; + if (SymbolEqualityComparer.Default.Equals(iface, tokenHandler)) + { + implementsHandler = true; + break; + } + } + if (!implementsHandler) + { + return; } - } - if (!implementsHandler) - { - return; } } diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs index a60149eab1..a39f43b0b3 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs @@ -74,10 +74,6 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context) targets.Add(sym); } } - if (targets.Count == 0) - { - return; - } context.RegisterOperationAction( ctx => AnalyzeInvocation(ctx, targets), @@ -95,15 +91,19 @@ private static void AnalyzeInvocation( return; } - INamedTypeSymbol containing = method.ContainingType; - if (containing is null || !targets.Contains(containing)) + bool isShim = method.IsOpcUaShim("UA0015"); + if (!isShim) { - return; - } + INamedTypeSymbol containing = method.ContainingType; + if (containing is null || !targets.Contains(containing)) + { + return; + } - if (!method.IsObsolete()) - { - return; + if (!method.IsObsolete()) + { + return; + } } context.ReportDiagnostic(Diagnostic.Create( diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs index addb57f361..7ba4ec9d92 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs @@ -66,24 +66,28 @@ private static void AnalyzePropertyReference(OperationAnalysisContext context) return; } - if (!property.IsObsolete()) + bool isShim = property.IsOpcUaShim("UA0018"); + if (!isShim) { - return; - } + if (!property.IsObsolete()) + { + return; + } - INamedTypeSymbol containing = property.ContainingType; - if (containing is null) - { - return; - } + INamedTypeSymbol containing = property.ContainingType; + if (containing is null) + { + return; + } - string typeName = containing.Name; - if (typeName is null || - !(typeName == CertificateIdentifierTypeSuffix || - typeName.EndsWith(CertificateIdentifierTypeSuffix, System.StringComparison.Ordinal) || - typeName.Contains(CertificateIdentifierTypeSuffix))) - { - return; + string typeName = containing.Name; + if (typeName is null || + !(typeName == CertificateIdentifierTypeSuffix || + typeName.EndsWith(CertificateIdentifierTypeSuffix, System.StringComparison.Ordinal) || + typeName.Contains(CertificateIdentifierTypeSuffix))) + { + return; + } } context.ReportDiagnostic(Diagnostic.Create( diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs index 90c9501b4c..5ac178c21e 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs @@ -32,6 +32,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; namespace Opc.Ua.CodeFixers.Analyzers { @@ -65,14 +66,23 @@ private static void AnalyzePropertyReference(OperationAnalysisContext context) { IPropertyReferenceOperation reference = (IPropertyReferenceOperation)context.Operation; IPropertySymbol property = reference.Property; - if (property is null || property.Name != "GlobalFactory" || !property.IsStatic) + if (property is null || property.Name != "GlobalFactory") { return; } - INamedTypeSymbol containing = property.ContainingType; - if (containing is null || containing.ToDisplayString() != EncodeableFactoryTypeName) + + bool isShim = property.IsOpcUaShim("UA0020"); + if (!isShim) { - return; + if (!property.IsStatic) + { + return; + } + INamedTypeSymbol containing = property.ContainingType; + if (containing is null || containing.ToDisplayString() != EncodeableFactoryTypeName) + { + return; + } } ImmutableDictionary properties = ImmutableDictionary.Empty @@ -90,14 +100,23 @@ private static void AnalyzeInvocation(OperationAnalysisContext context) { IInvocationOperation invocation = (IInvocationOperation)context.Operation; IMethodSymbol method = invocation.TargetMethod; - if (method is null || method.Name != "Create" || method.IsStatic || method.Parameters.Length != 0) + if (method is null || method.Name != "Create") { return; } - INamedTypeSymbol containing = method.ContainingType; - if (containing is null || containing.ToDisplayString() != EncodeableFactoryTypeName) + + bool isShim = method.IsOpcUaShim("UA0020"); + if (!isShim) { - return; + if (method.IsStatic || method.Parameters.Length != 0) + { + return; + } + INamedTypeSymbol containing = method.ContainingType; + if (containing is null || containing.ToDisplayString() != EncodeableFactoryTypeName) + { + return; + } } ImmutableDictionary properties = ImmutableDictionary.Empty diff --git a/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs b/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs index 69e124b39e..25f2128fce 100644 --- a/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs +++ b/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs @@ -58,6 +58,49 @@ public static bool IsObsolete(this ISymbol symbol) return false; } + /// + /// True iff carries + /// [Opc.Ua.OpcUaShimAttribute(ruleId)] for the given + /// . For extension methods reduced from a + /// static declaration, also inspects + /// since the attribute lives on the original declaration. + /// + public static bool IsOpcUaShim(this ISymbol symbol, string ruleId) + { + if (symbol is null) + { + return false; + } + if (HasShimAttribute(symbol, ruleId)) + { + return true; + } + if (symbol is IMethodSymbol method && method.ReducedFrom != null) + { + return HasShimAttribute(method.ReducedFrom, ruleId); + } + return false; + } + + private static bool HasShimAttribute(ISymbol symbol, string ruleId) + { + foreach (AttributeData attr in symbol.GetAttributes()) + { + INamedTypeSymbol cls = attr.AttributeClass; + if (cls is null || cls.ToDisplayString() != "Opc.Ua.OpcUaShimAttribute") + { + continue; + } + if (attr.ConstructorArguments.Length == 1 && + attr.ConstructorArguments[0].Value is string id && + id == ruleId) + { + return true; + } + } + return false; + } + /// /// True iff is declared (directly or via override) /// on the named type referenced by . diff --git a/Tools/Opc.Ua.CodeFixers/NugetREADME.md b/Tools/Opc.Ua.CodeFixers/NugetREADME.md index 0b0f6d4656..868a5992cf 100644 --- a/Tools/Opc.Ua.CodeFixers/NugetREADME.md +++ b/Tools/Opc.Ua.CodeFixers/NugetREADME.md @@ -1,24 +1,35 @@ -# OPC UA migration analyzers and code fixers +# OPC UA migration analyzers, code fixers, and compatibility shim -This package ships Roslyn analyzers and code fixers that help consumers -migrate from OPC UA .NET Standard 1.5.378 to 2.0. +## What you get -Install in your consumer project: +A single NuGet install (`OPCFoundation.NetStandard.Opc.Ua.CodeFixers`) that +ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: -```xml - -``` +- a Roslyn **analyzer + code-fixer** set (`UA0001`–`UA0020`) that flags every + pattern covered by [`Docs/MigrationGuide.md`](../../Docs/MigrationGuide.md) + and, where safe, applies the fix automatically; and +- a **compatibility shim** assembly (`Opc.Ua.CodeFixers.Shim.dll`) that + re-supplies the obsolete extension surface 1.6 moved or removed, so most + consumer projects still compile after the upgrade. + +## How to migrate -Then rebuild — the analyzer will flag each of the 17 patterns covered by the -[`Docs/MigrationGuide.md`](../../Docs/MigrationGuide.md) and, where safe, -offer an automatic code fix. +1. Add the 1.6 OPC UA packages **and** the CodeFixers package to your + consumer project: -## Severity model + ```xml + + ``` -| Severity | Meaning | -| -------- | --------------------------------------------------------------------------------------------- | -| Warning | Mechanical fix that is safe to apply across the whole solution in one go. | -| Info | Fix requires manual review (e.g. UA0001 telemetry plumbing, UA0011 async signature promotion). | +2. Run `dotnet build`. Your code should compile: the shim covers the + `[Obsolete]` extension surface that 1.6 moved or removed, so what remains + are warnings rather than errors. +3. Walk through the `UA00xx` analyzer warnings in the IDE and apply the + offered auto-fixes. A handful (`UA0001`, `UA0011`, `UA0015`, `UA0018`) are + `Info`-level and need a manual review. +4. Once the project is warning-free, remove the + `OPCFoundation.NetStandard.Opc.Ua.CodeFixers` package reference. You are + on clean 1.6 with no shim dependency. ## Rules @@ -42,6 +53,66 @@ offer an automatic code fix. | UA0019 | Warning | `new DataValue(StatusCode[, ts])` | | UA0020 | Warning | `EncodeableFactory.GlobalFactory` / `Create()` | +## What the shim provides + +`Opc.Ua.CodeFixers.Shim.dll` is delivered as a regular reference assembly and +re-exposes the 1.5.378 surface in two flavors: + +- **Moved obsolete extensions** the 1.6 libraries no longer carry inline: + `NodeId` / `Variant` / `DataValue` null-check helpers, `Session` sync + helpers, `Subscription` sync helpers, `ApplicationInstance` helpers, + `ServerBase.Start` / `Stop`, `TransportChannel` APM (`BeginX` / `EndX`), + `ChannelBase` static factory methods, and similar surface. +- **New shims for genuinely-removed members**: + - `EncodeableFactory.GlobalFactory` + - `CertificateIdentifier.Certificate` (throws `NotSupportedException`) + - sync wrappers for + `IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}` + - sync + APM wrappers for the GDS / LDS client APIs. + +## What the shim does NOT cover + +These changes are source-level only; no extension method can paper over them. +Use the listed analyzer fix. + +- `== null` / `!= null` on now-struct types — use the **UA0003** fixer. +- `?.` member access on now-struct types — use the **UA0004** fixer. +- `using var x = new CertificateIdentifier(...)` — use the **UA0010** fixer + to drop the `using` / `Dispose` call. +- `[DataContract]` / `[DataMember]` on configuration extension classes — use + the **UA0009** fixer. +- Removed `Collection` wrappers such as `Int32Collection`, + `NodeIdCollection`, etc. — use the **UA0002** fixer to rewrite to + `List` or `ArrayOf`. + +## Sync-over-async caveat + +The sync shims (for example `handler.Encrypt(bytes)`, +`gdsClient.RegisterApplication(...)`, the `Session` / `Subscription` sync +helpers) wrap their `*Async` counterparts via +`Task.Run(() => …Async(...)).GetAwaiter().GetResult()`. This is intended as +a **migration aid only**: it keeps legacy call sites compiling while you port +them to `async`/`await`. Do not leave these calls on production hot paths — +follow the `UA0011` / `UA0015` guidance and switch to the async APIs. + +## TreatWarningsAsErrors recipe + +If your project sets `true` +and you cannot relax it during the migration window, exclude the migration +diagnostics from the failure set: + +```xml + + true + $(NoWarn);CS0618;UA0001;UA0002;UA0003;UA0004;UA0005;UA0006;UA0007;UA0008;UA0009;UA0010;UA0011;UA0012;UA0014;UA0015;UA0018;UA0019;UA0020 + +``` + +Remove each entry as you finish fixing the corresponding rule, and drop the +whole block once the CodeFixers package is removed. + +## Suppression recipes + To suppress an individual rule for a single line: ```csharp diff --git a/Tools/Opc.Ua.SourceGeneration.Core/Generators/ClientApiTemplates.cs b/Tools/Opc.Ua.SourceGeneration.Core/Generators/ClientApiTemplates.cs index d008db8344..cf32bfbbe8 100644 --- a/Tools/Opc.Ua.SourceGeneration.Core/Generators/ClientApiTemplates.cs +++ b/Tools/Opc.Ua.SourceGeneration.Core/Generators/ClientApiTemplates.cs @@ -168,7 +168,11 @@ public interface I{{Tokens.ServiceSet}}ClientMethods{{Tokens.BaseInterfaces}} try { - global::Opc.Ua.IServiceResponse? genericResponse = TransportChannel.SendRequest(request); + global::Opc.Ua.IServiceResponse? genericResponse = TransportChannel + .SendRequestAsync(request, global::System.Threading.CancellationToken.None) + .AsTask() + .GetAwaiter() + .GetResult(); if (genericResponse == null) { @@ -200,7 +204,28 @@ public interface I{{Tokens.ServiceSet}}ClientMethods{{Tokens.BaseInterfaces}} UpdateRequestHeader(request, requestHeader == null, "{{Tokens.Name}}"); - return TransportChannel.BeginSendRequest(request, callback, asyncState); + global::System.Threading.Tasks.Task task = TransportChannel + .SendRequestAsync(request, global::System.Threading.CancellationToken.None) + .AsTask(); + global::System.Threading.Tasks.TaskCompletionSource tcs = + new global::System.Threading.Tasks.TaskCompletionSource(asyncState); + task.ContinueWith(t => + { + if (t.IsFaulted) + { + tcs.TrySetException(t.Exception!.InnerExceptions); + } + else if (t.IsCanceled) + { + tcs.TrySetCanceled(); + } + else + { + tcs.TrySetResult(t.Result); + } + callback?.Invoke(tcs.Task); + }, global::System.Threading.Tasks.TaskScheduler.Default); + return tcs.Task; } /// @@ -213,7 +238,10 @@ public interface I{{Tokens.ServiceSet}}ClientMethods{{Tokens.BaseInterfaces}} try { - global::Opc.Ua.IServiceResponse? genericResponse = TransportChannel.EndSendRequest(result); + global::Opc.Ua.IServiceResponse? genericResponse = + ((global::System.Threading.Tasks.Task)result) + .GetAwaiter() + .GetResult(); if (genericResponse == null) { diff --git a/Tools/SourceGeneration.slnx b/Tools/SourceGeneration.slnx index 563b07a295..6b06b605ae 100644 --- a/Tools/SourceGeneration.slnx +++ b/Tools/SourceGeneration.slnx @@ -6,8 +6,10 @@ + + diff --git a/UA.slnx b/UA.slnx index 04b510e641..8fb71d45a0 100644 --- a/UA.slnx +++ b/UA.slnx @@ -122,7 +122,9 @@ + + From 3a7aa6b2c0af966af6e556c671b17c4df0cecebc Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 12:22:47 +0200 Subject: [PATCH 3/8] [Tools] Add UA0021 CertificateValidator rename rule and enable NuGet packaging Phase 7: UA0021 covers the 1.5.378 to 1.6 rename of CertificateValidator and CertificateValidationEventArgs (discovered during Phase 6.F samples dogfood). Rule is Info-level / diagnostic-only because the 1.6 replacement is structurally different (event-based -> async result + callback pattern); no auto-fix is feasible and no shim is provided. Adds analyzer at Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs with dual-mode detection (semantic when the legacy Obsolete symbol still resolves, syntactic fallback gated by 'using Opc.Ua' when the type is fully removed). Tests increase from 94 to 99 (5 new UA0021 tests). Phase 8: enable bundled NuGet packaging for OPCFoundation.NetStandard.Opc.Ua.CodeFixers. Adds a hand-authored Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec that combines the analyzer DLL (analyzers/dotnet/cs/Opc.Ua.CodeFixers.dll) and the shim DLL (lib//Opc.Ua.CodeFixers.Shim.dll) for all six TFMs (net472, net48, netstandard2.1, net8.0, net9.0, net10.0). Declares the six OPCFoundation.NetStandard.Opc.Ua.* runtime dependencies per TFM group. Opc.Ua.CodeFixers.csproj switched from IsPackable=false to true with GeneratePackageOnBuild, a _BuildShimAllTfmsBeforePack target that triggers shim builds for every required TFM, and NuspecProperties set inside the GenerateNuspec target so NBGV's computed PackageVersion is captured. Smoke test on a throwaway net10.0 console project resolves the analyzer + shim cleanly and produces only the expected UA00xx warnings, zero errors. Other: Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj suppresses CS0419 (pre-existing baseline failure on net472/net48/netstandard2.1 from the OpcUaShimAttribute cref to ObsoleteAttribute - ambiguous on TFMs where System.Runtime forwards the type). NugetREADME.md gains the UA0021 row and a one-line RS1038 packaging note (analyzer + code-fix shipped in one assembly is deliberate, suppression documented). Verification: dotnet build 0 errors; Opc.Ua.CodeFixers.Tests 99/99 passing; Opc.Ua.CodeFixers.Shim.Tests 8 pass + 3 ignored. --- .../Analyzers/UA0021Tests.cs | 148 ++++++++++++ .../Stubs/OpcUaStubs.cs | 12 + .../Opc.Ua.CodeFixers.Shim.csproj | 7 +- .../AnalyzerReleases.Unshipped.md | 1 + ...A0021CertificateValidatorRenameAnalyzer.cs | 213 ++++++++++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 7 + .../Diagnostics/DiagnosticIds.cs | 1 + Tools/Opc.Ua.CodeFixers/NugetREADME.md | 7 + .../Opc.Ua.CodeFixers.csproj | 53 ++++- .../Opc.Ua.CodeFixers.nuspec | 83 +++++++ 10 files changed, 518 insertions(+), 14 deletions(-) create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs new file mode 100644 index 0000000000..90eba8c954 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs @@ -0,0 +1,148 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0021 (CertificateValidator / CertificateValidationEventArgs structural rename). + /// The rule is diagnostic-only; there is no accompanying code fix because the 1.6 + /// replacement is structural (event-based per-error accept handler -> async + /// ValidateAsync returning CertificateValidationResult plus AcceptError callback). + /// + [TestFixture] + public class UA0021Tests + { + [Test] + public async Task ReportsDiagnosticOnCertificateValidatorReferenceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(CertificateValidator v) { } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0021CertificateValidatorRenameAnalyzer(), source); + + Diagnostic? ua0021 = diags.SingleOrDefault(d => d.Id == "UA0021"); + Assert.That(ua0021, Is.Not.Null); + Assert.That( + ua0021!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("CertificateValidator")); + } + + [Test] + public async Task ReportsDiagnosticOnCertificateValidationEventArgsReferenceAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(CertificateValidationEventArgs e) { } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0021CertificateValidatorRenameAnalyzer(), source); + + Diagnostic? ua0021 = diags.SingleOrDefault(d => d.Id == "UA0021"); + Assert.That(ua0021, Is.Not.Null); + Assert.That( + ua0021!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("CertificateValidationEventArgs")); + } + + [Test] + public async Task DoesNotReportOnReplacementTypesAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(ICertificateManager m, ICertificateValidatorEx v, CertificateValidationResult r) { } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0021CertificateValidatorRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0021"), Is.False); + } + + [Test] + public async Task DoesNotReportOnUnrelatedSimilarlyNamedTypeAsync() + { + // A user-defined CertificateValidator in a non-Opc.Ua namespace, with no + // 'using Opc.Ua;' anywhere — the symbol resolves to the user's own type + // (not [Obsolete]) and the syntactic fallback is also gated by the using. + const string source = """ + namespace Other.Pki + { + public class CertificateValidator { } + public class CertificateValidationEventArgs { } + class C + { + static void M(CertificateValidator v, CertificateValidationEventArgs e) { } + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0021CertificateValidatorRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0021"), Is.False); + } + + [Test] + public async Task ReportsExactlyTwoDiagnosticsForBothLegacyTypesAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static void M(CertificateValidator v, CertificateValidationEventArgs e) { } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0021CertificateValidatorRenameAnalyzer(), source); + + Assert.That(diags.Count(d => d.Id == "UA0021"), Is.EqualTo(2)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs index d96f9cd9d3..988f17837f 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs @@ -423,6 +423,18 @@ public class ServiceMessageContext public EncodeableFactory Factory { get; } = new EncodeableFactory(); } + // ─── Stubs for UA0021 (CertificateValidator / CertificateValidationEventArgs rename) ─── + // The legacy types are kept here so the analyzer's "symbol-present + [Obsolete]" branch + // can be exercised. The 1.6 replacements (ICertificateManager, ICertificateValidatorEx, + // CertificateValidationResult) are stubbed to verify the analyzer does NOT fire on them. + [Obsolete("Use ICertificateManager (via CertificateManagerFactory.Create) instead. See MigrationGuide.md#ua0021.")] + public class CertificateValidator { } + [Obsolete("Use CertificateValidationResult returned from ICertificateValidatorEx.ValidateAsync instead. See MigrationGuide.md#ua0021.")] + public class CertificateValidationEventArgs : EventArgs { } + public interface ICertificateManager { } + public interface ICertificateValidatorEx { } + public class CertificateValidationResult { } + // ─── OpcUaShim marker attribute and shim wrappers used by analyzer tests ─── [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] public sealed class OpcUaShimAttribute : Attribute diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj b/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj index 2e13dcdace..9694b0a4e7 100644 --- a/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj +++ b/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj @@ -7,8 +7,11 @@ false true enable - - $(NoWarn);CS0618 + + $(NoWarn);CS0618;CS0419 diff --git a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md index 50dfdb0b95..ec838b12c3 100644 --- a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md +++ b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md @@ -22,3 +22,4 @@ UA0015 | Migration | Info | Replace sync/APM members on GDS/LDS clients wit UA0018 | Migration | Info | Replace `CertificateIdentifier.Certificate` getter with `CertificateIdentifierResolver.ResolveAsync(...)`. UA0019 | Migration | Warning | Replace `new DataValue(StatusCode[, ts])` with `DataValue.FromStatusCode(...)`. UA0020 | Migration | Warning | Replace `EncodeableFactory.GlobalFactory` / `EncodeableFactory.Create()` with `ServiceMessageContext.Factory` / `Fork()`. +UA0021 | Migration | Info | Replace `CertificateValidator` / `CertificateValidationEventArgs` with the 1.6 `ICertificateManager` / `ICertificateValidatorEx` / `CertificateValidationResult` pipeline. See MigrationGuide.md. diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs new file mode 100644 index 0000000000..9e3a5705b5 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs @@ -0,0 +1,213 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0021: Detect references to the legacy Opc.Ua.CertificateValidator class and + /// Opc.Ua.CertificateValidationEventArgs, which were removed in 1.6 in favour of the + /// new ICertificateManager / ICertificateValidatorEx / + /// CertificateValidationResult pipeline. + /// + /// + /// Diagnostic-only: the 1.5.378 -> 1.6 change is structural (event-based per-error + /// accept handler became an async ValidateAsync call returning a + /// CertificateValidationResult, with per-error accept logic moving to + /// CertificateValidationOptions.AcceptError). There is therefore no accompanying + /// code-fix provider; consumers must perform the migration manually using + /// Docs/MigrationGuide.md#certificatemanager-and-segregated-interfaces. + /// + /// Detection strategy: + /// + /// If the legacy type is present in the compilation (via the 1.5.378 stack or the shim + /// package), fire when an [Obsolete]-marked type with the matching full name is + /// referenced. + /// If the legacy type has been genuinely removed (consumer is on the bare 1.6 stack + /// and the call site no longer compiles), fall back to a syntactic match on the bare + /// identifier name, scoped to source files that import Opc.Ua. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0021CertificateValidatorRenameAnalyzer : DiagnosticAnalyzer + { + private const string OpcUaNamespace = "Opc.Ua"; + private const string CertificateValidatorTypeName = "CertificateValidator"; + private const string CertificateValidationEventArgsTypeName = "CertificateValidationEventArgs"; + private const string CertificateValidatorFullName = OpcUaNamespace + "." + CertificateValidatorTypeName; + private const string CertificateValidationEventArgsFullName = + OpcUaNamespace + "." + CertificateValidationEventArgsTypeName; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0021_CertificateValidatorRename); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static startContext => + { + INamedTypeSymbol? legacyValidator = + startContext.Compilation.GetTypeByMetadataName(CertificateValidatorFullName); + INamedTypeSymbol? legacyEventArgs = + startContext.Compilation.GetTypeByMetadataName(CertificateValidationEventArgsFullName); + + bool hasLegacySymbol = legacyValidator is not null || legacyEventArgs is not null; + + if (hasLegacySymbol) + { + startContext.RegisterSyntaxNodeAction( + ctx => AnalyzeIdentifierSymbol(ctx, legacyValidator, legacyEventArgs), + SyntaxKind.IdentifierName); + } + else + { + startContext.RegisterSyntaxNodeAction( + AnalyzeIdentifierSyntactic, + SyntaxKind.IdentifierName); + } + }); + } + + private static void AnalyzeIdentifierSymbol( + SyntaxNodeAnalysisContext context, + INamedTypeSymbol? legacyValidator, + INamedTypeSymbol? legacyEventArgs) + { + IdentifierNameSyntax identifier = (IdentifierNameSyntax)context.Node; + string text = identifier.Identifier.ValueText; + if (text != CertificateValidatorTypeName && text != CertificateValidationEventArgsTypeName) + { + return; + } + + SymbolInfo info = context.SemanticModel.GetSymbolInfo(identifier, context.CancellationToken); + ISymbol? symbol = info.Symbol; + if (symbol is null) + { + return; + } + + INamedTypeSymbol? namedType = symbol as INamedTypeSymbol + ?? (symbol as IMethodSymbol)?.ContainingType; + if (namedType is null) + { + return; + } + + bool matchesLegacy = + (legacyValidator is not null + && SymbolEqualityComparer.Default.Equals(namedType, legacyValidator)) || + (legacyEventArgs is not null + && SymbolEqualityComparer.Default.Equals(namedType, legacyEventArgs)); + if (!matchesLegacy) + { + return; + } + + if (!namedType.IsObsolete()) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0021_CertificateValidatorRename, + identifier.GetLocation(), + namedType.Name)); + } + + private static void AnalyzeIdentifierSyntactic(SyntaxNodeAnalysisContext context) + { + IdentifierNameSyntax identifier = (IdentifierNameSyntax)context.Node; + string text = identifier.Identifier.ValueText; + if (text != CertificateValidatorTypeName && text != CertificateValidationEventArgsTypeName) + { + return; + } + + if (!HasOpcUaUsing(identifier)) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0021_CertificateValidatorRename, + identifier.GetLocation(), + text)); + } + + private static bool HasOpcUaUsing(SyntaxNode node) + { + for (SyntaxNode? current = node; current is not null; current = current.Parent) + { + if (current is BaseNamespaceDeclarationSyntax ns && + ContainsOpcUaUsing(ns.Usings)) + { + return true; + } + + if (current is CompilationUnitSyntax compilationUnit) + { + return ContainsOpcUaUsing(compilationUnit.Usings); + } + } + + return false; + } + + private static bool ContainsOpcUaUsing(SyntaxList usings) + { + foreach (UsingDirectiveSyntax @using in usings) + { + if (@using.Alias is not null || @using.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) + { + continue; + } + if (@using.Name is null) + { + continue; + } + if (string.Equals(@using.Name.ToString(), OpcUaNamespace, StringComparison.Ordinal)) + { + return true; + } + } + return false; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs index 16de94b1e5..2cba0510b5 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs @@ -175,5 +175,12 @@ private static DiagnosticDescriptor Create( "'{0}' was replaced in 2.0. Use '{1}' instead.", DiagnosticSeverity.Warning, "EncodeableFactory.GlobalFactory was removed (consumers now obtain the factory from ServiceMessageContext.Factory) and EncodeableFactory.Create was renamed to Fork."); + + public static readonly DiagnosticDescriptor UA0021_CertificateValidatorRename = Create( + DiagnosticIds.UA0021, + "CertificateValidator / CertificateValidationEventArgs renamed in 1.6", + "'{0}' was replaced in 1.6 by the new CertificateManager pipeline (ICertificateManager / ICertificateValidatorEx / CertificateValidationResult). The migration is structural (event-based -> async result + AcceptError callback) — see MigrationGuide.md#ua0021.", + DiagnosticSeverity.Info, + "The CertificateValidator class and CertificateValidationEventArgs were removed in 1.6. The new ICertificateManager (composed of ICertificateValidatorEx, ICertificateRegistry, ICertificateTrustListManager, ICertificateLifecycle) replaces them; per-error accept logic moves from the CertificateValidation event to CertificateValidationOptions.AcceptError. This rule is diagnostic-only because the migration changes the API shape (no mechanical rename)."); } } diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs index fe51b43a67..d637e366ad 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs @@ -54,6 +54,7 @@ internal static class DiagnosticIds public const string UA0018 = "UA0018"; public const string UA0019 = "UA0019"; public const string UA0020 = "UA0020"; + public const string UA0021 = "UA0021"; /// The diagnostic category every UA00xx rule belongs to. public const string Category = "Migration"; diff --git a/Tools/Opc.Ua.CodeFixers/NugetREADME.md b/Tools/Opc.Ua.CodeFixers/NugetREADME.md index 868a5992cf..47c8a51034 100644 --- a/Tools/Opc.Ua.CodeFixers/NugetREADME.md +++ b/Tools/Opc.Ua.CodeFixers/NugetREADME.md @@ -52,6 +52,7 @@ ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: | UA0018 | Info | `CertificateIdentifier.Certificate` getter | | UA0019 | Warning | `new DataValue(StatusCode[, ts])` | | UA0020 | Warning | `EncodeableFactory.GlobalFactory` / `Create()` | +| UA0021 | Info | `CertificateValidator` / `CertificateValidationEventArgs` (structural rename in 1.6) | ## What the shim provides @@ -111,6 +112,12 @@ diagnostics from the failure set: Remove each entry as you finish fixing the corresponding rule, and drop the whole block once the CodeFixers package is removed. +## Packaging note + +The analyzer DLL also hosts the matching `CodeFixProvider` types. This is a +deliberate single-assembly design; `RS1038` (recommending separation) is +suppressed at the project level via ``. + ## Suppression recipes To suppress an individual rule for a single line: diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj index 80226864ea..3e33f5f5e1 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj @@ -12,13 +12,25 @@ itself doesn't reference Workspaces types. --> $(NoWarn);RS1007;RS1038 - + $(PackagePrefix).Opc.Ua.CodeFixers true - false + true + true true true + + false + $(MSBuildThisFileDirectory)Opc.Ua.CodeFixers.nuspec + $(MSBuildThisFileDirectory) + @@ -31,19 +43,36 @@ - - + + - - - - - + + + + false + all + + + + + + + + + + version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.Shim\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.Opc.Ua.CodeFixers.props + - - $(TargetsForTfmSpecificContentInPackage);_AddAnalyzerToPackage - diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec new file mode 100644 index 0000000000..c8ebd9e2c5 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec @@ -0,0 +1,83 @@ + + + + OPCFoundation.NetStandard.Opc.Ua.CodeFixers + $version$ + OPC UA .NET Standard 1.5.378 to 1.6 migration analyzers, code fixers, and compatibility shim + OPC Foundation + OPC Foundation + true + LICENSE.txt + https://github.com/OPCFoundation/UA-.NETStandard + images/logo.jpg + NugetREADME.md + OPC UA .NET Standard migration analyzers and code fixers (UA0001-UA0020) bundled with the Opc.Ua.CodeFixers.Shim compatibility assembly that re-exposes the 1.5.378 obsolete surface to ease the move to 1.6. + Copyright (c) 2004-2025 OPC Foundation, Inc + OPCFoundation OPC UA analyzer codefix roslyn migration shim + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a5302a74e44f91a0984079d5bc5928691048dc11 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:21:55 +0200 Subject: [PATCH 4/8] [Tools] Phase 11 - address dogfood findings (F1/F2/F3 + UA0022) Fixes three bugs surfaced by the Phase 10 samples dogfood and adds a new analyzer rule for the CertificateValidator property rename. F1 - analyzer auto-load via NuGet props injection. The hand-authored .nuspec packaged the analyzer at analyzers/dotnet/cs/ but NuGet's restore-time analyzer detection did not register it in the consumer's project.assets.json. Two coordinated fixes here: * Renamed the build props file from OPCFoundation.Opc.Ua.CodeFixers.props to OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props so it matches the package id exactly. NuGet auto-imports build/.props into consumer csprojs; the previous mismatched name prevented auto-import. * Added an item to the props file as a belt-and-suspenders measure. Once both routes are in place the analyzer loads transparently, so consumers no longer need the explicit workaround. F2 - DataValueObsolete extension(ExtensionObject) typo (Shims/Types/BuiltIn/BuiltInType.cs). Changed extension(ExtensionObject) to extension(DataValue) so the six obsolete static helpers (IsGood / IsBad / IsUncertain / IsNotGood / IsNotBad / IsNotUncertain) bind as DataValue.IsGood(...) again. The bug was inherited from upstream master at Phase 6.C move time and surfaced as a CS1929 compile error on the dogfood sample. Adds DataValueObsoleteShimTests with two regression tests. F3 - UA0021 namespace heuristic relaxation (UA0021CertificateValidatorRenameAnalyzer.cs). Replaced the strict "using Opc.Ua;" check with a HasOpcUaContext helper that accepts any using directive whose name is Opc.Ua or starts with Opc.Ua. plus namespace declarations under the same prefix. Real-world consumer code rarely imports the bare Opc.Ua namespace - sub-namespace imports like Opc.Ua.Configuration and Opc.Ua.Server are the norm. Three new tests cover the relaxation. F5 / UA0022 - ApplicationConfiguration.CertificateValidator and ServerBase.CertificateValidator removed in 2.0. New analyzer UA0022CertificateValidatorPropertyRenameAnalyzer with a dual-mode design that mirrors UA0021: semantic path when the legacy property is still present as [Obsolete], syntactic fallback when the property has been fully removed. Code-fix mechanically renames the property identifier to CertificateManager. Five tests cover both receivers, the negative case, and the fix. NugetREADME row added and the rule range bumped to UA0001-UA0022. Test counts: analyzer suite 99 -> 107; shim suite 8 active -> 10 active. Combined build 0 errors. Dogfood re-run confirmed the F1 props injection now auto-loads the analyzer (UA0007 fires on a probe without manual wiring). Out of scope / deferred: - F6 (bool -> ITelemetryContext parameter swap across multiple ctors) - generalizes beyond a single rule; deferred to a future PR. - Upstream issue for F2 - file separately, not blocking. - A second-sample dogfood (e.g. ConsoleAggregationServer) - follow-up. --- .../DataValueObsoleteShimTests.cs | 74 +++++ .../AnalyzerHarness.cs | 41 +++ .../Analyzers/UA0021Tests.cs | 80 +++++ .../Analyzers/UA0022Tests.cs | 161 ++++++++++ .../Stubs/OpcUaStubs.cs | 12 + .../Shims/Types/BuiltIn/BuiltInType.cs | 2 +- .../AnalyzerReleases.Unshipped.md | 1 + ...A0021CertificateValidatorRenameAnalyzer.cs | 33 +- ...tificateValidatorPropertyRenameAnalyzer.cs | 283 ++++++++++++++++++ ...rtificateValidatorPropertyRenameCodeFix.cs | 104 +++++++ .../Diagnostics/DiagnosticDescriptors.cs | 7 + .../Diagnostics/DiagnosticIds.cs | 1 + Tools/Opc.Ua.CodeFixers/NugetREADME.md | 3 +- ...dation.NetStandard.Opc.Ua.CodeFixers.props | 15 + .../OPCFoundation.Opc.Ua.CodeFixers.props | 10 - .../Opc.Ua.CodeFixers.csproj | 4 +- .../Opc.Ua.CodeFixers.nuspec | 2 +- 17 files changed, 811 insertions(+), 22 deletions(-) create mode 100644 Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs create mode 100644 Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs create mode 100644 Tools/Opc.Ua.CodeFixers/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs create mode 100644 Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props delete mode 100644 Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs b/Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs new file mode 100644 index 0000000000..f820ccee8b --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs @@ -0,0 +1,74 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.CodeFixers.Shim.Tests +{ + /// + /// Runtime tests for the obsolete static DataValue.IsGood / + /// IsBad shim helpers defined in DataValueObsolete. Regression + /// guard for the Phase 11.A fix that corrected the extension receiver type + /// from ExtensionObject to DataValue; the wrong receiver type + /// would have made these statics unreachable via the DataValue. + /// qualifier (which is exactly how callers invoke them). + /// + [TestFixture] + [Category("Shim")] + public class DataValueObsoleteShimTests + { + [Test] + public Task IsGoodOnDefaultDataValueReturnsTrueAsync() + { +#pragma warning disable CS0618 // Intentional shim call. + bool isGood = DataValue.IsGood(default(DataValue)); +#pragma warning restore CS0618 + + Assert.That(isGood, Is.True); + return Task.CompletedTask; + } + + [Test] + public Task IsBadOnBadStatusCodeReturnsTrueAsync() + { + // Construct via FromStatusCode to avoid the obsolete numeric overload + // ambiguity flagged on the DataValue(StatusCode) constructor. + StatusCode bad = Opc.Ua.Types.StatusCodes.Bad; + DataValue dv = DataValue.FromStatusCode(bad); + +#pragma warning disable CS0618 // Intentional shim call. + bool isBad = DataValue.IsBad(dv); +#pragma warning restore CS0618 + + Assert.That(isBad, Is.True); + return Task.CompletedTask; + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs b/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs index ab4e4877fb..ddf3cfeb22 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs @@ -87,6 +87,28 @@ public static CSharpCompilation Compile(string userSource, string assemblyName = return CSharpCompilation.Create(assemblyName, trees, s_baseReferences, options); } + /// + /// Compile WITHOUT the OPC UA stub surface, so + /// analyzers that branch on "is the legacy symbol present in the compilation?" + /// take their symbol-absent fallback. Used by tests that exercise UA0021's + /// syntactic-fallback path (when the consumer is on bare 1.6 and the legacy + /// CertificateValidator/CertificateValidationEventArgs types are + /// no longer defined anywhere). + /// + public static CSharpCompilation CompileWithoutStubs(string userSource, string assemblyName = "TestAssembly") + { + CSharpParseOptions parseOptions = new CSharpParseOptions(LanguageVersion.CSharp13); + SyntaxTree[] trees = + [ + CSharpSyntaxTree.ParseText(userSource, parseOptions, "Test.cs"), + ]; + CSharpCompilationOptions options = new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary, + allowUnsafe: false, + nullableContextOptions: NullableContextOptions.Enable); + return CSharpCompilation.Create(assemblyName, trees, s_baseReferences, options); + } + /// /// Run against and /// return only the analyzer's diagnostics (compiler diagnostics are filtered out). @@ -96,6 +118,25 @@ public static async Task> GetAnalyzerDiagnosticsAsync string userSource) { CSharpCompilation compilation = Compile(userSource); + return await RunAsync(analyzer, compilation).ConfigureAwait(false); + } + + /// + /// Run against compiled + /// WITHOUT the OPC UA stub surface. See . + /// + public static async Task> GetAnalyzerDiagnosticsWithoutStubsAsync( + DiagnosticAnalyzer analyzer, + string userSource) + { + CSharpCompilation compilation = CompileWithoutStubs(userSource); + return await RunAsync(analyzer, compilation).ConfigureAwait(false); + } + + private static async Task> RunAsync( + DiagnosticAnalyzer analyzer, + CSharpCompilation compilation) + { CompilationWithAnalyzers withAnalyzers = compilation.WithAnalyzers( ImmutableArray.Create(analyzer), new CompilationWithAnalyzersOptions( diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs index 90eba8c954..c2ae18046f 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs @@ -144,5 +144,85 @@ static void M(CertificateValidator v, CertificateValidationEventArgs e) { } Assert.That(diags.Count(d => d.Id == "UA0021"), Is.EqualTo(2)); } + + /// + /// Regression test for the Phase 11.A relaxation of the syntactic-fallback + /// using-directive gate. Real-world OPC UA consumer code typically imports + /// sub-namespaces like using Opc.Ua.Server; rather than the bare + /// using Opc.Ua;. Before the fix, the syntactic fallback only fired + /// when the bare directive was present, so UA0021 silently missed every + /// reference in files that imported only sub-namespaces. The fix accepts + /// any using directive whose name starts with Opc.Ua. + /// + [Test] + public async Task ReportsDiagnosticInFileWithOnlyOpcUaServerUsingAsync() + { + const string source = """ + using Opc.Ua.Server; // sub-namespace, no bare Opc.Ua + class C + { + static void M() => System.Console.WriteLine(typeof(CertificateValidator).FullName); + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsWithoutStubsAsync( + new UA0021CertificateValidatorRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0021"), Is.True, + "UA0021 must fire when the file imports any Opc.Ua sub-namespace (not just bare Opc.Ua)."); + } + + /// + /// Defensive check: code declared inside the Opc.Ua.* namespace tree + /// (e.g. a consumer extending the stack) must also trigger the syntactic + /// fallback even when there is no using directive at all. + /// + [Test] + public async Task ReportsDiagnosticInNamespaceUnderOpcUaTreeAsync() + { + const string source = """ + namespace Opc.Ua.Extensions + { + class C + { + static void M() => System.Console.WriteLine(typeof(CertificateValidator).FullName); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsWithoutStubsAsync( + new UA0021CertificateValidatorRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0021"), Is.True); + } + + /// + /// Negative companion to the relaxation: a file that imports neither + /// Opc.Ua nor any sub-namespace, and is not declared under the + /// Opc.Ua tree, must NOT trigger the syntactic fallback even if + /// it happens to mention an identifier named CertificateValidator. + /// + [Test] + public async Task DoesNotReportInFileWithNoOpcUaUsingOrNamespaceAsync() + { + const string source = """ + namespace Other.Pki + { + class CertificateValidator { } + class C + { + static void M() => System.Console.WriteLine(typeof(CertificateValidator).FullName); + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsWithoutStubsAsync( + new UA0021CertificateValidatorRenameAnalyzer(), source); + + Assert.That(diags.Any(d => d.Id == "UA0021"), Is.False); + } } } diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs new file mode 100644 index 0000000000..c4b7f715c5 --- /dev/null +++ b/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs @@ -0,0 +1,161 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NUnit.Framework; +using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.CodeFixers.CodeFixes; + +namespace Opc.Ua.CodeFixers.Tests.Analyzers +{ + /// + /// Tests for UA0022 (ApplicationConfiguration.CertificateValidator / + /// ServerBase.CertificateValidator property rename to CertificateManager). + /// + [TestFixture] + public class UA0022Tests + { + [Test] + public async Task ReportsDiagnosticOnApplicationConfigurationCertificateValidatorAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object? M(ApplicationConfiguration cfg) => cfg.CertificateValidator; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0022CertificateValidatorPropertyRenameAnalyzer(), source).ConfigureAwait(false); + + Diagnostic? ua0022 = diags.SingleOrDefault(d => d.Id == "UA0022"); + Assert.That(ua0022, Is.Not.Null, + "Expected UA0022 to fire on ApplicationConfiguration.CertificateValidator access."); + Assert.That( + ua0022!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("ApplicationConfiguration")); + } + + [Test] + public async Task ReportsDiagnosticOnServerBaseCertificateValidatorAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object? M(ServerBase sb) => sb.CertificateValidator; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0022CertificateValidatorPropertyRenameAnalyzer(), source).ConfigureAwait(false); + + Diagnostic? ua0022 = diags.SingleOrDefault(d => d.Id == "UA0022"); + Assert.That(ua0022, Is.Not.Null, + "Expected UA0022 to fire on ServerBase.CertificateValidator access."); + Assert.That( + ua0022!.GetMessage(System.Globalization.CultureInfo.InvariantCulture), + Does.Contain("ServerBase")); + } + + [Test] + public async Task DoesNotReportOnApplicationConfigurationCertificateManagerAccessAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object? M(ApplicationConfiguration cfg) => cfg.CertificateManager; + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0022CertificateValidatorPropertyRenameAnalyzer(), source).ConfigureAwait(false); + + Assert.That(diags.Any(d => d.Id == "UA0022"), Is.False, + "Modern CertificateManager access must not trigger UA0022."); + } + + [Test] + public async Task DoesNotReportOnUnrelatedTypeCertificateValidatorPropertyAsync() + { + // No 'using Opc.Ua;' and the type name is unrelated; both detection paths + // must remain silent. + const string source = """ + namespace Other.Pki + { + public class Foo + { + public int CertificateValidator { get; } + } + class C + { + static int M(Foo f) => f.CertificateValidator; + } + } + """; + + ImmutableArray diags = await AnalyzerHarness + .GetAnalyzerDiagnosticsAsync(new UA0022CertificateValidatorPropertyRenameAnalyzer(), source).ConfigureAwait(false); + + Assert.That(diags.Any(d => d.Id == "UA0022"), Is.False, + "Unrelated CertificateValidator property on a non-Opc.Ua type must not trigger UA0022."); + } + + [Test] + public async Task FixRewritesCertificateValidatorToCertificateManagerAsync() + { + const string source = """ + using Opc.Ua; + class C + { + static object? M(ApplicationConfiguration cfg) => cfg.CertificateValidator; + } + """; + const string expected = """ + using Opc.Ua; + class C + { + static object? M(ApplicationConfiguration cfg) => cfg.CertificateManager; + } + """; + + string fixedSource = await AnalyzerHarness.ApplyFixAsync( + new UA0022CertificateValidatorPropertyRenameAnalyzer(), + new UA0022CertificateValidatorPropertyRenameCodeFix(), + source).ConfigureAwait(false); + + Assert.That(fixedSource, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs index 988f17837f..0de252acbd 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs +++ b/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs @@ -335,6 +335,18 @@ public class ApplicationConfiguration { public static T ParseExtension() where T : new() => new(); public static void UpdateExtension(T value) { } + + // ─── Stubs for UA0022 (CertificateValidator → CertificateManager property rename) ─── + [Obsolete("Use CertificateManager instead.")] + public object? CertificateValidator { get; set; } + public object? CertificateManager { get; set; } + } + + public class ServerBase + { + [Obsolete("Use CertificateManager instead.")] + public object? CertificateValidator { get; set; } + public object? CertificateManager { get; set; } } // ─── Stubs for UA0010 (Remove IDisposable on cert/identity types) ─── diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs index 5537ef4860..bb1e3e58a6 100644 --- a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs +++ b/Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs @@ -133,7 +133,7 @@ public static bool IsNullOrEmpty(LocalizedText value) /// public static class DataValueObsolete { - extension(ExtensionObject) + extension(DataValue) { /// /// Returns true if the status code is good. diff --git a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md index ec838b12c3..8f50880eb5 100644 --- a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md +++ b/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md @@ -23,3 +23,4 @@ UA0018 | Migration | Info | Replace `CertificateIdentifier.Certificate` get UA0019 | Migration | Warning | Replace `new DataValue(StatusCode[, ts])` with `DataValue.FromStatusCode(...)`. UA0020 | Migration | Warning | Replace `EncodeableFactory.GlobalFactory` / `EncodeableFactory.Create()` with `ServiceMessageContext.Factory` / `Fork()`. UA0021 | Migration | Info | Replace `CertificateValidator` / `CertificateValidationEventArgs` with the 1.6 `ICertificateManager` / `ICertificateValidatorEx` / `CertificateValidationResult` pipeline. See MigrationGuide.md. +UA0022 | Migration | Warning | Replace `ApplicationConfiguration.CertificateValidator` / `ServerBase.CertificateValidator` property access with `.CertificateManager`. diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs index 9e3a5705b5..b2993de8fb 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs @@ -59,7 +59,9 @@ namespace Opc.Ua.CodeFixers.Analyzers /// referenced. /// If the legacy type has been genuinely removed (consumer is on the bare 1.6 stack /// and the call site no longer compiles), fall back to a syntactic match on the bare - /// identifier name, scoped to source files that import Opc.Ua. + /// identifier name, scoped to source files that import any Opc.Ua namespace (bare + /// using Opc.Ua; or any sub-namespace such as using Opc.Ua.Server;) or that + /// declare a namespace under the Opc.Ua tree. /// /// [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -160,7 +162,7 @@ private static void AnalyzeIdentifierSyntactic(SyntaxNodeAnalysisContext context return; } - if (!HasOpcUaUsing(identifier)) + if (!HasOpcUaContext(identifier)) { return; } @@ -171,14 +173,16 @@ private static void AnalyzeIdentifierSyntactic(SyntaxNodeAnalysisContext context text)); } - private static bool HasOpcUaUsing(SyntaxNode node) + private static bool HasOpcUaContext(SyntaxNode node) { for (SyntaxNode? current = node; current is not null; current = current.Parent) { - if (current is BaseNamespaceDeclarationSyntax ns && - ContainsOpcUaUsing(ns.Usings)) + if (current is BaseNamespaceDeclarationSyntax ns) { - return true; + if (IsOpcUaNamespaceName(ns.Name) || ContainsOpcUaUsing(ns.Usings)) + { + return true; + } } if (current is CompilationUnitSyntax compilationUnit) @@ -202,12 +206,27 @@ private static bool ContainsOpcUaUsing(SyntaxList usings) { continue; } - if (string.Equals(@using.Name.ToString(), OpcUaNamespace, StringComparison.Ordinal)) + if (IsOpcUaNamespaceName(@using.Name)) { return true; } } return false; } + + private static bool IsOpcUaNamespaceName(NameSyntax? name) + { + if (name is null) + { + return false; + } + string text = name.ToString(); + // Match bare "Opc.Ua" or any sub-namespace such as "Opc.Ua.Server", + // "Opc.Ua.Configuration", "Opc.Ua.Client.ComplexTypes" etc. We require a '.' + // separator after "Opc.Ua" so that a hypothetical sibling namespace like + // "Opc.UaFoo" does not match. + return string.Equals(text, OpcUaNamespace, StringComparison.Ordinal) + || text.StartsWith(OpcUaNamespace + ".", StringComparison.Ordinal); + } } } diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs new file mode 100644 index 0000000000..271a54b074 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs @@ -0,0 +1,283 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.CodeFixers.Helpers; + +namespace Opc.Ua.CodeFixers.Analyzers +{ + /// + /// UA0022: Detect access to the legacy + /// ApplicationConfiguration.CertificateValidator / + /// ServerBase.CertificateValidator properties (removed in 2.0) and + /// recommend the new CertificateManager property (type + /// ICertificateManager). + /// + /// + /// Dual-mode detection mirrors UA0021: + /// + /// Semantic path () when + /// the legacy CertificateValidator property is still present and + /// marked [Obsolete]. + /// Syntactic fallback () + /// when the legacy property has been removed entirely and the call site no + /// longer compiles — gated by using Opc.Ua; and a receiver whose + /// (possibly error) type name still ends with + /// ApplicationConfiguration / ServerBase. + /// + /// The receiver short name is passed as the {0} message arg. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UA0022CertificateValidatorPropertyRenameAnalyzer : DiagnosticAnalyzer + { + private const string OpcUaNamespace = "Opc.Ua"; + private const string CertificateValidatorPropertyName = "CertificateValidator"; + private const string ApplicationConfigurationTypeName = "ApplicationConfiguration"; + private const string ServerBaseTypeName = "ServerBase"; + private const string ApplicationConfigurationFullName = + OpcUaNamespace + "." + ApplicationConfigurationTypeName; + private const string ServerBaseFullName = OpcUaNamespace + "." + ServerBaseTypeName; + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(DiagnosticDescriptors.UA0022_CertificateValidatorPropertyRename); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static startContext => + { + INamedTypeSymbol? appConfig = + startContext.Compilation.GetTypeByMetadataName(ApplicationConfigurationFullName); + INamedTypeSymbol? serverBase = + startContext.Compilation.GetTypeByMetadataName(ServerBaseFullName); + + startContext.RegisterOperationAction( + ctx => AnalyzePropertyReference(ctx, appConfig, serverBase), + OperationKind.PropertyReference); + + startContext.RegisterSyntaxNodeAction( + ctx => AnalyzeMemberAccessSyntactic(ctx, appConfig, serverBase), + SyntaxKind.SimpleMemberAccessExpression); + }); + } + + private static void AnalyzePropertyReference( + OperationAnalysisContext context, + INamedTypeSymbol? appConfig, + INamedTypeSymbol? serverBase) + { + IPropertyReferenceOperation reference = (IPropertyReferenceOperation)context.Operation; + IPropertySymbol property = reference.Property; + if (property is null || property.Name != CertificateValidatorPropertyName) + { + return; + } + + INamedTypeSymbol containing = property.ContainingType; + if (containing is null) + { + return; + } + + string? receiverName = null; + if (appConfig is not null && IsOrInheritsFrom(containing, appConfig)) + { + receiverName = ApplicationConfigurationTypeName; + } + else if (serverBase is not null && IsOrInheritsFrom(containing, serverBase)) + { + receiverName = ServerBaseTypeName; + } + + if (receiverName is null) + { + return; + } + + if (!property.IsObsolete()) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0022_CertificateValidatorPropertyRename, + reference.Syntax.GetLocation(), + receiverName)); + } + + private static void AnalyzeMemberAccessSyntactic( + SyntaxNodeAnalysisContext context, + INamedTypeSymbol? appConfig, + INamedTypeSymbol? serverBase) + { + MemberAccessExpressionSyntax memberAccess = (MemberAccessExpressionSyntax)context.Node; + if (memberAccess.Name.Identifier.ValueText != CertificateValidatorPropertyName) + { + return; + } + + // Skip if semantic path already matched (avoid double-fire). + SymbolInfo symInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken); + if (symInfo.Symbol is IPropertySymbol resolved && + resolved.ContainingType is INamedTypeSymbol containingResolved) + { + string fullName = containingResolved.ToDisplayString(); + if (fullName == ApplicationConfigurationFullName || + fullName == ServerBaseFullName) + { + // Semantic action handles it (and gates on [Obsolete]). + return; + } + if ((appConfig is not null && IsOrInheritsFrom(containingResolved, appConfig)) || + (serverBase is not null && IsOrInheritsFrom(containingResolved, serverBase))) + { + return; + } + } + + if (!HasOpcUaUsing(memberAccess)) + { + return; + } + + TypeInfo receiverType = context.SemanticModel.GetTypeInfo( + memberAccess.Expression, + context.CancellationToken); + ITypeSymbol? type = receiverType.Type ?? receiverType.ConvertedType; + string? receiverName = ClassifyReceiverName(type); + if (receiverName is null) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UA0022_CertificateValidatorPropertyRename, + memberAccess.GetLocation(), + receiverName)); + } + + private static string? ClassifyReceiverName(ITypeSymbol? type) + { + if (type is null) + { + return null; + } + string name = type.Name; + if (string.IsNullOrEmpty(name)) + { + // Error symbol with no name — be lenient: examine the display string. + string display = type.ToDisplayString(); + if (display.Contains(ApplicationConfigurationTypeName, StringComparison.Ordinal)) + { + return ApplicationConfigurationTypeName; + } + if (display.Contains(ServerBaseTypeName, StringComparison.Ordinal)) + { + return ServerBaseTypeName; + } + return null; + } + + if (name.Contains(ApplicationConfigurationTypeName, StringComparison.Ordinal)) + { + return ApplicationConfigurationTypeName; + } + if (name.Contains(ServerBaseTypeName, StringComparison.Ordinal)) + { + return ServerBaseTypeName; + } + return null; + } + + private static bool IsOrInheritsFrom(INamedTypeSymbol candidate, INamedTypeSymbol target) + { + SymbolEqualityComparer eq = SymbolEqualityComparer.Default; + INamedTypeSymbol? current = candidate; + while (current is not null) + { + if (eq.Equals(current, target)) + { + return true; + } + current = current.BaseType; + } + return false; + } + + private static bool HasOpcUaUsing(SyntaxNode node) + { + for (SyntaxNode? current = node; current is not null; current = current.Parent) + { + if (current is BaseNamespaceDeclarationSyntax ns && + ContainsOpcUaUsing(ns.Usings)) + { + return true; + } + + if (current is CompilationUnitSyntax compilationUnit) + { + return ContainsOpcUaUsing(compilationUnit.Usings); + } + } + + return false; + } + + private static bool ContainsOpcUaUsing(SyntaxList usings) + { + foreach (UsingDirectiveSyntax @using in usings) + { + if (@using.Alias is not null || @using.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) + { + continue; + } + if (@using.Name is null) + { + continue; + } + string text = @using.Name.ToString(); + if (string.Equals(text, OpcUaNamespace, StringComparison.Ordinal) || + text.StartsWith(OpcUaNamespace + ".", StringComparison.Ordinal)) + { + return true; + } + } + return false; + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs new file mode 100644 index 0000000000..8615a49ad3 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs @@ -0,0 +1,104 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Opc.Ua.CodeFixers.Diagnostics; + +namespace Opc.Ua.CodeFixers.CodeFixes +{ + /// + /// UA0022 code fix: rewrite xxx.CertificateValidator as + /// xxx.CertificateManager. Only the property identifier is + /// renamed; downstream member access on the (now differently-typed) + /// ICertificateManager result may still need manual review. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UA0022CertificateValidatorPropertyRenameCodeFix)), Shared] + public sealed class UA0022CertificateValidatorPropertyRenameCodeFix : CodeFixProvider + { + private const string NewPropertyName = "CertificateManager"; + + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(DiagnosticIds.UA0022); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = (await context.Document.GetSyntaxRootAsync(context.CancellationToken) + .ConfigureAwait(false))!; + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + MemberAccessExpressionSyntax memberAccess = node.AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + if (memberAccess is null || + memberAccess.Name.Identifier.ValueText != "CertificateValidator") + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use 'CertificateManager' property", + createChangedDocument: ct => ApplyAsync(context.Document, memberAccess, ct), + equivalenceKey: DiagnosticIds.UA0022), + diagnostic); + } + } + + private static async Task ApplyAsync( + Document document, + MemberAccessExpressionSyntax memberAccess, + CancellationToken cancellationToken) + { + SyntaxToken oldIdentifier = memberAccess.Name.Identifier; + SyntaxToken newIdentifier = SyntaxFactory.Identifier( + oldIdentifier.LeadingTrivia, + NewPropertyName, + oldIdentifier.TrailingTrivia); + MemberAccessExpressionSyntax newMemberAccess = memberAccess + .WithName(SyntaxFactory.IdentifierName(newIdentifier)); + + SyntaxNode root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!; + SyntaxNode newRoot = root.ReplaceNode(memberAccess, newMemberAccess); + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs index 2cba0510b5..35f9ef7c3e 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs @@ -182,5 +182,12 @@ private static DiagnosticDescriptor Create( "'{0}' was replaced in 1.6 by the new CertificateManager pipeline (ICertificateManager / ICertificateValidatorEx / CertificateValidationResult). The migration is structural (event-based -> async result + AcceptError callback) — see MigrationGuide.md#ua0021.", DiagnosticSeverity.Info, "The CertificateValidator class and CertificateValidationEventArgs were removed in 1.6. The new ICertificateManager (composed of ICertificateValidatorEx, ICertificateRegistry, ICertificateTrustListManager, ICertificateLifecycle) replaces them; per-error accept logic moves from the CertificateValidation event to CertificateValidationOptions.AcceptError. This rule is diagnostic-only because the migration changes the API shape (no mechanical rename)."); + + public static readonly DiagnosticDescriptor UA0022_CertificateValidatorPropertyRename = Create( + DiagnosticIds.UA0022, + "ApplicationConfiguration.CertificateValidator / ServerBase.CertificateValidator renamed in 2.0", + "'{0}.CertificateValidator' was removed in 2.0 — use '{0}.CertificateManager' (type ICertificateManager)", + DiagnosticSeverity.Warning, + "Configure via CertificateManagerFactory.Create(securityConfiguration, telemetry, ...). See MigrationGuide.md."); } } diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs index d637e366ad..10f41e2a49 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs @@ -55,6 +55,7 @@ internal static class DiagnosticIds public const string UA0019 = "UA0019"; public const string UA0020 = "UA0020"; public const string UA0021 = "UA0021"; + public const string UA0022 = "UA0022"; /// The diagnostic category every UA00xx rule belongs to. public const string Category = "Migration"; diff --git a/Tools/Opc.Ua.CodeFixers/NugetREADME.md b/Tools/Opc.Ua.CodeFixers/NugetREADME.md index 47c8a51034..b39e401465 100644 --- a/Tools/Opc.Ua.CodeFixers/NugetREADME.md +++ b/Tools/Opc.Ua.CodeFixers/NugetREADME.md @@ -5,7 +5,7 @@ A single NuGet install (`OPCFoundation.NetStandard.Opc.Ua.CodeFixers`) that ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: -- a Roslyn **analyzer + code-fixer** set (`UA0001`–`UA0020`) that flags every +- a Roslyn **analyzer + code-fixer** set (`UA0001`–`UA0022`) that flags every pattern covered by [`Docs/MigrationGuide.md`](../../Docs/MigrationGuide.md) and, where safe, applies the fix automatically; and - a **compatibility shim** assembly (`Opc.Ua.CodeFixers.Shim.dll`) that @@ -53,6 +53,7 @@ ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: | UA0019 | Warning | `new DataValue(StatusCode[, ts])` | | UA0020 | Warning | `EncodeableFactory.GlobalFactory` / `Create()` | | UA0021 | Info | `CertificateValidator` / `CertificateValidationEventArgs` (structural rename in 1.6) | +| UA0022 | Warning | `ApplicationConfiguration.CertificateValidator` / `ServerBase.CertificateValidator` (renamed in 2.0 to `.CertificateManager`) | ## What the shim provides diff --git a/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props b/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props new file mode 100644 index 0000000000..52bf0b4fc5 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props @@ -0,0 +1,15 @@ + + + + + + diff --git a/Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props b/Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props deleted file mode 100644 index 0e7f751e81..0000000000 --- a/Tools/Opc.Ua.CodeFixers/OPCFoundation.Opc.Ua.CodeFixers.props +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj index 3e33f5f5e1..5f3043699e 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj @@ -44,7 +44,7 @@ - + @@ -72,7 +72,7 @@ inside GetBuildVersion). Static evaluation in a PropertyGroup is too early. --> - version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.Shim\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.Opc.Ua.CodeFixers.props + version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.Shim\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec index c8ebd9e2c5..ad558be608 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec @@ -75,7 +75,7 @@ - + From 70ed572a66ced8e114530fbaad953aca4e81044b Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:51:19 +0200 Subject: [PATCH 5/8] [Tools] Add 1.5.378-compatible obsolete ctor to GlobalDiscoverySampleServer The 2.0 ctor inserted a required ITelemetryContext parameter BEFORE the trailing 'bool autoApprove = true' parameter, which silently breaks 1.5.378 callers that wrote: new GlobalDiscoverySampleServer(database, request, group, userDb, true) because the trailing 'true' now binds to ITelemetryContext (compile error). This was the lone F6-shape error surfaced by the Phase 11 CodeFixers dogfood run against UA-.NETStandard-Samples. Add an [Obsolete] back-compat overload matching the 1.5.378 signature exactly, forwarding to the modern ctor with telemetry: null!. Existing in-tree callers (test fixtures, Aot tests) already use the new ITelemetryContext signature, so this is purely additive and gives sample-style code a soft landing. --- .../GlobalDiscoverySampleServer.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 2b416a6ca2..874b8c4159 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -74,6 +74,27 @@ public GlobalDiscoverySampleServer( m_autoApprove = autoApprove; } + /// + /// Back-compat ctor matching the 1.5.378 signature (no ). + /// Forwards to the modern ctor with a null telemetry context. + /// + /// + /// Preserved so 1.5.378-style sample code (`new GlobalDiscoverySampleServer(database, + /// request, certificateGroup, userDatabase, autoApprove)`) continues to compile against + /// 2.0 without re-ordering the call site. Consumers should pass an explicit + /// via the non-obsolete ctor. + /// + [Obsolete("Use the constructor that takes an ITelemetryContext parameter instead.")] + public GlobalDiscoverySampleServer( + IApplicationsDatabase database, + ICertificateRequest request, + ICertificateGroup certificateGroup, + IUserDatabase userDatabase, + bool autoApprove = true) + : this(database, request, certificateGroup, userDatabase, telemetry: null!, autoApprove) + { + } + /// /// Called before the server starts. Registers GDS-specific /// encodeable types in the server's message context factory, From 861fa6ee13a06d66d5095f1f35d02ab6968f2b81 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 15:01:24 +0200 Subject: [PATCH 6/8] [Tools] Split CodeFixers into analyzer + code-fix DLLs (Roslyn 4.x API) Previously the single Opc.Ua.CodeFixers.dll bundled both DiagnosticAnalyzer and CodeFixProvider types and referenced Microsoft.CodeAnalysis 5.3 (.NET SDK 10's csc-internal version). This caused csc.exe to silently *fail* to load the analyzer: csc.exe ships only Microsoft.CodeAnalysis.dll + CSharp.dll in its bincore, not Workspaces. When the analyzer DLL was JIT-loaded, lazy resolution of Workspaces types from CodeFix code paths crashed the analyzer host, which swallowed the failure silently. Dogfood result: 0 UA diagnostics across all 42 sample projects despite the analyzer being on csc's /analyzer: line. Fix: * Split into Opc.Ua.CodeFixers.dll (analyzers only, references Microsoft. CodeAnalysis.CSharp only) and Opc.Ua.CodeFixers.CodeFixes.dll (code-fix providers, references Microsoft.CodeAnalysis.CSharp.Workspaces). Both ship under analyzers/dotnet/cs/; csc.exe loads the first, Workspaces- aware hosts (VS, dotnet format) load both. * Pin Microsoft.CodeAnalysis.CSharp to 4.14.0 via VersionOverride in both analyzer projects. The repo-wide 5.3 version is the csc compiler version, not a stable analyzer API; analyzers must target the stable 4.x API. * Share DiagnosticIds/DiagnosticDescriptors + Helpers via to avoid a NuGet restore cycle (CodeFixes ProjectReferences would loop). * Hoist UA0008.MethodNameProperty and UA0020.{FormProperty,FormGlobalFactory, FormCreate} into a shared internal WellKnownProperties.cs so the analyzer + code-fix copies can pull from the same string constants. * Nuspec ships both DLLs in analyzers/dotnet/cs/. * Auto-import props (build/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props) references both DLLs as items. Verified: * Pack/restore produces a package with both DLLs. * /reportAnalyzer in a real consumer build confirms all 19 analyzers initialize and execute (UA0001-UA0022). * All 107 unit tests pass on net10.0. Also updates .github/agents/opcua-v20-migration.agent.md to put the CodeFixers NuGet install at the top of the migration workflow, so any user upgrading from 1.5.378 to 2.0 starts by adding one PackageReference and lets the analyzer+shim do as much of the work as possible before falling back to the categorical manual rules. --- .github/agents/opcua-v20-migration.agent.md | 110 +++++++++++++++++- .../Opc.Ua.CodeFixers.Tests.csproj | 1 + .../UA0002RemovedCollectionTypeCodeFix.cs | 1 - .../UA0003NullCheckOnStructTypeCodeFix.cs | 0 .../UA0004ConditionalAccessOnStructCodeFix.cs | 0 .../UA0005ByteArrayToByteStringCodeFix.cs | 0 .../UA0006ObsoleteVariantCtorCodeFix.cs | 0 .../UA0007ObsoleteNodeIdStringCtorCodeFix.cs | 0 .../UA0008SessionCallParamsObjectCodeFix.cs | 3 +- .../UA0009DataContractToDataTypeCodeFix.cs | 0 .../UA0010RemoveDisposableCodeFix.cs | 0 ...rtificateFactoryStaticToInstanceCodeFix.cs | 0 .../CodeFixes/UA0014DataValueIsGoodCodeFix.cs | 0 .../UA0019DataValueStatusCodeCtorCodeFix.cs | 0 .../UA0020EncodeableFactoryRenameCodeFix.cs | 5 +- ...rtificateValidatorPropertyRenameCodeFix.cs | 0 .../Opc.Ua.CodeFixers.CodeFixes.csproj | 48 ++++++++ .../Properties/AssemblyInfo.cs | 32 +++++ .../UA0008SessionCallParamsObjectAnalyzer.cs | 2 +- .../UA0020EncodeableFactoryRenameAnalyzer.cs | 8 +- .../Diagnostics/WellKnownProperties.cs | 56 +++++++++ Tools/Opc.Ua.CodeFixers/NugetREADME.md | 21 +++- ...dation.NetStandard.Opc.Ua.CodeFixers.props | 2 + .../Opc.Ua.CodeFixers.csproj | 23 +++- .../Opc.Ua.CodeFixers.nuspec | 1 + UA.slnx | 1 + 26 files changed, 289 insertions(+), 25 deletions(-) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs (99%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0010RemoveDisposableCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0014DataValueIsGoodCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.CodeFixers.CodeFixes}/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs (100%) create mode 100644 Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj create mode 100644 Tools/Opc.Ua.CodeFixers.CodeFixes/Properties/AssemblyInfo.cs create mode 100644 Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs diff --git a/.github/agents/opcua-v20-migration.agent.md b/.github/agents/opcua-v20-migration.agent.md index ab1dc07f31..048ae22e83 100644 --- a/.github/agents/opcua-v20-migration.agent.md +++ b/.github/agents/opcua-v20-migration.agent.md @@ -9,13 +9,111 @@ You are an expert migration agent for upgrading OPC UA .NET Standard application ## Strategy -1. **Build first**: Run `dotnet build` to identify all errors. -2. **Categorize errors**: Group errors by type (struct nullability, collection types, Variant/object, encoder/decoder, etc.). -3. **Fix in order**: Apply fixes in the priority order defined below — some fixes resolve cascading errors. -4. **Rebuild and iterate**: After each batch of fixes, rebuild to verify progress and catch new errors. -5. **Do not suppress warnings**: Fix [Obsolete] warnings properly using the replacement API, do not add `#pragma warning disable`. +**Start with the CodeFixers NuGet package** — it ships an analyzer + code-fix set (UA0001–UA0022) and a compatibility shim DLL that together handle most mechanical migrations automatically. Only fall back to the manual rules below for patterns the analyzers do not cover or that require structural redesign. -## Priority Order for Fixes +1. **Install the migration package**: Add the `OPCFoundation.NetStandard.Opc.Ua.CodeFixers` NuGet to every project that references an `OPCFoundation.NetStandard.Opc.Ua.*` package, as a build-only dependency: + + ```xml + + ``` + + The package bundles two payloads: + - **Analyzer + code-fix DLLs** (`Opc.Ua.CodeFixers.dll`, `Opc.Ua.CodeFixers.CodeFixes.dll`) loaded into csc.exe and the IDE. + - **Compatibility shim** (`Opc.Ua.CodeFixers.Shim.dll`) that re-exposes the 1.5.378 obsolete extension surface so 1.5.378-style call sites compile against 2.0 with warnings instead of errors. + +2. **Bump the OPC UA package versions to `2.0.*-*`** in every consumer project. Do NOT remove existing `OPCFoundation.NetStandard.Opc.Ua.*` references — just update their `Version` attribute. + +3. **Run `dotnet restore` then `dotnet build`**. The shim usually gets the project to compile; what remains are `[Obsolete]` warnings (CS0618) and `UA0001`–`UA0022` analyzer diagnostics that point at the patterns still using the old surface. + +4. **Apply analyzer auto-fixes**: in Visual Studio, hover each `UA00xx` diagnostic and apply the offered Quick Fix. From the command line, run: + + ```bash + dotnet format analyzers .sln \ + --diagnostics UA0002 UA0003 UA0004 UA0005 UA0006 UA0007 UA0008 \ + UA0009 UA0010 UA0012 UA0014 UA0019 UA0020 UA0022 \ + --severity warn + ``` + + The 14 listed rules ship batch code-fixes. The remaining `UA0001`/`UA0011`/`UA0015`/`UA0018`/`UA0021` are diagnostic-only — they require manual judgement. + +5. **Walk the remaining manual patterns** — anything the analyzers did not flag is covered by the categorical rules below. + +6. **Remove the CodeFixers package** once the project is warning-free. You are then on clean 2.0 with no shim dependency. + +7. **Do not suppress `[Obsolete]` or `UA00xx` warnings.** Fix them using the documented replacement; obsolete API will be removed in the next minor release. + +## What the CodeFixers package covers + +| ID | Default | Auto-fix | Replaces | +| ------ | -------- | -------- | --------------------------------------------------------------------------------------- | +| UA0001 | Info | No | `Utils.Trace` / `Utils.LogX` — manually inject `ILogger` via `ITelemetryContext` | +| UA0002 | Warning | Yes | Removed `Collection` wrappers → `ArrayOf` / `List` | +| UA0003 | Warning | Yes | `x == null` on now-struct built-in types → `x.IsNull` | +| UA0004 | Warning | Yes | `?.` on now-struct built-in types → direct access | +| UA0005 | Warning | Yes | `byte[]` where `ByteString` is expected → `.ToByteString()` | +| UA0006 | Warning | Yes | `new Variant(object\|DateTime\|Guid\|byte[])` → `Variant.From(...)` | +| UA0007 | Warning | Yes | `new NodeId(string)` / `new ExpandedNodeId(string)` → `Parse(...)` | +| UA0008 | Warning | Yes | `Session.Call(..., params object[])` → wrap args with `Variant.From` | +| UA0009 | Warning | Yes | `[DataContract]`/`[DataMember]` on config extensions → `[DataType]`/`[DataField]` | +| UA0010 | Warning | Yes | `using`/`Dispose` on `CertificateIdentifier`/`UserIdentity`/`IUserIdentityTokenHandler` | +| UA0011 | Info | No | Sync `IUserIdentityTokenHandler.Encrypt/Decrypt/Sign/Verify` → await `*Async` | +| UA0012 | Warning | Yes | `CertificateFactory.*` static helpers → instance methods | +| UA0014 | Warning | Yes | `DataValue.IsGood(dv)` static helper → `dv.IsGood` | +| UA0015 | Info | No | Sync / APM members on GDS / LDS clients → await `*Async` | +| UA0018 | Info | No | `CertificateIdentifier.Certificate` getter → `LoadCertificate2Async` | +| UA0019 | Warning | Yes | `new DataValue(StatusCode[, ts])` → `new DataValue { StatusCode = ..., ... }` | +| UA0020 | Warning | Yes | `EncodeableFactory.GlobalFactory` / `Create()` → `ServiceMessageContext.Factory` / `Fork()` | +| UA0021 | Info | No | `CertificateValidator` / `CertificateValidationEventArgs` (structural rename — see §X) | +| UA0022 | Warning | Yes | `ApplicationConfiguration.CertificateValidator` / `ServerBase.CertificateValidator` → `.CertificateManager` | + +## What the shim covers + +`Opc.Ua.CodeFixers.Shim.dll` re-exposes the 1.5.378 surface as C# 14 `extension` members so 1.5.378 call sites continue to compile: + +- **Moved obsolete extensions**: `NodeId` / `Variant` / `DataValue` null-check helpers, `Session` sync helpers, `Subscription` sync helpers, `ApplicationInstance` helpers, `ServerBase.Start` / `Stop`, `TransportChannel` APM, `ChannelBase` static factory methods, and similar surface. +- **New shims for genuinely-removed members**: `EncodeableFactory.GlobalFactory`, `CertificateIdentifier.Certificate` (throws), sync wrappers for `IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}`, sync + APM wrappers for GDS / LDS client APIs. + +## What the shim does NOT cover + +These changes are source-level only; no extension method can paper over them. Use the listed analyzer fix. + +- `== null` / `!= null` on now-struct types — use the **UA0003** fixer (auto). +- `?.` member access on now-struct types — use the **UA0004** fixer (auto). +- `using var x = new CertificateIdentifier(...)` — use **UA0010** to drop the `using`. +- `[DataContract]`/`[DataMember]` on configuration extensions — use **UA0009**. +- Removed `Collection` wrappers — use **UA0002**. +- `CertificateValidator` type rename — see §Certificate validation pipeline below (manual). +- Removed pre-generated source-generator output — see §Source Generation (manual). + +## Sync-over-async caveat + +The sync shims (e.g. `handler.Encrypt(bytes)`, `gdsClient.RegisterApplication(...)`, the `Session` / `Subscription` sync helpers) wrap their `*Async` counterparts via `Task.Run(() => …Async(...)).GetAwaiter().GetResult()`. This is intended as a **migration aid only**: it keeps legacy call sites compiling while you port them to `async`/`await`. Do not leave these calls on production hot paths — follow the `UA0011` / `UA0015` guidance and switch to the async APIs. + +## TreatWarningsAsErrors recipe + +If your project sets `true` and you cannot relax it during the migration window, exclude the migration diagnostics from the failure set: + +```xml + + true + $(NoWarn);CS0618;UA0001;UA0002;UA0003;UA0004;UA0005;UA0006;UA0007;UA0008;UA0009;UA0010;UA0011;UA0012;UA0014;UA0015;UA0018;UA0019;UA0020;UA0021;UA0022 + +``` + +Remove each entry as you finish fixing the corresponding rule, and drop the whole block once the CodeFixers package is removed. + +## Known compatibility gaps + +- **Legacy `.NET Framework` projects using the pre-SDK `xmlns="http://schemas.microsoft.com/developer/msbuild/2003"` format** do not honour `PackageReference` injection via `Directory.Build.targets`. To get the analyzer / shim into a legacy WinForms project, add the `` directly to the project file's existing ``. +- **Projects with `true` and 100+ migration errors** may abort csc.exe before the analyzer reaches some patterns. Apply the `` recipe above, run the analyzer, then re-enable warnings-as-errors. + +## Manual migration rules (anything the analyzers / shim do not cover) + +The sections below remain the canonical reference for patterns that require human judgement. Apply them in the priority order shown — some fixes resolve cascading errors. + +### Priority order for fixes Apply changes in this order to minimize cascading errors: diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj b/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj index 62ce4998b1..f1bbf69fea 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj +++ b/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj @@ -27,5 +27,6 @@ + diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs index ee232a17bc..bab08ebfbe 100644 --- a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs +++ b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs @@ -37,7 +37,6 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Analyzers; using Opc.Ua.CodeFixers.Diagnostics; using Opc.Ua.CodeFixers.Helpers; diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs index 663ff57c1e..f9bc3913af 100644 --- a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs +++ b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs @@ -38,7 +38,6 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Analyzers; using Opc.Ua.CodeFixers.Diagnostics; namespace Opc.Ua.CodeFixers.CodeFixes @@ -73,7 +72,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } if (!diagnostic.Properties.TryGetValue( - UA0008SessionCallParamsObjectAnalyzer.MethodNameProperty, + WellKnownProperties.MethodName, out string methodName)) { continue; diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0010RemoveDisposableCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0010RemoveDisposableCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0014DataValueIsGoodCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0014DataValueIsGoodCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs index 318d047da8..23d4b3d715 100644 --- a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs +++ b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs @@ -36,7 +36,6 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Analyzers; using Opc.Ua.CodeFixers.Diagnostics; namespace Opc.Ua.CodeFixers.CodeFixes @@ -64,9 +63,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) foreach (Diagnostic diagnostic in context.Diagnostics) { if (!diagnostic.Properties.TryGetValue( - UA0020EncodeableFactoryRenameAnalyzer.FormProperty, + WellKnownProperties.Form, out string form) || - form != UA0020EncodeableFactoryRenameAnalyzer.FormCreate) + form != WellKnownProperties.FormCreate) { continue; } diff --git a/Tools/Opc.Ua.CodeFixers/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs rename to Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj b/Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj new file mode 100644 index 0000000000..5ad863c024 --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj @@ -0,0 +1,48 @@ + + + netstandard2.0 + false + true + $(AssemblyPrefix).CodeFixers.CodeFixes + Opc.Ua.CodeFixers.CodeFixes + OPC UA .NET Standard migration code-fix providers (1.5.378 to 2.0). Companion to Opc.Ua.CodeFixers analyzers. + + $(NoWarn);RS1007;RS1038;RS2008 + + false + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/Properties/AssemblyInfo.cs b/Tools/Opc.Ua.CodeFixers.CodeFixes/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2b9848014c --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers.CodeFixes/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; + +[assembly: CLSCompliant(false)] diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs index 7aeabf6e7c..8865111252 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs @@ -47,7 +47,7 @@ namespace Opc.Ua.CodeFixers.Analyzers [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UA0008SessionCallParamsObjectAnalyzer : DiagnosticAnalyzer { - public const string MethodNameProperty = "MethodName"; + public const string MethodNameProperty = WellKnownProperties.MethodName; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.UA0008_SessionCallParamsObject); diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs index 5ac178c21e..8ff75b2c8c 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs +++ b/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs @@ -45,9 +45,11 @@ namespace Opc.Ua.CodeFixers.Analyzers [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UA0020EncodeableFactoryRenameAnalyzer : DiagnosticAnalyzer { - public const string FormProperty = "Form"; - public const string FormGlobalFactory = "A"; - public const string FormCreate = "B"; + // Public surface preserved for source compatibility; values delegate to the + // shared WellKnownProperties (also linked into the CodeFixes assembly). + public const string FormProperty = WellKnownProperties.Form; + public const string FormGlobalFactory = WellKnownProperties.FormGlobalFactory; + public const string FormCreate = WellKnownProperties.FormCreate; private const string EncodeableFactoryTypeName = "Opc.Ua.EncodeableFactory"; diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs b/Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs new file mode 100644 index 0000000000..0f89a8dd4d --- /dev/null +++ b/Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs @@ -0,0 +1,56 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +namespace Opc.Ua.CodeFixers.Diagnostics +{ + /// + /// Shared DiagnosticDescriptor.Properties keys passed from analyzers + /// to their companion code-fix providers. + /// + /// + /// Defined here (in the analyzer DLL) and linked into the companion CodeFixes + /// project so both assemblies can use the exact same string constants without + /// the CodeFixes assembly needing a ProjectReference back to the analyzer + /// (which would create a NuGet restore cycle). + /// + internal static class WellKnownProperties + { + /// UA0008: method name extracted from a Session.Call invocation. + public const string MethodName = "MethodName"; + + /// UA0020: form discriminator key for EncodeableFactory rename. + public const string Form = "Form"; + + /// UA0020 form: legacy EncodeableFactory.GlobalFactory getter. + public const string FormGlobalFactory = "A"; + + /// UA0020 form: legacy factory.Create() instance call. + public const string FormCreate = "B"; + } +} diff --git a/Tools/Opc.Ua.CodeFixers/NugetREADME.md b/Tools/Opc.Ua.CodeFixers/NugetREADME.md index b39e401465..275d360889 100644 --- a/Tools/Opc.Ua.CodeFixers/NugetREADME.md +++ b/Tools/Opc.Ua.CodeFixers/NugetREADME.md @@ -115,9 +115,24 @@ whole block once the CodeFixers package is removed. ## Packaging note -The analyzer DLL also hosts the matching `CodeFixProvider` types. This is a -deliberate single-assembly design; `RS1038` (recommending separation) is -suppressed at the project level via ``. +The package ships **two analyzer DLLs** under `analyzers/dotnet/cs/`: + +- `Opc.Ua.CodeFixers.dll` — the analyzer assembly. Targets `Microsoft.CodeAnalysis 4.x` + (the stable analyzer API) and references **only** `Microsoft.CodeAnalysis.CSharp` so it + loads cleanly in csc.exe's analyzer host (which ships only `Microsoft.CodeAnalysis.dll` + + `CSharp.dll`, not `Workspaces`). All `DiagnosticAnalyzer` types live here. +- `Opc.Ua.CodeFixers.CodeFixes.dll` — the code-fix assembly. References + `Microsoft.CodeAnalysis.CSharp.Workspaces` and hosts all `CodeFixProvider` types. + Loaded only by Workspaces-aware hosts (Visual Studio / `dotnet format`). + +This split is necessary because shipping a single DLL that references `Workspaces` +silently fails to load in csc.exe at command-line build time — csc loads the assembly +but JIT-resolution of `Workspaces` types fails (DLL not in bincore), and the analyzer +host swallows the load failure, producing zero diagnostics. Splitting keeps the +analyzer host happy while preserving full IDE/`dotnet format` code-fix functionality. + +`RS1038` (suggesting separation) is the Roslyn rule that recommends this layout; +it is satisfied implicitly by the two-DLL design. ## Suppression recipes diff --git a/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props b/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props index 52bf0b4fc5..f2ec3c296b 100644 --- a/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props +++ b/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props @@ -11,5 +11,7 @@ + diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj index 5f3043699e..b94fb71c45 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj @@ -33,8 +33,12 @@ here in a static PropertyGroup would capture empty version. --> - - + + @@ -58,21 +62,28 @@ all - + bin\$(Configuration)\\ that the nuspec entries reference. + CodeFixes cannot be a ProjectReference here (it back-references this analyzer + for shared DiagnosticIds, which creates a NuGet restore cycle); we invoke its + build via an MSBuild task instead. --> + - version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.Shim\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props + version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.dll;codeFixesDll=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.CodeFixes\bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.CodeFixes.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.Shim\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec index ad558be608..47cf3fb7f1 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec +++ b/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec @@ -69,6 +69,7 @@ + diff --git a/UA.slnx b/UA.slnx index 12f9538c99..54dcb086ba 100644 --- a/UA.slnx +++ b/UA.slnx @@ -124,6 +124,7 @@ + From 2b920fa8f752fe8c4fe6797bc6c2c8cd4e6d407d Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:05:02 +0200 Subject: [PATCH 7/8] [Tools] Rename CodeFixers -> MigrationAnalyzer / MigrationHelpers User-facing rename to better convey purpose: the package was about "migrating" 1.5.378 callers to 2.0, not about generic Roslyn code fixers. Directory / project / assembly / package / namespace renames: Tools/Opc.Ua.CodeFixers -> Tools/Opc.Ua.MigrationAnalyzer Tools/Opc.Ua.CodeFixers.CodeFixes -> Tools/Opc.Ua.MigrationAnalyzer.CodeFixes Tools/Opc.Ua.CodeFixers.Shim -> Tools/Opc.Ua.MigrationHelpers Tests/Opc.Ua.CodeFixers.Tests -> Tests/Opc.Ua.MigrationAnalyzer.Tests Tests/Opc.Ua.CodeFixers.Shim.Tests -> Tests/Opc.Ua.MigrationHelpers.Tests OPCFoundation.NetStandard.Opc.Ua.CodeFixers -> OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer Assembly: Opc.Ua.CodeFixers -> Opc.Ua.MigrationAnalyzer Opc.Ua.CodeFixers.CodeFixes -> Opc.Ua.MigrationAnalyzer.CodeFixes Opc.Ua.CodeFixers.Shim -> Opc.Ua.MigrationHelpers Namespaces: Opc.Ua.CodeFixers.* -> Opc.Ua.MigrationAnalyzer.* Opc.Ua.CodeFixers.Shim -> Opc.Ua.MigrationHelpers Solution layout: Tools/SourceGeneration.slnx -> Tools/Roslyn.slnx (now hosts both source-generation and migration-analyzer projects, plus the CodeFixes project that was missing from the prior slnx.) Central package pinning: Microsoft.CodeAnalysis.CSharp 5.3.0 -> 4.14.0 Microsoft.CodeAnalysis.CSharp.Workspaces 5.3.0 -> 4.14.0 Microsoft.CodeAnalysis.Workspaces.Common 5.3.0 -> 4.14.0 (the analyzer assembly must target the stable 4.x analyzer API; the 5.x range was the csc-internal version and unsafe for analyzers. Per-project VersionOverride removed since the central pin now covers it.) Documentation: Docs/MigrationGuide.md - one ref updated to new package id. .github/agents/opcua-v20-migration.agent.md - all DLL/package/namespace references in the agent text updated to the new names. Test fixes shaken out by the full-slnx build: * Removed redundant async/await in AnalyzerHarness (RCS1174) - was hidden under Roslynator's stricter analyzer that fired post-rename rebuild. * Polyfilled [GeneratedRegex] partial method in OpcUaShimAttributeInventoryTests for net472/net48 - the attribute is .NET 7+ only; on legacy TFMs we fall back to a static compiled Regex. Verified: * dotnet build UA.slnx -c Debug succeeds (0 errors, 2 expected NU1702 warnings from the netstandard2.0 analyzer csproj cross-referencing the multi-TFM helpers project). * dotnet test ... MigrationAnalyzer.Tests passes 107/107 on net10.0. * dotnet test ... MigrationHelpers.Tests passes 10/10 + 3 skipped. * dotnet pack ... MigrationAnalyzer.csproj produces a package with two analyzer DLLs (analyzers/dotnet/cs/) + six TFMs of helpers under lib/. * Analyzer DLL references only Microsoft.CodeAnalysis 4.14.0 + Microsoft.CodeAnalysis.CSharp 4.14.0 (Workspaces-free, csc-safe). --- .github/agents/opcua-v20-migration.agent.md | 20 +++++++------- Directory.Packages.props | 6 ++--- Docs/MigrationGuide.md | 2 +- .../Opc.Ua.Configuration.csproj | 2 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 2 +- .../AnalyzerHarness.cs | 10 +++---- .../Analyzers/UA0001Tests.cs | 4 +-- .../Analyzers/UA0002Tests.cs | 6 ++--- .../Analyzers/UA0003Tests.cs | 6 ++--- .../Analyzers/UA0004Tests.cs | 6 ++--- .../Analyzers/UA0005Tests.cs | 6 ++--- .../Analyzers/UA0006Tests.cs | 6 ++--- .../Analyzers/UA0007Tests.cs | 6 ++--- .../Analyzers/UA0008Tests.cs | 6 ++--- .../Analyzers/UA0009Tests.cs | 6 ++--- .../Analyzers/UA0010Tests.cs | 4 +-- .../Analyzers/UA0011Tests.cs | 4 +-- .../Analyzers/UA0012Tests.cs | 6 ++--- .../Analyzers/UA0014Tests.cs | 6 ++--- .../Analyzers/UA0015Tests.cs | 4 +-- .../Analyzers/UA0018Tests.cs | 4 +-- .../Analyzers/UA0019Tests.cs | 6 ++--- .../Analyzers/UA0020Tests.cs | 6 ++--- .../Analyzers/UA0021Tests.cs | 4 +-- .../Analyzers/UA0022Tests.cs | 6 ++--- .../Opc.Ua.MigrationAnalyzer.Tests.csproj} | 8 +++--- .../Properties/AssemblyInfo.cs | 0 .../Stubs/OpcUaStubs.cs | 2 +- .../CertificateIdentifierShimTests.cs | 2 +- .../DataValueObsoleteShimTests.cs | 2 +- .../EncodeableFactoryShimTests.cs | 2 +- .../GlobalDiscoveryServerClientShimTests.cs | 2 +- .../LocalDiscoveryServerClientShimTests.cs | 2 +- .../Opc.Ua.MigrationHelpers.Tests.csproj} | 6 ++--- .../OpcUaShimAttributeInventoryTests.cs | 8 +++++- .../Properties/AssemblyInfo.cs | 0 .../UserIdentityTokenHandlerShimTests.cs | 2 +- .../Opc.Ua.Types.Tests.csproj | 2 +- .../UA0002RemovedCollectionTypeCodeFix.cs | 6 ++--- .../UA0003NullCheckOnStructTypeCodeFix.cs | 4 +-- .../UA0004ConditionalAccessOnStructCodeFix.cs | 4 +-- .../UA0005ByteArrayToByteStringCodeFix.cs | 4 +-- .../UA0006ObsoleteVariantCtorCodeFix.cs | 4 +-- .../UA0007ObsoleteNodeIdStringCtorCodeFix.cs | 4 +-- .../UA0008SessionCallParamsObjectCodeFix.cs | 4 +-- .../UA0009DataContractToDataTypeCodeFix.cs | 4 +-- .../UA0010RemoveDisposableCodeFix.cs | 4 +-- ...rtificateFactoryStaticToInstanceCodeFix.cs | 4 +-- .../CodeFixes/UA0014DataValueIsGoodCodeFix.cs | 4 +-- .../UA0019DataValueStatusCodeCtorCodeFix.cs | 4 +-- .../UA0020EncodeableFactoryRenameCodeFix.cs | 4 +-- ...rtificateValidatorPropertyRenameCodeFix.cs | 4 +-- ...Opc.Ua.MigrationAnalyzer.CodeFixes.csproj} | 20 +++++++------- .../Properties/AssemblyInfo.cs | 0 .../AnalyzerReleases.Shipped.md | 0 .../AnalyzerReleases.Unshipped.md | 0 .../UA0001UtilsTraceToILoggerAnalyzer.cs | 6 ++--- .../UA0002RemovedCollectionTypeAnalyzer.cs | 6 ++--- .../UA0003NullCheckOnStructTypeAnalyzer.cs | 6 ++--- ...UA0004ConditionalAccessOnStructAnalyzer.cs | 6 ++--- .../UA0005ByteArrayToByteStringAnalyzer.cs | 6 ++--- .../UA0006ObsoleteVariantCtorAnalyzer.cs | 6 ++--- .../UA0007ObsoleteNodeIdStringCtorAnalyzer.cs | 4 +-- .../UA0008SessionCallParamsObjectAnalyzer.cs | 6 ++--- .../UA0009DataContractToDataTypeAnalyzer.cs | 6 ++--- .../UA0010RemoveDisposableAnalyzer.cs | 6 ++--- .../UA0011TokenHandlerSyncToAsyncAnalyzer.cs | 6 ++--- ...tificateFactoryStaticToInstanceAnalyzer.cs | 6 ++--- .../UA0014DataValueIsGoodAnalyzer.cs | 4 +-- .../Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs | 6 ++--- ...ertificateIdentifierCertificateAnalyzer.cs | 6 ++--- .../UA0019DataValueStatusCodeCtorAnalyzer.cs | 4 +-- .../UA0020EncodeableFactoryRenameAnalyzer.cs | 6 ++--- ...A0021CertificateValidatorRenameAnalyzer.cs | 6 ++--- ...tificateValidatorPropertyRenameAnalyzer.cs | 6 ++--- .../Diagnostics/DiagnosticDescriptors.cs | 2 +- .../Diagnostics/DiagnosticIds.cs | 4 +-- .../Diagnostics/WellKnownProperties.cs | 2 +- .../Helpers/SymbolExtensions.cs | 2 +- .../Helpers/UaSymbols.cs | 2 +- .../NugetREADME.md | 18 ++++++------- ...etStandard.Opc.Ua.MigrationAnalyzer.props} | 10 +++---- .../Opc.Ua.MigrationAnalyzer.csproj} | 26 +++++++++---------- .../Opc.Ua.MigrationAnalyzer.nuspec} | 22 ++++++++-------- .../Properties/AssemblyInfo.cs | 0 .../Marker/OpcUaShimAttribute.cs | 2 +- .../Opc.Ua.MigrationHelpers.csproj} | 6 ++--- .../Properties/AssemblyInfo.cs | 0 .../Shims/Client/Session/Session.cs | 0 .../Subscription/Classic/Subscription.cs | 0 .../Configuration/ApplicationInstance.cs | 0 .../Certificates/CertificateIdentifier.cs | 0 .../Shims/Core/Stack/Client/ChannelBase.cs | 0 .../Configuration/ApplicationConfiguration.cs | 0 .../Shims/Core/Stack/Server/ServerBase.cs | 0 .../Core/Stack/Transport/TransportChannel.cs | 0 .../Stack/Types/IUserIdentityTokenHandler.cs | 0 .../Core/Types/Encoders/EncodeableFactory.cs | 0 .../GlobalDiscoveryServerClient.cs | 0 .../LocalDiscoveryServerClient.cs | 0 .../ServerPushConfigurationClient.cs | 0 .../Shims/Types/BuiltIn/BuiltInType.cs | 0 .../readme.md | 2 +- Tools/{SourceGeneration.slnx => Roslyn.slnx} | 11 ++++---- UA.slnx | 12 ++++----- 105 files changed, 248 insertions(+), 241 deletions(-) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/AnalyzerHarness.cs (96%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0001Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0002Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0003Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0004Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0005Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0006Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0007Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0008Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0009Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0010Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0011Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0012Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0014Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0015Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0018Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0019Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0020Tests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0021Tests.cs (99%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Analyzers/UA0022Tests.cs (97%) rename Tests/{Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj => Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj} (79%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Properties/AssemblyInfo.cs (100%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationAnalyzer.Tests}/Stubs/OpcUaStubs.cs (99%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/CertificateIdentifierShimTests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/DataValueObsoleteShimTests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/EncodeableFactoryShimTests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/GlobalDiscoveryServerClientShimTests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/LocalDiscoveryServerClientShimTests.cs (98%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj => Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj} (87%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/OpcUaShimAttributeInventoryTests.cs (94%) rename Tests/{Opc.Ua.CodeFixers.Tests => Opc.Ua.MigrationHelpers.Tests}/Properties/AssemblyInfo.cs (100%) rename Tests/{Opc.Ua.CodeFixers.Shim.Tests => Opc.Ua.MigrationHelpers.Tests}/UserIdentityTokenHandlerShimTests.cs (99%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs (97%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs (97%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs (99%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0010RemoveDisposableCodeFix.cs (96%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0014DataValueIsGoodCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs (98%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj => Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj} (72%) rename Tools/{Opc.Ua.CodeFixers.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixes}/Properties/AssemblyInfo.cs (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/AnalyzerReleases.Shipped.md (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/AnalyzerReleases.Unshipped.md (100%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0010RemoveDisposableAnalyzer.cs (98%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0014DataValueIsGoodAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs (97%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs (98%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs (98%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Diagnostics/DiagnosticDescriptors.cs (99%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Diagnostics/DiagnosticIds.cs (96%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Diagnostics/WellKnownProperties.cs (98%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Helpers/SymbolExtensions.cs (99%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/Helpers/UaSymbols.cs (99%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationAnalyzer}/NugetREADME.md (90%) rename Tools/{Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props => Opc.Ua.MigrationAnalyzer/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props} (77%) rename Tools/{Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj => Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.csproj} (75%) rename Tools/{Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec => Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec} (83%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationAnalyzer}/Properties/AssemblyInfo.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Marker/OpcUaShimAttribute.cs (97%) rename Tools/{Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj => Opc.Ua.MigrationHelpers/Opc.Ua.MigrationHelpers.csproj} (82%) rename Tools/{Opc.Ua.CodeFixers => Opc.Ua.MigrationHelpers}/Properties/AssemblyInfo.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Client/Session/Session.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Client/Subscription/Classic/Subscription.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Configuration/ApplicationInstance.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Security/Certificates/CertificateIdentifier.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Stack/Client/ChannelBase.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Stack/Server/ServerBase.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Stack/Transport/TransportChannel.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Core/Types/Encoders/EncodeableFactory.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/Shims/Types/BuiltIn/BuiltInType.cs (100%) rename Tools/{Opc.Ua.CodeFixers.Shim => Opc.Ua.MigrationHelpers}/readme.md (95%) rename Tools/{SourceGeneration.slnx => Roslyn.slnx} (72%) diff --git a/.github/agents/opcua-v20-migration.agent.md b/.github/agents/opcua-v20-migration.agent.md index 048ae22e83..5529706aeb 100644 --- a/.github/agents/opcua-v20-migration.agent.md +++ b/.github/agents/opcua-v20-migration.agent.md @@ -9,19 +9,19 @@ You are an expert migration agent for upgrading OPC UA .NET Standard application ## Strategy -**Start with the CodeFixers NuGet package** — it ships an analyzer + code-fix set (UA0001–UA0022) and a compatibility shim DLL that together handle most mechanical migrations automatically. Only fall back to the manual rules below for patterns the analyzers do not cover or that require structural redesign. +**Start with the MigrationAnalyzer NuGet package** — it ships an analyzer + code-fix set (UA0001–UA0022) and a compatibility shim DLL that together handle most mechanical migrations automatically. Only fall back to the manual rules below for patterns the analyzers do not cover or that require structural redesign. -1. **Install the migration package**: Add the `OPCFoundation.NetStandard.Opc.Ua.CodeFixers` NuGet to every project that references an `OPCFoundation.NetStandard.Opc.Ua.*` package, as a build-only dependency: +1. **Install the migration package**: Add the `OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` NuGet to every project that references an `OPCFoundation.NetStandard.Opc.Ua.*` package, as a build-only dependency: ```xml - ``` The package bundles two payloads: - - **Analyzer + code-fix DLLs** (`Opc.Ua.CodeFixers.dll`, `Opc.Ua.CodeFixers.CodeFixes.dll`) loaded into csc.exe and the IDE. - - **Compatibility shim** (`Opc.Ua.CodeFixers.Shim.dll`) that re-exposes the 1.5.378 obsolete extension surface so 1.5.378-style call sites compile against 2.0 with warnings instead of errors. + - **Analyzer + code-fix DLLs** (`Opc.Ua.MigrationAnalyzer.dll`, `Opc.Ua.MigrationAnalyzer.CodeFixes.dll`) loaded into csc.exe and the IDE. + - **Compatibility shim** (`Opc.Ua.MigrationHelpers.dll`) that re-exposes the 1.5.378 obsolete extension surface so 1.5.378-style call sites compile against 2.0 with warnings instead of errors. 2. **Bump the OPC UA package versions to `2.0.*-*`** in every consumer project. Do NOT remove existing `OPCFoundation.NetStandard.Opc.Ua.*` references — just update their `Version` attribute. @@ -40,11 +40,11 @@ You are an expert migration agent for upgrading OPC UA .NET Standard application 5. **Walk the remaining manual patterns** — anything the analyzers did not flag is covered by the categorical rules below. -6. **Remove the CodeFixers package** once the project is warning-free. You are then on clean 2.0 with no shim dependency. +6. **Remove the MigrationAnalyzer package** once the project is warning-free. You are then on clean 2.0 with no shim dependency. 7. **Do not suppress `[Obsolete]` or `UA00xx` warnings.** Fix them using the documented replacement; obsolete API will be removed in the next minor release. -## What the CodeFixers package covers +## What the MigrationAnalyzer package covers | ID | Default | Auto-fix | Replaces | | ------ | -------- | -------- | --------------------------------------------------------------------------------------- | @@ -70,7 +70,7 @@ You are an expert migration agent for upgrading OPC UA .NET Standard application ## What the shim covers -`Opc.Ua.CodeFixers.Shim.dll` re-exposes the 1.5.378 surface as C# 14 `extension` members so 1.5.378 call sites continue to compile: +`Opc.Ua.MigrationHelpers.dll` re-exposes the 1.5.378 surface as C# 14 `extension` members so 1.5.378 call sites continue to compile: - **Moved obsolete extensions**: `NodeId` / `Variant` / `DataValue` null-check helpers, `Session` sync helpers, `Subscription` sync helpers, `ApplicationInstance` helpers, `ServerBase.Start` / `Stop`, `TransportChannel` APM, `ChannelBase` static factory methods, and similar surface. - **New shims for genuinely-removed members**: `EncodeableFactory.GlobalFactory`, `CertificateIdentifier.Certificate` (throws), sync wrappers for `IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}`, sync + APM wrappers for GDS / LDS client APIs. @@ -102,11 +102,11 @@ If your project sets `true` and y ``` -Remove each entry as you finish fixing the corresponding rule, and drop the whole block once the CodeFixers package is removed. +Remove each entry as you finish fixing the corresponding rule, and drop the whole block once the MigrationAnalyzer package is removed. ## Known compatibility gaps -- **Legacy `.NET Framework` projects using the pre-SDK `xmlns="http://schemas.microsoft.com/developer/msbuild/2003"` format** do not honour `PackageReference` injection via `Directory.Build.targets`. To get the analyzer / shim into a legacy WinForms project, add the `` directly to the project file's existing ``. +- **Legacy `.NET Framework` projects using the pre-SDK `xmlns="http://schemas.microsoft.com/developer/msbuild/2003"` format** do not honour `PackageReference` injection via `Directory.Build.targets`. To get the analyzer / shim into a legacy WinForms project, add the `` directly to the project file's existing ``. - **Projects with `true` and 100+ migration errors** may abort csc.exe before the analyzer reaches some patterns. Apply the `` recipe above, run the analyzer, then re-enable warnings-as-errors. ## Manual migration rules (anything the analyzers / shim do not cover) diff --git a/Directory.Packages.props b/Directory.Packages.props index fd1e813434..04c2c90c35 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,9 +20,9 @@ - - - + + + diff --git a/Docs/MigrationGuide.md b/Docs/MigrationGuide.md index b15eb6e4d9..06775291e1 100644 --- a/Docs/MigrationGuide.md +++ b/Docs/MigrationGuide.md @@ -89,7 +89,7 @@ This document outlines the breaking changes introduced from version to version. ## Migrating from 1.5.378 to 2.0.x -> **Automate the migration.** Add the `OPCFoundation.NetStandard.Opc.Ua.CodeFixers` analyzer package to your projects to receive analyzer warnings and one-click fixes for the patterns in this guide. Rule IDs `UA0001`-`UA0020` map directly to the sections below. +> **Automate the migration.** Add the `OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` analyzer package to your projects to receive analyzer warnings and one-click fixes for the patterns in this guide. Rule IDs `UA0001`-`UA0020` map directly to the sections below. Version 2.0 introduces a major architectural change from pre-generated code files to runtime source generation and more efficient memory use with a several major Breaking Changes requiring changes to your applications. diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index e41a672830..e46eb13c53 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -12,7 +12,7 @@ - + $(PackageId).Debug diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 8be7b7f832..c1fde8f251 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -17,7 +17,7 @@ - + $(PackageId).Debug diff --git a/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/AnalyzerHarness.cs similarity index 96% rename from Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/AnalyzerHarness.cs index ddf3cfeb22..2edade78ae 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/AnalyzerHarness.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/AnalyzerHarness.cs @@ -43,7 +43,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; -namespace Opc.Ua.CodeFixers.Tests +namespace Opc.Ua.MigrationAnalyzer.Tests { /// /// Lightweight, no-extra-package analyzer + code-fix test harness. Lifts the @@ -113,24 +113,24 @@ public static CSharpCompilation CompileWithoutStubs(string userSource, string as /// Run against and /// return only the analyzer's diagnostics (compiler diagnostics are filtered out). /// - public static async Task> GetAnalyzerDiagnosticsAsync( + public static Task> GetAnalyzerDiagnosticsAsync( DiagnosticAnalyzer analyzer, string userSource) { CSharpCompilation compilation = Compile(userSource); - return await RunAsync(analyzer, compilation).ConfigureAwait(false); + return RunAsync(analyzer, compilation); } /// /// Run against compiled /// WITHOUT the OPC UA stub surface. See . /// - public static async Task> GetAnalyzerDiagnosticsWithoutStubsAsync( + public static Task> GetAnalyzerDiagnosticsWithoutStubsAsync( DiagnosticAnalyzer analyzer, string userSource) { CSharpCompilation compilation = CompileWithoutStubs(userSource); - return await RunAsync(analyzer, compilation).ConfigureAwait(false); + return RunAsync(analyzer, compilation); } private static async Task> RunAsync( diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0001Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0001Tests.cs index 30970eab72..c94cfaab97 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0001Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0001Tests.cs @@ -33,9 +33,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.MigrationAnalyzer.Analyzers; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0001 (Utils.Trace / Utils.LogX -> ILogger). Diagnostic-only; diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs index 2493bd8985..a64b519fb9 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0002Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0002 (removed <Type>Collection wrappers). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs index 81999b6641..e61b44350c 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0003Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0003 (null comparison on now-struct built-in type). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs index 15b3dc2e42..40847c48a2 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0004Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs @@ -33,10 +33,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0004 (null-conditional access on now-struct built-in type). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs index 5da291ccf2..14e3b1a19e 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0005Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs @@ -33,10 +33,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0005 (byte[] passed where ByteString is now expected). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs index b349ccc961..2de63a5ed2 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0006Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0006 (obsolete Variant(object|DateTime|Guid|byte[]) constructors). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs index a9693de270..8f7785a36f 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0007Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0007 (obsolete NodeId/ExpandedNodeId string constructor). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs index dfe4482863..6502c595af 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0008Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0008 (Session.Call params object[] → params Variant[]). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs index 7451103929..5e98075309 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0009Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs @@ -33,10 +33,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0009 ([DataContract]/[DataMember] -> [DataType]/[DataTypeField]). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0010Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0010Tests.cs index a7f2b2c2fd..a7a0745586 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0010Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0010Tests.cs @@ -32,9 +32,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.MigrationAnalyzer.Analyzers; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0010 (using/Dispose on identity types that are no longer diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0011Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0011Tests.cs index 8ba2840269..2702646659 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0011Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0011Tests.cs @@ -33,9 +33,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.MigrationAnalyzer.Analyzers; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0011 (IUserIdentityTokenHandler synchronous Encrypt/Decrypt/Sign/Verify diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs index 066b258573..20306b5c43 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0012Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0012 (obsolete static CertificateFactory members). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs index ee1446c669..35b7fa088c 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0014Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0014 (DataValue.IsGood static helper -> instance property). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0015Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0015Tests.cs index 88d132c979..af966ec1d4 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0015Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0015Tests.cs @@ -33,9 +33,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.MigrationAnalyzer.Analyzers; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0015 (obsolete sync/APM members on GDS/LDS discovery clients). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0018Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0018Tests.cs index e2df1bd489..d872c2f2a7 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0018Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0018Tests.cs @@ -33,9 +33,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.MigrationAnalyzer.Analyzers; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0018 (obsolete CertificateIdentifier.Certificate getter). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs index 1efbc8d6af..b9217428df 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0019Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0019 (obsolete DataValue(StatusCode[,DateTimeUtc]) constructor). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs index 11d24856b3..85da6e7cf7 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0020Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs @@ -36,10 +36,10 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0020 (EncodeableFactory renames: GlobalFactory and Create). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0021Tests.cs similarity index 99% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0021Tests.cs index c2ae18046f..b61af0517d 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0021Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0021Tests.cs @@ -32,9 +32,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; +using Opc.Ua.MigrationAnalyzer.Analyzers; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0021 (CertificateValidator / CertificateValidationEventArgs structural rename). diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs similarity index 97% rename from Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs index c4b7f715c5..20ee143640 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Analyzers/UA0022Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using NUnit.Framework; -using Opc.Ua.CodeFixers.Analyzers; -using Opc.Ua.CodeFixers.CodeFixes; +using Opc.Ua.MigrationAnalyzer.Analyzers; +using Opc.Ua.MigrationAnalyzer.CodeFixes; -namespace Opc.Ua.CodeFixers.Tests.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { /// /// Tests for UA0022 (ApplicationConfiguration.CertificateValidator / diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj similarity index 79% rename from Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj index f1bbf69fea..ded6185a3c 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Opc.Ua.CodeFixers.Tests.csproj +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj @@ -1,7 +1,7 @@ - + $(TestsTargetFrameworks) - Opc.Ua.CodeFixers.Tests + Opc.Ua.MigrationAnalyzer.Tests true true enable @@ -26,7 +26,7 @@ - - + + diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/Properties/AssemblyInfo.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/Properties/AssemblyInfo.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Properties/AssemblyInfo.cs diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Stubs/OpcUaStubs.cs similarity index 99% rename from Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Tests/Stubs/OpcUaStubs.cs index 0de252acbd..5ee667f970 100644 --- a/Tests/Opc.Ua.CodeFixers.Tests/Stubs/OpcUaStubs.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Stubs/OpcUaStubs.cs @@ -27,7 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -namespace Opc.Ua.CodeFixers.Tests +namespace Opc.Ua.MigrationAnalyzer.Tests { /// /// Minimal hand-written OPC UA 2.0 stubs used by the analyzer tests. diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/CertificateIdentifierShimTests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/CertificateIdentifierShimTests.cs index 835931f830..73052af447 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/CertificateIdentifierShimTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/CertificateIdentifierShimTests.cs @@ -31,7 +31,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/DataValueObsoleteShimTests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/DataValueObsoleteShimTests.cs index f820ccee8b..a1a7b303f9 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/DataValueObsoleteShimTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/DataValueObsoleteShimTests.cs @@ -30,7 +30,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Runtime tests for the obsolete static DataValue.IsGood / diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/EncodeableFactoryShimTests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/EncodeableFactoryShimTests.cs index 2c8d0e3b0f..cabe47ee18 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/EncodeableFactoryShimTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/EncodeableFactoryShimTests.cs @@ -30,7 +30,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/GlobalDiscoveryServerClientShimTests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/GlobalDiscoveryServerClientShimTests.cs index e157ffac57..1ac4eaab3a 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/GlobalDiscoveryServerClientShimTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/GlobalDiscoveryServerClientShimTests.cs @@ -31,7 +31,7 @@ using NUnit.Framework; using Opc.Ua.Gds.Client; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/LocalDiscoveryServerClientShimTests.cs similarity index 98% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/LocalDiscoveryServerClientShimTests.cs index b0b42a7f31..6b1d1ccdd1 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/LocalDiscoveryServerClientShimTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/LocalDiscoveryServerClientShimTests.cs @@ -31,7 +31,7 @@ using NUnit.Framework; using Opc.Ua.Gds.Client; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj b/Tests/Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj similarity index 87% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj rename to Tests/Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj index 96f4a15ea6..df38cc487e 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/Opc.Ua.CodeFixers.Shim.Tests.csproj +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj @@ -1,7 +1,7 @@ - + $(TestsTargetFrameworks) - Opc.Ua.CodeFixers.Shim.Tests + Opc.Ua.MigrationHelpers.Tests true true enable @@ -24,7 +24,7 @@ - + diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/OpcUaShimAttributeInventoryTests.cs similarity index 94% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/OpcUaShimAttributeInventoryTests.cs index 1635ff5309..387fda1a12 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/OpcUaShimAttributeInventoryTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/OpcUaShimAttributeInventoryTests.cs @@ -35,7 +35,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Meta-tests that scan every -marked @@ -47,8 +47,14 @@ namespace Opc.Ua.CodeFixers.Shim.Tests [Category("Shim")] public partial class OpcUaShimAttributeInventoryTests { +#if NET7_0_OR_GREATER [GeneratedRegex(@"^UA\d{4}$", RegexOptions.CultureInvariant)] private static partial Regex RuleIdRegex(); +#else + private static Regex RuleIdRegex() => s_ruleIdRegex; + private static readonly Regex s_ruleIdRegex = + new(@"^UA\d{4}$", RegexOptions.CultureInvariant | RegexOptions.Compiled); +#endif private static IEnumerable ShimMembers() { diff --git a/Tests/Opc.Ua.CodeFixers.Tests/Properties/AssemblyInfo.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from Tests/Opc.Ua.CodeFixers.Tests/Properties/AssemblyInfo.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/Properties/AssemblyInfo.cs diff --git a/Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs b/Tests/Opc.Ua.MigrationHelpers.Tests/UserIdentityTokenHandlerShimTests.cs similarity index 99% rename from Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs rename to Tests/Opc.Ua.MigrationHelpers.Tests/UserIdentityTokenHandlerShimTests.cs index 6523687249..0ab28e22e5 100644 --- a/Tests/Opc.Ua.CodeFixers.Shim.Tests/UserIdentityTokenHandlerShimTests.cs +++ b/Tests/Opc.Ua.MigrationHelpers.Tests/UserIdentityTokenHandlerShimTests.cs @@ -33,7 +33,7 @@ using NUnit.Framework; using Opc.Ua.Security.Certificates; -namespace Opc.Ua.CodeFixers.Shim.Tests +namespace Opc.Ua.MigrationHelpers.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj b/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj index ba0f946b92..e1f60532d5 100644 --- a/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj +++ b/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj @@ -44,7 +44,7 @@ - + diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs index bab08ebfbe..e7b74f93b2 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs @@ -37,10 +37,10 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0002 code fix: rewrite every reference to a removed diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs index 96bea749db..841b1a0896 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0003 code fix: rewrite x == null as x.IsNull (or diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs index e6cb506289..2976f5b379 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0004 code fix: drop the leading ?. of a null-conditional chain diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs index 5bc933853a..7dc36e2f18 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0005 code fix: append .ToByteString() to a byte[] diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs index 7e2e793e84..2d72fd324c 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0006 code fix: rewrite obsolete new Variant(...) as diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs index 80657c3a2e..7cff63b214 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0007 code fix: rewrite new NodeId(s) / new ExpandedNodeId(s) diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs index f9bc3913af..86078b4cc8 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs @@ -38,9 +38,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0008 code fix: wrap each variadic argument of ISession.Call / diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs index 987a2814cd..15dd6c3121 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs @@ -38,9 +38,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0009 code fix: rewrite diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs index 9bba413216..117d6d337f 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs @@ -32,9 +32,9 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0010 code fix: intentionally diagnostic-only — removing the diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs index a0b937ad7d..6ab5aa8989 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0012 code fix: rewrite CertificateFactory.X(args) as diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs index 34c14ded5d..44b1481e2d 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs @@ -38,9 +38,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0014 code fix: rewrite DataValue.IsGood(dv) (or the diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs index 4cb80c5954..f6a01fdc2a 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0019 code fix: rewrite new DataValue(sc) / new DataValue(sc, ts) diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs index 23d4b3d715..d51c0b9c05 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs @@ -36,9 +36,9 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0020 code fix: rewrite instance factory.Create() as diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs index 8615a49ad3..37b3060662 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs @@ -37,9 +37,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixes { /// /// UA0022 code fix: rewrite xxx.CertificateValidator as diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj similarity index 72% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj index 5ad863c024..4edf32df29 100644 --- a/Tools/Opc.Ua.CodeFixers.CodeFixes/Opc.Ua.CodeFixers.CodeFixes.csproj +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj @@ -3,9 +3,9 @@ netstandard2.0 false true - $(AssemblyPrefix).CodeFixers.CodeFixes - Opc.Ua.CodeFixers.CodeFixes - OPC UA .NET Standard migration code-fix providers (1.5.378 to 2.0). Companion to Opc.Ua.CodeFixers analyzers. + $(AssemblyPrefix).MigrationAnalyzer.CodeFixes + Opc.Ua.MigrationAnalyzer.CodeFixes + OPC UA .NET Standard migration code-fix providers (1.5.378 to 2.0). Companion to Opc.Ua.MigrationAnalyzer analyzers. $(NoWarn);RS1007;RS1038;RS2008 + Opc.Ua.MigrationAnalyzer package via a path reference in the nuspec. --> false @@ -30,19 +30,19 @@ linked types are compiled into both DLLs but only one copy is loaded per analyzer host, so no runtime collision. --> - - - - - - + diff --git a/Tools/Opc.Ua.CodeFixers.CodeFixes/Properties/AssemblyInfo.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Properties/AssemblyInfo.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.CodeFixes/Properties/AssemblyInfo.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Properties/AssemblyInfo.cs diff --git a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Shipped.md b/Tools/Opc.Ua.MigrationAnalyzer/AnalyzerReleases.Shipped.md similarity index 100% rename from Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Shipped.md rename to Tools/Opc.Ua.MigrationAnalyzer/AnalyzerReleases.Shipped.md diff --git a/Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md b/Tools/Opc.Ua.MigrationAnalyzer/AnalyzerReleases.Unshipped.md similarity index 100% rename from Tools/Opc.Ua.CodeFixers/AnalyzerReleases.Unshipped.md rename to Tools/Opc.Ua.MigrationAnalyzer/AnalyzerReleases.Unshipped.md diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs index 20288b221b..52c9369b07 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0001UtilsTraceToILoggerAnalyzer.cs @@ -37,10 +37,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0001: Flag calls to the obsolete static Opc.Ua.Utils.Trace and diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs index 4c486cae06..9e2992d1a6 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0002RemovedCollectionTypeAnalyzer.cs @@ -32,10 +32,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0002: Detect references to removed <Type>Collection diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs index a8b39327c4..a64d43a295 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0003NullCheckOnStructTypeAnalyzer.cs @@ -32,10 +32,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0003: Detect x == null / x != null comparisons on diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs index ed28327a61..23fea6ffc0 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0004ConditionalAccessOnStructAnalyzer.cs @@ -33,10 +33,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0004: Detect null-conditional access (?.) whose receiver is diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs index 9c114438fc..0291ffc6b0 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0005ByteArrayToByteStringAnalyzer.cs @@ -34,10 +34,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0005: Detect call sites passing a byte[] argument where the diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs index 4fb306deed..08ee46646f 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0006ObsoleteVariantCtorAnalyzer.cs @@ -31,10 +31,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0006: Detect new Variant(object|DateTime|Guid|byte[]) obsolete diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs index 83c3bdeac4..41e2698a5c 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0007ObsoleteNodeIdStringCtorAnalyzer.cs @@ -31,9 +31,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0007: Detect new NodeId(string) / new ExpandedNodeId(string) diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs index 8865111252..74ea43612b 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0008SessionCallParamsObjectAnalyzer.cs @@ -33,10 +33,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0008: Detect ISession.Call / ISession.CallAsync diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs index 878cdb627f..a66cdc9413 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0009DataContractToDataTypeAnalyzer.cs @@ -31,10 +31,10 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0009: Flag classes annotated with diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0010RemoveDisposableAnalyzer.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0010RemoveDisposableAnalyzer.cs index d5a2d8b221..2c17d28a59 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0010RemoveDisposableAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0010RemoveDisposableAnalyzer.cs @@ -33,10 +33,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0010: Detect using declarations / statements whose variable diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs index bf98de8918..aa029555b8 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0011TokenHandlerSyncToAsyncAnalyzer.cs @@ -32,10 +32,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0011: Detect calls to the obsolete synchronous diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs index 8505894501..e7f3e2915d 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0012CertificateFactoryStaticToInstanceAnalyzer.cs @@ -32,10 +32,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0012: Detect calls to obsolete static CertificateFactory members diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0014DataValueIsGoodAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0014DataValueIsGoodAnalyzer.cs index 369809d8a5..064fc3185e 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0014DataValueIsGoodAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0014DataValueIsGoodAnalyzer.cs @@ -32,9 +32,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0014: Replace the obsolete static DataValue.IsGood(dv) / diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs index a39f43b0b3..44f494b202 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0015GdsSyncToAsyncAnalyzer.cs @@ -32,10 +32,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0015: Detect calls to obsolete synchronous / APM members on the diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs index 7ba4ec9d92..323154a91d 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0018CertificateIdentifierCertificateAnalyzer.cs @@ -31,10 +31,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0018: Detect reads of the obsolete Certificate getter on diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs index 410ae25032..4de6a130fd 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0019DataValueStatusCodeCtorAnalyzer.cs @@ -31,9 +31,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0019: Detect new DataValue(StatusCode) and diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs index 8ff75b2c8c..cf895ce415 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0020EncodeableFactoryRenameAnalyzer.cs @@ -31,10 +31,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0020: Detect references to the obsolete diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs index b2993de8fb..91f21b025a 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0021CertificateValidatorRenameAnalyzer.cs @@ -33,10 +33,10 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0021: Detect references to the legacy Opc.Ua.CertificateValidator class and diff --git a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs index 271a54b074..f88e345277 100644 --- a/Tools/Opc.Ua.CodeFixers/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Analyzers/UA0022CertificateValidatorPropertyRenameAnalyzer.cs @@ -34,10 +34,10 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using Opc.Ua.CodeFixers.Diagnostics; -using Opc.Ua.CodeFixers.Helpers; +using Opc.Ua.MigrationAnalyzer.Diagnostics; +using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.CodeFixers.Analyzers +namespace Opc.Ua.MigrationAnalyzer.Analyzers { /// /// UA0022: Detect access to the legacy diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs b/Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/DiagnosticDescriptors.cs similarity index 99% rename from Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/DiagnosticDescriptors.cs index 35f9ef7c3e..53995385d5 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticDescriptors.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/DiagnosticDescriptors.cs @@ -29,7 +29,7 @@ using Microsoft.CodeAnalysis; -namespace Opc.Ua.CodeFixers.Diagnostics +namespace Opc.Ua.MigrationAnalyzer.Diagnostics { /// /// Centralised registry of every shipped diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs b/Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/DiagnosticIds.cs similarity index 96% rename from Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/DiagnosticIds.cs index 10f41e2a49..f5e7d95f7c 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/DiagnosticIds.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/DiagnosticIds.cs @@ -27,11 +27,11 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -namespace Opc.Ua.CodeFixers.Diagnostics +namespace Opc.Ua.MigrationAnalyzer.Diagnostics { /// /// Stable identifiers for every diagnostic shipped by the - /// OPCFoundation.NetStandard.Opc.Ua.CodeFixers analyzer package. + /// OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer analyzer package. /// Keep IDs immutable across releases — consumers may use them /// in #pragma warning disable or .editorconfig. /// diff --git a/Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs b/Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/WellKnownProperties.cs similarity index 98% rename from Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/WellKnownProperties.cs index 0f89a8dd4d..794cd5eec9 100644 --- a/Tools/Opc.Ua.CodeFixers/Diagnostics/WellKnownProperties.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Diagnostics/WellKnownProperties.cs @@ -27,7 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -namespace Opc.Ua.CodeFixers.Diagnostics +namespace Opc.Ua.MigrationAnalyzer.Diagnostics { /// /// Shared DiagnosticDescriptor.Properties keys passed from analyzers diff --git a/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs b/Tools/Opc.Ua.MigrationAnalyzer/Helpers/SymbolExtensions.cs similarity index 99% rename from Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Helpers/SymbolExtensions.cs index 25f2128fce..38656a0346 100644 --- a/Tools/Opc.Ua.CodeFixers/Helpers/SymbolExtensions.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Helpers/SymbolExtensions.cs @@ -30,7 +30,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; -namespace Opc.Ua.CodeFixers.Helpers +namespace Opc.Ua.MigrationAnalyzer.Helpers { /// /// Shared helpers for symbol-shape queries used by multiple analyzers. diff --git a/Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs b/Tools/Opc.Ua.MigrationAnalyzer/Helpers/UaSymbols.cs similarity index 99% rename from Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Helpers/UaSymbols.cs index c4c07374b9..eadef898f9 100644 --- a/Tools/Opc.Ua.CodeFixers/Helpers/UaSymbols.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer/Helpers/UaSymbols.cs @@ -32,7 +32,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; -namespace Opc.Ua.CodeFixers.Helpers +namespace Opc.Ua.MigrationAnalyzer.Helpers { /// /// Cached lookup of well-known OPC UA s for a diff --git a/Tools/Opc.Ua.CodeFixers/NugetREADME.md b/Tools/Opc.Ua.MigrationAnalyzer/NugetREADME.md similarity index 90% rename from Tools/Opc.Ua.CodeFixers/NugetREADME.md rename to Tools/Opc.Ua.MigrationAnalyzer/NugetREADME.md index 275d360889..e886ced14c 100644 --- a/Tools/Opc.Ua.CodeFixers/NugetREADME.md +++ b/Tools/Opc.Ua.MigrationAnalyzer/NugetREADME.md @@ -2,23 +2,23 @@ ## What you get -A single NuGet install (`OPCFoundation.NetStandard.Opc.Ua.CodeFixers`) that +A single NuGet install (`OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer`) that ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: - a Roslyn **analyzer + code-fixer** set (`UA0001`–`UA0022`) that flags every pattern covered by [`Docs/MigrationGuide.md`](../../Docs/MigrationGuide.md) and, where safe, applies the fix automatically; and -- a **compatibility shim** assembly (`Opc.Ua.CodeFixers.Shim.dll`) that +- a **compatibility shim** assembly (`Opc.Ua.MigrationHelpers.dll`) that re-supplies the obsolete extension surface 1.6 moved or removed, so most consumer projects still compile after the upgrade. ## How to migrate -1. Add the 1.6 OPC UA packages **and** the CodeFixers package to your +1. Add the 1.6 OPC UA packages **and** the MigrationAnalyzer package to your consumer project: ```xml - + ``` 2. Run `dotnet build`. Your code should compile: the shim covers the @@ -28,7 +28,7 @@ ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: offered auto-fixes. A handful (`UA0001`, `UA0011`, `UA0015`, `UA0018`) are `Info`-level and need a manual review. 4. Once the project is warning-free, remove the - `OPCFoundation.NetStandard.Opc.Ua.CodeFixers` package reference. You are + `OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` package reference. You are on clean 1.6 with no shim dependency. ## Rules @@ -57,7 +57,7 @@ ships **two things** to help migrate from OPC UA .NET Standard 1.5.378 to 1.6: ## What the shim provides -`Opc.Ua.CodeFixers.Shim.dll` is delivered as a regular reference assembly and +`Opc.Ua.MigrationHelpers.dll` is delivered as a regular reference assembly and re-exposes the 1.5.378 surface in two flavors: - **Moved obsolete extensions** the 1.6 libraries no longer carry inline: @@ -111,17 +111,17 @@ diagnostics from the failure set: ``` Remove each entry as you finish fixing the corresponding rule, and drop the -whole block once the CodeFixers package is removed. +whole block once the MigrationAnalyzer package is removed. ## Packaging note The package ships **two analyzer DLLs** under `analyzers/dotnet/cs/`: -- `Opc.Ua.CodeFixers.dll` — the analyzer assembly. Targets `Microsoft.CodeAnalysis 4.x` +- `Opc.Ua.MigrationAnalyzer.dll` — the analyzer assembly. Targets `Microsoft.CodeAnalysis 4.x` (the stable analyzer API) and references **only** `Microsoft.CodeAnalysis.CSharp` so it loads cleanly in csc.exe's analyzer host (which ships only `Microsoft.CodeAnalysis.dll` + `CSharp.dll`, not `Workspaces`). All `DiagnosticAnalyzer` types live here. -- `Opc.Ua.CodeFixers.CodeFixes.dll` — the code-fix assembly. References +- `Opc.Ua.MigrationAnalyzer.CodeFixes.dll` — the code-fix assembly. References `Microsoft.CodeAnalysis.CSharp.Workspaces` and hosts all `CodeFixProvider` types. Loaded only by Workspaces-aware hosts (Visual Studio / `dotnet format`). diff --git a/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props b/Tools/Opc.Ua.MigrationAnalyzer/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props similarity index 77% rename from Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props rename to Tools/Opc.Ua.MigrationAnalyzer/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props index f2ec3c296b..1b252e63f4 100644 --- a/Tools/Opc.Ua.CodeFixers/OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props +++ b/Tools/Opc.Ua.MigrationAnalyzer/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props @@ -1,4 +1,4 @@ - + - - + + diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj b/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.csproj similarity index 75% rename from Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj rename to Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.csproj index b94fb71c45..f8c3bfb153 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.csproj +++ b/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.csproj @@ -3,8 +3,8 @@ netstandard2.0 false true - $(AssemblyPrefix).CodeFixers - Opc.Ua.CodeFixers + $(AssemblyPrefix).MigrationAnalyzer + Opc.Ua.MigrationAnalyzer OPC UA .NET Standard migration analyzers and code fixers (1.5.378 to 2.0). - $(PackagePrefix).Opc.Ua.CodeFixers + $(PackagePrefix).Opc.Ua.MigrationAnalyzer true true true @@ -26,9 +26,9 @@ false - $(MSBuildThisFileDirectory)Opc.Ua.CodeFixers.nuspec + $(MSBuildThisFileDirectory)Opc.Ua.MigrationAnalyzer.nuspec $(MSBuildThisFileDirectory) - @@ -44,11 +44,11 @@ - + - + @@ -57,7 +57,7 @@ assets file. We mark it as a build-only dependency (no compile reference, no transitive flow). The actual multi-TFM build happens in the target below. --> - + false all @@ -70,20 +70,20 @@ for shared DiagnosticIds, which creates a NuGet restore cycle); we invoke its build via an MSBuild task instead. --> - - - + - version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.dll;codeFixesDll=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.CodeFixes\bin\$(Configuration)\netstandard2.0\Opc.Ua.CodeFixers.CodeFixes.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.CodeFixers.Shim\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.CodeFixers.props + version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.MigrationAnalyzer.dll;codeFixesDll=$(MSBuildThisFileDirectory)..\Opc.Ua.MigrationAnalyzer.CodeFixes\bin\$(Configuration)\netstandard2.0\Opc.Ua.MigrationAnalyzer.CodeFixes.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.MigrationHelpers\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props diff --git a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec b/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec similarity index 83% rename from Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec rename to Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec index 47cf3fb7f1..84161e9c5c 100644 --- a/Tools/Opc.Ua.CodeFixers/Opc.Ua.CodeFixers.nuspec +++ b/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec @@ -1,7 +1,7 @@ - OPCFoundation.NetStandard.Opc.Ua.CodeFixers + OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer $version$ OPC UA .NET Standard 1.5.378 to 1.6 migration analyzers, code fixers, and compatibility shim OPC Foundation @@ -11,7 +11,7 @@ https://github.com/OPCFoundation/UA-.NETStandard images/logo.jpg NugetREADME.md - OPC UA .NET Standard migration analyzers and code fixers (UA0001-UA0020) bundled with the Opc.Ua.CodeFixers.Shim compatibility assembly that re-exposes the 1.5.378 obsolete surface to ease the move to 1.6. + OPC UA .NET Standard migration analyzers and code fixers (UA0001-UA0020) bundled with the Opc.Ua.MigrationHelpers compatibility assembly that re-exposes the 1.5.378 obsolete surface to ease the move to 1.6. Copyright (c) 2004-2025 OPC Foundation, Inc OPCFoundation OPC UA analyzer codefix roslyn migration shim true @@ -68,15 +68,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Properties/AssemblyInfo.cs b/Tools/Opc.Ua.MigrationAnalyzer/Properties/AssemblyInfo.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Properties/AssemblyInfo.cs rename to Tools/Opc.Ua.MigrationAnalyzer/Properties/AssemblyInfo.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs b/Tools/Opc.Ua.MigrationHelpers/Marker/OpcUaShimAttribute.cs similarity index 97% rename from Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs rename to Tools/Opc.Ua.MigrationHelpers/Marker/OpcUaShimAttribute.cs index 52659f8eca..77af2637f4 100644 --- a/Tools/Opc.Ua.CodeFixers.Shim/Marker/OpcUaShimAttribute.cs +++ b/Tools/Opc.Ua.MigrationHelpers/Marker/OpcUaShimAttribute.cs @@ -33,7 +33,7 @@ namespace Opc.Ua { /// /// Marks an API member as a 1.5.378 → 1.6 migration shim. Used by the - /// Opc.Ua.CodeFixers analyzer to map calls that bind to a shim + /// Opc.Ua.MigrationAnalyzer analyzer to map calls that bind to a shim /// extension back to the underlying UA00xx diagnostic rule, so /// consumers get the same migration guidance whether they call the /// shim directly or the legacy API in source. diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj b/Tools/Opc.Ua.MigrationHelpers/Opc.Ua.MigrationHelpers.csproj similarity index 82% rename from Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj rename to Tools/Opc.Ua.MigrationHelpers/Opc.Ua.MigrationHelpers.csproj index 9694b0a4e7..0ce2647e94 100644 --- a/Tools/Opc.Ua.CodeFixers.Shim/Opc.Ua.CodeFixers.Shim.csproj +++ b/Tools/Opc.Ua.MigrationHelpers/Opc.Ua.MigrationHelpers.csproj @@ -1,9 +1,9 @@ - + - $(AssemblyPrefix).CodeFixers.Shim + $(AssemblyPrefix).MigrationHelpers $(LibTargetFrameworks) Opc.Ua - OPC UA 1.5.378 → 1.6 compatibility shim. Provides the obsolete extension-method surface that 1.6 removed, marked [Obsolete] so the matching Opc.Ua.CodeFixers analyzer rules guide consumers off it. Ships in the OPCFoundation.NetStandard.Opc.Ua.CodeFixers NuGet alongside the analyzer DLL. + OPC UA 1.5.378 → 1.6 compatibility shim. Provides the obsolete extension-method surface that 1.6 removed, marked [Obsolete] so the matching Opc.Ua.MigrationAnalyzer analyzer rules guide consumers off it. Ships in the OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer NuGet alongside the analyzer DLL. false true enable diff --git a/Tools/Opc.Ua.CodeFixers/Properties/AssemblyInfo.cs b/Tools/Opc.Ua.MigrationHelpers/Properties/AssemblyInfo.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers/Properties/AssemblyInfo.cs rename to Tools/Opc.Ua.MigrationHelpers/Properties/AssemblyInfo.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Session/Session.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Client/Session/Session.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Session/Session.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Client/Session/Session.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Subscription/Classic/Subscription.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Client/Subscription/Classic/Subscription.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Client/Subscription/Classic/Subscription.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Client/Subscription/Classic/Subscription.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Configuration/ApplicationInstance.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Configuration/ApplicationInstance.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Configuration/ApplicationInstance.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Configuration/ApplicationInstance.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Security/Certificates/CertificateIdentifier.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Security/Certificates/CertificateIdentifier.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Security/Certificates/CertificateIdentifier.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Security/Certificates/CertificateIdentifier.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Client/ChannelBase.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Client/ChannelBase.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Client/ChannelBase.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Client/ChannelBase.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Configuration/ApplicationConfiguration.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Server/ServerBase.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Server/ServerBase.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Server/ServerBase.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Server/ServerBase.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Transport/TransportChannel.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Transport/TransportChannel.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Transport/TransportChannel.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Transport/TransportChannel.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Stack/Types/IUserIdentityTokenHandler.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Types/Encoders/EncodeableFactory.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Core/Types/Encoders/EncodeableFactory.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Core/Types/Encoders/EncodeableFactory.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Core/Types/Encoders/EncodeableFactory.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Gds.Client.Common/GlobalDiscoveryServerClient.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Gds.Client.Common/LocalDiscoveryServerClient.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Gds.Client.Common/ServerPushConfigurationClient.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs b/Tools/Opc.Ua.MigrationHelpers/Shims/Types/BuiltIn/BuiltInType.cs similarity index 100% rename from Tools/Opc.Ua.CodeFixers.Shim/Shims/Types/BuiltIn/BuiltInType.cs rename to Tools/Opc.Ua.MigrationHelpers/Shims/Types/BuiltIn/BuiltInType.cs diff --git a/Tools/Opc.Ua.CodeFixers.Shim/readme.md b/Tools/Opc.Ua.MigrationHelpers/readme.md similarity index 95% rename from Tools/Opc.Ua.CodeFixers.Shim/readme.md rename to Tools/Opc.Ua.MigrationHelpers/readme.md index ab8d454035..7eafefd398 100644 --- a/Tools/Opc.Ua.CodeFixers.Shim/readme.md +++ b/Tools/Opc.Ua.MigrationHelpers/readme.md @@ -2,7 +2,7 @@ This project provides extension-method shims for the obsolete API surface that the 1.6 release line is moving away from. It ships in the -`OPCFoundation.NetStandard.Opc.Ua.CodeFixers` NuGet package alongside the +`OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` NuGet package alongside the analyzer DLL. ## Directory convention diff --git a/Tools/SourceGeneration.slnx b/Tools/Roslyn.slnx similarity index 72% rename from Tools/SourceGeneration.slnx rename to Tools/Roslyn.slnx index 6b06b605ae..d63b64630e 100644 --- a/Tools/SourceGeneration.slnx +++ b/Tools/Roslyn.slnx @@ -5,11 +5,12 @@ - - - - - + + + + + + diff --git a/UA.slnx b/UA.slnx index 54dcb086ba..a16240a3d1 100644 --- a/UA.slnx +++ b/UA.slnx @@ -121,11 +121,11 @@ - - - - - + + + + + @@ -157,7 +157,7 @@ - + From 0a019c89df39b00b923a403c5ecadf09d383068b Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:12:20 +0200 Subject: [PATCH 8/8] [Tools] Rename CodeFixes -> CodeFixer, MigrationHelpers -> MigrationAnalyzer.Core, flatten Shims Three follow-up renames: Tools/Opc.Ua.MigrationAnalyzer.CodeFixes -> Tools/Opc.Ua.MigrationAnalyzer.CodeFixer Tools/Opc.Ua.MigrationHelpers -> Tools/Opc.Ua.MigrationAnalyzer.Core Tests/Opc.Ua.MigrationHelpers.Tests -> Tests/Opc.Ua.MigrationAnalyzer.Core.Tests Also collapses the nested Shims/ folder in the Core project so the shim type folders (Client/, Configuration/, Core/, Gds.Client.Common/, Types/) sit side-by-side with the .csproj at the project root. The old Shims/Foo/Bar.cs path now lives directly at Foo/Bar.cs. Marker/, Properties/, readme.md are unchanged. Assembly / namespace / package id rename in lock-step: Opc.Ua.MigrationAnalyzer.CodeFixes (assembly + namespace) -> Opc.Ua.MigrationAnalyzer.CodeFixer Opc.Ua.MigrationHelpers (assembly) -> Opc.Ua.MigrationAnalyzer.Core Namespace inside the Core (shim) project stays Opc.Ua.* / sub-namespaces. Package id OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer unchanged. UA.slnx / Tools/Roslyn.slnx project paths updated; analyzer csproj's nuspec property paths, MSBuild self-invoked targets, and ProjectReferences all point at the renamed directories / csproj filenames. Verified: * dotnet build UA.slnx -c Debug succeeds (0 errors, 572 pre-existing CA2007 / NU1702 warnings). * dotnet test ... MigrationAnalyzer.Tests passes 107/107 on net10.0. * dotnet test ... MigrationAnalyzer.Core.Tests passes 10/10 (+3 skipped). * dotnet pack ... MigrationAnalyzer.csproj produces the package with analyzers/dotnet/cs/{Opc.Ua.MigrationAnalyzer.dll, Opc.Ua.MigrationAnalyzer.CodeFixer.dll} + six TFMs of Opc.Ua.MigrationAnalyzer.Core.dll under lib/. --- .github/agents/opcua-v20-migration.agent.md | 6 ++--- .../Opc.Ua.Configuration.csproj | 2 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 2 +- .../CertificateIdentifierShimTests.cs | 2 +- .../DataValueObsoleteShimTests.cs | 2 +- .../EncodeableFactoryShimTests.cs | 2 +- .../GlobalDiscoveryServerClientShimTests.cs | 2 +- .../LocalDiscoveryServerClientShimTests.cs | 2 +- ...pc.Ua.MigrationAnalyzer.Core.Tests.csproj} | 4 ++-- .../OpcUaShimAttributeInventoryTests.cs | 2 +- .../Properties/AssemblyInfo.cs | 0 .../UserIdentityTokenHandlerShimTests.cs | 2 +- .../Analyzers/UA0002Tests.cs | 2 +- .../Analyzers/UA0003Tests.cs | 2 +- .../Analyzers/UA0004Tests.cs | 2 +- .../Analyzers/UA0005Tests.cs | 2 +- .../Analyzers/UA0006Tests.cs | 2 +- .../Analyzers/UA0007Tests.cs | 2 +- .../Analyzers/UA0008Tests.cs | 2 +- .../Analyzers/UA0009Tests.cs | 2 +- .../Analyzers/UA0012Tests.cs | 2 +- .../Analyzers/UA0014Tests.cs | 2 +- .../Analyzers/UA0019Tests.cs | 2 +- .../Analyzers/UA0020Tests.cs | 2 +- .../Analyzers/UA0022Tests.cs | 2 +- .../Opc.Ua.MigrationAnalyzer.Tests.csproj | 2 +- .../Opc.Ua.Types.Tests.csproj | 2 +- .../UA0002RemovedCollectionTypeCodeFix.cs | 2 +- .../UA0003NullCheckOnStructTypeCodeFix.cs | 2 +- .../UA0004ConditionalAccessOnStructCodeFix.cs | 2 +- .../UA0005ByteArrayToByteStringCodeFix.cs | 2 +- .../UA0006ObsoleteVariantCtorCodeFix.cs | 2 +- .../UA0007ObsoleteNodeIdStringCtorCodeFix.cs | 2 +- .../UA0008SessionCallParamsObjectCodeFix.cs | 2 +- .../UA0009DataContractToDataTypeCodeFix.cs | 2 +- .../UA0010RemoveDisposableCodeFix.cs | 2 +- ...rtificateFactoryStaticToInstanceCodeFix.cs | 2 +- .../CodeFixes/UA0014DataValueIsGoodCodeFix.cs | 2 +- .../UA0019DataValueStatusCodeCtorCodeFix.cs | 2 +- .../UA0020EncodeableFactoryRenameCodeFix.cs | 2 +- ...rtificateValidatorPropertyRenameCodeFix.cs | 2 +- ...Opc.Ua.MigrationAnalyzer.CodeFixer.csproj} | 4 ++-- .../Properties/AssemblyInfo.cs | 0 .../Client/Session/Session.cs | 0 .../Subscription/Classic/Subscription.cs | 0 .../Configuration/ApplicationInstance.cs | 0 .../Certificates/CertificateIdentifier.cs | 0 .../Core/Stack/Client/ChannelBase.cs | 0 .../Configuration/ApplicationConfiguration.cs | 0 .../Core/Stack/Server/ServerBase.cs | 0 .../Core/Stack/Transport/TransportChannel.cs | 0 .../Stack/Types/IUserIdentityTokenHandler.cs | 0 .../Core/Types/Encoders/EncodeableFactory.cs | 0 .../GlobalDiscoveryServerClient.cs | 0 .../LocalDiscoveryServerClient.cs | 0 .../ServerPushConfigurationClient.cs | 0 .../Marker/OpcUaShimAttribute.cs | 0 .../Opc.Ua.MigrationAnalyzer.Core.csproj} | 2 +- .../Properties/AssemblyInfo.cs | 0 .../Types/BuiltIn/BuiltInType.cs | 0 .../readme.md | 0 Tools/Opc.Ua.MigrationAnalyzer/NugetREADME.md | 6 ++--- ...NetStandard.Opc.Ua.MigrationAnalyzer.props | 4 ++-- .../Opc.Ua.MigrationAnalyzer.csproj | 22 +++++++++---------- .../Opc.Ua.MigrationAnalyzer.nuspec | 16 +++++++------- Tools/Roslyn.slnx | 6 ++--- UA.slnx | 6 ++--- 67 files changed, 76 insertions(+), 76 deletions(-) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/CertificateIdentifierShimTests.cs (98%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/DataValueObsoleteShimTests.cs (98%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/EncodeableFactoryShimTests.cs (98%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/GlobalDiscoveryServerClientShimTests.cs (98%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/LocalDiscoveryServerClientShimTests.cs (98%) rename Tests/{Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj => Opc.Ua.MigrationAnalyzer.Core.Tests/Opc.Ua.MigrationAnalyzer.Core.Tests.csproj} (88%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/OpcUaShimAttributeInventoryTests.cs (99%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/Properties/AssemblyInfo.cs (100%) rename Tests/{Opc.Ua.MigrationHelpers.Tests => Opc.Ua.MigrationAnalyzer.Core.Tests}/UserIdentityTokenHandlerShimTests.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs (98%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0010RemoveDisposableCodeFix.cs (98%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0014DataValueIsGoodCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs (99%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj => Opc.Ua.MigrationAnalyzer.CodeFixer/Opc.Ua.MigrationAnalyzer.CodeFixer.csproj} (95%) rename Tools/{Opc.Ua.MigrationAnalyzer.CodeFixes => Opc.Ua.MigrationAnalyzer.CodeFixer}/Properties/AssemblyInfo.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Client/Session/Session.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Client/Subscription/Classic/Subscription.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Configuration/ApplicationInstance.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Security/Certificates/CertificateIdentifier.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Stack/Client/ChannelBase.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Stack/Configuration/ApplicationConfiguration.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Stack/Server/ServerBase.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Stack/Transport/TransportChannel.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Stack/Types/IUserIdentityTokenHandler.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Core/Types/Encoders/EncodeableFactory.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Gds.Client.Common/GlobalDiscoveryServerClient.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Gds.Client.Common/LocalDiscoveryServerClient.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Gds.Client.Common/ServerPushConfigurationClient.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers => Opc.Ua.MigrationAnalyzer.Core}/Marker/OpcUaShimAttribute.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Opc.Ua.MigrationHelpers.csproj => Opc.Ua.MigrationAnalyzer.Core/Opc.Ua.MigrationAnalyzer.Core.csproj} (95%) rename Tools/{Opc.Ua.MigrationHelpers => Opc.Ua.MigrationAnalyzer.Core}/Properties/AssemblyInfo.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers/Shims => Opc.Ua.MigrationAnalyzer.Core}/Types/BuiltIn/BuiltInType.cs (100%) rename Tools/{Opc.Ua.MigrationHelpers => Opc.Ua.MigrationAnalyzer.Core}/readme.md (100%) diff --git a/.github/agents/opcua-v20-migration.agent.md b/.github/agents/opcua-v20-migration.agent.md index 5529706aeb..7d6cdd68f2 100644 --- a/.github/agents/opcua-v20-migration.agent.md +++ b/.github/agents/opcua-v20-migration.agent.md @@ -20,8 +20,8 @@ You are an expert migration agent for upgrading OPC UA .NET Standard application ``` The package bundles two payloads: - - **Analyzer + code-fix DLLs** (`Opc.Ua.MigrationAnalyzer.dll`, `Opc.Ua.MigrationAnalyzer.CodeFixes.dll`) loaded into csc.exe and the IDE. - - **Compatibility shim** (`Opc.Ua.MigrationHelpers.dll`) that re-exposes the 1.5.378 obsolete extension surface so 1.5.378-style call sites compile against 2.0 with warnings instead of errors. + - **Analyzer + code-fix DLLs** (`Opc.Ua.MigrationAnalyzer.dll`, `Opc.Ua.MigrationAnalyzer.CodeFixer.dll`) loaded into csc.exe and the IDE. + - **Compatibility shim** (`Opc.Ua.MigrationAnalyzer.Core.dll`) that re-exposes the 1.5.378 obsolete extension surface so 1.5.378-style call sites compile against 2.0 with warnings instead of errors. 2. **Bump the OPC UA package versions to `2.0.*-*`** in every consumer project. Do NOT remove existing `OPCFoundation.NetStandard.Opc.Ua.*` references — just update their `Version` attribute. @@ -70,7 +70,7 @@ You are an expert migration agent for upgrading OPC UA .NET Standard application ## What the shim covers -`Opc.Ua.MigrationHelpers.dll` re-exposes the 1.5.378 surface as C# 14 `extension` members so 1.5.378 call sites continue to compile: +`Opc.Ua.MigrationAnalyzer.Core.dll` re-exposes the 1.5.378 surface as C# 14 `extension` members so 1.5.378 call sites continue to compile: - **Moved obsolete extensions**: `NodeId` / `Variant` / `DataValue` null-check helpers, `Session` sync helpers, `Subscription` sync helpers, `ApplicationInstance` helpers, `ServerBase.Start` / `Stop`, `TransportChannel` APM, `ChannelBase` static factory methods, and similar surface. - **New shims for genuinely-removed members**: `EncodeableFactory.GlobalFactory`, `CertificateIdentifier.Certificate` (throws), sync wrappers for `IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}`, sync + APM wrappers for GDS / LDS client APIs. diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index e46eb13c53..445d863361 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -12,7 +12,7 @@ - + $(PackageId).Debug diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index c1fde8f251..eff7f5e7d0 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -17,7 +17,7 @@ - + $(PackageId).Debug diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/CertificateIdentifierShimTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/CertificateIdentifierShimTests.cs similarity index 98% rename from Tests/Opc.Ua.MigrationHelpers.Tests/CertificateIdentifierShimTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/CertificateIdentifierShimTests.cs index 73052af447..d52c50cb7c 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/CertificateIdentifierShimTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/CertificateIdentifierShimTests.cs @@ -31,7 +31,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/DataValueObsoleteShimTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/DataValueObsoleteShimTests.cs similarity index 98% rename from Tests/Opc.Ua.MigrationHelpers.Tests/DataValueObsoleteShimTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/DataValueObsoleteShimTests.cs index a1a7b303f9..337b48a127 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/DataValueObsoleteShimTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/DataValueObsoleteShimTests.cs @@ -30,7 +30,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Runtime tests for the obsolete static DataValue.IsGood / diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/EncodeableFactoryShimTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/EncodeableFactoryShimTests.cs similarity index 98% rename from Tests/Opc.Ua.MigrationHelpers.Tests/EncodeableFactoryShimTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/EncodeableFactoryShimTests.cs index cabe47ee18..5fc367b909 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/EncodeableFactoryShimTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/EncodeableFactoryShimTests.cs @@ -30,7 +30,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/GlobalDiscoveryServerClientShimTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/GlobalDiscoveryServerClientShimTests.cs similarity index 98% rename from Tests/Opc.Ua.MigrationHelpers.Tests/GlobalDiscoveryServerClientShimTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/GlobalDiscoveryServerClientShimTests.cs index 1ac4eaab3a..42c65bf196 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/GlobalDiscoveryServerClientShimTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/GlobalDiscoveryServerClientShimTests.cs @@ -31,7 +31,7 @@ using NUnit.Framework; using Opc.Ua.Gds.Client; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/LocalDiscoveryServerClientShimTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/LocalDiscoveryServerClientShimTests.cs similarity index 98% rename from Tests/Opc.Ua.MigrationHelpers.Tests/LocalDiscoveryServerClientShimTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/LocalDiscoveryServerClientShimTests.cs index 6b1d1ccdd1..36026ced9c 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/LocalDiscoveryServerClientShimTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/LocalDiscoveryServerClientShimTests.cs @@ -31,7 +31,7 @@ using NUnit.Framework; using Opc.Ua.Gds.Client; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/Opc.Ua.MigrationAnalyzer.Core.Tests.csproj similarity index 88% rename from Tests/Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/Opc.Ua.MigrationAnalyzer.Core.Tests.csproj index df38cc487e..bccfc5b165 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/Opc.Ua.MigrationHelpers.Tests.csproj +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/Opc.Ua.MigrationAnalyzer.Core.Tests.csproj @@ -1,7 +1,7 @@ $(TestsTargetFrameworks) - Opc.Ua.MigrationHelpers.Tests + Opc.Ua.MigrationAnalyzer.Core.Tests true true enable @@ -24,7 +24,7 @@ - + diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/OpcUaShimAttributeInventoryTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/OpcUaShimAttributeInventoryTests.cs similarity index 99% rename from Tests/Opc.Ua.MigrationHelpers.Tests/OpcUaShimAttributeInventoryTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/OpcUaShimAttributeInventoryTests.cs index 387fda1a12..ab013b7681 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/OpcUaShimAttributeInventoryTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/OpcUaShimAttributeInventoryTests.cs @@ -35,7 +35,7 @@ using System.Threading.Tasks; using NUnit.Framework; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Meta-tests that scan every -marked diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/Properties/AssemblyInfo.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from Tests/Opc.Ua.MigrationHelpers.Tests/Properties/AssemblyInfo.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/Properties/AssemblyInfo.cs diff --git a/Tests/Opc.Ua.MigrationHelpers.Tests/UserIdentityTokenHandlerShimTests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/UserIdentityTokenHandlerShimTests.cs similarity index 99% rename from Tests/Opc.Ua.MigrationHelpers.Tests/UserIdentityTokenHandlerShimTests.cs rename to Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/UserIdentityTokenHandlerShimTests.cs index 0ab28e22e5..71f47fa7bb 100644 --- a/Tests/Opc.Ua.MigrationHelpers.Tests/UserIdentityTokenHandlerShimTests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/UserIdentityTokenHandlerShimTests.cs @@ -33,7 +33,7 @@ using NUnit.Framework; using Opc.Ua.Security.Certificates; -namespace Opc.Ua.MigrationHelpers.Tests +namespace Opc.Ua.MigrationAnalyzer.Core.Tests { /// /// Runtime tests for . diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs index a64b519fb9..667d83caf6 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0002Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs index e61b44350c..7c2c26257b 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0003Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs index 40847c48a2..ec04cd1a37 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0004Tests.cs @@ -34,7 +34,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs index 14e3b1a19e..a7fbabfd4e 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0005Tests.cs @@ -34,7 +34,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs index 2de63a5ed2..3859553903 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0006Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs index 8f7785a36f..e80ff78d2f 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0007Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs index 6502c595af..0b38170d56 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0008Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs index 5e98075309..9dbc8fbcd1 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0009Tests.cs @@ -34,7 +34,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs index 20306b5c43..dfd082daa2 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0012Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs index 35b7fa088c..ac575a7f84 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0014Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs index b9217428df..628778bbcb 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0019Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs index 85da6e7cf7..db650c0331 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0020Tests.cs @@ -37,7 +37,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs index 20ee143640..6eefdb5baa 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Analyzers/UA0022Tests.cs @@ -33,7 +33,7 @@ using Microsoft.CodeAnalysis; using NUnit.Framework; using Opc.Ua.MigrationAnalyzer.Analyzers; -using Opc.Ua.MigrationAnalyzer.CodeFixes; +using Opc.Ua.MigrationAnalyzer.CodeFixer; namespace Opc.Ua.MigrationAnalyzer.Tests.Analyzers { diff --git a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj index ded6185a3c..9c4e2a9f88 100644 --- a/Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj +++ b/Tests/Opc.Ua.MigrationAnalyzer.Tests/Opc.Ua.MigrationAnalyzer.Tests.csproj @@ -27,6 +27,6 @@ - + diff --git a/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj b/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj index e1f60532d5..6d535d3db6 100644 --- a/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj +++ b/Tests/Opc.Ua.Types.Tests/Opc.Ua.Types.Tests.csproj @@ -44,7 +44,7 @@ - + diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs index e7b74f93b2..b4c60cef71 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0002RemovedCollectionTypeCodeFix.cs @@ -40,7 +40,7 @@ using Opc.Ua.MigrationAnalyzer.Diagnostics; using Opc.Ua.MigrationAnalyzer.Helpers; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0002 code fix: rewrite every reference to a removed diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs index 841b1a0896..c39d0643b7 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0003NullCheckOnStructTypeCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0003 code fix: rewrite x == null as x.IsNull (or diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs index 2976f5b379..1b5895f3b7 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0004ConditionalAccessOnStructCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0004 code fix: drop the leading ?. of a null-conditional chain diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs index 7dc36e2f18..5505f7f8b0 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0005ByteArrayToByteStringCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0005 code fix: append .ToByteString() to a byte[] diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs index 2d72fd324c..1e59647385 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0006ObsoleteVariantCtorCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0006 code fix: rewrite obsolete new Variant(...) as diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs index 7cff63b214..a579cc6dab 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0007ObsoleteNodeIdStringCtorCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0007 code fix: rewrite new NodeId(s) / new ExpandedNodeId(s) diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs index 86078b4cc8..db6fea1f02 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0008SessionCallParamsObjectCodeFix.cs @@ -40,7 +40,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0008 code fix: wrap each variadic argument of ISession.Call / diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs index 15dd6c3121..9738dfaed5 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0009DataContractToDataTypeCodeFix.cs @@ -40,7 +40,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0009 code fix: rewrite diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0010RemoveDisposableCodeFix.cs similarity index 98% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0010RemoveDisposableCodeFix.cs index 117d6d337f..2c2f3bb6ab 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0010RemoveDisposableCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0010RemoveDisposableCodeFix.cs @@ -34,7 +34,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0010 code fix: intentionally diagnostic-only — removing the diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs index 6ab5aa8989..347a71d206 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0012CertificateFactoryStaticToInstanceCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0012 code fix: rewrite CertificateFactory.X(args) as diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0014DataValueIsGoodCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0014DataValueIsGoodCodeFix.cs index 44b1481e2d..7aba8054c6 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0014DataValueIsGoodCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0014DataValueIsGoodCodeFix.cs @@ -40,7 +40,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0014 code fix: rewrite DataValue.IsGood(dv) (or the diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs index f6a01fdc2a..6397813619 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0019DataValueStatusCodeCtorCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0019 code fix: rewrite new DataValue(sc) / new DataValue(sc, ts) diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs index d51c0b9c05..1a7e58e8e6 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0020EncodeableFactoryRenameCodeFix.cs @@ -38,7 +38,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0020 code fix: rewrite instance factory.Create() as diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs similarity index 99% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs index 37b3060662..82d43fa22a 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/CodeFixes/UA0022CertificateValidatorPropertyRenameCodeFix.cs @@ -39,7 +39,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Opc.Ua.MigrationAnalyzer.Diagnostics; -namespace Opc.Ua.MigrationAnalyzer.CodeFixes +namespace Opc.Ua.MigrationAnalyzer.CodeFixer { /// /// UA0022 code fix: rewrite xxx.CertificateValidator as diff --git a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/Opc.Ua.MigrationAnalyzer.CodeFixer.csproj similarity index 95% rename from Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj rename to Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/Opc.Ua.MigrationAnalyzer.CodeFixer.csproj index 4edf32df29..44bd41af1c 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer.CodeFixes/Opc.Ua.MigrationAnalyzer.CodeFixes.csproj +++ b/Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/Opc.Ua.MigrationAnalyzer.CodeFixer.csproj @@ -3,8 +3,8 @@ netstandard2.0 false true - $(AssemblyPrefix).MigrationAnalyzer.CodeFixes - Opc.Ua.MigrationAnalyzer.CodeFixes + $(AssemblyPrefix).MigrationAnalyzer.CodeFixer + Opc.Ua.MigrationAnalyzer.CodeFixer OPC UA .NET Standard migration code-fix providers (1.5.378 to 2.0). Companion to Opc.Ua.MigrationAnalyzer analyzers. - + false all - + - - @@ -83,7 +83,7 @@ inside GetBuildVersion). Static evaluation in a PropertyGroup is too early. --> - version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.MigrationAnalyzer.dll;codeFixesDll=$(MSBuildThisFileDirectory)..\Opc.Ua.MigrationAnalyzer.CodeFixes\bin\$(Configuration)\netstandard2.0\Opc.Ua.MigrationAnalyzer.CodeFixes.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.MigrationHelpers\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props + version=$(PackageVersion);configuration=$(Configuration);repoRoot=$(MSBuildThisFileDirectory)..\..;analyzerDll=$(MSBuildThisFileDirectory)bin\$(Configuration)\netstandard2.0\Opc.Ua.MigrationAnalyzer.dll;codeFixesDll=$(MSBuildThisFileDirectory)..\Opc.Ua.MigrationAnalyzer.CodeFixer\bin\$(Configuration)\netstandard2.0\Opc.Ua.MigrationAnalyzer.CodeFixer.dll;shimBin=$(MSBuildThisFileDirectory)..\Opc.Ua.MigrationAnalyzer.Core\bin\$(Configuration);readme=$(MSBuildThisFileDirectory)NugetREADME.md;propsFile=$(MSBuildThisFileDirectory)OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props diff --git a/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec b/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec index 84161e9c5c..09379d0ff0 100644 --- a/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec +++ b/Tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec @@ -11,7 +11,7 @@ https://github.com/OPCFoundation/UA-.NETStandard images/logo.jpg NugetREADME.md - OPC UA .NET Standard migration analyzers and code fixers (UA0001-UA0020) bundled with the Opc.Ua.MigrationHelpers compatibility assembly that re-exposes the 1.5.378 obsolete surface to ease the move to 1.6. + OPC UA .NET Standard migration analyzers and code fixers (UA0001-UA0020) bundled with the Opc.Ua.MigrationAnalyzer.Core compatibility assembly that re-exposes the 1.5.378 obsolete surface to ease the move to 1.6. Copyright (c) 2004-2025 OPC Foundation, Inc OPCFoundation OPC UA analyzer codefix roslyn migration shim true @@ -69,13 +69,13 @@ - - - - - - - + + + + + + + diff --git a/Tools/Roslyn.slnx b/Tools/Roslyn.slnx index d63b64630e..129c946761 100644 --- a/Tools/Roslyn.slnx +++ b/Tools/Roslyn.slnx @@ -6,11 +6,11 @@ - + - - + + diff --git a/UA.slnx b/UA.slnx index a16240a3d1..ce8d7d67dc 100644 --- a/UA.slnx +++ b/UA.slnx @@ -122,10 +122,10 @@ - + - - + +