diff --git a/src/Weaviate.Client.Analyzers.Tests/VectorizerFactoryAnalyzerTests.cs b/src/Weaviate.Client.Analyzers.Tests/VectorizerFactoryAnalyzerTests.cs index bb6f25ec..e81be4fc 100644 --- a/src/Weaviate.Client.Analyzers.Tests/VectorizerFactoryAnalyzerTests.cs +++ b/src/Weaviate.Client.Analyzers.Tests/VectorizerFactoryAnalyzerTests.cs @@ -64,14 +64,30 @@ public class VectorizerFactory { Model = model // Missing: BaseURL = baseURL + // Also missing: ImageFields, TextFields, VideoFields (no params) }; } }"; - var expected = VerifyCS - .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) - .WithLocation(0) - .WithArguments("TestVectorizer", "BaseURL"); + var expected = new[] + { + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "BaseURL"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "ImageFields"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "TextFields"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "VideoFields"), + }; await VerifyCS.VerifyAnalyzerAsync(testCode, expected); } @@ -90,12 +106,18 @@ public class VectorizerFactory { public VectorizerConfig TestVectorizer( string? model = null, - string? baseURL = null + string? baseURL = null, + WeightedFields? imageFields = null, + WeightedFields? textFields = null, + WeightedFields? videoFields = null ) => new Models.Vectorizer.TestVectorizer { Model = model, - BaseURL = baseURL + BaseURL = baseURL, + ImageFields = imageFields, + TextFields = textFields, + VideoFields = videoFields }; } }"; @@ -116,7 +138,7 @@ namespace Weaviate.Client public class VectorizerFactory { - public VectorizerConfig TestVectorizer( + public VectorizerConfig {|#0:TestVectorizer|}( WeightedFields imageFields, WeightedFields textFields, WeightedFields videoFields @@ -126,15 +148,26 @@ WeightedFields videoFields ImageFields = imageFields, TextFields = textFields, VideoFields = videoFields, - {|#0:Weights = VectorizerWeights.FromWeightedFields(imageFields, textFields)|} + {|#1:Weights = VectorizerWeights.FromWeightedFields(imageFields, textFields)|} }; } }"; - var expected = VerifyCS - .Diagnostic(VectorizerFactoryAnalyzer.MissingWeightFieldDiagnosticId) - .WithLocation(0) - .WithArguments("TestVectorizer", "videoFields"); + var expected = new[] + { + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingWeightFieldDiagnosticId) + .WithLocation(1) + .WithArguments("TestVectorizer", "videoFields"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "Model"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "BaseURL"), + }; await VerifyCS.VerifyAnalyzerAsync(testCode, expected); } @@ -155,10 +188,14 @@ public class VectorizerFactory public VectorizerConfig TestVectorizer( WeightedFields imageFields, WeightedFields textFields, - WeightedFields videoFields + WeightedFields videoFields, + string? model = null, + string? baseURL = null ) => new Models.Vectorizer.TestVectorizer { + Model = model, + BaseURL = baseURL, ImageFields = imageFields, TextFields = textFields, VideoFields = videoFields, @@ -186,10 +223,14 @@ public class VectorizerFactory public VectorizerConfig TestVectorizer( WeightedFields imageFields, WeightedFields textFields, - WeightedFields videoFields + WeightedFields videoFields, + string? model = null, + string? baseURL = null ) => new Models.Vectorizer.TestVectorizer { + Model = model, + BaseURL = baseURL, ImageFields = imageFields, TextFields = textFields, VideoFields = videoFields, @@ -214,11 +255,19 @@ namespace Weaviate.Client public class VectorizerFactory { public VectorizerConfig TestVectorizer( - string? model = null + string? model = null, + string? baseURL = null, + WeightedFields? imageFields = null, + WeightedFields? textFields = null, + WeightedFields? videoFields = null ) => new Models.Vectorizer.TestVectorizer { - Model = model + Model = model, + BaseURL = baseURL, + ImageFields = imageFields, + TextFields = textFields, + VideoFields = videoFields }; } }"; @@ -246,14 +295,30 @@ public class VectorizerFactoryMulti { Model = model // Missing: BaseURL = baseURL + // Also missing: ImageFields, TextFields, VideoFields (no params) }; } }"; - var expected = VerifyCS - .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) - .WithLocation(0) - .WithArguments("TestVectorizer", "BaseURL"); + var expected = new[] + { + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "BaseURL"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "ImageFields"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "TextFields"), + VerifyCS + .Diagnostic(VectorizerFactoryAnalyzer.MissingPropertyDiagnosticId) + .WithLocation(0) + .WithArguments("TestVectorizer", "VideoFields"), + }; await VerifyCS.VerifyAnalyzerAsync(testCode, expected); } diff --git a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md index 5bc309ac..50eab87e 100644 --- a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Shipped.md @@ -3,5 +3,7 @@ ### New Rules Rule ID | Category | Severity | Notes -------------|----------|----------|----------------------------------------------------- -WEAVIATE001 | Usage | Error | AutoArray should only be used as method parameter +------------|----------|----------|----------------------------------------------------------------- +WEAVIATE001 | Usage | Warning | AutoArrayUsageAnalyzer - AutoArray should only be used as method parameter +WEAVIATE002 | Usage | Error | VectorizerFactoryAnalyzer - Missing property initialization +WEAVIATE003 | Usage | Error | VectorizerFactoryAnalyzer - Missing field in Weights calculation diff --git a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md index e463f122..fb2ed19f 100644 --- a/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/Weaviate.Client.Analyzers/AnalyzerReleases.Unshipped.md @@ -1,8 +1,5 @@ ### New Rules -Rule ID | Category | Severity | Notes ---------|----------|----------|------- -WEAVIATE001 | Usage | Error | AutoArrayUsageAnalyzer -WEAVIATE002 | Usage | Warning | VectorizerFactoryAnalyzer - Missing property initialization -WEAVIATE003 | Usage | Warning | VectorizerFactoryAnalyzer - Missing field in Weights calculation +Rule ID | Category | Severity | Notes +------------|----------|----------|----------------------------------------------------------------- diff --git a/src/Weaviate.Client.Analyzers/AutoArrayUsageAnalyzer.cs b/src/Weaviate.Client.Analyzers/AutoArrayUsageAnalyzer.cs index 8a650eec..c5c9683f 100644 --- a/src/Weaviate.Client.Analyzers/AutoArrayUsageAnalyzer.cs +++ b/src/Weaviate.Client.Analyzers/AutoArrayUsageAnalyzer.cs @@ -24,7 +24,7 @@ public class AutoArrayUsageAnalyzer : DiagnosticAnalyzer Title, MessageFormat, Category, - DiagnosticSeverity.Error, + DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description ); diff --git a/src/Weaviate.Client.Analyzers/VectorizerFactoryAnalyzer.cs b/src/Weaviate.Client.Analyzers/VectorizerFactoryAnalyzer.cs index 92b3f4da..0570fb7e 100644 --- a/src/Weaviate.Client.Analyzers/VectorizerFactoryAnalyzer.cs +++ b/src/Weaviate.Client.Analyzers/VectorizerFactoryAnalyzer.cs @@ -16,11 +16,11 @@ public class VectorizerFactoryAnalyzer : DiagnosticAnalyzer private const string Category = "Usage"; private static readonly LocalizableString MissingPropertyTitle = - "Vectorizer factory method missing property initialization"; + "Vectorizer factory method missing property initialization or parameter"; private static readonly LocalizableString MissingPropertyMessageFormat = - "Factory method creating '{0}' does not initialize property '{1}'"; + "Factory method creating '{0}' does not have a way to set property '{1}'. Add a parameter for it or initialize it in the object initializer."; private static readonly LocalizableString MissingPropertyDescription = - "All properties of a vectorizer config should be initializable through the factory method."; + "All public properties of a vectorizer config should be initializable through factory method parameters or explicitly initialized in the object initializer to ensure completeness."; private static readonly LocalizableString MissingWeightFieldTitle = "Vectorizer factory method missing field in Weights calculation"; @@ -34,7 +34,7 @@ public class VectorizerFactoryAnalyzer : DiagnosticAnalyzer MissingPropertyTitle, MissingPropertyMessageFormat, Category, - DiagnosticSeverity.Warning, + DiagnosticSeverity.Error, isEnabledByDefault: true, description: MissingPropertyDescription ); @@ -44,7 +44,7 @@ public class VectorizerFactoryAnalyzer : DiagnosticAnalyzer MissingWeightFieldTitle, MissingWeightFieldMessageFormat, Category, - DiagnosticSeverity.Warning, + DiagnosticSeverity.Error, isEnabledByDefault: true, description: MissingWeightFieldDescription ); @@ -169,24 +169,27 @@ SemanticModel semanticModel // Check if there's a corresponding parameter for this property var propertyNameLower = property.Name.ToLowerInvariant(); - // Handle special mappings (e.g., BaseURL -> baseURL, VectorizeCollectionName -> vectorizeCollectionName) + // Convert PascalCase to camelCase for property name + var camelCasePropertyName = + property.Name.Length > 0 + ? char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1) + : property.Name; + var hasCorrespondingParameter = parameterNames.Contains(propertyNameLower) - || parameterNames.Contains( - property.Name.Substring(0, 1).ToLowerInvariant() + property.Name.Substring(1) - ); - - // Only warn if there's a parameter but it's not being used - if (hasCorrespondingParameter) - { - var diagnostic = Diagnostic.Create( - MissingPropertyRule, - methodDeclaration.Identifier.GetLocation(), - namedType.Name, - property.Name - ); - context.ReportDiagnostic(diagnostic); - } + || parameterNames.Contains(camelCasePropertyName.ToLowerInvariant()); + + // Warn if property is not initialized AND either: + // 1. There's a corresponding parameter (parameter exists but not used), OR + // 2. There's NO parameter and property isn't initialized (property can't be set via factory) + // This ensures all properties can be set through the factory method + var diagnostic = Diagnostic.Create( + MissingPropertyRule, + methodDeclaration.Identifier.GetLocation(), + namedType.Name, + property.Name + ); + context.ReportDiagnostic(diagnostic); } }