org.ballerinalang.langserver.formatting.FormattingSourceGen.java Source code

Java tutorial

Introduction

Here is the source code for org.ballerinalang.langserver.formatting.FormattingSourceGen.java

Source

/*
 * Copyright (c) 2018, WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ballerinalang.langserver.formatting;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Temporary source gen class for formatting.
 */
public class FormattingSourceGen {
    private static Map<String, JsonObject> anonTypes = new HashMap<>();

    /**
     * process the given json before source generation.
     *
     * @param json       Current JSON AST node
     * @param parentKind Parent kind
     * @return {@link JsonObject} Processed AST node
     */
    public static JsonObject build(JsonObject json, String parentKind) {
        String kind = json.get("kind").getAsString();
        for (Map.Entry<String, JsonElement> child : json.entrySet()) {
            if (!child.getKey().equals("position") && !child.getKey().equals("ws")) {
                if (child.getValue().isJsonObject() && child.getValue().getAsJsonObject().get("kind") != null) {
                    json.add(child.getKey(), build(child.getValue().getAsJsonObject(), kind));
                } else if (child.getValue().isJsonArray()) {
                    JsonArray childArray = child.getValue().getAsJsonArray();
                    for (int j = 0; j < childArray.size(); j++) {
                        JsonElement childItem = childArray.get(j);
                        if ("CompilationUnit".equals(kind)
                                && childItem.getAsJsonObject().get("kind").getAsString().equals("Function")
                                && childItem.getAsJsonObject().has("lambda")
                                && childItem.getAsJsonObject().get("lambda").getAsBoolean()) {
                            childArray.remove(j);
                            j--;
                        } else if (childItem.isJsonObject() && childItem.getAsJsonObject().get("kind") != null) {
                            build(childItem.getAsJsonObject(), kind);
                        }
                    }
                }
            }
        }
        modifyNode(json, parentKind);
        return json;
    }

    /**
     * Get the source of the given node.
     *
     * @param node Node to generate the ballerina source for
     * @return {@link String} Generated source string
     */
    public static String getSourceOf(JsonObject node) {
        StringBuilder sourceBuilder = new StringBuilder();
        for (JsonObject wsItem : extractWS(node)) {
            sourceBuilder.append(wsItem.get("ws").getAsString()).append(wsItem.get("text").getAsString());
        }
        return sourceBuilder.toString();
    }

    /**
     * Extract Whitespaces from the given node sorted by the token index.
     *
     * @param node node to extract the white spaces
     * @return {@link List} list of white spaces
     */
    public static List<JsonObject> extractWS(JsonObject node) {
        return unify(getSortedWSList(node));
    }

    /**
     * Reconcile whitespaces for the given node as to the attach point and the start index.
     *
     * @param node        Json Node to be added in to the node tree
     * @param attachPoint Attach point for the node
     * @param tree        whole Json AST tree
     * @param startIndex  Start index where the node to be added
     */
    public static void reconcileWS(JsonObject node, JsonArray attachPoint, JsonObject tree, int startIndex) {
        List<JsonObject> attachWS;
        List<JsonObject> nodeWS = extractWS(node);
        List<JsonObject> astWS = getSortedWSList(tree);

        if (startIndex == -1) {
            if (attachPoint.size() > 0) {
                attachWS = extractWS(attachPoint.get(attachPoint.size() - 1).getAsJsonObject());
                startIndex = attachWS.get(attachWS.size() - 1).get("i").getAsInt() + 1;
            } else {
                startIndex = 0;
            }
        }

        int nodeFirstIndex = nodeWS.get(0).get("i").getAsInt();
        int diff = startIndex - nodeFirstIndex;

        nodeWS.forEach(ws -> ws.addProperty("i", ws.get("i").getAsInt() + diff));

        int lastIndex = nodeWS.get(nodeWS.size() - 1).get("i").getAsInt();
        int treeDiff = (lastIndex - startIndex) + 1;

        for (JsonObject ws : astWS) {
            if (ws.get("i").getAsInt() >= startIndex) {
                ws.addProperty("i", ws.get("i").getAsInt() + treeDiff);
            }
        }
    }

    /**
     * Add new whitespace object to given target node on given start index
     * and then update the rest of the tree accordingly.
     *
     * @param targetNode target node where new WS to be added
     * @param tree       AST for the whole source file
     * @param ws         Whitespace
     * @param text       text in the whitespace
     * @param isStatic   set the Static state of the ws
     * @param startIndex start index for the whitespace
     */
    public static void addNewWS(JsonObject targetNode, JsonObject tree, String ws, String text, boolean isStatic,
            int startIndex) {
        JsonObject newWS = new JsonObject();
        List<JsonObject> astWS = getSortedWSList(tree);
        List<JsonObject> nodeWS = extractWS(targetNode);

        if (startIndex == -1) {
            startIndex = nodeWS.get(nodeWS.size() - 1).getAsJsonObject().get("i").getAsInt();
        }

        newWS.addProperty("i", startIndex);
        newWS.addProperty("static", isStatic);
        newWS.addProperty(FormattingConstants.WS, ws);
        newWS.addProperty(FormattingConstants.TEXT, text);

        targetNode.getAsJsonArray(FormattingConstants.WS).add(newWS);

        for (JsonObject wsItem : astWS) {
            if (wsItem.get("i").getAsInt() >= startIndex) {
                wsItem.addProperty("i", wsItem.get("i").getAsInt() + 1);
            }
        }
    }

