Skip to content

Commit

Permalink
Add code action for RootElementTypeMustMatchDoctypedecl
Browse files Browse the repository at this point in the history
Signed-off-by: David Kwon <[email protected]>
  • Loading branch information
dkwon17 authored and angelozerr committed May 7, 2020
1 parent c06f71a commit 888ee23
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
Expand Down Expand Up @@ -76,16 +77,23 @@ public static CodeAction insert(String title, Position position, String insertTe

public static CodeAction replace(String title, Range range, String replaceText, TextDocumentItem document,
Diagnostic diagnostic) {
TextEdit replace = new TextEdit(range, replaceText);
return replace(title, Collections.singletonList(replace), document,
diagnostic);
}

public static CodeAction replace(String title, List<TextEdit> replace, TextDocumentItem document,
Diagnostic diagnostic) {

CodeAction insertContentAction = new CodeAction(title);
insertContentAction.setKind(CodeActionKind.QuickFix);
insertContentAction.setDiagnostics(Arrays.asList(diagnostic));
TextEdit edit = new TextEdit(range, replaceText);

VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
document.getUri(), document.getVersion());

TextDocumentEdit textDocumentEdit = new TextDocumentEdit(versionedTextDocumentIdentifier, Collections.singletonList(edit));
TextDocumentEdit textDocumentEdit = new TextDocumentEdit(versionedTextDocumentIdentifier,
replace);
WorkspaceEdit workspaceEdit = new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)));

insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.EqRequiredInAttributeCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.MarkupEntityMismatchCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.OpenQuoteExpectedCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.RootElementTypeMustMatchDoctypedeclCodeAction;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.diagnostics.IXMLErrorCode;
import org.eclipse.lemminx.utils.XMLPositionUtility;
Expand Down Expand Up @@ -232,5 +233,6 @@ public static void registerCodeActionParticipants(Map<String, ICodeActionPartici
codeActions.put(OpenQuoteExpected.getCode(), new OpenQuoteExpectedCodeAction());
codeActions.put(MarkupEntityMismatch.getCode(), new MarkupEntityMismatchCodeAction());
codeActions.put(ETagRequired.getCode(), new ETagRequiredCodeAction());
codeActions.put(RootElementTypeMustMatchDoctypedecl.getCode(), new RootElementTypeMustMatchDoctypedeclCodeAction());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*******************************************************************************
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants.codeactions;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.lemminx.commons.CodeActionFactory;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.IComponentProvider;
import org.eclipse.lemminx.settings.XMLFormattingOptions;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;

/**
* Code action for RootElementTypeMustMatchDoctypedecl
*
* This class depends on the diagnostic message (Diagnostic#message) to
* determine the correct root name.
*
* This class assumes that the diagnostic message is in this format:
* Document root element "<current root name>", must match DOCTYPE root "<expected root name>".
*/
public class RootElementTypeMustMatchDoctypedeclCodeAction implements ICodeActionParticipant {

@Override
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
XMLFormattingOptions formattingSettings, IComponentProvider componentProvider) {
DOMElement root = document.getDocumentElement();
if (root == null) {
return;
}
Range rootStartRange = XMLPositionUtility.selectStartTagName(root);
if (!range.equals(rootStartRange)) {
return;
}

String currentRootText = getCurrentRoot(diagnostic.getMessage());
if (currentRootText == null || !currentRootText.equals(root.getNodeName())) {
return;
}

String doctypeRootText = getDoctypeRoot(diagnostic.getMessage());
if (doctypeRootText == null) {
return;
}

List<TextEdit> replace= new ArrayList<>();
addTextEdits(root, doctypeRootText, replace);

CodeAction action = CodeActionFactory.replace(
"Replace with '" + doctypeRootText + "'",
replace, document.getTextDocument(), diagnostic);
codeActions.add(action);
}

private void addTextEdits(DOMElement root, String newText, List<TextEdit> replace) {
replace.add(new TextEdit(XMLPositionUtility.selectStartTagName(root), newText));

if (root.isClosed() && !root.isSelfClosed()) {
replace.add(new TextEdit(XMLPositionUtility.selectEndTagName(root), newText));
}
}

/**
* Returns the current root name, extracted from <code>message</code>
*
* The provided <code>message</code> must match this format:
* ... root element "<current root name>", must match DOCTYPE ...
*
* @param message the message to extract the root name from
* @return the current root name, extracted from <code>message</code>
*/
private static String getCurrentRoot(String message) {
String preText = "root element \"";
String postText = "\", must match DOCTYPE";
int preMatch = message.indexOf(preText);
if (preMatch < 0) {
return null;
}
int postMatch = message.indexOf(postText);
if (postMatch < 0) {
return null;
}

return message.substring(preMatch + preText.length(), postMatch);
}

/**
* Returns the doctype root name, extracted from <code>message</code>
*
* The provided <code>message</code> must match this format:
* ... DOCTYPE root "<root name>".
*
* @param message the message to extract the root name from
* @return the DOCTYPE root name, extracted from <code>message</code>
*/
private static String getDoctypeRoot(String message) {
String preText = "DOCTYPE root \"";
int preMatch = message.indexOf(preText);
if (preMatch < 0) {
return null;
}
return message.substring(preMatch + preText.length(), message.length() - 2);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ public void testQuoteRequiredInXMLDecl() throws Exception {
}

@Test
public void testRootElementTypeMustMatchDoctypedecl() {
public void testRootElementTypeMustMatchDoctypedecl() throws Exception {
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<!DOCTYPE efgh [ \r\n" +
Expand All @@ -433,7 +433,26 @@ public void testRootElementTypeMustMatchDoctypedecl() {
"]> \r\n" +
"<abcd/>";

testDiagnosticsFor(xml, d(5, 1, 5, 5, XMLSyntaxErrorCode.RootElementTypeMustMatchDoctypedecl));
String expectedMessage = "Document root element \"abcd\", must match DOCTYPE root \"efgh\".";
Diagnostic d = d(5, 1, 5, 5, XMLSyntaxErrorCode.RootElementTypeMustMatchDoctypedecl, expectedMessage);
testDiagnosticsFor(xml, d);
testCodeActionsFor(xml, d, ca(d, te(5, 1, 5, 5, "efgh")));
}

@Test
public void testRootElementTypeMustMatchDoctypedecl2() throws Exception {
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<!DOCTYPE efgh [ \r\n" +
"<!ELEMENT abcd (#PCDATA)>\r\n" +
"<!ELEMENT efgh (#PCDATA)>\r\n" +
"]> \r\n" +
"<abcd>test</abcd>";

String expectedMessage = "Document root element \"abcd\", must match DOCTYPE root \"efgh\".";
Diagnostic d = d(5, 1, 5, 5, XMLSyntaxErrorCode.RootElementTypeMustMatchDoctypedecl, expectedMessage);
testDiagnosticsFor(xml, d);
testCodeActionsFor(xml, d, ca(d, te(5, 1, 5, 5, "efgh"), te(5, 12, 5, 16, "efgh")));
}

@Test
Expand Down

0 comments on commit 888ee23

Please sign in to comment.