"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLocalDeclarations = exports.getAllDeclarationsInTree = exports.getGlobalDeclarations = void 0;
const LSP = require("vscode-languageserver/node");
const TreeSitterUtil = require("./tree-sitter");
const TREE_SITTER_TYPE_TO_LSP_KIND = {
    // These keys are using underscores as that's the naming convention in tree-sitter.
    environment_variable_assignment: LSP.SymbolKind.Variable,
    function_definition: LSP.SymbolKind.Function,
    variable_assignment: LSP.SymbolKind.Variable,
};
const GLOBAL_DECLARATION_LEAF_NODE_TYPES = new Set([
    'if_statement',
    'function_definition',
]);
/**
 * Returns declarations (functions or variables) from a given root node
 * that would be available after sourcing the file. This currently does
 * not include global variables defined inside if statements or functions
 * as we do not do any flow tracing.
 *
 * Will only return one declaration per symbol name – the latest definition.
 * This behavior is consistent with how Bash behaves, but differs between
 * LSP servers.
 *
 * Used when finding declarations for sourced files and to get declarations
 * for the entire workspace.
 */
function getGlobalDeclarations({ tree, uri, }) {
    const globalDeclarations = {};
    TreeSitterUtil.forEach(tree.rootNode, (node) => {
        const followChildren = !GLOBAL_DECLARATION_LEAF_NODE_TYPES.has(node.type);
        const symbol = getDeclarationSymbolFromNode({ node, uri });
        if (symbol) {
            const word = symbol.name;
            globalDeclarations[word] = symbol;
        }
        return followChildren;
    });
    return globalDeclarations;
}
exports.getGlobalDeclarations = getGlobalDeclarations;
/**
 * Returns all declarations (functions or variables) from a given tree.
 * This includes local variables.
 */
function getAllDeclarationsInTree({ tree, uri, }) {
    const symbols = [];
    TreeSitterUtil.forEach(tree.rootNode, (node) => {
        const symbol = getDeclarationSymbolFromNode({ node, uri });
        if (symbol) {
            symbols.push(symbol);
        }
    });
    return symbols;
}
exports.getAllDeclarationsInTree = getAllDeclarationsInTree;
/**
 * Returns declarations available for the given file and location.
 * The heuristics used is a simplification compared to bash behaviour,
 * but deemed good enough, compared to the complexity of flow tracing.
 *
 * Used when getting declarations for the current scope.
 */
function getLocalDeclarations({ node, rootNode, uri, }) {
    const declarations = {};
    // Bottom up traversal to capture all local and scoped declarations
    const walk = (node) => {
        // NOTE: there is also node.walk
        if (node) {
            for (const childNode of node.children) {
                let symbol = null;
                // local variables
                if (childNode.type === 'declaration_command') {
                    const variableAssignmentNode = childNode.children.filter((child) => child.type === 'variable_assignment')[0];
                    if (variableAssignmentNode) {
                        symbol = nodeToSymbolInformation({
                            node: variableAssignmentNode,
                            uri,
                        });
                    }
                }
                else if (childNode.type === 'for_statement') {
                    const variableNode = childNode.child(1);
                    if (variableNode && variableNode.type === 'variable_name') {
                        symbol = LSP.SymbolInformation.create(variableNode.text, LSP.SymbolKind.Variable, TreeSitterUtil.range(variableNode), uri);
                    }
                }
                else {
                    symbol = getDeclarationSymbolFromNode({ node: childNode, uri });
                }
                if (symbol) {
                    if (!declarations[symbol.name]) {
                        declarations[symbol.name] = [];
                    }
                    declarations[symbol.name].push(symbol);
                }
            }
            walk(node.parent);
        }
    };
    walk(node);
    // Top down traversal to add missing global variables from within functions
    Object.entries(getAllGlobalVariableDeclarations({
        rootNode,
        uri,
    })).map(([name, symbols]) => {
        if (!declarations[name]) {
            declarations[name] = symbols;
        }
    });
    return declarations;
}
exports.getLocalDeclarations = getLocalDeclarations;
function getAllGlobalVariableDeclarations({ uri, rootNode, }) {
    const declarations = {};
    TreeSitterUtil.forEach(rootNode, (node) => {
        var _a;
        if (node.type === 'variable_assignment' &&
            // exclude local variables
            ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) !== 'declaration_command') {
            const symbol = nodeToSymbolInformation({ node, uri });
            if (symbol) {
                if (!declarations[symbol.name]) {
                    declarations[symbol.name] = [];
                }
                declarations[symbol.name].push(symbol);
            }
        }
        return;
    });
    return declarations;
}
function nodeToSymbolInformation({ node, uri, }) {
    var _a, _b;
    const named = node.firstNamedChild;
    if (named === null) {
        return null;
    }
    const containerName = ((_b = (_a = TreeSitterUtil.findParent(node, (p) => p.type === 'function_definition')) === null || _a === void 0 ? void 0 : _a.firstNamedChild) === null || _b === void 0 ? void 0 : _b.text) || '';
    const kind = TREE_SITTER_TYPE_TO_LSP_KIND[node.type];
    return LSP.SymbolInformation.create(named.text, kind || LSP.SymbolKind.Variable, TreeSitterUtil.range(node), uri, containerName);
}
function getDeclarationSymbolFromNode({ node, uri, }) {
    var _a, _b;
    if (TreeSitterUtil.isDefinition(node)) {
        return nodeToSymbolInformation({ node, uri });
    }
    else if (node.type === 'command' && node.text.startsWith(': ')) {
        // : does argument expansion and retains the side effects.
        // A common usage is to define default values of environment variables, e.g. : "${VARIABLE:="default"}".
        const variableNode = (_b = (_a = node.namedChildren
            .find((c) => c.type === 'string')) === null || _a === void 0 ? void 0 : _a.namedChildren.find((c) => c.type === 'expansion')) === null || _b === void 0 ? void 0 : _b.namedChildren.find((c) => c.type === 'variable_name');
        if (variableNode) {
            return LSP.SymbolInformation.create(variableNode.text, LSP.SymbolKind.Variable, TreeSitterUtil.range(variableNode), uri);
        }
    }
    return null;
}
//# sourceMappingURL=declarations.js.map