Skip to content

Commit

Permalink
noNamespaceSchemaLocation file missing CodeAction
Browse files Browse the repository at this point in the history
A CodeAction for the error `schema-reference.4` that
creates the file referenced in
`xsi:noNamespaceSchemaLocation` if its missing.

Closes eclipse-lemminx#691

Signed-off-by: David Thompson <davthomp@redhat.com>
  • Loading branch information
datho7561 authored and angelozerr committed Jun 19, 2020
1 parent f8a0ab1 commit ce1a2ad
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,12 @@ public void updateClientCapabilities(ClientCapabilities capabilities,
if (extendedClientCapabilities != null) {
// Extended client capabilities
sharedSettings.getCodeLensSettings().setCodeLens(extendedClientCapabilities.getCodeLens());
sharedSettings.setActionableNotificationSupport(extendedClientCapabilities.isActionableNotificationSupport());
sharedSettings
.setActionableNotificationSupport(extendedClientCapabilities.isActionableNotificationSupport());
sharedSettings.setOpenSettingsCommandSupport(extendedClientCapabilities.isOpenSettingsCommandSupport());
}
// Workspace settings
sharedSettings.getWorkspaceSettings().setCapabilities(capabilities.getWorkspace());
}

@Override
Expand All @@ -195,8 +198,7 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().doHover(xmlDocument, params.getPosition(), sharedSettings,
cancelChecker);
return getXMLLanguageService().doHover(xmlDocument, params.getPosition(), sharedSettings, cancelChecker);
});
}

Expand Down Expand Up @@ -240,16 +242,16 @@ public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> docume
}) //
.collect(Collectors.toList());
}
SymbolInformationResult result = getXMLLanguageService()
.findSymbolInformations(xmlDocument, symbolSettings, cancelChecker);
SymbolInformationResult result = getXMLLanguageService().findSymbolInformations(xmlDocument, symbolSettings,
cancelChecker);
resultLimitExceeded = result.isResultLimitExceeded();
symbols = result.stream() //
.map(s -> {
Either<SymbolInformation, DocumentSymbol> e = Either.forLeft(s);
return e;
}) //
.collect(Collectors.toList());

