Skip to content

Commit

Permalink
XML validation should aggregate XSD errors where is referenced
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#768

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Oct 22, 2020
1 parent 3aa5bb7 commit 030a620
Show file tree
Hide file tree
Showing 24 changed files with 435 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ public void updateClientCapabilities(ClientCapabilities capabilities,
sharedSettings.getCompletionSettings().setCapabilities(textDocumentClientCapabilities.getCompletion());
sharedSettings.getFoldingSettings().setCapabilities(textDocumentClientCapabilities.getFoldingRange());
sharedSettings.getHoverSettings().setCapabilities(textDocumentClientCapabilities.getHover());
sharedSettings.getValidationSettings()
.setCapabilities(textDocumentClientCapabilities.getPublishDiagnostics());
codeActionLiteralSupport = textDocumentClientCapabilities.getCodeAction() != null
&& textDocumentClientCapabilities.getCodeAction().getCodeActionLiteralSupport() != null;
hierarchicalDocumentSymbolSupport = textDocumentClientCapabilities.getDocumentSymbol() != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ public String getPublicIdWithoutQuotes() {
return publicId != null ? publicId.getParameterWithoutFirstAndLastChar() : null;
}

public DTDDeclParameter getPublicIdNode() {
return publicId;
}

/**
* @param publicId the publicId to set
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public enum DTDErrorCode implements IXMLErrorCode {
MSG_MARKUP_NOT_RECOGNIZED_IN_DTD, //
MSG_NOTATION_NAME_REQUIRED_IN_NOTATIONDECL, //
MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN, //
MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED, //
MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN, MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED, //
MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL, //
NotationDeclUnterminated, //
OpenQuoteExpected, //
Expand Down Expand Up @@ -184,7 +184,8 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
return XMLPositionUtility.getLastValidDTDDeclParameterOrUnrecognized(offset, document);
}

case MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN: {
case MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN:
case MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN: {
return XMLPositionUtility.getElementDeclMissingContentOrCategory(offset, document);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.apache.xerces.xni.parser.XMLEntityResolver;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.extensions.contentmodel.ContentModelPlugin;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings;
import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lsp4j.Diagnostic;
Expand All @@ -36,7 +37,8 @@ public ContentModelDiagnosticsParticipant(ContentModelPlugin contentModelPlugin)
}

@Override
public void doDiagnostics(DOMDocument xmlDocument, List<Diagnostic> diagnostics, CancelChecker monitor) {
public void doDiagnostics(DOMDocument xmlDocument, List<Diagnostic> diagnostics,
XMLValidationSettings validationSettings, CancelChecker monitor) {
if (xmlDocument.isDTD() || DOMUtils.isXSD(xmlDocument)) {
// Don't validate DTD / XML Schema with XML validator
return;
Expand All @@ -45,9 +47,8 @@ public void doDiagnostics(DOMDocument xmlDocument, List<Diagnostic> diagnostics,
// associations settings., ...)
XMLEntityResolver entityResolver = xmlDocument.getResolverExtensionManager();
// Process validation
XMLValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics,
contentModelPlugin.getContentModelSettings(),
contentModelPlugin.getContentModelManager().getGrammarPool(), monitor);
XMLValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics, validationSettings,
contentModelPlugin.getContentModelManager(), monitor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,29 @@
*/
package org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.xerces.xni.XMLLocator;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.contentmodel.model.ReferencedGrammarInfo;
import org.eclipse.lemminx.extensions.contentmodel.participants.DTDErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSchemaErrorCode;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSyntaxErrorCode;
import org.eclipse.lemminx.extensions.xerces.AbstractLSPErrorReporter;
import org.eclipse.lemminx.extensions.xsd.participants.XSDErrorCode;
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticRelatedInformation;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

/**
Expand All @@ -31,8 +45,19 @@ public class LSPErrorReporterForXML extends AbstractLSPErrorReporter {

private static final String XML_DIAGNOSTIC_SOURCE = "xml";

public LSPErrorReporterForXML(DOMDocument xmlDocument, List<Diagnostic> diagnostics) {
private final ContentModelManager contentModelManager;

private Set<ReferencedGrammarInfo> referencedGrammars;

private Map<String, ReferencedGrammarDiagnosticsInfo> referencedGrammarDiagnosticsInfoCache;

private final boolean relatedInformation;

public LSPErrorReporterForXML(DOMDocument xmlDocument, List<Diagnostic> diagnostics,
ContentModelManager contentModelManager, boolean relatedInformation) {
super(XML_DIAGNOSTIC_SOURCE, xmlDocument, diagnostics);
this.contentModelManager = contentModelManager;
this.relatedInformation = relatedInformation;
}

/**
Expand All @@ -45,7 +70,8 @@ public LSPErrorReporterForXML(DOMDocument xmlDocument, List<Diagnostic> diagnost
* @return the LSP range from the SAX error.
*/
@Override
protected Range toLSPRange(XMLLocator location, String key, Object[] arguments, DOMDocument document) {
protected Range toLSPRange(XMLLocator location, String key, Object[] arguments, String message,
DiagnosticSeverity diagnosticSeverity, DOMDocument document) {
// try adjust positions for XML syntax error
XMLSyntaxErrorCode syntaxCode = XMLSyntaxErrorCode.get(key);
if (syntaxCode != null) {
Expand All @@ -65,20 +91,152 @@ protected Range toLSPRange(XMLLocator location, String key, Object[] arguments,
// try adjust positions for DTD error
DTDErrorCode dtdCode = DTDErrorCode.get(key);
if (dtdCode != null) {
Range range = DTDErrorCode.toLSPRange(location, dtdCode, arguments, document);
if (range != null) {
return range;
String documentURI = location.getExpandedSystemId();
if (documentURI.endsWith(document.getDocumentURI())) {
Range range = DTDErrorCode.toLSPRange(location, dtdCode, arguments, document);
if (range != null) {
return range;
}
} else {
fillReferencedGrammarDiagnostic(location, key, arguments, message, diagnosticSeverity,
document.getResolverExtensionManager(), dtdCode, null, documentURI);
return NO_RANGE;
}
} else {
XSDErrorCode xsdCode = XSDErrorCode.get(key);
if (xsdCode != null) {
// The error comes from the referenced XSD (with xsi:schemaLocation, xml-model,
// etc)

// Try to get the declared xsi:schemaLocation, xsi:noNamespaceLocation range
// which declares the XSD.
String grammarURI = location.getExpandedSystemId();
fillReferencedGrammarDiagnostic(location, key, arguments, message, diagnosticSeverity,
document.getResolverExtensionManager(), null, xsdCode, grammarURI);
return NO_RANGE;
}
}
}
}
return null;
}

/**
* Create a diagnostic root where XSD, DTD which have the error if needed and
* attach the error as related information if LSP client support it.
*
* @param location the Xerces location.
* @param key the Xerces error key
* @param arguments the Xerces error arguments
* @param message the Xerces error message
* @param diagnosticSeverity the the Xerces severity
* @param resolverExtensionManager the resolver
* @param dtdCode the DTD error code and null otherwise.
* @param xsdCode the XSD error code and null otherwise.
* @param grammarURI the referenced grammar URI.
*/
private void fillReferencedGrammarDiagnostic(XMLLocator location, String key, Object[] arguments, String message,
DiagnosticSeverity diagnosticSeverity, URIResolverExtensionManager resolverExtensionManager,
DTDErrorCode dtdCode, XSDErrorCode xsdCode, String grammarURI) {
// Create diagnostic where DDT, XSD which have errors is declared if needed
ReferencedGrammarDiagnosticsInfo info = getReferencedGrammarDiagnosticsInfo(grammarURI,
resolverExtensionManager);
info.addError();
if (relatedInformation && info.getGrammarDocument() != null) {
DOMDocument grammarDocument = info.getGrammarDocument();
Range range = dtdCode != null ? DTDErrorCode.toLSPRange(location, dtdCode, arguments, grammarDocument)
: XSDErrorCode.toLSPRange(location, xsdCode, arguments, grammarDocument);
if (range == null) {
range = createDefaultRange(location, grammarDocument);
}
if (range == null) {
try {
range = new Range(new Position(0, 0), grammarDocument.positionAt(grammarDocument.getEnd()));
} catch (BadLocationException e) {
}
}
DiagnosticRelatedInformation r = new DiagnosticRelatedInformation(
range != null ? new Location(grammarURI, range) : null, message);
info.addDiagnosticRelatedInformation(r);
}
}

/**
* Returns the referenced grammar diagnostics info from the given grammar URI.
*
* @param grammarURI the referenced grammar URI.
* @param resolverExtensionManager the resolver used to load the DOM document of
* the referenced grammar.
* @return
*/
private ReferencedGrammarDiagnosticsInfo getReferencedGrammarDiagnosticsInfo(String grammarURI,
URIResolverExtensionManager resolverExtensionManager) {
if (referencedGrammarDiagnosticsInfoCache == null) {
referencedGrammarDiagnosticsInfoCache = new HashMap<>();
}
ReferencedGrammarDiagnosticsInfo info = referencedGrammarDiagnosticsInfoCache.get(grammarURI);
if (info == null) {
// Create diagnostic where DDT, XSD which have errors is declared
Range range = getReferencedGrammarRange(grammarURI);
String message = "";
Diagnostic diagnostic = super.addDiagnostic(range, message, DiagnosticSeverity.Error, "");
// Register the diagnostic as root diagnostic for the XSD, DTD grammar uri
info = new ReferencedGrammarDiagnosticsInfo(grammarURI, resolverExtensionManager, diagnostic);
referencedGrammarDiagnosticsInfoCache.put(grammarURI, info);
}
return info;
}

private Range getReferencedGrammarRange(String grammarURI) {
Set<ReferencedGrammarInfo> referencedGrammars = getReferencedGrammars();
for (ReferencedGrammarInfo referencedGrammarInfo : referencedGrammars) {
if (grammarURI.equals(referencedGrammarInfo.getResolvedURIInfo().getResolvedURI())) {
DOMRange range = referencedGrammarInfo.getIdentifier() != null
? referencedGrammarInfo.getIdentifier().getRange()
: null;
if (range != null) {
return XMLPositionUtility.createRange(range);
}
}
}
// Set the error range in the root start tag
return XMLPositionUtility.selectRootStartTag(getDOMDocument());
}

private Set<ReferencedGrammarInfo> getReferencedGrammars() {
if (referencedGrammars != null) {
return referencedGrammars;
}
return referencedGrammars = contentModelManager.getReferencedGrammarInfos(super.getDOMDocument());
}

@Override
protected boolean isIgnoreFatalError(String key) {
// Don't stop the validation when there are
// * EntityNotDeclared error
return DTDErrorCode.EntityNotDeclared.name().equals(key);
}

public void endReport() {
if (referencedGrammarDiagnosticsInfoCache == null) {
return;
}
// When a XML is validated by a DTD or XSD which have syntax error, the XSD, DTD
// grammar is cached in the pool.
// This behavior is annoying because when XML is validate in the second time,
// Xerces uses the cached XSD, DTD grammar (which have syntax error)
// and the referenced grammar error disappear.

// To fix this problem, the grammar pool is updated by removing the referenced
// grammars which have problems.
LSPXMLGrammarPool grammarPool = (LSPXMLGrammarPool) contentModelManager.getGrammarPool();
if (grammarPool == null) {
return;
}
// Remove referenced grammar which have problem from the Xerces pool cache.
Set<String> grammarURIs = referencedGrammarDiagnosticsInfoCache.keySet();
for (String grammarURI : grammarURIs) {
grammarPool.removeGrammar(grammarURI);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ private Grammar removeGrammar(XMLGrammarDescription desc) {
}
}

public void removeGrammar(String grammarURI) {
for (Entry entry : fGrammars) {
if (entry != null) {
if (grammarURI.equals(entry.desc.getExpandedSystemId())) {
removeGrammar(entry.desc);
return;
}
}
}
}

@Override
public void lockPool() {
// Do nothing
Expand Down Expand Up @@ -243,4 +254,5 @@ protected void clear() {
}
}
}

}
Loading

0 comments on commit 030a620

Please sign in to comment.