# OPC UA migration analyzers, code fixers, source generator, and compatibility shim ## What you get A single NuGet install (`OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer`) that ships **three Roslyn components + a runtime shim** to help migrate from OPC UA .NET Standard 1.5.378 to 2.0: - a Roslyn **analyzer + code-fixer** set (26 implemented rules through `UA0030`, excluding `UA0013`, `UA0016`, `UA0017`, and the shim-only `UA0029`) that flags the automatable patterns covered by the [2.0 migration guide](../../docs/migrate/2.0.x/README.md) and, where safe, applies the fix automatically; - a Roslyn **source generator** (`Opc.Ua.MigrationAnalyzer.Generator.dll`) that emits per-consumer `public sealed [Obsolete] class Collection : List` shims for every `Collection` wrapper the consumer references but that 2.0 removed — **including** model-compiled `Collection` patterns, not just the built-in ones. Element types renamed across the 1.5.378 → 2.0 boundary (`DateTime`→`DateTimeUtc`, `Guid`→`Uuid`, `byte[]`→`ByteString`) are pinned through a small override table; the legacy `XmlElementCollection` interpretation is pinned to `System.Xml.XmlElement` to disambiguate it from `Opc.Ua.XmlElement`. Everything else resolves from consumer source declarations, then from exact `System.` or `Opc.Ua.` metadata names; and - a **compatibility shim** assembly (`Opc.Ua.MigrationAnalyzer.Core.dll`) that re-supplies the obsolete extension surface 2.0 moved or removed, so most consumer projects still compile after the upgrade. > ℹ **The generator emits `public` shim types** so legacy `Collection` usage > can continue compiling while you migrate incrementally. Keep treating each > `[Obsolete]` + `UA0002` site as a migration step to `List` / `ArrayOf`. ## How to migrate 1. Add the 2.0 OPC UA packages **and** the MigrationAnalyzer package to your consumer project. The package is public on nuget.org; the floating version resolves the latest published `2.0.0-preview.N` release: ```xml ``` 2. Run `dotnet build`. Your code should compile: the shim covers the `[Obsolete]` extension surface that 2.0 moved or removed; the source generator covers `Collection` wrappers; 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`, `UA0021`) are `Info`-level and need a manual review. The warning-level `UA0023`–`UA0028` and `UA0030` are also diagnostic-only. A single generator diagnostic (`MIG01`) fires when the generator can't resolve a unique element type from consumer source or exact `System.` / `Opc.Ua.` metadata names. Migrate the site manually or define the legacy wrapper explicitly. Calls to shimmed `SecurityPolicies` statics surface as `CS0618` messages tagged `UA0029`; no analyzer reports that marker, so migrate those calls manually. 4. Once the project is warning-free, remove the `OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` package reference. You are on clean 2.0 with no shim dependency. ## 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()` | | UA0021 | Info | `CertificateValidator` / `CertificateValidationEventArgs` (structural rename in 2.0) | | UA0022 | Warning | `ApplicationConfiguration.CertificateValidator` / `ServerBase.CertificateValidator` (renamed in 2.0 to `.CertificateManager`) | | UA0023 | Warning | Legacy PubSub top-level types and `UaPubSubApplication.Create*` | | UA0024 | Warning | Exposed server, session, and subscription diagnostics locks | | UA0025 | Warning | `ILocalNode.DataLock` / `Node.DataLock` | | UA0026 | Warning | `BaseVariableValue.Lock` | | UA0027 | Warning | `NodeBrowser.DataLock` | | UA0028 | Warning | `ApplicationConfiguration.PropertiesLock` | | UA0029 | — | Shim/manual marker only: `SecurityPolicies` lookup and cryptography statics moved to `ISecurityPolicyRegistry`; no analyzer currently reports this ID | | UA0030 | Warning | Server `ISubscription` publish-pipeline members and `SessionPublishQueue` became internal | ## What the shim provides `Opc.Ua.MigrationAnalyzer.Core.dll` is delivered as a regular reference assembly and re-exposes the 1.5.378 surface in two flavors: - **Moved obsolete extensions** the 2.0 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. - `SecurityPolicies` lookup and cryptography statics marked for manual `ISecurityPolicyRegistry` migration (`UA0029`; surfaced as `CS0618`, not an analyzer diagnostic). ## 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`, then manually use `ArrayOf` at applicable API boundaries. ## 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);CS0612;CS0618;MIG01;UA0001;UA0002;UA0003;UA0004;UA0005;UA0006;UA0007;UA0008;UA0009;UA0010;UA0011;UA0012;UA0014;UA0015;UA0018;UA0019;UA0020;UA0021;UA0022;UA0023;UA0024;UA0025;UA0026;UA0027;UA0028;UA0030 ``` Remove each entry as you finish fixing the corresponding rule, and drop the whole block once the MigrationAnalyzer package is removed. ## Packaging note The package ships **three Roslyn component DLLs** under `analyzers/dotnet/roslyn./cs/`. The .NET SDK loads that folder when its compiler supports the Roslyn API and ignores it otherwise, so an older host cleanly skips the analyzer rather than failing to load it: | Roslyn API | Minimum host | | --- | --- | | 4.14 | Visual Studio 2022 17.14 / .NET SDK 9.0.300+ | | 5.0 | Visual Studio 2026 18.0 / .NET 10 SDK | .NET SDK 9.0.100 and 9.0.200 carry Roslyn 4.12 and 4.13 respectively; neither can load the package's oldest analyzer payload. Always use the latest available .NET SDK servicing release in the supported 9.x or 10.x band; servicing releases contain the security fixes needed for production. - `Opc.Ua.MigrationAnalyzer.dll` — the analyzer assembly. 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.MigrationAnalyzer.CodeFixer.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`). - `Opc.Ua.MigrationAnalyzer.Generator.dll` — the source generator that emits the `Collection` shims. The analyzer / code-fix 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 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 ```