Skip to content

Commit 448a797

Browse files
committed
Feature request: extend "microprofile(unknown)" to detect references
from property files Fixes redhat-developer/vscode-microprofile#600 Signed-off-by: azerr <azerr@redhat.com>
1 parent 5b16598 commit 448a797

4 files changed

Lines changed: 89 additions & 23 deletions

File tree

microprofile.ls/org.eclipse.lsp4mp.ls/src/main/java/org/eclipse/lsp4mp/services/properties/CompletionData.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
import org.eclipse.lsp4j.CompletionItem;
1717
import org.eclipse.lsp4mp.commons.utils.JSONUtility;
1818

19-
import com.google.gson.JsonObject;
20-
2119
/**
2220
* Represents data sent to for completionItem/resolve
2321
*

microprofile.ls/org.eclipse.lsp4mp.ls/src/main/java/org/eclipse/lsp4mp/services/properties/PropertiesFileValidator.java

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

microprofile.ls/org.eclipse.lsp4mp.ls/src/test/java/org/eclipse/lsp4mp/services/properties/PropertiesFileAssert.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,29 @@ public static void assertDiagnostics(List<Diagnostic> actual, Diagnostic... expe
732732
}
733733

734734
public static void assertDiagnostics(List<Diagnostic> actual, List<Diagnostic> expected) {
735-
assertEquals("Unexpected diagnostics:\n", expected, actual);
735+
// Sort both lists by position to make comparison order-independent
736+
List<Diagnostic> sortedActual = new ArrayList<>(actual);
737+
List<Diagnostic> sortedExpected = new ArrayList<>(expected);
738+
739+
java.util.Comparator<Diagnostic> diagnosticComparator = (d1, d2) -> {
740+
int lineCompare = Integer.compare(d1.getRange().getStart().getLine(),
741+
d2.getRange().getStart().getLine());
742+
if (lineCompare != 0) {
743+
return lineCompare;
744+
}
745+
int charCompare = Integer.compare(d1.getRange().getStart().getCharacter(),
746+
d2.getRange().getStart().getCharacter());
747+
if (charCompare != 0) {
748+
return charCompare;
749+
}
750+
// If same position, compare by message to have stable ordering
751+
return d1.getMessage().compareTo(d2.getMessage());
752+
};
753+
754+
sortedActual.sort(diagnosticComparator);
755+
sortedExpected.sort(diagnosticComparator);
756+
757+
assertEquals("Unexpected diagnostics:\n", sortedExpected, sortedActual);
736758
}
737759

738760
public static Diagnostic d(int line, int startCharacter, int endCharacter, String message,

microprofile.ls/org.eclipse.lsp4mp.ls/src/test/java/org/eclipse/lsp4mp/services/properties/expressions/PropertiesFileExpressionDiagnosticsTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ public void validateUnclosedPropertyExpression() {
7070
String value = "test.property = hello\n" + //
7171
"other.property = ${test.property";
7272
testDiagnosticsFor(value, //
73-
d(0, 0, 13, "Unrecognized property 'test.property', it is not referenced in any Java files",
74-
DiagnosticSeverity.Warning, ValidationType.unknown), //
7573
d(1, 0, 14, "Unrecognized property 'other.property', it is not referenced in any Java files",
7674
DiagnosticSeverity.Warning, ValidationType.unknown), //
7775
d(1, 17, 32, "Missing '}'", DiagnosticSeverity.Error, ValidationType.syntax));
@@ -180,8 +178,6 @@ public void validatePropertyFilePropertyInPropertyExpression() {
180178
String value = "test.property = hello\n" + //
181179
"other.property = ${test.property}";
182180
testDiagnosticsFor(value, //
183-
d(0, 0, 13, "Unrecognized property 'test.property', it is not referenced in any Java files",
184-
DiagnosticSeverity.Warning, ValidationType.unknown), //
185181
d(1, 0, 14, "Unrecognized property 'other.property', it is not referenced in any Java files",
186182
DiagnosticSeverity.Warning, ValidationType.unknown));
187183
}
@@ -204,4 +200,12 @@ public void validateValueForLevelBasedOnRuleWithExpression() throws BadLocationE
204200
d(0, 35, 38, "Bad level \"XXX\"", DiagnosticSeverity.Error, ValidationType.value));
205201

206202
}
203+
204+
@Test
205+
public void validateReferencedPropertyExpressions() {
206+
String value = "ENV_LEVEL=info\n" + //
207+
"quarkus.log.file.level=${ENV_LEVEL}";
208+
testDiagnosticsFor(value);
209+
}
210+
207211
}

0 commit comments

Comments
 (0)