    /**
     * Get the start position suitable in the given attach point where new node to be added.
     *
     * @param node         Parent of the attach point
     * @param attachPoint  Name of the attach point where the new node to be added
     * @param insertBefore start position to add the new node above existing node
     * @return {@link int} start position suitable for the new node to be added
     */
    public static int getStartPosition(JsonObject node, String attachPoint, int insertBefore) {
        int startPosition = 0;
        int prevPosition;
        String kind = node.get("kind").getAsString();
        switch (kind) {
        case "CompilationUnit":
            if (node.has("topLevelNodes") && attachPoint.equals("imports")) {
                JsonArray topLevelNodes = node.getAsJsonArray("topLevelNodes");

                // Collect all the imports available in top level.
                List<JsonObject> importsWS = new ArrayList<>();
                for (int i = 0; i < topLevelNodes.size(); i++) {
                    if (topLevelNodes.get(i).getAsJsonObject().get("kind").getAsString().equals("Import")) {
                        importsWS.add(topLevelNodes.get(i).getAsJsonObject());
                    }
                }

                // Find the last position of the imports to add the new import.
                prevPosition = 0;
                for (JsonObject ws : importsWS) {
                    List<JsonObject> extractedWS = extractWS(ws);
                    int lastIndex = extractedWS.get(extractedWS.size() - 1).get("i").getAsInt();
                    if (prevPosition < lastIndex) {
                        prevPosition = lastIndex;
                    }
                }

                // Start position for the new import.
                startPosition = prevPosition + 1;
            }
            break;
        case "Service":
        case "Function":
            JsonArray nodeWS = node.getAsJsonArray(FormattingConstants.WS);
            if (node.has("resource") && node.get("resource").getAsBoolean() || kind.equals("Service")) {

                JsonArray annotationAttachments = node.has("annotationAttachments")
                        ? node.getAsJsonArray("annotationAttachments")
                        : node.getAsJsonArray("annAttachments");
                if ((node.has("annAttachments") || node.has("annotationAttachments"))
                        && attachPoint.equals("annAttachments")) {
                    startPosition = getCollectionStartPosition(annotationAttachments,
                            nodeWS.get(0).getAsJsonObject().get("i").getAsInt() - 1, insertBefore);
                }

                // TODO: handle calculation for the param start position.

                JsonObject typeDefinition = kind.equals("Service") && node.has("typeDefinition")
                        && node.getAsJsonObject("typeDefinition").get("service").getAsBoolean()
                                ? node.getAsJsonObject("typeDefinition")
                                : null;

                if (typeDefinition != null && typeDefinition.has("typeNode")
                        && typeDefinition.getAsJsonObject("typeNode").has(FormattingConstants.WS)) {
                    JsonArray typeDefinitionWS = typeDefinition.getAsJsonObject("typeNode")
                            .getAsJsonArray(FormattingConstants.WS);
                    prevPosition = findOpeningBrace(typeDefinitionWS);
                } else {
                    prevPosition = findOpeningBrace(nodeWS);
                }

                if (node.has("endpointNodes")) {
                    // If attachment point endpointNodes, calculate the position to add the new node in to endpoint
                    // Else calculate the position of the last whitespace token.
                    if (attachPoint.equals("endpointNodes")) {
                        startPosition = getCollectionStartPosition(node.getAsJsonArray("endpointNodes"),
                                prevPosition, insertBefore);
                    } else if (node.getAsJsonArray("endpointNodes").size() > 0) {
                        List<JsonObject> endpointWS = extractWS(node.getAsJsonArray("endpointNodes")
                                .get(node.getAsJsonArray("endpointNodes").size() - 1).getAsJsonObject());
                        prevPosition = endpointWS.get(endpointWS.size() - 1).get("i").getAsInt();
                    }
                }

                if (kind.equals("Service")) {
                    if (node.has("vars")) {
                        JsonArray vars = node.getAsJsonArray("vars");
                        if (attachPoint.equals("vars")) {
                            startPosition = getCollectionStartPosition(vars, prevPosition, insertBefore);
                        } else if (vars.size() > 0) {
                            List<JsonObject> varWS = extractWS(vars.get(vars.size() - 1).getAsJsonObject());
                            prevPosition = varWS.get(varWS.size() - 1).get("i").getAsInt();
                        }
                    }

                    if (node.has("resources")) {
                        JsonArray resources = node.getAsJsonArray("resources");
                        if (attachPoint.equals("resources")) {
                            startPosition = getCollectionStartPosition(resources, prevPosition, insertBefore);
                        } else if (resources.size() > 0) {
                            List<JsonObject> resourceWS = extractWS(
                                    resources.get(resources.size() - 1).getAsJsonObject());
                            prevPosition = resourceWS.get(resourceWS.size() - 1).get("i").getAsInt();
                        }
                    }
                }

                if (kind.equals("Function") && node.has("resource") && node.get("resource").getAsBoolean()) {
                    if (node.has("workers")) {
                        if (attachPoint.equals("workers")) {
                            startPosition = getCollectionStartPosition(node.getAsJsonArray("workers"), prevPosition,
                                    insertBefore);
                        } else if (node.getAsJsonArray("workers").size() > 0) {
                            List<JsonObject> workerWS = extractWS(node.getAsJsonArray("workers")
                                    .get(node.getAsJsonArray("workers").size() - 1).getAsJsonObject());
                            prevPosition = workerWS.get(workerWS.size() - 1).get("i").getAsInt();
                        }
                    }

                    if (node.has(FormattingConstants.BODY) && attachPoint.equals("statements")) {
                        startPosition = getCollectionStartPosition(
                                node.getAsJsonObject(FormattingConstants.BODY).getAsJsonArray(attachPoint),
                                prevPosition, insertBefore);
                    }
                }
            }
            break;
        default:
            if (node.has(FormattingConstants.WS)) {
                startPosition = findOpeningBrace(node.getAsJsonArray(FormattingConstants.WS)) + 1;
            }
            break;
        }
        return startPosition;
    }

    private static List<JsonObject> getSortedWSList(JsonObject node) {
        List<JsonObject> wsCollection = new ArrayList<>();
        collectWSFromNode(node, wsCollection);
        wsCollection.sort(Comparator.comparingInt(a -> a.get("i").getAsInt()));
        return wsCollection;
    }

    private static List<JsonObject> unify(List<JsonObject> toBeUnified) {
        List<JsonObject> unified = new ArrayList<>();
        JsonObject prevWS = null;
        for (JsonObject wsItem : toBeUnified) {
            if (prevWS == null) {
                prevWS = wsItem;
                unified.add(prevWS);
            } else if (prevWS.get("i").getAsInt() != wsItem.get("i").getAsInt() && !prevWS.equals(wsItem)) {
                unified.add(wsItem);
                prevWS = wsItem;
            }
        }

        return unified;
    }

