@@ -67,6 +67,7 @@ class PropertiesFileValidator {
6767 private final MicroProfileValidationSettings validationSettings ;
6868 private final PropertiesFileExtensionRegistry extensionRegistry ;
6969 private final Map <String , List <Property >> existingProperties ;
70+ private final Map <String , Property > potentiallyUnknownProperties ;
7071 private Set <String > declaredProperties ;
7172 private Map <String , ItemMetadata > availableProperties ;
7273
@@ -82,6 +83,7 @@ public PropertiesFileValidator(MicroProfileProjectInfo projectInfo, List<Diagnos
8283 this .validationSettings = validationSettings ;
8384 this .extensionRegistry = extensionRegistry ;
8485 this .existingProperties = new HashMap <String , List <Property >>();
86+ this .potentiallyUnknownProperties = new HashMap <String , Property >();
8587 // to be lazily init
8688 this .declaredProperties = null ;
8789 this .availableProperties = null ;
@@ -101,6 +103,7 @@ public void validate(PropertiesModel document, CancelChecker cancelChecker) {
101103
102104 addDiagnosticsForDuplicates ();
103105 addDiagnosticsForMissingRequired (document );
106+ addDiagnosticsForUnknownProperties ();
104107 }
105108
106109 private void validateProperty (Property property , CancelChecker cancelChecker ) {
@@ -196,8 +199,8 @@ private void validateUnknownProperty(String propertyName, Property property) {
196199 // The unknown validation must be ignored for this property name
197200 return ;
198201 }
199- addDiagnostic ( "Unrecognized property '" + propertyName + "', it is not referenced in any Java files" ,
200- property . getKey (), severity , ValidationType . unknown . name () );
202+ // Store the property for later validation after all expressions are processed
203+ potentiallyUnknownProperties . put ( propertyName , property );
201204 }
202205
203206 // ---------------- Property value validation
@@ -308,24 +311,17 @@ private void validatePropertyValueExpressions(String propertyName, ItemMetadata
308311 if (expressionSeverity == null || syntaxSeverity == null ) {
309312 return ;
310313 }
314+
315+ // Lazy initialization: collect declared properties, available properties, and referenced properties
316+ // in a single pass when processing the first expression
317+ if (declaredProperties == null ) {
318+ initializePropertiesCollections (property .getOwnerModel ());
319+ }
320+
311321 for (Node child : property .getValue ().getChildren ()) {
312322 if (child != null && child .getNodeType () == NodeType .PROPERTY_VALUE_EXPRESSION ) {
313323 PropertyValueExpression propValExpr = (PropertyValueExpression ) child ;
314324 if (expressionSeverity != null ) {
315- if (declaredProperties == null ) {
316- // Collect names of all properties defined in the configuration file and the
317- // project information
318- declaredProperties = property .getOwnerModel ().getChildren ().stream ().filter (n -> {
319- return n .getNodeType () == NodeType .PROPERTY ;
320- }).map (prop -> {
321- return ((Property ) prop ).getPropertyNameWithProfile ();
322- }).collect (Collectors .toSet ());
323-
324- availableProperties = projectInfo .getProperties ()//
325- .stream () //
326- .collect (Collectors .toMap (ItemMetadata ::getName , Function .identity (), (i1 , i2 ) -> i1 ));
327- }
328-
329325 String refdProp = propValExpr .getReferencedPropertyName ();
330326 if (!declaredProperties .contains (refdProp )) {
331327 // The referenced property name doesn't reference a property inside the file
@@ -429,6 +425,52 @@ private void addDiagnosticsForRequiredIfNoValue(String propertyName, DiagnosticS
429425 }
430426 }
431427
428+ private void addDiagnosticsForUnknownProperties () {
429+ // Properties that are referenced in expressions have already been removed from potentiallyUnknownProperties
430+ potentiallyUnknownProperties .forEach ((propertyName , property ) -> {
431+ DiagnosticSeverity severity = validationSettings .getUnknown ().getDiagnosticSeverity (propertyName );
432+ if (severity != null ) {
433+ addDiagnostic ("Unrecognized property '" + propertyName + "', it is not referenced in any Java files" ,
434+ property .getKey (), severity , ValidationType .unknown .name ());
435+ }
436+ });
437+ }
438+
439+ /**
440+ * Initialize properties collections in a single pass over the properties model.
441+ * Collects:
442+ * - declaredProperties: all property names defined in the file
443+ * - availableProperties: all properties from project metadata
444+ * Also removes referenced properties from potentiallyUnknownProperties
445+ */
446+ private void initializePropertiesCollections (PropertiesModel model ) {
447+ declaredProperties = new java .util .HashSet <>();
448+
449+ // Single pass to collect declared properties and remove referenced ones from potentially unknown
450+ for (Node node : model .getChildren ()) {
451+ if (node .getNodeType () == NodeType .PROPERTY ) {
452+ Property prop = (Property ) node ;
453+ // Collect declared property
454+ declaredProperties .add (prop .getPropertyNameWithProfile ());
455+
456+ // Remove referenced properties from potentiallyUnknownProperties
457+ if (prop .getValue () != null ) {
458+ for (Node child : prop .getValue ().getChildren ()) {
459+ if (child .getNodeType () == NodeType .PROPERTY_VALUE_EXPRESSION ) {
460+ PropertyValueExpression expr = (PropertyValueExpression ) child ;
461+ potentiallyUnknownProperties .remove (expr .getReferencedPropertyName ());
462+ }
463+ }
464+ }
465+ }
466+ }
467+
468+ // Collect available properties from project metadata
469+ availableProperties = projectInfo .getProperties ()
470+ .stream ()
471+ .collect (Collectors .toMap (ItemMetadata ::getName , Function .identity (), (i1 , i2 ) -> i1 ));
472+ }
473+
432474 Diagnostic addDiagnostic (String message , Node node , DiagnosticSeverity severity , String code ) {
433475 Range range = PositionUtils .createRange (node );
434476 return addDiagnostic (message , range , severity , code );
0 commit comments