Quickfix for health-check-type: none

This commit is contained in:
Kris De Volder
2017-04-24 15:09:13 -07:00
parent a9891c6952
commit 7ce8ea1a0e
16 changed files with 259 additions and 86 deletions

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.reconcile;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParseException;
/**
@@ -23,6 +25,8 @@ public class ReconcileException extends ValueParseException implements ProblemTy
private static final long serialVersionUID = 1L;
private final ProblemType problemType;
private ReplacementQuickfix replacement = null; //Optional info to create a 'replacement quickfix' for the value
public ReconcileException(String message, ProblemType problemType) {
super(message);
this.problemType = problemType;
@@ -37,4 +41,17 @@ public class ReconcileException extends ValueParseException implements ProblemTy
public ProblemType getProblemType() {
return problemType;
}
public ReconcileException fixWith(ReplacementQuickfix replacement) {
//Silently ignore if the fix is either null or doesn't provide a proper replacement text.
if (replacement!=null && StringUtil.hasText(replacement.replacement)) {
Assert.isLegal(this.replacement==null, "Multiple fixes not yet supported");
this.replacement = replacement;
}
return this;
}
public ReplacementQuickfix getReplacement() {
return replacement;
}
}

View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.languageserver.reconcile;
/**
* An info object that can be attached to a {@link ReconcileException} to indicate that
* the problem can be fixed by a simple value substitution at the exact location of
* the problem marker.
*
* @author Kris De Volder
*/
public class ReplacementQuickfix {
public final String msg;
public final String replacement;
public ReplacementQuickfix(String msg, String replacement) {
super();
this.msg = msg;
this.replacement = replacement;
}
@Override
public String toString() {
return "ReplacementQuickfix [msg=" + msg + ", replacement=" + replacement + "]";
}
}

View File