    private static int getCollectionStartPosition(JsonArray collection, int entryPoint, int insertBefore) {
        int startPosition;
        if (collection.size() > 0) {
            startPosition = getPositionToInsertBefore(collection, insertBefore);
        } else {
            startPosition = entryPoint + 1;
        }
        return startPosition;
    }

    private static int getPositionToInsertBefore(JsonArray collection, int insertBefore) {
        int startPosition = -1;
        if (collection.size() > 0) {
            if (insertBefore == -1) {
                List<JsonObject> statementWS = extractWS(collection.get(collection.size() - 1).getAsJsonObject());
                startPosition = statementWS.get(statementWS.size() - 1).get("i").getAsInt() + 1;
            } else {
                List<JsonObject> statementWS = extractWS(collection.get(insertBefore).getAsJsonObject());
                startPosition = statementWS.get(statementWS.size() - 1).get("i").getAsInt();
            }
        }
        return startPosition;
    }

    private static int findOpeningBrace(JsonArray ws) {
        int index = -1;
        for (int i = 0; i < ws.size(); i++) {
            if (ws.get(i).getAsJsonObject().get(FormattingConstants.TEXT).getAsString().equals("{")) {
                index = ws.get(i).getAsJsonObject().get("i").getAsInt();
                break;
            }
        }

        return index;
    }