if (resultLimitExceeded) {
// send warning
getLimitExceededWarnings().onResultLimitExceeded(xmlDocument.getTextDocument().getUri(),
Expand Down Expand Up @@ -560,8 +562,8 @@ private static <R, M> CompletableFuture<R> computeModelAsync(CompletableFuture<M

public LimitExceededWarnings getLimitExceededWarnings() {
if (this.limitExceededWarnings == null) {
this.limitExceededWarnings =
new LimitExceededWarnings(this.xmlLanguageServer.getLanguageClient(), sharedSettings);
this.limitExceededWarnings = new LimitExceededWarnings(this.xmlLanguageServer.getLanguageClient(),
sharedSettings);
}
return this.limitExceededWarnings;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@

import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.CreateFileOptions;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextEdit;
Expand Down Expand Up @@ -117,4 +120,32 @@ public static CodeAction replaceAt(String title, String replaceText, TextDocumen
insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}

/**
* Makes a CodeAction to create a file and add content to the file.
*
* @param title The displayed name of the CodeAction
* @param docURI The file to create
* @param content The text to put into the newly created document.
* @param diagnostic The diagnostic that this CodeAction will fix
*/
public static CodeAction createFile(String title, String docURI, String content, Diagnostic diagnostic) {

List<Either<TextDocumentEdit, ResourceOperation>> actionsToTake = new ArrayList<>(2);

actionsToTake.add(Either.forRight(new CreateFile(docURI, new CreateFileOptions(false, true))));

VersionedTextDocumentIdentifier identifier = new VersionedTextDocumentIdentifier(docURI, 0);
TextEdit te = new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), content);
actionsToTake.add(Either.forLeft(new TextDocumentEdit(identifier, Collections.singletonList(te))));

WorkspaceEdit createAndAddContentEdit = new WorkspaceEdit(actionsToTake);

CodeAction codeAction = new CodeAction(title);
codeAction.setEdit(createAndAddContentEdit);
codeAction.setDiagnostics(Collections.singletonList(diagnostic));
codeAction.setKind(CodeActionKind.QuickFix);

return codeAction;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ public class ContentModelCodeActionParticipant implements ICodeActionParticipant

public ContentModelCodeActionParticipant() {
codeActionParticipants = new HashMap<>();
XMLSyntaxErrorCode.registerCodeActionParticipants(codeActionParticipants);
DTDErrorCode.registerCodeActionParticipants(codeActionParticipants);
XMLSchemaErrorCode.registerCodeActionParticipants(codeActionParticipants);
XSDErrorCode.registerCodeActionParticipants(codeActionParticipants);
}

@Override
Expand All @@ -47,10 +43,29 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen
if (diagnostic == null || diagnostic.getCode() == null || !diagnostic.getCode().isLeft()) {
return;
}
registerCodeActionsIfNeeded(sharedSettings);
ICodeActionParticipant participant = codeActionParticipants.get(diagnostic.getCode().getLeft());
if (participant != null) {
participant.doCodeAction(diagnostic, range, document, codeActions, sharedSettings,
componentProvider);
participant.doCodeAction(diagnostic, range, document, codeActions, sharedSettings, componentProvider);
}
}

/**
* Register code action if needed.
*
* @param sharedSettings the shared settings.
*/
private void registerCodeActionsIfNeeded(SharedSettings sharedSettings) {
if (codeActionParticipants.isEmpty()) {
synchronized (codeActionParticipants) {
if (!codeActionParticipants.isEmpty()) {
return;
}
XMLSyntaxErrorCode.registerCodeActionParticipants(codeActionParticipants);
DTDErrorCode.registerCodeActionParticipants(codeActionParticipants);
XMLSchemaErrorCode.registerCodeActionParticipants(codeActionParticipants, sharedSettings);
XSDErrorCode.registerCodeActionParticipants(codeActionParticipants);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.cvc_complex_type_4CodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.cvc_enumeration_validCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.cvc_type_3_1_1CodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.schema_reference_4CodeAction;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.diagnostics.IXMLErrorCode;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ResourceOperationKind;

/**
* XML Schema error code.
Expand Down Expand Up @@ -164,7 +167,7 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
case cvc_pattern_valid: {
String value = getString(arguments[0]);
Range result = XMLPositionUtility.selectAttributeValueByGivenValueAt(value, offset, document);
if(result != null) {
if (result != null) {
return result;
}
return XMLPositionUtility.selectTrimmedText(offset, document);
Expand Down Expand Up @@ -257,7 +260,8 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
return null;
}

public static void registerCodeActionParticipants(Map<String, ICodeActionParticipant> codeActions) {
public static void registerCodeActionParticipants(Map<String, ICodeActionParticipant> codeActions,
SharedSettings sharedSettings) {
codeActions.put(cvc_complex_type_2_4_a.getCode(), new cvc_complex_type_2_4_aCodeAction());
codeActions.put(cvc_complex_type_2_4_c.getCode(), new cvc_complex_type_2_4_aCodeAction());
codeActions.put(cvc_complex_type_2_3.getCode(), new cvc_complex_type_2_3CodeAction());
Expand All @@ -269,5 +273,9 @@ public static void registerCodeActionParticipants(Map<String, ICodeActionPartici
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());

if (sharedSettings.getWorkspaceSettings().isResourceOperationSupported(ResourceOperationKind.Create)) {
codeActions.put(schema_reference_4.getCode(), new schema_reference_4CodeAction());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*******************************************************************************
* 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants.codeactions;

import java.nio.file.Path;
import java.nio.file.Paths;
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.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;

/**
* Code Action that creates a schema file referenced by
* xsi:noNamespaceSchemaLocation if it is missing
*/
public class schema_reference_4CodeAction implements ICodeActionParticipant {

private static final Pattern PATH_FINDING_REGEX = Pattern.compile("[^']+'file:///(.+)',.*");

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

String missingFilePath = getPathFromDiagnostic(diagnostic);
if (StringUtils.isEmpty(missingFilePath)) {
return;
}
Path p = Paths.get(missingFilePath);
if (p.toFile().exists()) {
return;
}

// TODO: use the generator
String schemaTemplate = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n\n</xs:schema>";

CodeAction makeSchemaFile = CodeActionFactory.createFile("Generate missing file '" + p.toFile().getName() + "'",
"file:///" + missingFilePath, schemaTemplate, diagnostic);

codeActions.add(makeSchemaFile);
}

/**
* Extract the file to create from the diagnostic. Example diagnostic:
* schema_reference.4: Failed to read schema document
* 'file:///home/dthompson/Documents/TestFiles/potationCoordination.xsd',
* because 1) could not find the document; 2) the document could not be read; 3)
* the root element of the document is not <xsd:schema>.
*/
private String getPathFromDiagnostic(Diagnostic diagnostic) {
Matcher m = PATH_FINDING_REGEX.matcher(diagnostic.getMessage());
if (m.find()) {
return m.group(1);
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ public class SharedSettings {
private final XMLCodeLensSettings codeLensSettings;
private final XMLHoverSettings hoverSettings;
private final XMLPreferences preferences;

private final XMLWorkspaceSettings workspaceSettings;
private boolean actionableNotificationSupport;
private boolean openSettingsCommandSupport;
private boolean createFileResourceOperationSupport;

public SharedSettings() {
this.completionSettings = new XMLCompletionSettings();
Expand All @@ -39,8 +40,10 @@ public SharedSettings() {
this.codeLensSettings = new XMLCodeLensSettings();
this.hoverSettings = new XMLHoverSettings();
this.preferences = new XMLPreferences();
this.workspaceSettings = new XMLWorkspaceSettings();
this.actionableNotificationSupport = false;
this.openSettingsCommandSupport = false;

}

public SharedSettings(SharedSettings newSettings) {
Expand Down Expand Up @@ -87,13 +90,19 @@ public XMLPreferences getPreferences() {
return preferences;
}

public XMLWorkspaceSettings getWorkspaceSettings() {
return workspaceSettings;
}

/**
* Returns true if the client supports actionable notifications and false otherwise
* Returns true if the client supports actionable notifications and false
* otherwise
*
* See {@link org.eclipse.lemminx.customservice.ActionableNotification} and
* {@link org.eclipse.lemminx.customservice.XMLLanguageClientAPI}
*
* @return true if the client supports actionable notifications and false otherwise
* @return true if the client supports actionable notifications and false
* otherwise
*/
public boolean isActionableNotificationSupport() {
return actionableNotificationSupport;
Expand All @@ -109,11 +118,13 @@ public void setActionableNotificationSupport(boolean actionableNotificationSuppo
}

/**
* Returns true if the client supports the open settings command and false otherwise
* Returns true if the client supports the open settings command and false
* otherwise
*
* See {@link org.eclipse.lemminx.client.ClientCommands#OPEN_SETTINGS}
*
* @return true if the client supports the open settings command and false otherwise
* @return true if the client supports the open settings command and false
* otherwise
*/
public boolean isOpenSettingsCommandSupport() {
return openSettingsCommandSupport;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* 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.settings;

import org.eclipse.lsp4j.WorkspaceClientCapabilities;

/**
* A wrapper around LSP {@link WorkspaceClientCapabilities}.
*
*/
public class XMLWorkspaceSettings {

private WorkspaceClientCapabilities workspace;

public void setCapabilities(WorkspaceClientCapabilities workspace) {
this.workspace = workspace;
}

/**
* Returns true if the client can support the given resource operation kind and
* false otherwise.
*
* @param kind the resource operation kind.
* @return true if the client can support the given resource operation kind and
* false otherwise.
*/
public boolean isResourceOperationSupported(String kind) {
return workspace != null && workspace.getWorkspaceEdit() != null
&& workspace.getWorkspaceEdit().getResourceOperations() != null
&& workspace.getWorkspaceEdit().getResourceOperations().contains(kind);
}

}
Loading

0 comments on commit ce1a2ad

Please sign in to comment.