Skip to content
This repository has been archived by the owner on Mar 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #61 from eclipse/master
Browse files Browse the repository at this point in the history
TargetNamespace.1/2 error range
  • Loading branch information
enxio authored Jun 10, 2020
2 parents 0f5eec8 + c1aeb8a commit f356a3d
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@

import org.apache.xerces.xni.XMLLocator;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation;
import org.eclipse.lemminx.dom.SchemaLocation;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.TargetNamespace_1CodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.TargetNamespace_2CodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.cvc_attribute_3CodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.cvc_complex_type_2_1CodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.cvc_complex_type_2_3CodeAction;
Expand Down Expand Up @@ -74,6 +77,7 @@ public enum XMLSchemaErrorCode implements IXMLErrorCode {
cvc_maxInclusive_valid("cvc-maxInclusive-valid"), // https://wiki.xmldation.com/Support/validator/cvc-maxinclusive-valid
cvc_minExclusive_valid("cvc-minExclusive-valid"), // https://wiki.xmldation.com/Support/validator/cvc-minexclusive-valid
cvc_minInclusive_valid("cvc-minInclusive-valid"), // https://wiki.xmldation.com/Support/validator/cvc-mininclusive-valid
TargetNamespace_1("TargetNamespace.1"), //
TargetNamespace_2("TargetNamespace.2"), SchemaLocation("SchemaLocation"), schema_reference_4("schema_reference.4"), //
src_element_3("src-element.3");

Expand Down Expand Up @@ -138,7 +142,6 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
case cvc_elt_1_a:
case cvc_complex_type_4:
case src_element_3:
case TargetNamespace_2:
return XMLPositionUtility.selectStartTagName(offset, document);
case cvc_complex_type_3_2_2: {
String attrName = getString(arguments[1]);
Expand Down Expand Up @@ -245,6 +248,10 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
}
case cvc_type_3_1_2:
return XMLPositionUtility.selectStartTagName(offset, document);
case TargetNamespace_1:
return XMLPositionUtility.selectRootAttributeValue(DOMAttr.XMLNS_ATTR, document);
case TargetNamespace_2:
return XMLPositionUtility.selectRootStartTag(document);
default:
}
return null;
Expand All @@ -260,5 +267,7 @@ public static void registerCodeActionParticipants(Map<String, ICodeActionPartici
codeActions.put(cvc_complex_type_3_2_2.getCode(), new cvc_complex_type_3_2_2CodeAction());
codeActions.put(cvc_enumeration_valid.getCode(), new cvc_enumeration_validCodeAction());
codeActions.put(cvc_complex_type_2_1.getCode(), new cvc_complex_type_2_1CodeAction());
codeActions.put(TargetNamespace_1.getCode(), new TargetNamespace_1CodeAction());
codeActions.put(TargetNamespace_2.getCode(), new TargetNamespace_2CodeAction());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* 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.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.eclipse.lemminx.commons.CodeActionFactory;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.IComponentProvider;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Range;

/**
* CodeAction to replace an incorrect namespace in an .xml document.
*
* Changes the value of the xmlns attribute of the root element of the .xml
* document to the declared namespace of the referenced .xsd document.
*/
public class TargetNamespace_1CodeAction implements ICodeActionParticipant {

private static final Pattern NAMESPACE_EXTRACTOR = Pattern.compile("'([^']+)'\\.");

@Override
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
SharedSettings sharedSettings, IComponentProvider componentProvider) {

String namespace = extractNamespace(diagnostic.getMessage());
if (StringUtils.isEmpty(namespace)) {
return;
}
String quote = sharedSettings.getPreferences().getQuotationAsString();
// @formatter:off
CodeAction replaceNamespace = CodeActionFactory.replace(
"Replace with '" + namespace + "'",
diagnostic.getRange(),
quote + namespace + quote,
document.getTextDocument(),
diagnostic);
// @formatter:on
codeActions.add(replaceNamespace);
}

private static String extractNamespace(String diagnosticMessage) {
// The error message has this form:
// TargetNamespace.1: Expecting namespace 'NaN', but the target namespace of the
// schema document is 'http://two-letter-name'.
Matcher nsMatcher = NAMESPACE_EXTRACTOR.matcher(diagnosticMessage);
if (nsMatcher.find()) {
return nsMatcher.group(1);
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* 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.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.lemminx.commons.CodeActionFactory;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.IComponentProvider;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

/**
* Code action to add the missing xmlns declaration to the root element in an
* .xml document.
*
* Finds the namespace of the referenced .xsd. Adds the xmlns attribute to the
* root of .xml, and sets its value to the .xsd namespace.
*/
public class TargetNamespace_2CodeAction implements ICodeActionParticipant {

private static final Pattern NAMESPACE_EXTRACTOR = Pattern.compile("'([^']+)'\\.");

@Override
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
SharedSettings sharedSettings, IComponentProvider componentProvider) {

String namespace = extractNamespace(diagnostic.getMessage());
if (StringUtils.isEmpty(namespace)) {
return;
}
DOMNode root = document.getDocumentElement();
if (root == null) {
return;
}
Position tagEnd = XMLPositionUtility.selectStartTagName(root).getEnd();
String quote = sharedSettings.getPreferences().getQuotationAsString();
// @formatter:off
CodeAction addNamespaceDecl = CodeActionFactory.insert(
"Declare '" + namespace + "' as the namespace",
tagEnd,
" xmlns=" + quote + namespace + quote,
document.getTextDocument(),
diagnostic);
// @formatter:on
codeActions.add(addNamespaceDecl);
}

private static String extractNamespace(String diagnosticMessage) {
// The error message has this form:
// TargetNamespace.2: Expecting no namespace, but the schema document has a
// target namespace of 'http://docbook.org/ns/docbook'.
Matcher nsMatcher = NAMESPACE_EXTRACTOR.matcher(diagnosticMessage);
if (nsMatcher.find()) {
return nsMatcher.group(1);
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,34 @@ public static Range selectRootStartTag(DOMDocument document) {
return selectStartTagName(root);
}

/**
* Finds the root element of the given document and returns the
* attribute value <code>Range</code> for the attribute
* <code>attrName</code>.
*
* If <code>attrName</code> is not declared then null is returned.
*
* @param attrName The name of the attribute to find the range of the value for
* @param document The document to use the root element of
* @return The range in <code>document</code> where the declared value of
* attribute <code>attrName</code> resides (including quotations),
* or null if the attriubte is not declared.
*/
public static Range selectRootAttributeValue(String attrName, DOMDocument document) {
DOMNode root = document.getDocumentElement();
if (root == null) {
root = document.getChild(0);
}
if (root == null) {
return null;
}
DOMAttr attr = root.getAttributeNode(attrName);
if (attr == null) {
return null;
}
return selectAttributeValue(attr);
}

public static Range selectStartTagName(int offset, DOMDocument document) {
DOMNode element = document.findNodeAt(offset);
if (element != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSchemaErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
import org.eclipse.lemminx.settings.QuoteStyle;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -577,6 +580,69 @@ public void cvc_complex_type_2_2_withText() throws Exception {
testDiagnosticsFor(xml, diagnosticBob, diagnostic_cvc_2_2);
}

@Test
public void testTargetNamespace_1Normal() throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + //
"<?xml-model href=\"src/test/resources/xsd/two-letter-name.xsd\"?>\n" + //
"<two-letter-name xmlns=\"BAD_NS\">Io</two-letter-name>";
Diagnostic targetNamespace = d(2, 23, 2, 31, XMLSchemaErrorCode.TargetNamespace_1, "TargetNamespace.1: Expecting namespace 'BAD_NS', but the target namespace of the schema document is 'http://two-letter-name'.");
testDiagnosticsFor(xml,
targetNamespace,
d(2, 1, 2, 16, XMLSchemaErrorCode.cvc_elt_1_a, "cvc-elt.1.a: Cannot find the declaration of element 'two-letter-name'.")
);
testCodeActionsFor(xml, targetNamespace, ca(targetNamespace, te(2, 23, 2, 31, "\"http://two-letter-name\"")));
}

@Test
public void testTargetNamespace_1ShortNS() throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + //
"<?xml-model href=\"src/test/resources/xsd/two-letter-name.xsd\"?>\n" + //
"<two-letter-name xmlns=\"_\">Io</two-letter-name>";
Diagnostic targetNamespace = d(2, 23, 2, 26, XMLSchemaErrorCode.TargetNamespace_1, "TargetNamespace.1: Expecting namespace '_', but the target namespace of the schema document is 'http://two-letter-name'.");
testDiagnosticsFor(xml,
targetNamespace,
d(2, 1, 2, 16, XMLSchemaErrorCode.cvc_elt_1_a, "cvc-elt.1.a: Cannot find the declaration of element 'two-letter-name'.")
);
testCodeActionsFor(xml, targetNamespace, ca(targetNamespace, te(2, 23, 2, 26, "\"http://two-letter-name\"")));
}

@Test
public void testTargetNamespace_1SingleQuotes() throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + //
"<?xml-model href=\"src/test/resources/xsd/two-letter-name.xsd\"?>\n" + //
"<two-letter-name xmlns=\"_\">Io</two-letter-name>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred);
settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes);
Diagnostic targetNamespace = d(2, 23, 2, 26, XMLSchemaErrorCode.TargetNamespace_1, "TargetNamespace.1: Expecting namespace '_', but the target namespace of the schema document is 'http://two-letter-name'.");
testCodeActionsFor(xml, targetNamespace, settings, ca(targetNamespace, te(2, 23, 2, 26, "'http://two-letter-name'")));
}

@Test
public void testTargetNamespace_2() throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + //
"<?xml-model href=\"src/test/resources/xsd/two-letter-name.xsd\"?>\n" + //
"<two-letter-name>Io</two-letter-name>";
Diagnostic targetNamespace = d(2, 1, 2, 16, XMLSchemaErrorCode.TargetNamespace_2, "TargetNamespace.2: Expecting no namespace, but the schema document has a target namespace of 'http://two-letter-name'.");
testDiagnosticsFor(xml,
targetNamespace,
d(2, 1, 2, 16, XMLSchemaErrorCode.cvc_elt_1_a, "cvc-elt.1.a: Cannot find the declaration of element 'two-letter-name'.")
);
testCodeActionsFor(xml, targetNamespace, ca(targetNamespace, te(2, 16, 2, 16, " xmlns=\"http://two-letter-name\"")));
}

@Test
public void testTargetNamespace_2SingleQuotes() throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + //
"<?xml-model href=\"src/test/resources/xsd/two-letter-name.xsd\"?>\n" + //
"<two-letter-name>Io</two-letter-name>";
SharedSettings settings = new SharedSettings();
settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred);
settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes);
Diagnostic targetNamespace = d(2, 1, 2, 16, XMLSchemaErrorCode.TargetNamespace_2, "TargetNamespace.2: Expecting no namespace, but the schema document has a target namespace of 'http://two-letter-name'.");
testCodeActionsFor(xml, targetNamespace, settings, ca(targetNamespace, te(2, 16, 2, 16, " xmlns='http://two-letter-name'")));
}

private static void testDiagnosticsFor(String xml, Diagnostic... expected) {
XMLAssert.testDiagnosticsFor(xml, "src/test/resources/catalogs/catalog.xml", expected);
}
Expand Down
10 changes: 10 additions & 0 deletions org.eclipse.lemminx/src/test/resources/xsd/two-letter-name.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://two-letter-name">
<xs:element name="two-letter-name">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:pattern value="[A-Z][a-z]"></xs:pattern>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:schema>

0 comments on commit f356a3d

Please sign in to comment.