All Downloads are FREE. Search and download functionalities are using the official Maven repository.

es.iti.wakamiti.lsp.internal.WorkspaceDiagnosticHelper Maven / Gradle / Ivy

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package es.iti.wakamiti.lsp.internal;

import static java.util.stream.Collectors.*;

import java.util.*;
import java.util.stream.Stream;

import es.iti.wakamiti.api.util.Pair;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;



public class WorkspaceDiagnosticHelper {


    private final GherkinWorkspace workspace;
    private final Map>> quickFixes = new HashMap<>();



    public WorkspaceDiagnosticHelper(GherkinWorkspace workspace) {
        this.workspace = workspace;
    }


    public Stream retrieveCodeActions(String uri, List diagnostics) {
        List ranges = diagnostics.stream().map(Diagnostic::getRange).collect(toList());
        return quickFixes
            .getOrDefault(uri, Map.of())
            .entrySet().stream()
            .filter(e->ranges.contains(e.getKey()))
            .flatMap(e->e.getValue().stream());
    }



    public void registerWorkspaceQuickFixes(Map> diagnosticsPerDocument) {
        quickFixes.clear();
        for (var documentDiagnostic : diagnosticsPerDocument.entrySet()) {
            String uri = documentDiagnostic.getKey();
            for (var diagnostic : documentDiagnostic.getValue()) {
                if (!diagnostic.getMessage().equals("There is no implementation scenario with this ID")) {
                    continue;
                }
                String id = diagnostic.getRelatedInformation().get(0).getMessage();
                var codeAction = createImplementationCodeAction(uri, id, diagnostic);
                quickFixes
                    .computeIfAbsent(uri, x->new HashMap<>())
                    .computeIfAbsent(diagnostic.getRange(), x->new ArrayList<>())
                    .add(codeAction);
            }
        }
    }


    public Stream  computeInterDocumentDiagnostics(
        List documentDiagnostics
    ) {

        Map> diagnosticsPerDocument = new HashMap<>();
        documentDiagnostics.forEach(document->
            diagnosticsPerDocument.put(document.uri(), document.diagnostics())
        );


        var definitionIds = workspace.documentAssessors.values().stream()
            .filter(GherkinDocumentAssessor::isDefinition)
            .flatMap(GherkinDocumentAssessor::retriveIdTagSegment)
            .collect(toList())
        ;
        var implementationIds = workspace.documentAssessors.values().stream()
            .filter(GherkinDocumentAssessor::isImplementation)
            .flatMap(GherkinDocumentAssessor::retriveIdTagSegment)
            .collect(toList())
        ;

        var repeatedDefs = computeRepeatedID(diagnosticsPerDocument, definitionIds);
        var repeatedImpls = computeRepeatedID(diagnosticsPerDocument, implementationIds);
        var nonLinkedDefs = computeNonLinkedID(
            diagnosticsPerDocument,
            definitionIds,
            implementationIds,
            "There is no implementation scenario with this ID"
        );
        var nonLinkedImpls = computeNonLinkedID(
            diagnosticsPerDocument,
            implementationIds,
            definitionIds,
            "There is no definition scenario with this ID"
        );

        var linkableIds = Stream.concat(definitionIds.stream(), implementationIds.stream())
            .map(DocumentSegment::content)
            .filter(id -> !repeatedDefs.contains(id))
            .filter(id -> !repeatedImpls.contains(id))
            .filter(id -> !nonLinkedDefs.contains(id))
            .filter(id -> !nonLinkedImpls.contains(id))
            .collect(toSet());

        computeLinkMap(definitionIds, implementationIds, linkableIds);

        registerWorkspaceQuickFixes(diagnosticsPerDocument);

        return diagnosticsPerDocument.entrySet().stream()
            .map(entry->new DocumentDiagnostics(entry.getKey(), entry.getValue()));
    }




    private Set computeRepeatedID(
        Map> diagnosticsPerDocument,
        List definitionIds
    ) {
        Set repeatedIds = new HashSet<>();
        for (var segment : definitionIds) {
            boolean idIsRepeated = definitionIds.stream()
                .filter(id -> segment != id)
                .map(DocumentSegment::content)
                .anyMatch(segment.content()::equals);

            if (idIsRepeated) {
                diagnosticsPerDocument
                    .computeIfAbsent(segment.uri(), x->new ArrayList<>())
                    .add(DocumentDiagnosticHelper.diagnostic(
                        DiagnosticSeverity.Error,
                        segment.uri(),
                        segment.range(),
                        "Identifier already used",
                        segment.content()
                    ));
                repeatedIds.add(segment.content());
            }
        }
        return repeatedIds;
    }



