Skip to content

Commit

Permalink
Merge pull request #32354 from vespa-engine/theodorkl/LSPAddAttribute…
Browse files Browse the repository at this point in the history
…QuickAction

LSP: Verify that attribute functions are referencing fields that are stored as an attribute
  • Loading branch information
Mangern authored Sep 5, 2024
2 parents 0a259db + 1473a30 commit ff7f832
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static enum DiagnosticCode {
DEPRECATED_TOKEN_INDEX,
DEPRECATED_TOKEN_SUMMARY_TO,
DEPRECATED_TOKEN_SEARCH,
FEATURES_INHERITS_NON_PARENT
FEATURES_INHERITS_NON_PARENT,
FIELD_ARGUMENT_MISSING_INDEXING_TYPE
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
import ai.vespa.schemals.lsp.codeaction.utils.CodeActionUtils;
import ai.vespa.schemals.lsp.rename.SchemaRename;
import ai.vespa.schemals.parser.Node;
import ai.vespa.schemals.parser.ast.NL;
import ai.vespa.schemals.parser.ast.RBRACE;
import ai.vespa.schemals.parser.ast.attributeElm;
import ai.vespa.schemals.parser.ast.dataType;
import ai.vespa.schemals.parser.ast.fieldBodyElm;
import ai.vespa.schemals.parser.ast.fieldElm;
import ai.vespa.schemals.parser.ast.indexInsideField;
import ai.vespa.schemals.parser.ast.indexingElm;
import ai.vespa.schemals.parser.ast.inheritsDocument;
import ai.vespa.schemals.parser.ast.inheritsRankProfile;
import ai.vespa.schemals.parser.ast.openLbrace;
Expand Down Expand Up @@ -393,6 +397,78 @@ private CodeAction fixFeaturesInheritsNonParent(EventCodeActionContext context,
return action;
}

private CodeAction fixFieldArgumentMissingIndexingType(EventCodeActionContext context, Diagnostic diagnostic) {

SchemaNode offendingNode = CSTUtils.getSymbolAtPosition(context.document.getRootNode(), context.position);
if (offendingNode == null || !offendingNode.hasSymbol()) return null;
Optional<Symbol> symbol = context.schemaIndex.findSymbol(offendingNode.getSymbol());
if (symbol.isEmpty()) return null;

SchemaNode node = symbol.get().getNode().getParent();

Optional<SchemaNode> indexingNode = Optional.empty();
for (SchemaNode child : node) {
if (child.isASTInstance(fieldBodyElm.class)) {
SchemaNode grandChild = child.get(0);
if (grandChild != null && grandChild.isASTInstance(indexingElm.class)) {
indexingNode = Optional.of(grandChild);
}
}
}

CodeAction action = basicQuickFix("Add attribute as indexing attribute", diagnostic);

if (indexingNode.isPresent()) {
action.setEdit(CodeActionUtils.simpleEdit(context, indexingNode.get().getRange().getEnd(), " | attribute"));

} else {
// Add indexing node as well
Optional<SchemaNode> rBraceNode = Optional.empty();

for (int i = 0; i < node.size(); i++) {
SchemaNode child = node.get(i);
if (child.isASTInstance(RBRACE.class)) {
rBraceNode = Optional.of(child);
break;
}
}

if (rBraceNode.isEmpty()) return null;

// Calculate indent
SchemaNode fieldKeywordNode = node.get(0);
if (fieldKeywordNode == null) return null;

String insertIndentString = StringUtils.getIndentString(context.document.getCurrentContent(), fieldKeywordNode);
int indent = StringUtils.countSpaceIndents(insertIndentString);

String newText = StringUtils.spaceIndent(StringUtils.TAB_SIZE) + "indexing: attribute\n" + StringUtils.spaceIndent(indent);

// Check is a leading new line is necessary
SchemaNode prevSibling = rBraceNode.get().getPrevious();
if (prevSibling == null) return null;
boolean newLineNecessary = false;
if (!prevSibling.isASTInstance(NL.class)) {
if (prevSibling.isASTInstance(openLbrace.class)) {
SchemaNode lastLbraceChild = prevSibling.get(prevSibling.size() - 1);
if (!lastLbraceChild.isASTInstance(NL.class)) {
newLineNecessary = true;
}
} else {
newLineNecessary = true;
}
}

if (newLineNecessary) {
newText = "\n" + StringUtils.spaceIndent(indent) + newText;
}

action.setEdit(CodeActionUtils.simpleEdit(context, rBraceNode.get().getRange().getStart(), newText));
}

return action;
}

@Override
public List<Either<Command, CodeAction>> getActions(EventCodeActionContext context) {
List<Either<Command, CodeAction>> result = new ArrayList<>();
Expand Down Expand Up @@ -448,6 +524,9 @@ public List<Either<Command, CodeAction>> getActions(EventCodeActionContext conte
case FEATURES_INHERITS_NON_PARENT:
result.add(Either.forRight(fixFeaturesInheritsNonParent(context, diagnostic)));
break;
case FIELD_ARGUMENT_MISSING_INDEXING_TYPE:
result.add(Either.forRight(fixFieldArgumentMissingIndexingType(context, diagnostic)));
break;
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public static WorkspaceEdit simpleEditList(EventCodeActionContext context, List<
return simpleEditList(context, edits, context.document);
}

public static WorkspaceEdit simpleEdit(EventCodeActionContext context, Position position, String newText) {
return simpleEdit(context, new Range(position, position), newText);
}

public static WorkspaceEdit simpleEdit(EventCodeActionContext context, Range range, String newText) {
return simpleEditList(context, List.of(new TextEdit(range, newText)));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package ai.vespa.schemals.schemadocument.resolvers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
Expand Down Expand Up @@ -53,6 +50,7 @@ public static Optional<Diagnostic> resolveFieldArgument(ParseContext context, Un
.setRange(node.getRange())
.setMessage("The referenced field are missing one of the following indexing types: " + missingFields)
.setSeverity(DiagnosticSeverity.Error)
.setCode(SchemaDiagnostic.DiagnosticCode.FIELD_ARGUMENT_MISSING_INDEXING_TYPE)
.build());

}
Expand Down

0 comments on commit ff7f832

Please sign in to comment.