"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSourceCommands = void 0;
const fs = require("fs");
const path = require("path");
const directive_1 = require("../shellcheck/directive");
const discriminate_1 = require("./discriminate");
const fs_1 = require("./fs");
const TreeSitterUtil = require("./tree-sitter");
const SOURCING_COMMANDS = ['source', '.'];
/**
 * Analysis the given tree for source commands.
 */
function getSourceCommands({ fileUri, rootPath, tree, }) {
    const sourceCommands = [];
    const rootPaths = [path.dirname(fileUri), rootPath].filter(Boolean);
    TreeSitterUtil.forEach(tree.rootNode, (node) => {
        const sourcedPathInfo = getSourcedPathInfoFromNode({ node });
        if (sourcedPathInfo) {
            const { sourcedPath, parseError } = sourcedPathInfo;
            const uri = sourcedPath ? resolveSourcedUri({ rootPaths, sourcedPath }) : null;
            sourceCommands.push({
                range: TreeSitterUtil.range(node),
                uri,
                error: uri ? null : parseError || 'failed to resolve path',
            });
        }
        return true;
    });
    return sourceCommands;
}
exports.getSourceCommands = getSourceCommands;
function getSourcedPathInfoFromNode({ node, }) {
    var _a, _b, _c;
    if (node.type === 'command') {
        const [commandNameNode, argumentNode] = node.namedChildren;
        if (!commandNameNode || !argumentNode) {
            return null;
        }
        if (commandNameNode.type === 'command_name' &&
            SOURCING_COMMANDS.includes(commandNameNode.text)) {
            const previousCommentNode = ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.type) === 'comment' ? node.previousSibling : null;
            if (previousCommentNode === null || previousCommentNode === void 0 ? void 0 : previousCommentNode.text.includes('shellcheck')) {
                const directives = (0, directive_1.parseShellCheckDirective)(previousCommentNode.text);
                const sourcedPath = (_b = directives.find((0, discriminate_1.discriminate)('type', 'source'))) === null || _b === void 0 ? void 0 : _b.path;
                if (sourcedPath === '/dev/null') {
                    return null;
                }
                if (sourcedPath) {
                    return {
                        sourcedPath,
                    };
                }
                const isNotFollowErrorDisabled = !!directives
                    .filter((0, discriminate_1.discriminate)('type', 'disable'))
                    .flatMap(({ rules }) => rules)
                    .find((rule) => rule === 'SC1091');
                if (isNotFollowErrorDisabled) {
                    return null;
                }
                const rootFolder = (_c = directives.find((0, discriminate_1.discriminate)('type', 'source-path'))) === null || _c === void 0 ? void 0 : _c.path;
                if (rootFolder && rootFolder !== 'SCRIPTDIR' && argumentNode.type === 'word') {
                    return {
                        sourcedPath: path.join(rootFolder, argumentNode.text),
                    };
                }
            }
            if (argumentNode.type === 'word') {
                return {
                    sourcedPath: argumentNode.text,
                };
            }
            if (argumentNode.type === 'string' || argumentNode.type === 'raw_string') {
                const children = argumentNode.namedChildren;
                if (children.length === 0 ||
                    (children.length === 1 && children[0].type === 'string_content')) {
                    return {
                        sourcedPath: argumentNode.text.slice(1, -1),
                    };
                }
            }
            // TODO: we could try to parse any ShellCheck "source "directive
            // # shellcheck source=src/examples/config.sh
            return {
                parseError: `non-constant source not supported`,
            };
        }
    }
    return null;
}
/**
 * Tries to resolve the given sourced path and returns a URI if possible.
 * - Converts a relative paths to absolute paths
 * - Converts a tilde path to an absolute path
 * - Resolves the path
 *
 * NOTE: for future improvements:
 * "If filename does not contain a slash, file names in PATH are used to find
 *  the directory containing filename." (see https://ss64.com/osx/source.html)
 */
function resolveSourcedUri({ rootPaths, sourcedPath, }) {
    if (sourcedPath.startsWith('~')) {
        sourcedPath = (0, fs_1.untildify)(sourcedPath);
    }
    if (sourcedPath.startsWith('/')) {
        if (fs.existsSync(sourcedPath)) {
            return `file://${sourcedPath}`;
        }
        return null;
    }
    // resolve  relative path
    for (const rootPath of rootPaths) {
        const potentialPath = path.join(rootPath.replace('file://', ''), sourcedPath);
        // check if path is a file
        if (fs.existsSync(potentialPath)) {
            return `file://${potentialPath}`;
        }
    }
    return null;
}
//# sourceMappingURL=sourcing.js.map