    private Set computeNonLinkedID(
        Map> diagnosticsPerDocument,
        List sources,
        List destinations,
        String message
    ) {
        Set nonLinkedIds = new HashSet<>();
        sources.stream()
        .filter(id -> destinations.stream().map(DocumentSegment::content).noneMatch(id.content()::equals))
        .forEach(segment -> {
            diagnosticsPerDocument
                .computeIfAbsent(segment.uri(), x->new ArrayList<>())
                .add(DocumentDiagnosticHelper.diagnostic(
                    DiagnosticSeverity.Warning,
                    segment.uri(),
                    segment.range(),
                    message,
                    segment.content()
                ));
            nonLinkedIds.add(segment.content());
        });
        return nonLinkedIds;
    }





    private void computeLinkMap(
        List definitionIds,
        List implementationIds,
        Set linkableIds
    ) {
        workspace.linkMap.clear();
        var definitionsMap = definitionIds.stream().collect(toMap(DocumentSegment::content,x->x));
        var implementationsMap = implementationIds.stream().collect(toMap(DocumentSegment::content,x->x));
        for (String id : linkableIds) {
            var definitionId = definitionsMap.get(id);
            var implementationId = implementationsMap.get(id);
            workspace.linkMap.put(id, new Pair<>(definitionId, implementationId));
        }
    }






    private CodeAction createImplementationCodeAction(String uri, String id, Diagnostic diagnostic) {

        List> changes = new ArrayList<>();

        WorkspaceEdit workspaceEdit = new WorkspaceEdit();
        CodeAction codeAction = new CodeAction();
        codeAction.setEdit(workspaceEdit);
        codeAction.setKind(CodeActionKind.QuickFix);
        codeAction.setIsPreferred(Boolean.TRUE);
        codeAction.setTitle("Create implementation");
        codeAction.setEdit(workspaceEdit);
        codeAction.setDiagnostics(List.of(diagnostic));
        workspaceEdit.setDocumentChanges(changes );

        GherkinDocumentAssessor definition = workspace.document(uri);
        GherkinDocumentAssessor implementation = definition.obtainIdTags()
            .stream()
            .map(TextSegment::content)
            .filter(workspace.linkMap::containsKey)
            .map(workspace.linkMap::get)
            .map(Pair::value)
            .map(DocumentSegment::uri)
            .map(workspace::document)
            .findAny()
            .orElse(null);

        if (implementation == null) {

            var definitionFilename = definition.path().getFileName().toString();
            var newFilename = definitionFilename.replace(".", "-impl.");
            var implementationUri = definition.path().getParent().resolve(newFilename).toString();
            var createFile = new CreateFile(implementationUri);
            createFile.setOptions(new CreateFileOptions(false, false));
            changes.add(Either.forRight(createFile));

            String newFileContent =
                Snippets.implementationFeatureSnippet(definition) +
                Snippets.implementationScenarioSnippet(id, definition, implementation);

            TextEdit textEdit = new TextEdit(startOfLine(0), newFileContent);
            var textDocument = new VersionedTextDocumentIdentifier(implementationUri,null);
            TextDocumentEdit textDocumentEdit = new TextDocumentEdit();
            textDocumentEdit.setTextDocument(textDocument);
            textDocumentEdit.setEdits(List.of(textEdit));
            changes.add(Either.forLeft(textDocumentEdit));

        } else {
            String newText = Snippets.implementationScenarioSnippet(id, definition, implementation);
            Range range = startOfLine(implementation.numberOfLines());
            TextEdit textEdit = new TextEdit(range, newText);
            var textDocument = new VersionedTextDocumentIdentifier(implementation.uri(),null);
            TextDocumentEdit textDocumentEdit = new TextDocumentEdit();
            textDocumentEdit.setTextDocument(textDocument);
            textDocumentEdit.setEdits(List.of(textEdit));
            changes.add(Either.forLeft(textDocumentEdit));
        }
        return codeAction;
    }



    private Range startOfLine(int line) {
        return new Range(new Position(line,0), new Position(line,0));
    }






}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy