diff --git a/src/DynamoRevit.All.sln b/src/DynamoRevit.All.sln
index 70bd05e904..6c6ac4312c 100644
--- a/src/DynamoRevit.All.sln
+++ b/src/DynamoRevit.All.sln
@@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitNodes", "Libraries\Rev
{133FC760-5699-46D9-BEA6-E816B5F01016} = {133FC760-5699-46D9-BEA6-E816B5F01016}
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitNodes.Analyzers", "Libraries\RevitNodes.Analyzers\RevitNodes.Analyzers.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitNodesUI", "Libraries\RevitNodesUI\RevitNodesUI.csproj", "{75940ACC-3708-4526-8D91-7E3365BAF682}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitServices", "Libraries\RevitServices\RevitServices.csproj", "{E4701F9E-41AB-4044-8166-85D924FEB632}"
@@ -134,6 +136,14 @@ Global
{0BC2A611-BD0E-4FCC-A1DE-81F14ED369B2}.Release|NET100.Build.0 = Release|NET100
{0BC2A611-BD0E-4FCC-A1DE-81F14ED369B2}.Release|NET80.ActiveCfg = Release|NET80
{0BC2A611-BD0E-4FCC-A1DE-81F14ED369B2}.Release|NET80.Build.0 = Release|NET80
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|NET100.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|NET100.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|NET80.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|NET80.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|NET100.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|NET100.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|NET80.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|NET80.Build.0 = Release|Any CPU
{75940ACC-3708-4526-8D91-7E3365BAF682}.Debug|NET100.ActiveCfg = Debug|NET100
{75940ACC-3708-4526-8D91-7E3365BAF682}.Debug|NET100.Build.0 = Debug|NET100
{75940ACC-3708-4526-8D91-7E3365BAF682}.Debug|NET80.ActiveCfg = Debug|NET80
@@ -228,6 +238,7 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0BC2A611-BD0E-4FCC-A1DE-81F14ED369B2} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13}
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13}
{75940ACC-3708-4526-8D91-7E3365BAF682} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13}
{E4701F9E-41AB-4044-8166-85D924FEB632} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13}
{0E492D35-2310-4849-9694-A2A53C09F21B} = {F4D44BC0-32CF-4E58-AD2A-F19CE1450B00}
diff --git a/src/Libraries/RevitNodes.Analyzers/AnalyzerReleases.Shipped.md b/src/Libraries/RevitNodes.Analyzers/AnalyzerReleases.Shipped.md
new file mode 100644
index 0000000000..0dd62e3147
--- /dev/null
+++ b/src/Libraries/RevitNodes.Analyzers/AnalyzerReleases.Shipped.md
@@ -0,0 +1,2 @@
+## Release 1.0
+### New Rules
diff --git a/src/Libraries/RevitNodes.Analyzers/AnalyzerReleases.Unshipped.md b/src/Libraries/RevitNodes.Analyzers/AnalyzerReleases.Unshipped.md
new file mode 100644
index 0000000000..a072149254
--- /dev/null
+++ b/src/Libraries/RevitNodes.Analyzers/AnalyzerReleases.Unshipped.md
@@ -0,0 +1,8 @@
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|--------------------
+ DN001 | Usage | Warning | Reports when code uses a type, method, or property marked with [NodeObsolete] attribute
+ DN002 | Usage | Error | Reports when code uses a type, method, or property marked with [NodeObsolete(IsError = true)] attribute
+
diff --git a/src/Libraries/RevitNodes.Analyzers/NodeObsoleteAnalyzer.cs b/src/Libraries/RevitNodes.Analyzers/NodeObsoleteAnalyzer.cs
new file mode 100644
index 0000000000..03e2700e19
--- /dev/null
+++ b/src/Libraries/RevitNodes.Analyzers/NodeObsoleteAnalyzer.cs
@@ -0,0 +1,276 @@
+using System;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace RevitNodes.Analyzers
+{
+ ///
+ /// Analyzer that detects usage of types, methods, or properties marked with [NodeObsolete] attribute
+ /// from DynamoVisualProgramming dependencies and reports compiler warnings.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class NodeObsoleteAnalyzer : DiagnosticAnalyzer
+ {
+ // Diagnostic IDs
+ public const string DiagnosticId = "DN001";
+ public const string ErrorDiagnosticId = "DN002";
+
+ // Diagnostic categories
+ private const string Category = "Usage";
+
+ // Diagnostic descriptors
+ private static readonly DiagnosticDescriptor WarningRule = new DiagnosticDescriptor(
+ DiagnosticId,
+ "Node is obsolete",
+ "{0}",
+ Category,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "The node marked with [NodeObsolete] should not be used.");
+
+ private static readonly DiagnosticDescriptor ErrorRule = new DiagnosticDescriptor(
+ ErrorDiagnosticId,
+ "Node is obsolete (error)",
+ "{0}",
+ Category,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "The node marked with [NodeObsolete] should not be used.");
+
+ public override ImmutableArray SupportedDiagnostics =>
+ ImmutableArray.Create(WarningRule, ErrorRule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.IdentifierName);
+ context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
+ context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
+ }
+
+ private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
+ {
+ var identifierName = (IdentifierNameSyntax)context.Node;
+
+ // Get the symbol for the identifier
+ var symbolInfo = context.SemanticModel.GetSymbolInfo(identifierName, context.CancellationToken);
+ if (symbolInfo.Symbol == null)
+ return;
+
+ CheckAndReportObsolete(symbolInfo.Symbol, identifierName.GetLocation(), context);
+ }
+
+ private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
+ {
+ var invocation = (InvocationExpressionSyntax)context.Node;
+ var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken);
+
+ if (symbolInfo.Symbol != null)
+ {
+ CheckAndReportObsolete(symbolInfo.Symbol, invocation.GetLocation(), context);
+ }
+ }
+
+ private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
+ {
+ var memberAccess = (MemberAccessExpressionSyntax)context.Node;
+ var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccess, context.CancellationToken);
+
+ if (symbolInfo.Symbol != null)
+ {
+ CheckAndReportObsolete(symbolInfo.Symbol, memberAccess.Name.GetLocation(), context);
+ }
+ }
+
+ private static void CheckAndReportObsolete(ISymbol symbol, Location location, SyntaxNodeAnalysisContext context)
+ {
+ var nodeObsoleteAttribute = GetNodeObsoleteAttribute(symbol, context.Compilation);
+ if (nodeObsoleteAttribute == null)
+ return;
+
+ var message = GetObsoleteMessage(nodeObsoleteAttribute);
+ var isError = GetIsError(nodeObsoleteAttribute);
+ var symbolName = GetSymbolDisplayName(symbol);
+
+ var diagnosticMessage = string.IsNullOrEmpty(message)
+ ? $"{symbolName} is obsolete."
+ : $"{symbolName} is obsolete: {message}";
+
+ var rule = isError ? ErrorRule : WarningRule;
+ var diagnostic = Diagnostic.Create(
+ rule,
+ location,
+ diagnosticMessage);
+
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ private static INamedTypeSymbol GetNodeObsoleteAttributeType(Compilation compilation)
+ {
+ // Try multiple possible namespaces where NodeObsolete might be defined
+ var possibleNames = new[]
+ {
+ "Autodesk.DesignScript.Runtime.NodeObsoleteAttribute",
+ "Autodesk.DesignScript.Runtime.NodeObsolete",
+ "Dynamo.Graph.Nodes.NodeObsoleteAttribute",
+ "Dynamo.Graph.Nodes.NodeObsolete",
+ "NodeObsoleteAttribute",
+ "NodeObsolete"
+ };
+
+ foreach (var name in possibleNames)
+ {
+ var type = compilation.GetTypeByMetadataName(name);
+ if (type != null)
+ return type;
+ }
+
+ return null;
+ }
+
+ private static AttributeData GetNodeObsoleteAttribute(ISymbol symbol, Compilation compilation)
+ {
+ var nodeObsoleteAttributeType = GetNodeObsoleteAttributeType(compilation);
+ if (nodeObsoleteAttributeType == null)
+ return null;
+
+ // Check the symbol itself
+ foreach (var attribute in symbol.GetAttributes())
+ {
+ if (IsNodeObsoleteAttribute(attribute, nodeObsoleteAttributeType))
+ {
+ return attribute;
+ }
+ }
+
+ // Check the containing type if symbol is a method or property
+ if (symbol.ContainingType != null)
+ {
+ foreach (var attribute in symbol.ContainingType.GetAttributes())
+ {
+ if (IsNodeObsoleteAttribute(attribute, nodeObsoleteAttributeType))
+ {
+ return attribute;
+ }
+ }
+ }
+
+ // Check base types/interfaces for inherited attributes
+ if (symbol.ContainingType != null)
+ {
+ var baseType = symbol.ContainingType.BaseType;
+ while (baseType != null)
+ {
+ foreach (var attribute in baseType.GetAttributes())
+ {
+ if (IsNodeObsoleteAttribute(attribute, nodeObsoleteAttributeType))
+ {
+ return attribute;
+ }
+ }
+ baseType = baseType.BaseType;
+ }
+ }
+
+ return null;
+ }
+
+ private static bool IsNodeObsoleteAttribute(AttributeData attribute, INamedTypeSymbol expectedType)
+ {
+ if (attribute.AttributeClass == null)
+ return false;
+
+ // Direct type match
+ if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, expectedType))
+ return true;
+
+ // Name match (handles cases where the type might be from a different assembly)
+ if (attribute.AttributeClass.Name == "NodeObsoleteAttribute" ||
+ attribute.AttributeClass.Name == "NodeObsolete")
+ return true;
+
+ // Check if it derives from or implements the expected type
+ var currentType = attribute.AttributeClass;
+ while (currentType != null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(currentType, expectedType))
+ return true;
+ currentType = currentType.BaseType;
+ }
+
+ return false;
+ }
+
+ private static string GetObsoleteMessage(AttributeData attribute)
+ {
+ // Check constructor arguments first
+ if (attribute.ConstructorArguments.Length > 0)
+ {
+ var arg = attribute.ConstructorArguments[0];
+ if (arg.Kind == TypedConstantKind.Primitive && arg.Value is string message)
+ {
+ return message;
+ }
+ }
+
+ // Check named arguments for Message property
+ foreach (var namedArg in attribute.NamedArguments)
+ {
+ if (namedArg.Key == "Message" && namedArg.Value.Kind == TypedConstantKind.Primitive)
+ {
+ if (namedArg.Value.Value is string message)
+ {
+ return message;
+ }
+ }
+ }
+
+ return string.Empty;
+ }
+
+ private static bool GetIsError(AttributeData attribute)
+ {
+ // Check named arguments for IsError property
+ foreach (var namedArg in attribute.NamedArguments)
+ {
+ if (namedArg.Key == "IsError" && namedArg.Value.Kind == TypedConstantKind.Primitive)
+ {
+ if (namedArg.Value.Value is bool isError)
+ {
+ return isError;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static string GetSymbolDisplayName(ISymbol symbol)
+ {
+ // Use the symbol's display format for better readability
+ if (symbol is IMethodSymbol method)
+ {
+ return $"{method.ContainingType?.Name ?? ""}.{method.Name}";
+ }
+ if (symbol is IPropertySymbol property)
+ {
+ return $"{property.ContainingType?.Name ?? ""}.{property.Name}";
+ }
+ if (symbol is IFieldSymbol field)
+ {
+ return $"{field.ContainingType?.Name ?? ""}.{field.Name}";
+ }
+ if (symbol is INamedTypeSymbol type)
+ {
+ return type.Name;
+ }
+
+ return symbol.Name;
+ }
+ }
+}
+
diff --git a/src/Libraries/RevitNodes.Analyzers/README.md b/src/Libraries/RevitNodes.Analyzers/README.md
new file mode 100644
index 0000000000..43ed140731
--- /dev/null
+++ b/src/Libraries/RevitNodes.Analyzers/README.md
@@ -0,0 +1,62 @@
+# NodeObsolete Analyzer
+
+This Roslyn analyzer detects compile-time usage of types, methods, or properties marked with the `[NodeObsolete]` attribute from DynamoVisualProgramming dependencies.
+
+## Purpose
+
+When DynamoVisualProgramming marks APIs as obsolete using the `[NodeObsolete]` attribute, this analyzer ensures that your codebase gets compiler warnings (or errors) when you use those obsolete APIs, helping you migrate to newer alternatives before they're removed.
+
+## How It Works
+
+The analyzer:
+1. Scans your code during compilation
+2. Detects when you use symbols (classes, methods, properties) that have the `[NodeObsolete]` attribute
+3. The attribute can be defined in external assemblies (like DynamoVisualProgramming packages)
+4. Reports warnings or errors at the usage location
+
+## Diagnostic IDs
+
+- **DN001**: Warning when using a node/method/property marked with `[NodeObsolete]`
+- **DN002**: Error when using a node/method/property marked with `[NodeObsolete(IsError = true)]`
+
+## Integration
+
+The analyzer is automatically included when you build the `RevitNodes` project. It will analyze all code that references DynamoVisualProgramming assemblies.
+
+## Example
+
+If DynamoVisualProgramming has:
+
+```csharp
+[NodeObsolete("Use NewMethod instead")]
+public static void OldMethod() { }
+```
+
+And your code uses it:
+
+```csharp
+OldMethod(); // This will generate a compiler warning: "OldMethod is obsolete: Use NewMethod instead"
+```
+
+## Supported Usage Patterns
+
+The analyzer detects:
+- Direct type references: `var x = new ObsoleteType();`
+- Method invocations: `ObsoleteMethod();`
+- Property access: `var x = ObsoleteProperty;`
+- Member access: `obj.ObsoleteMember`
+
+## Attribute Detection
+
+The analyzer searches for `NodeObsolete` attributes in these namespaces:
+- `Autodesk.DesignScript.Runtime.NodeObsoleteAttribute`
+- `Autodesk.DesignScript.Runtime.NodeObsolete`
+- `Dynamo.Graph.Nodes.NodeObsoleteAttribute`
+- `Dynamo.Graph.Nodes.NodeObsolete`
+- And other common locations
+
+It also checks:
+- Attributes on the symbol itself
+- Attributes on containing types (for methods/properties)
+- Attributes on base types (inherited attributes)
+
diff --git a/src/Libraries/RevitNodes.Analyzers/RevitNodes.Analyzers.csproj b/src/Libraries/RevitNodes.Analyzers/RevitNodes.Analyzers.csproj
new file mode 100644
index 0000000000..b812c7e662
--- /dev/null
+++ b/src/Libraries/RevitNodes.Analyzers/RevitNodes.Analyzers.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.0
+ false
+ false
+ latest
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/Libraries/RevitNodes/RevitNodes.csproj b/src/Libraries/RevitNodes/RevitNodes.csproj
index 4d9940c1cf..394a48481c 100644
--- a/src/Libraries/RevitNodes/RevitNodes.csproj
+++ b/src/Libraries/RevitNodes/RevitNodes.csproj
@@ -82,4 +82,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/Libraries/RevitNodesUI/RevitNodesUI.csproj b/src/Libraries/RevitNodesUI/RevitNodesUI.csproj
index e4a62734e3..2b0b5fe22c 100644
--- a/src/Libraries/RevitNodesUI/RevitNodesUI.csproj
+++ b/src/Libraries/RevitNodesUI/RevitNodesUI.csproj
@@ -77,4 +77,7 @@
+
+
+
\ No newline at end of file