@@ -16,6 +16,7 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IDocument;
@@ -290,4 +291,8 @@ public class DocumentRegion implements CharSequence {
numChars = Math.min(getLength(), numChars);
return new DocumentRegion(doc, end-numChars, end);
}
public Range asRange() throws BadLocationException {
return doc.toRange(asRegion());
}
}

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.util.text;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.commons.util.BadLocationException;
public interface IDocument {
@@ -30,5 +31,6 @@ public interface IDocument {
String textBetween(int start, int end) throws BadLocationException;
LanguageId getLanguageId();
int getVersion();
Range toRange(IRegion asRegion) throws BadLocationException;
}

View File

@@ -11,7 +11,6 @@
package org.springframework.ide.vscode.commons.util.text;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -278,6 +277,7 @@ public class TextDocument implements IDocument {
return languageId;
}
@Override
public Range toRange(IRegion region) throws BadLocationException {
return toRange(region.getOffset(), region.getLength());
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.reconcile;
package org.springframework.ide.vscode.commons.yaml.quickfix;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
@@ -21,6 +21,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
import org.springframework.ide.vscode.commons.yaml.completion.YamlPathEdits;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import org.springframework.ide.vscode.commons.yaml.reconcile.MissingPropertiesData;
import org.springframework.ide.vscode.commons.yaml.reconcile.ReplaceStringData;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
@@ -33,6 +35,7 @@ import com.google.common.collect.ImmutableMap;
public class YamlQuickfixes {
public final QuickfixType MISSING_PROP_FIX;
public final QuickfixType SIMPLE_TEXT_EDIT;
public YamlQuickfixes(QuickfixRegistry r, SimpleTextDocumentService textDocumentService, YamlStructureProvider structureProvider) {
MISSING_PROP_FIX = r.register("MISSING_PROP_FIX", (Object _params) -> {
@@ -69,6 +72,24 @@ public class YamlQuickfixes {
//Something went wrong. Return empty edit object.
return new WorkspaceEdit(ImmutableMap.of(), null);
});
SIMPLE_TEXT_EDIT = r.register("SIMPLE_TEXT_EDIT", (_params) -> {
try {
ReplaceStringData params = new ObjectMapper().convertValue(_params, ReplaceStringData.class);
TextDocument _doc = textDocumentService.getDocument(params.getUri());
if (_doc!=null) {
return new WorkspaceEdit(
ImmutableMap.of(params.getUri(), ImmutableList.of(params.getEdit())),
null
);
}
} catch (Exception e) {
Log.log(e);
}
//Something went wrong. Return empty edit object.
//Something went wrong. Return empty edit object.
return new WorkspaceEdit(ImmutableMap.of(), null);
});
}
}

View File

@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.reconcile;
import org.eclipse.lsp4j.TextEdit;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.util.BadLocationException;
public class ReplaceStringData {
private String uri;
private TextEdit edit;
public ReplaceStringData() {}
public ReplaceStringData(DocumentRegion target, String newText) throws BadLocationException {
this.uri = target.getDocument().getUri();
this.edit = new TextEdit(target.asRange(), newText);
}
public TextEdit getEdit() {
return edit;
}
public void setEdit(TextEdit edit) {
this.edit = edit;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
@Override
public String toString() {
return "ReplaceStringData [uri=" + uri + ", edit=" + edit + "]";
}
}

View File

@@ -23,9 +23,13 @@ import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTypeProvider;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.IntegerRange;
@@ -38,9 +42,9 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes;
import org.springframework.ide.vscode.commons.yaml.schema.ASTDynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
@@ -181,21 +185,9 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
}
} catch (Exception e) {
ProblemType problemType = getProblemType(e);
DocumentRegion region = new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex());
if (e instanceof ValueParseException) {
ValueParseException parseException = (ValueParseException) e;
int start = parseException.getStartIndex() >= 0
? Math.min(node.getStartMark().getIndex() + parseException.getStartIndex(),
node.getEndMark().getIndex())
: node.getStartMark().getIndex();
int end = parseException.getEndIndex() >= 0
? Math.min(node.getStartMark().getIndex() + parseException.getEndIndex(),
node.getEndMark().getIndex())
: node.getEndMark().getIndex();
region = new DocumentRegion(doc, start, end);
}
DocumentRegion region = getRegion(e, doc, node);
String msg = getMessage(e);
valueParseError(type, region, msg, problemType);
valueParseError(type, region, msg, problemType, getValueReplacement(e));
}
}
} else {
@@ -208,6 +200,31 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
}
}
protected ReplacementQuickfix getValueReplacement(Exception _e) {
if (_e instanceof ReconcileException) {
ReconcileException e = (ReconcileException) _e;
return e.getReplacement();
}
return null;
}
protected DocumentRegion getRegion(Exception e, IDocument doc, Node node) {
DocumentRegion region = new DocumentRegion(doc, node.getStartMark().getIndex(), node.getEndMark().getIndex());
if (e instanceof ValueParseException) {
ValueParseException parseException = (ValueParseException) e;
int start = parseException.getStartIndex() >= 0
? Math.min(node.getStartMark().getIndex() + parseException.getStartIndex(),
node.getEndMark().getIndex())
: node.getStartMark().getIndex();
int end = parseException.getEndIndex() >= 0
? Math.min(node.getStartMark().getIndex() + parseException.getEndIndex(),
node.getEndMark().getIndex())
: node.getEndMark().getIndex();
region = new DocumentRegion(doc, start, end);
}
return region;
}
private String getMessage(Exception _e) {
Throwable e = ExceptionUtil.getDeepestCause(_e);
@@ -337,18 +354,24 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
}
}
private void valueParseError(YType type, Node node, String parseErrorMsg, ProblemType problemType) {
private void valueParseError(YType type, DocumentRegion region, String parseErrorMsg, ProblemType problemType, ReplacementQuickfix fix) {
if (!StringUtil.hasText(parseErrorMsg)) {
parseErrorMsg= "Couldn't parse as '"+describe(type)+"'";
}
problem(node, parseErrorMsg, problemType);
}
private void valueParseError(YType type, DocumentRegion region, String parseErrorMsg, ProblemType problemType) {
if (!StringUtil.hasText(parseErrorMsg)) {
parseErrorMsg= "Couldn't parse as '"+describe(type)+"'";
ReconcileProblemImpl problem = YamlSchemaProblems.problem(problemType, parseErrorMsg, region);
if (fix!=null && StringUtil.hasText(fix.replacement)) {
try {
problem.addQuickfix(
new QuickfixData<>(quickfixes.SIMPLE_TEXT_EDIT,
new ReplaceStringData(region, fix.replacement),
fix.msg
)
);
} catch (Exception e) {
Log.log(e);
}
}
problem(region, parseErrorMsg, problemType);
problems.accept(problem);
}
private void unknownBeanProperty(Node keyNode, YType type, String name) {
@@ -404,14 +427,6 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
problems.accept(YamlSchemaProblems.schemaProblem(msg, node));
}
private void problem(Node node, String msg, ProblemType problemType) {
problems.accept(YamlSchemaProblems.problem(problemType, msg, node));
}
private void problem(DocumentRegion region, String msg, ProblemType problemType) {
problems.accept(YamlSchemaProblems.problem(problemType, msg, region));
}
private void problem(DocumentRegion region, String msg) {
problems.accept(YamlSchemaProblems.schemaProblem(msg, region));
}

View File

@@ -15,6 +15,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemC
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
/**

View File

@@ -49,6 +49,7 @@ public class YamlSchemaProblems {
public static final ProblemType SYNTAX_PROBLEM = problemType("YamlSyntaxProblem");
public static final ProblemType SCHEMA_PROBLEM = problemType("YamlSchemaProblem");
public static final ProblemType DEPRECATED_PROPERTY = problemType("DeprecatedProperty", ProblemSeverity.WARNING);
public static final ProblemType DEPRECATED_VALUE = problemType("DeprecatedValue", ProblemSeverity.WARNING);
public static final ProblemType MISSING_PROPERTY = problemType("MissingProperty", ProblemSeverity.ERROR);
public static final ProblemType EXTRA_PROPERTY = problemType("ExtraProperty", ProblemSeverity.ERROR);

View File

@@ -26,14 +26,13 @@ import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
@@ -53,12 +52,25 @@ import reactor.core.publisher.Flux;
*/
public class YTypeFactory {
private static class Deprecation {
final String errorMsg;
final String replacement;
final String quickfixMsg;
public Deprecation(String errorMsg, String replacement, String quickfixMsg) {
super();
this.errorMsg = errorMsg;
this.replacement = replacement;
this.quickfixMsg = quickfixMsg;
}
}
public class EnumTypeBuilder {
private String name;
private String[] values;
private Map<String, String> deprecationMsgs = new HashMap<>();
private Map<String, Deprecation> deprecations = new HashMap<>();
public EnumTypeBuilder(String name, String[] values) {
this.name = name;
@@ -69,17 +81,15 @@ public class YTypeFactory {
EnumValueParser basicParser = new EnumValueParser(name, values);
YAtomicType t = yatomic(name);
t.addHints(getNonDeprecatedValues());
if (deprecationMsgs.isEmpty()) {
if (deprecations.isEmpty()) {
t.parseWith(basicParser);
} else {
t.parseWith(ValueParser.of((String value) -> {
basicParser.parse(value);
if (deprecationMsgs.containsKey(value)) {
String msg = deprecationMsgs.get(value);
if (!StringUtil.hasText(msg)) {
msg = "'"+value+"' is deprecated";
}
throw new ReconcileException(msg, YamlSchemaProblems.DEPRECATED_PROPERTY);
Deprecation d = deprecations.get(value);
if (d!=null) {
throw new ReconcileException(d.errorMsg, YamlSchemaProblems.DEPRECATED_VALUE)
.fixWith(new ReplacementQuickfix(d.quickfixMsg, d.replacement));
}
return value;
}));
@@ -89,13 +99,23 @@ public class YTypeFactory {
public EnumTypeBuilder deprecate(String value, String msg) {
Assert.isLegal(ImmutableSet.copyOf(values).contains(value));
deprecationMsgs.put(value, msg);
deprecations.put(value, new Deprecation(msg, null, null));
return this;
}
public EnumTypeBuilder deprecateWithReplacement(String value, String replacement) {
Assert.isLegal(ImmutableSet.copyOf(values).contains(value));
deprecations.put(value, new Deprecation(
"The value '"+value+"' is deprecated in favor of '"+replacement+"'",
replacement,
"Replace deprecated value '"+value+"' by '"+replacement+"'"
));
return this;
}
private String[] getNonDeprecatedValues() {
return Flux.fromArray(values)
.filter((value) -> !deprecationMsgs.containsKey(value))
.filter((value) -> !deprecations.containsKey(value))
.collectList()
.map(l -> l.toArray(new String[l.size()]))
.block();