    private static void collectWSFromNode(JsonObject node, List<JsonObject> wsCollection) {
        if (!node.has("skip")) {
            for (Map.Entry<String, JsonElement> child : node.entrySet()) {
                String childName = child.getKey();
                if (!"position".equals(childName) && !"parent".equals(childName)) {
                    if (child.getValue().isJsonObject() && child.getValue().getAsJsonObject().has("kind")) {
                        collectWSFromNode(child.getValue().getAsJsonObject(), wsCollection);
                    } else if (child.getValue().isJsonArray()) {
                        if ("ws".equals(childName)) {
                            for (JsonElement wsElement : child.getValue().getAsJsonArray()) {
                                wsCollection.add(wsElement.getAsJsonObject());
                            }
                        } else {
                            for (JsonElement wsElement : child.getValue().getAsJsonArray()) {
                                if (wsElement.isJsonObject() && wsElement.getAsJsonObject().has("kind")) {
                                    collectWSFromNode(wsElement.getAsJsonObject(), wsCollection);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private static void modifyNode(JsonObject node, String parentKind) {
        String kind = node.get("kind").getAsString();

        if ("CompilationUnit".equals(kind)) {
            if (node.has("ws")) {
                node.getAsJsonArray("ws").get(0).getAsJsonObject().addProperty("text", "");
            }
        }

        if ("If".equals(kind)) {
            if (node.getAsJsonObject("elseStatement") != null) {
                node.addProperty("ladderParent", true);
            }

            if (node.has("ws") && node.getAsJsonArray("ws").size() > 1
                    && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("else")
                    && node.getAsJsonArray("ws").get(1).getAsJsonObject().get("text").getAsString().equals("if")) {
                node.addProperty("isElseIfBlock", true);
            }
        }

        if ("Transaction".equals(kind) && node.has("condition") && node.getAsJsonObject("condition").has("value")) {
            JsonObject retry = null;
            if (node.has("failedBody") && node.getAsJsonObject("failedBody").has("statements")) {
                for (JsonElement statement : node.getAsJsonObject("failedBody").get("statements")
                        .getAsJsonArray()) {
                    if (statement.isJsonObject() && statement.getAsJsonObject().has("kind")
                            && statement.getAsJsonObject().get("kind").getAsString().equals("retry")) {
                        retry = statement.getAsJsonObject();
                    }
                }
            }

            if (node.has("committedBody") && node.getAsJsonObject("committedBody").has("statements")) {
                for (JsonElement statement : node.getAsJsonObject("committedBody").get("statements")
                        .getAsJsonArray()) {
                    if (statement.isJsonObject() && statement.getAsJsonObject().has("kind")
                            && statement.getAsJsonObject().get("kind").getAsString().equals("retry")) {
                        retry = statement.getAsJsonObject();
                    }
                }
            }

            if (node.has("transactionBody") && node.getAsJsonObject("transactionBody").has("statements")) {
                for (JsonElement statement : node.getAsJsonObject("transactionBody").get("statements")
                        .getAsJsonArray()) {
                    if (statement.isJsonObject() && statement.getAsJsonObject().has("kind")
                            && statement.getAsJsonObject().get("kind").getAsString().equals("retry")) {
                        retry = statement.getAsJsonObject();
                    }
                }
            }

            if (retry != null) {
                retry.addProperty("count", node.getAsJsonObject("condition").get("value").getAsString());
            }
        }

        if (("XmlCommentLiteral".equals(kind) || "XmlElementLiteral".equals(kind) || "XmlTextLiteral".equals(kind)
                || "XmlPiLiteral".equals(kind)) && node.has("ws") && node.getAsJsonArray("ws").get(0) != null
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().contains("xml")
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().contains("`")) {
            node.addProperty("root", true);
            node.addProperty("startLiteral",
                    node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
        }

        if ("XmlElementLiteral".equals(parentKind) || "XmlTextLiteral".equals(parentKind)
                || "XmlPiLiteral".equals(parentKind)) {
            node.addProperty("inTemplateLiteral", true);
        }

        if ("Annotation".equals(kind) && node.has("attachmentPoints")
                && node.getAsJsonArray("attachmentPoints").size() <= 0) {
            node.addProperty("noAttachmentPoints", true);
        }

        if ("Identifier".equals(kind)) {
            if (node.has("literal") && node.get("literal").getAsBoolean()) {
                node.addProperty("valueWithBar", "^\"" + node.get("value").getAsString() + "\"");
            } else {
                node.addProperty("valueWithBar", node.get("value").getAsString());
            }
        }

        if ("Import".equals(kind)) {
            if (node.getAsJsonObject("alias") != null && node.getAsJsonObject("alias").get("value") != null
                    && node.getAsJsonArray("packageName") != null && node.getAsJsonArray("packageName").size() != 0
                    && !node.getAsJsonObject("alias").get("value").getAsString()
                            .equals(node.getAsJsonArray("packageName")
                                    .get(node.getAsJsonArray("packageName").size() - 1).getAsJsonObject()
                                    .get("value").getAsString())) {
                node.addProperty("userDefinedAlias", true);
            }

            if ((node.getAsJsonArray("packageName") != null && node.getAsJsonArray("packageName").size() == 2
                    && node.getAsJsonArray("packageName").get(0).getAsJsonObject().get("value").getAsString()
                            .equals("transactions")
                    && node.getAsJsonArray("packageName").get(1).getAsJsonObject().get("value").getAsString()
                            .equals("coordinator"))
                    || (node.getAsJsonObject("alias") != null && node.getAsJsonObject("alias").get("value") != null
                            && node.getAsJsonObject("alias").get("value").getAsString().startsWith("."))) {
                node.addProperty("isInternal", true);
            }
        }

        if ("CompilationUnit".equals(parentKind) && ("Variable".equals(kind) || "Xmlns".equals(kind))) {
            node.addProperty("global", true);
        }

        if ("VariableDef".equals(kind)) {
            // TODO: refactor variable def whitespace.
            // Temporary remove variable def ws and add it to variable whitespaces.
            if (node.has("variable") && node.has(FormattingConstants.WS)
                    && node.getAsJsonObject("variable").has(FormattingConstants.WS)
                    && !(node.getAsJsonObject("variable").get("kind").getAsString().equals("TupleVariable")
                            || (node.getAsJsonObject("variable").has("symbolType")
                                    && node.getAsJsonObject("variable").getAsJsonArray("symbolType").size() > 0
                                    && node.getAsJsonObject("variable").getAsJsonArray("symbolType").get(0)
                                            .getAsString().equals("service")))) {
                node.getAsJsonObject("variable").getAsJsonArray(FormattingConstants.WS)
                        .addAll(node.getAsJsonArray(FormattingConstants.WS));
                node.remove(FormattingConstants.WS);
            }

            if (node.getAsJsonObject("variable") != null
                    && node.getAsJsonObject("variable").getAsJsonObject("typeNode") != null
                    && node.getAsJsonObject("variable").getAsJsonObject("typeNode").get("kind").getAsString()
                            .equals("EndpointType")) {
                node.getAsJsonObject("variable").addProperty("endpoint", true);
                node.addProperty("endpoint", true);
            }
        }

        if ("Variable".equals(kind)) {
            if ("ObjectType".equals(parentKind)) {
                node.addProperty("inObject", true);
            }

            if (node.has("typeNode") && node.getAsJsonObject("typeNode").has("isAnonType")
                    && node.getAsJsonObject("typeNode").get("isAnonType").getAsBoolean()) {
                node.addProperty("isAnonType", true);
            }

            if (node.has("initialExpression")) {
                node.getAsJsonObject("initialExpression").addProperty("isExpression", true);

                if (node.getAsJsonObject("initialExpression").has("async")
                        && node.getAsJsonObject("initialExpression").get("async").getAsBoolean()
                        && node.has("ws")) {
                    JsonArray ws = node.getAsJsonArray("ws");
                    for (int i = 0; i < ws.size(); i++) {
                        if (ws.get(i).getAsJsonObject().get("text").getAsString().equals("start")
                                && node.getAsJsonObject("initialExpression").has("ws")) {
                            node.getAsJsonObject("initialExpression").add("ws",
                                    addDataToArray(0, node.getAsJsonArray("ws").get(i),
                                            node.getAsJsonObject("initialExpression").getAsJsonArray("ws")));
                            node.getAsJsonArray("ws").remove(i);
                        }
                    }
                }
            }

            if (node.has("typeNode") && node.getAsJsonObject("typeNode").has("nullable")
                    && node.getAsJsonObject("typeNode").get("nullable").getAsBoolean()
                    && node.getAsJsonObject("typeNode").has("ws")) {
                JsonArray ws = node.getAsJsonObject("typeNode").get("ws").getAsJsonArray();
                for (int i = 0; i < ws.size(); i++) {
                    if (ws.get(i).getAsJsonObject().get("text").getAsString().equals("?")) {
                        node.getAsJsonObject("typeNode").addProperty("nullableOperatorAvailable", true);
                        break;
                    }
                }
            }

            if (node.has("typeNode") && node.getAsJsonObject("typeNode").has("ws") && !node.has("ws")) {
                node.addProperty("noVisibleName", true);
            }

            if (node.has("ws")) {
                JsonArray ws = node.getAsJsonArray("ws");
                for (int i = 0; i < ws.size(); i++) {
                    if (ws.get(i).getAsJsonObject().get("text").getAsString().equals(";")) {
                        node.addProperty("endWithSemicolon", true);
                    }

                    if (ws.get(i).getAsJsonObject().get("text").getAsString().equals(",")) {
                        node.addProperty("endWithComma", true);
                    }
                }
            }

            if (node.has("service") && node.get("service").getAsBoolean() && node.has("noVisibleName")
                    && node.get("noVisibleName").getAsBoolean()) {
                node.addProperty("skip", true);
            }

            if (node.has("name") && node.getAsJsonObject("name").get("value").getAsString().startsWith("0")) {
                node.addProperty("worker", true);
            }
        }

        if ("Service".equals(kind)) {
            if (!node.has("serviceTypeStruct")) {
                node.addProperty("isServiceTypeUnavailable", true);
            }

            if (node.has("resources")) {
                if (node.has("typeDefinition")) {
                    JsonObject typeDefinition = node.getAsJsonObject("typeDefinition");
                    JsonObject typeNode = typeDefinition.getAsJsonObject("typeNode");
                    if (typeNode.has("symbolType")
                            && typeNode.getAsJsonArray("symbolType").get(0).getAsString().equals("service")) {
                        JsonArray functions = typeNode.getAsJsonArray("functions");
                        for (JsonElement func : functions) {
                            JsonObject function = func.getAsJsonObject();
                            if (function.has("resource") && function.get("resource").getAsBoolean()) {
                                function.addProperty("skip", true);
                            }
                        }
                    }
                }
            }

            if (!node.has("anonymousEndpointBind") && node.has("boundEndpoints")
                    && node.getAsJsonArray("boundEndpoints").size() <= 0) {
                boolean bindAvailable = false;
                for (JsonElement ws : node.getAsJsonArray("ws")) {
                    if (ws.getAsJsonObject().get("text").getAsString().equals("bind")) {
                        bindAvailable = true;
                        break;
                    }
                }

                if (!bindAvailable) {
                    node.addProperty("bindNotAvailable", true);
                }
            }

            if (node.has("userDefinedTypeNode")
                    && node.getAsJsonObject("userDefinedTypeNode").has(FormattingConstants.WS)) {
                node.getAsJsonObject("userDefinedTypeNode").remove(FormattingConstants.WS);
            }
        }

        if ("Resource".equals(kind) && node.has("parameters") && node.getAsJsonArray("parameters").size() > 0
                && node.getAsJsonArray("parameters").get(0).getAsJsonObject().has("ws")) {
            for (JsonElement ws : node.getAsJsonArray("parameters").get(0).getAsJsonObject().getAsJsonArray("ws")) {
                if (ws.getAsJsonObject().get("text").getAsString().equals("endpoint")) {
                    JsonObject endpointParam = node.getAsJsonArray("parameters").get(0).getAsJsonObject();
                    String valueWithBar = endpointParam.get("name").getAsJsonObject().has("valueWithBar")
                            ? endpointParam.get("name").getAsJsonObject().get("valueWithBar").getAsString()
                            : endpointParam.get("name").getAsJsonObject().get("value").getAsString();

                    endpointParam.addProperty("serviceEndpoint", true);
                    endpointParam.get("name").getAsJsonObject().addProperty("value", endpointParam.get("name")
                            .getAsJsonObject().get("value").getAsString().replace("$", ""));
                    endpointParam.get("name").getAsJsonObject().addProperty("valueWithBar",
                            valueWithBar.replace("$", ""));
                    break;
                }
            }
        }

        if ("ForkJoin".equals(kind)) {
            if (node.getAsJsonObject("joinBody") != null) {
                node.getAsJsonObject("joinBody").add("position",
                        node.getAsJsonObject("joinResultVar").getAsJsonObject("position"));
            }

            if (node.getAsJsonObject("timeoutBody") != null) {
                node.getAsJsonObject("timeoutBody").add("position",
                        node.getAsJsonObject("timeOutExpression").getAsJsonObject("position"));
            }
        }

        if ("Match".equals(kind)) {
            if (node.has("structuredPatternClauses")) {
                JsonArray structuredPatternClauses = node.getAsJsonArray("structuredPatternClauses");
                for (JsonElement pattern : structuredPatternClauses) {
                    pattern.getAsJsonObject().addProperty("skip", true);
                }
            }

            if (node.has("staticPatternClauses")) {
                JsonArray staticPatternClauses = node.getAsJsonArray("staticPatternClauses");
                for (JsonElement pattern : staticPatternClauses) {
                    pattern.getAsJsonObject().addProperty("skip", true);
                }
            }
        }

        // Check if sorrounded by curlies
        if (("MatchStructuredPatternClause".equals(kind) || "MatchStaticPatternClause".equals(kind)
                || "MatchTypedPatternClause".equals(kind)) && node.has("ws")) {

            for (JsonElement wsItem : node.getAsJsonArray(FormattingConstants.WS)) {
                JsonObject currentWS = wsItem.getAsJsonObject();
                String text = currentWS.get(FormattingConstants.TEXT).getAsString();
                if (text.equals("{")) {
                    node.addProperty("withCurlies", true);
                    break;
                }
            }
        }

        // Check if sorrounded by parantheses
        if ("ValueType".equals(kind)) {
            if (node.has("ws") && node.getAsJsonArray("ws").size() > 2) {
                node.addProperty("withParantheses", true);
            }

            if (node.has("typeKind") && node.get("typeKind").getAsString().equals("nil") && node.has("ws")) {
                node.addProperty("emptyParantheses", true);
            }

            if (node.has("nullable") && node.get("nullable").getAsBoolean() && node.has("ws")) {
                for (int i = 0; i < node.get("ws").getAsJsonArray().size(); i++) {
                    if (node.get("ws").getAsJsonArray().get(i).getAsJsonObject().get("text").getAsString()
                            .equals("?")) {
                        node.addProperty("nullableOperatorAvailable", true);
                        break;
                    }
                }
            }
        }

        if ("UnionTypeNode".equals(kind) && node.has("ws")) {
            if (node.getAsJsonArray("ws").size() > 2) {
                for (JsonElement ws : node.getAsJsonArray("ws")) {
                    if (ws.getAsJsonObject().get("text").getAsString().equals("(")) {
                        node.addProperty("withParantheses", true);
                        break;
                    }
                }
            }

            JsonArray memberTypeNodes = node.get("memberTypeNodes").getAsJsonArray();
            for (int i = 0; i < memberTypeNodes.size(); i++) {
                if (memberTypeNodes.get(i).getAsJsonObject().has("nullable")
                        && memberTypeNodes.get(i).getAsJsonObject().get("nullable").getAsBoolean()) {
                    for (JsonElement ws : node.getAsJsonArray("ws")) {
                        if (ws.getAsJsonObject().get("text").getAsString().equals("?")) {
                            memberTypeNodes.get(i).getAsJsonObject().addProperty("nullableOperatorAvailable", true);
                            break;
                        }
                    }
                }
            }
        }

        if ("Function".equals(kind)) {
            if (node.has("returnTypeNode") && node.getAsJsonObject("returnTypeNode").has("ws")
                    && node.getAsJsonObject("returnTypeNode").getAsJsonArray("ws").size() > 0) {
                node.addProperty("hasReturns", true);
            }

            if (node.has("defaultableParameters")) {
                JsonArray defaultableParameters = node.getAsJsonArray("defaultableParameters");
                for (int i = 0; i < defaultableParameters.size(); i++) {
                    defaultableParameters.get(i).getAsJsonObject().addProperty("defaultable", true);
                    defaultableParameters.get(i).getAsJsonObject().addProperty("param", true);
                    defaultableParameters.get(i).getAsJsonObject().getAsJsonObject("variable")
                            .addProperty("defaultable", true);
                }
            }

            if (node.has("parameters")) {
                JsonArray parameters = node.getAsJsonArray("parameters");
                for (int i = 0; i < parameters.size(); i++) {
                    parameters.get(i).getAsJsonObject().addProperty("param", true);
                }
            }

            if (!(node.has("resource") && node.get("resource").getAsBoolean())) {
                // Sort and add all the parameters.
                JsonArray allParamsTemp = node.getAsJsonArray("parameters");
                allParamsTemp.addAll(node.getAsJsonArray("defaultableParameters"));
                List<JsonElement> allParamElements = new ArrayList<>();
                allParamsTemp.forEach(allParamElements::add);

                allParamElements.sort((a, b) -> {
                    int comparator = 0;
                    comparator = (((a.getAsJsonObject().getAsJsonObject("position").get("endColumn").getAsInt() > b
                            .getAsJsonObject().getAsJsonObject("position").get("startColumn").getAsInt())
                            && (a.getAsJsonObject().getAsJsonObject("position").get("endLine").getAsInt() == b
                                    .getAsJsonObject().getAsJsonObject("position").get("endLine").getAsInt()))
                            || (a.getAsJsonObject().getAsJsonObject("position").get("endLine").getAsInt() > b
                                    .getAsJsonObject().getAsJsonObject("position").get("endLine").getAsInt())) ? 1
                                            : -1;
                    return comparator;
                });

                JsonArray allParams = new JsonArray();
                allParamElements.forEach(allParams::add);
                node.add("allParams", allParams);
            }

            if (node.has("receiver") && !node.getAsJsonObject("receiver").has("ws")) {
                if (node.getAsJsonObject("receiver").has("typeNode")
                        && node.getAsJsonObject("receiver").getAsJsonObject("typeNode").has("ws")
                        && node.getAsJsonObject("receiver").getAsJsonObject("typeNode").getAsJsonArray("ws")
                                .size() > 0) {
                    for (JsonElement ws : node.get("ws").getAsJsonArray()) {
                        if (ws.getAsJsonObject().get("text").getAsString().equals("::")) {
                            node.addProperty("objectOuterFunction", true);
                            if (node.getAsJsonObject("receiver").getAsJsonObject("typeNode").getAsJsonArray("ws")
                                    .get(0).getAsJsonObject().get("text").getAsString().equals("function")) {
                                node.getAsJsonObject("receiver").getAsJsonObject("typeNode").getAsJsonArray("ws")
                                        .remove(0);
                            }
                            node.add("objectOuterFunctionTypeName", node.getAsJsonObject("receiver")
                                    .getAsJsonObject("typeNode").getAsJsonObject("typeName"));
                            break;
                        }
                    }
                } else {
                    node.addProperty("noVisibleReceiver", true);
                }
            }

            if (node.has("restParameters")
                    && (node.has("allParams") && node.getAsJsonArray("allParams").size() > 0)) {
                node.addProperty("hasRestParams", true);
            }

            if (node.has("restParameters")) {
                node.getAsJsonObject("restParameters").addProperty("param", true);
                if (node.getAsJsonObject("restParameters").has("typeNode")) {
                    node.getAsJsonObject("restParameters").getAsJsonObject("typeNode").addProperty("isRestParam",
                            true);
                }
            }
        }

        if ("TypeDefinition".equals(kind) && node.has("typeNode")) {
            if (!node.has("ws")) {
                node.addProperty("notVisible", true);
            }

            if (node.has("name")
                    && node.getAsJsonObject("name").get("value").getAsString().startsWith("$anonType$")) {
                anonTypes.put(node.getAsJsonObject("name").get("value").getAsString(),
                        node.getAsJsonObject("typeNode"));
            }

            if (node.getAsJsonObject("typeNode").get("kind").getAsString().equals("ObjectType")) {
                node.addProperty("isObjectType", true);
                if (node.has("ws")) {
                    JsonArray typeDefWS = node.getAsJsonArray("ws");
                    for (int i = 0; i < typeDefWS.size(); i++) {
                        if (typeDefWS.get(i).getAsJsonObject().get("text").getAsString().equals("abstract")) {
                            node.addProperty("isAbstractKeywordAvailable", true);
                        }
                    }
                }
            }

            if (node.getAsJsonObject("typeNode").get("kind").getAsString().equals("RecordType")) {
                node.addProperty("isRecordType", true);
                if (node.has("ws")) {
                    for (int i = 0; i < node.getAsJsonArray("ws").size(); i++) {
                        if (node.getAsJsonArray("ws").get(i).getAsJsonObject().get("text").getAsString()
                                .equals("record")) {
                            node.addProperty("isRecordKeywordAvailable", true);
                        }
                    }
                }
            }

            if (node.has("service") && node.get("service").getAsBoolean() && parentKind.equals("CompilationUnit")) {
                node.addProperty("skip", true);
            }
        }

        if ("ObjectType".equals(kind) && node.has("initFunction")) {
            if (!node.getAsJsonObject("initFunction").has("ws")) {
                node.getAsJsonObject("initFunction").addProperty("defaultConstructor", true);
            } else {
                node.getAsJsonObject("initFunction").addProperty("isConstructor", true);
            }
        }

        if ("RecordType".equals(kind) && node.has("restFieldType")) {
            node.addProperty("isRestFieldAvailable", true);
        }

        if ("TypeInitExpr".equals(kind)) {
            if (node.getAsJsonArray("expressions").size() <= 0) {
                node.addProperty("noExpressionAvailable", true);
            }

            if (node.has("ws")) {
                for (int i = 0; i < node.getAsJsonArray("ws").size(); i++) {
                    if (node.getAsJsonArray("ws").get(i).getAsJsonObject().get("text").getAsString().equals("(")) {
                        node.addProperty("hasParantheses", true);
                        break;
                    }
                }
            }

            if (!node.has("type")) {
                node.addProperty("noTypeAttached", true);
            } else {
                node.add("typeName", node.getAsJsonObject("type").get("typeName"));
            }
        }

        if ("Return".equals(kind) && node.has("expression")) {
            if (node.getAsJsonObject("expression").get("kind").getAsString().equals("Literal")) {
                if (node.getAsJsonObject("expression").get("value").getAsString().equals("()")) {
                    node.addProperty("noExpressionAvailable", true);
                }

                if (node.getAsJsonObject("expression").get("value").getAsString().equals("null")) {
                    node.getAsJsonObject("expression").addProperty("emptyParantheses", true);
                }
            }
            node.getAsJsonObject("expression").addProperty("isExpression", "true");
        }

        if ("Documentation".equals(kind)) {
            if (node.has("ws") && node.getAsJsonArray("ws").size() > 1) {
                node.addProperty("startDoc",
                        node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
            }

            for (int j = 0; j < node.getAsJsonArray("attributes").size(); j++) {
                JsonObject attribute = node.getAsJsonArray("attributes").get(j).getAsJsonObject();
                if (attribute.has("ws")) {
                    for (int i = 0; i < attribute.getAsJsonArray("ws").size(); i++) {
                        String text = attribute.getAsJsonArray("ws").get(i).getAsJsonObject().get("text")
                                .getAsString();
                        if (text.contains("{{") && !attribute.has("paramType")) {
                            int lastIndex = text.lastIndexOf("{{");
                            String paramType = text.substring(0, lastIndex);
                            String startCurl = text.substring(lastIndex, text.length());
                            attribute.getAsJsonArray("ws").get(i).getAsJsonObject().addProperty("text", paramType);
                            attribute.addProperty("paramType", paramType);

                            JsonObject ws = new JsonObject();
                            ws.addProperty("text", startCurl);
                            ws.addProperty("ws", "");
                            ws.addProperty("static", false);
                            attribute.add("ws", addDataToArray(++i, ws, attribute.getAsJsonArray("ws")));
                        }
                    }
                }
            }
        }

        // Tag rest variable nodes
        if (("Function".equals(kind) || "Resource".equals(kind)) && node.has("restParameters")) {
            node.getAsJsonObject("restParameters").addProperty("rest", true);
        }

        if ("PostIncrement".equals(kind)) {
            node.addProperty("operator",
                    (node.get("operatorKind").getAsString() + node.get("operatorKind").getAsString()));
        }

        if ("SelectExpression".equals(kind) && node.has("identifier")) {
            node.addProperty("identifierAvailable", true);
        }

        if ("StreamAction".equals(kind) && node.has("invokableBody")
                && node.getAsJsonObject("invokableBody").has("functionNode")) {
            node.getAsJsonObject("invokableBody").getAsJsonObject("functionNode").addProperty("isStreamAction",
                    true);
        }

        if ("StreamingInput".equals(kind) && node.has("alias")) {
            node.addProperty("aliasAvailable", true);
        }

        if ("IntRangeExpr".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0) {
            if (node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("[")) {
                node.addProperty("isWrappedWithBracket", true);
            } else if (node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("(")) {
                node.addProperty("isWrappedWithParenthesis", true);
            }
        }

        if ("FunctionType".equals(kind)) {
            if (node.has("returnTypeNode") && node.getAsJsonObject("returnTypeNode").has("ws")) {
                node.addProperty("hasReturn", true);
            }

            if (node.has("ws") && node.getAsJsonArray("ws").size() > 0
                    && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("(")) {
                node.addProperty("withParantheses", true);
            }
        }

        if ("Literal".equals(kind) && !"StringTemplateLiteral".equals(parentKind)) {
            if (node.has("ws") && node.getAsJsonArray("ws").size() == 1
                    && node.getAsJsonArray("ws").get(0).getAsJsonObject().has("text")) {
                node.addProperty("value",
                        node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
            }

            if ((node.get("value").getAsString().equals("nil") || node.get("value").getAsString().equals("null"))
                    && node.has("ws") && node.getAsJsonArray("ws").size() < 3
                    && node.getAsJsonArray("ws").get(0) != null
                    && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("(")) {
                node.addProperty("emptyParantheses", true);
            }
        }

        if ("Foreach".equals(kind) && node.has("ws")) {
            for (JsonElement ws : node.getAsJsonArray("ws")) {
                if (ws.getAsJsonObject().get("text").getAsString().equals("(")) {
                    node.addProperty("withParantheses", true);
                    break;
                }
            }
        }

        if ("Endpoint".equals(kind) && node.has("ws")) {
            for (JsonElement ws : node.getAsJsonArray("ws")) {
                if (ws.getAsJsonObject().get("text").getAsString().equals("=")) {
                    node.addProperty("isConfigAssignment", true);
                    break;
                }
            }
        }

        if ("UserDefinedType".equals(kind)) {
            if (node.has("ws") && node.has("nullable") && node.get("nullable").getAsBoolean()) {
                for (JsonElement ws : node.getAsJsonArray("ws")) {
                    if (ws.getAsJsonObject().get("text").getAsString().equals("?")) {
                        node.addProperty("nullableOperatorAvailable", true);
                        break;
                    }
                }
            }

            if (node.has("typeName") && node.getAsJsonObject("typeName").has("value")
                    && anonTypes.containsKey(node.getAsJsonObject("typeName").get("value").getAsString())) {
                node.addProperty("isAnonType", true);
                node.add("anonType", anonTypes.get(node.getAsJsonObject("typeName").get("value").getAsString()));
                anonTypes.remove(node.getAsJsonObject("typeName").get("value").getAsString());
            }
        }

        if ("ArrayType".equals(kind) && node.has("dimensions") && node.get("dimensions").getAsInt() > 0
                && node.has("ws")) {
            StringBuilder dimensionAsString = new StringBuilder();
            JsonObject startingBracket = null;
            JsonObject endingBracket;
            StringBuilder content = new StringBuilder();
            JsonArray ws = node.getAsJsonArray("ws");

            for (int j = 0; j < ws.size(); j++) {
                if (ws.get(j).getAsJsonObject().get("text").getAsString().equals("[")) {
                    startingBracket = ws.get(j).getAsJsonObject();
                } else if (ws.get(j).getAsJsonObject().get("text").getAsString().equals("]")) {
                    endingBracket = ws.get(j).getAsJsonObject();

                    dimensionAsString.append(startingBracket.get("text").getAsString()).append(content.toString())
                            .append(endingBracket.get("ws").getAsString())
                            .append(endingBracket.get("text").getAsString());

                    startingBracket = null;
                    content = new StringBuilder();
                } else if (startingBracket != null) {
                    content.append(ws.get(j).getAsJsonObject().get("ws").getAsString())
                            .append(ws.get(j).getAsJsonObject().get("text").getAsString());
                }
            }

            node.addProperty("dimensionAsString", dimensionAsString.toString());
        }

        if ("Block".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("else")) {
            node.addProperty("isElseBlock", true);
        }

        if ("FieldBasedAccessExpr".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("!")) {
            node.addProperty("errorLifting", true);
        }

        if ("StringTemplateLiteral".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().contains("string")
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().contains("`")) {
            node.addProperty("startTemplate",
                    node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
            literalWSAssignForTemplates(1, 2, node.getAsJsonArray("expressions"), node.getAsJsonArray("ws"), 2);
        }

        if ("ArrowExpr".equals(kind)) {
            if (node.has("ws") && node.getAsJsonArray("ws").size() > 0
                    && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("(")) {
                node.addProperty("hasParantheses", true);
            }

            if (node.has("parameters")) {
                JsonArray parameters = node.getAsJsonArray("parameters");
                for (int i = 0; i < parameters.size(); i++) {
                    JsonObject parameter = parameters.get(i).getAsJsonObject();
                    parameter.addProperty("arrowExprParam", true);
                }
            }
        }

        if ("PatternStreamingInput".equals(kind) && node.has("ws")
                && node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString().equals("(")) {
            node.addProperty("enclosedInParenthesis", true);
        }

        if ("SelectClause".equals(kind) && !node.has("ws")) {
            node.addProperty("notVisible", true);
        }

        if ("OrderByVariable".equals(kind)) {
            if (!node.has("ws")) {
                node.addProperty("noVisibleType", true);
            } else {
                node.addProperty("typeString",
                        node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
            }
        }

        if ("Deprecated".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0) {
            node.addProperty("deprecatedStart",
                    node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
        }

        if ("TypedescExpression".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0) {
            JsonArray typeDescWS = node.getAsJsonArray("ws");
            if (typeDescWS.get(0).getAsJsonObject().get("text").getAsString().equals("object")) {
                node.addProperty("isObject", true);
            } else if (typeDescWS.get(0).getAsJsonObject().get("text").getAsString().equals("record")) {
                node.addProperty("isRecord", true);
            }
        }

        if ("CompoundAssignment".equals(kind) && node.has("ws") && node.getAsJsonArray("ws").size() > 0) {
            node.addProperty("compoundOperator",
                    node.getAsJsonArray("ws").get(0).getAsJsonObject().get("text").getAsString());
        }

        if ("Assignment".equals(kind) && node.has("expression")) {
            node.getAsJsonObject("expression").addProperty("isExpression", true);
        }
    }

    private static void literalWSAssignForTemplates(int currentWs, int nextWs, JsonArray literals, JsonArray ws,
            int wsStartLocation) {
        if (literals.size() == (ws.size() - wsStartLocation)) {
            for (int i = 0; i < literals.size(); i++) {
                if (literals.get(i).getAsJsonObject().get("kind").getAsString().equals("Literal")) {
                    if (!literals.get(i).getAsJsonObject().has("ws")) {
                        literals.get(i).getAsJsonObject().add("ws", new JsonArray());
                    }

                    stringTemplateSourceFromWS(currentWs, nextWs, literals, ws, i);

                    if (i == (literals.size() - 1)) {
                        literals.get(i).getAsJsonObject().get("ws").getAsJsonArray().add(ws.get(currentWs));
                        literals.get(i).getAsJsonObject().addProperty("value",
                                ws.get(currentWs).getAsJsonObject().get("text").getAsString());
                        literals.get(i).getAsJsonObject().addProperty("lastNodeValue", true);
                        // TODO: use splice.
                        ws.remove(currentWs);
                    }
                }
            }
        } else if ((literals.size() - 1) == (ws.size() - wsStartLocation)) {
            for (int i = 0; i < literals.size(); i++) {
                if (literals.get(i).getAsJsonObject().get("kind").getAsString().equals("Literal")) {
                    if (!literals.get(i).getAsJsonObject().has("ws")) {
                        literals.get(i).getAsJsonObject().add("ws", new JsonArray());
                    }

                    stringTemplateSourceFromWS(currentWs, nextWs, literals, ws, i);
                }
            }
        }
    }

    private static void stringTemplateSourceFromWS(int currentWs, int nextWs, JsonArray literals, JsonArray ws,
            int i) {
        if (ws.get(currentWs).getAsJsonObject().get("text").getAsString().contains("${")) {
            literals.get(i).getAsJsonObject().get("ws").getAsJsonArray().add(ws.get(currentWs));
            literals.get(i).getAsJsonObject().addProperty("value",
                    ws.get(currentWs).getAsJsonObject().get("text").getAsString());
            // TODO: use splice
            ws.remove(currentWs);
            literals.get(i).getAsJsonObject().addProperty("startTemplateLiteral", true);

        } else if (ws.get(currentWs).getAsJsonObject().get("text").getAsString().contains("}")) {
            literals.get(i).getAsJsonObject().get("ws").getAsJsonArray().add(ws.get(currentWs));
            if (ws.get(nextWs).getAsJsonObject().get("text").getAsString().contains("${")) {
                literals.get(i).getAsJsonObject().get("ws").getAsJsonArray().add(ws.get(nextWs));
                literals.get(i).getAsJsonObject().addProperty("value",
                        ws.get(nextWs).getAsJsonObject().get("text").getAsString());
                literals.get(i).getAsJsonObject().addProperty("startTemplateLiteral", true);
                // TODO: use splice
                ws.remove(nextWs);
            }
            // TODO: use splice
            ws.remove(currentWs);
            literals.get(i).getAsJsonObject().addProperty("endTemplateLiteral", true);
        }
    }

    private static JsonArray addDataToArray(int index, JsonElement element, JsonArray ws) {
        int length = ws.size() + 1;
        JsonArray array = new JsonArray();
        boolean added = false;

        for (int i = 0; i < length; i++) {
            if (i == index) {
                array.add(element);
                added = true;
            } else if (added) {
                array.add(ws.get(i - 1));
            } else {
                array.add(ws.get(i));
            }
        }

        return array;
    }
}