Make .properties reconciler aware of document marker
See: https://github.com/spring-projects/sts4/issues/533 Signed-off-by: Kris De Volder <kdevolder@pivotal.io>
This commit is contained in:
@@ -351,4 +351,12 @@ public class DocumentRegion implements CharSequence, IRegion {
|
||||
return getStart();
|
||||
}
|
||||
|
||||
public boolean isAtStartOfLine() {
|
||||
if (start==0) {
|
||||
return true;
|
||||
}
|
||||
String charBefore = this.textBefore(1).toString();
|
||||
return charBefore.equals("\n"); //This should work on windows too because windows uses "\r\n" but that still ends with "\n".
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.java.properties.parser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -34,7 +35,11 @@ public final class PropertiesAst {
|
||||
public List<Node> getAllNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
public List<Node> getNodes(Predicate<Node> test) {
|
||||
return nodes.stream().filter(test).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves AST nodes of specific type
|
||||
* @param clazz Type of AST nodes
|
||||
|
||||
@@ -66,4 +66,8 @@ public class DuplicateNameChecker {
|
||||
"Duplicate property '"+decodedKey+"'", nameRegion));
|
||||
}
|
||||
|
||||
public void startNewSubDocument() {
|
||||
seen.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
|
||||
import org.springframework.ide.vscode.java.properties.parser.Parser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.KeyValuePair;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Comment;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Node;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesFileEscapes;
|
||||
|
||||
@@ -108,31 +109,38 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
results.ast.getNodes(KeyValuePair.class).forEach(pair -> {
|
||||
results.ast.getNodes(n -> n instanceof KeyValuePair || n instanceof Comment).forEach(node -> {
|
||||
try {
|
||||
DocumentRegion propertyNameRegion = createRegion(doc, pair.getKey());
|
||||
String keyName = PropertiesFileEscapes.unescape(propertyNameRegion.toString());
|
||||
duplicateNameChecker.check(propertyNameRegion);
|
||||
PropertyInfo validProperty = SpringPropertyIndex.findLongestValidProperty(index, keyName);
|
||||
if (validProperty!=null) {
|
||||
//TODO: Remove last remnants of 'IRegion trimmedRegion' here and replace
|
||||
// it all with just passing around 'fullName' DocumentRegion. This may require changes
|
||||
// in PropertyNavigator (probably these changes are also for the better making it simpler as well)
|
||||
if (validProperty.isDeprecated()) {
|
||||
problemCollector.accept(problemDeprecated(propertyNameRegion, validProperty, quickFixes.DEPRECATED_PROPERTY));
|
||||
if (node instanceof Comment) {
|
||||
if (isDocumentMarker(doc, (Comment)node)) {
|
||||
duplicateNameChecker.startNewSubDocument();
|
||||
}
|
||||
int offset = validProperty.getId().length() + propertyNameRegion.getStart();
|
||||
PropertyNavigator navigator = new PropertyNavigator(doc, problemCollector, typeUtilProvider.getTypeUtil(sourceLinks, doc), propertyNameRegion);
|
||||
Type valueType = navigator.navigate(offset, TypeParser.parse(validProperty.getType()));
|
||||
if (valueType!=null) {
|
||||
reconcileType(doc, valueType, pair.getValue(), problemCollector);
|
||||
}
|
||||
} else { //validProperty==null
|
||||
//The name is invalid, with no 'prefix' of the name being a valid property name.
|
||||
PropertyInfo similarEntry = index.findLongestCommonPrefixEntry(propertyNameRegion.toString());
|
||||
CharSequence validPrefix = commonPrefix(similarEntry.getId(), keyName);
|
||||
problemCollector.accept(problemUnkownProperty(propertyNameRegion, similarEntry, validPrefix, quickFixes.MISSING_PROPERTY));
|
||||
} //end: validProperty==null
|
||||
} else if (node instanceof KeyValuePair) {
|
||||
KeyValuePair pair = (KeyValuePair) node;
|
||||
DocumentRegion propertyNameRegion = createRegion(doc, pair.getKey());
|
||||
String keyName = PropertiesFileEscapes.unescape(propertyNameRegion.toString());
|
||||
duplicateNameChecker.check(propertyNameRegion);
|
||||
PropertyInfo validProperty = SpringPropertyIndex.findLongestValidProperty(index, keyName);
|
||||
if (validProperty!=null) {
|
||||
//TODO: Remove last remnants of 'IRegion trimmedRegion' here and replace
|
||||
// it all with just passing around 'fullName' DocumentRegion. This may require changes
|
||||
// in PropertyNavigator (probably these changes are also for the better making it simpler as well)
|
||||
if (validProperty.isDeprecated()) {
|
||||
problemCollector.accept(problemDeprecated(propertyNameRegion, validProperty, quickFixes.DEPRECATED_PROPERTY));
|
||||
}
|
||||
int offset = validProperty.getId().length() + propertyNameRegion.getStart();
|
||||
PropertyNavigator navigator = new PropertyNavigator(doc, problemCollector, typeUtilProvider.getTypeUtil(sourceLinks, doc), propertyNameRegion);
|
||||
Type valueType = navigator.navigate(offset, TypeParser.parse(validProperty.getType()));
|
||||
if (valueType!=null) {
|
||||
reconcileType(doc, valueType, pair.getValue(), problemCollector);
|
||||
}
|
||||
} else { //validProperty==null
|
||||
//The name is invalid, with no 'prefix' of the name being a valid property name.
|
||||
PropertyInfo similarEntry = index.findLongestCommonPrefixEntry(propertyNameRegion.toString());
|
||||
CharSequence validPrefix = commonPrefix(similarEntry.getId(), keyName);
|
||||
problemCollector.accept(problemUnkownProperty(propertyNameRegion, similarEntry, validPrefix, quickFixes.MISSING_PROPERTY));
|
||||
} //end: validProperty==null
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
@@ -144,6 +152,12 @@ public class SpringPropertiesReconcileEngine implements IReconcileEngine {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDocumentMarker(IDocument doc, Comment node) {
|
||||
DocumentRegion region = createRegion(doc, node).trimEnd();
|
||||
String t = region.toString();
|
||||
return t.equals("#---") && region.isAtStartOfLine();
|
||||
}
|
||||
|
||||
protected SpringPropertyProblem problemDeprecated(DocumentRegion region, PropertyInfo property, QuickfixType fixType) {
|
||||
SpringPropertyProblem p = problem(PROP_DEPRECATED,
|
||||
TypeUtil.deprecatedPropertyMessage(
|
||||
|
||||
@@ -283,6 +283,7 @@ public class PropertyIndexHarness {
|
||||
data("spring.batch.job.enabled", "java.lang.Boolean", "true", "Execute all Spring Batch jobs in the context on startup.");
|
||||
data("spring.batch.job.names", "java.lang.String", "", "Comma-separated list of job names to execute on startup. By default, all Jobs\n found in the context are executed.");
|
||||
data("spring.batch.schema", "java.lang.String", "classpath:org/springframework/batch/core/schema-@@platform@@.sql", "Path to the SQL file to use to initialize the database schema.");
|
||||
data("spring.config.activate.on-profile", "java.lang.String[]", null, "Activate on blah.");
|
||||
data("spring.config.location", "java.lang.String", null, "Config file locations.");
|
||||
data("spring.config.name", "java.lang.String", "application", "Config file name.");
|
||||
data("spring.dao.exceptiontranslation.enabled", "java.lang.Boolean", "true", "Enable the PersistenceExceptionTranslationPostProcessor.");
|
||||
|
||||
@@ -77,6 +77,66 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
|
||||
private static final ProjectCustomizer WITH_EMPTY_APPLICATION_YML = projectContents -> {
|
||||
projectContents.createFile("src/main/resources/application.yml", "");
|
||||
};
|
||||
|
||||
@Test public void reconcilesWithMultiDocuments() throws Exception {
|
||||
//See: https://github.com/spring-projects/sts4/issues/533
|
||||
|
||||
defaultTestData();
|
||||
|
||||
//lowest bar: just disable the warning
|
||||
Editor editor = newEditor(
|
||||
"spring.config.activate.on-profile=foo\n" +
|
||||
"server.port=8888\n" +
|
||||
"#---\n" +
|
||||
"spring.config.activate.on-profile=bar\n" +
|
||||
"server.port=8080\n"
|
||||
);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
|
||||
//better: still detect duplicates within same section
|
||||
editor = newEditor(
|
||||
"spring.application.name=frodo\n" +
|
||||
"spring.config.activate.on-profile=foo\n" +
|
||||
"server.port=8888\n" +
|
||||
"spring.application.name=frodo\n" +
|
||||
"#---\n" +
|
||||
"spring.config.activate.on-profile=bar\n" +
|
||||
"server.port=8080\n" +
|
||||
"server.port=9090\n"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"spring.application.name|Duplicate",
|
||||
"spring.application.name|Duplicate",
|
||||
"server.port|Duplicate",
|
||||
"server.port|Duplicate"
|
||||
);
|
||||
|
||||
//nitpick 1: leading spaces before the marker means... it is not a marker
|
||||
editor = newEditor(
|
||||
"spring.config.activate.on-profile=foo\n" +
|
||||
"server.port=8888\n" +
|
||||
" #---\n" +
|
||||
"spring.config.activate.on-profile=bar\n" +
|
||||
"server.port=8080\n"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"spring.config.activate.on-profile|Duplicate",
|
||||
"server.port|Duplicate",
|
||||
"spring.config.activate.on-profile|Duplicate",
|
||||
"server.port|Duplicate"
|
||||
);
|
||||
|
||||
//nitpick 2: trailing spaces after the marker are ignored
|
||||
editor = newEditor(
|
||||
"spring.config.activate.on-profile=foo\n" +
|
||||
"server.port=8888\n" +
|
||||
"#--- \t\n" +
|
||||
"spring.config.activate.on-profile=bar\n" +
|
||||
"server.port=8080\n"
|
||||
);
|
||||
editor.assertProblems(/*NONE*/);
|
||||
|
||||
}
|
||||
|
||||
@Test public void inheritedPojoProperties() throws Exception {
|
||||
//See https://github.com/spring-projects/sts4/issues/116
|
||||
|
||||
Reference in New Issue
Block a user