"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNewScriptStartTag = exports.findChildOfKind = exports.getQuotePreference = exports.getFormatCodeBasis = exports.isKitTypePath = exports.gatherIdentifiers = exports.gatherDescendants = exports.isInReactiveStatement = exports.findRenderFunction = exports.isReactiveStatement = exports.findNodeAtSpan = exports.findClosestContainingNode = exports.findContainingNode = exports.isAfterSvelte2TsxPropsReturn = exports.SnapshotMap = exports.getStoreOffsetOf$storeDeclaration = exports.is$storeVariableIn$storeDeclaration = exports.get$storeOffsetOf$storeDeclaration = exports.isStoreVariableIn$storeDeclaration = exports.isPartOfImportStatement = exports.isTextSpanInGeneratedCode = exports.isInGeneratedCode = exports.surroundWithIgnoreComments = exports.IGNORE_END_COMMENT = exports.IGNORE_START_COMMENT = exports.isComponentAtPosition = exports.getComponentAtPosition = void 0;
const typescript_1 = __importDefault(require("typescript"));
const documents_1 = require("../../../lib/documents");
const ComponentInfoProvider_1 = require("../ComponentInfoProvider");
const utils_1 = require("../../../utils");
const fileCollection_1 = require("../../../lib/documents/fileCollection");
/**
 * If the given original position is within a Svelte starting tag,
 * return the snapshot of that component.
 */
function getComponentAtPosition(lang, doc, tsDoc, originalPosition) {
    var _a;
    if (tsDoc.parserError) {
        return null;
    }
    if ((0, documents_1.isInTag)(originalPosition, doc.scriptInfo) ||
        (0, documents_1.isInTag)(originalPosition, doc.moduleScriptInfo)) {
        // Inside script tags -> not a component
        return null;
    }
    const node = (0, documents_1.getNodeIfIsInComponentStartTag)(doc.html, doc.offsetAt(originalPosition));
    if (!node) {
        return null;
    }
    const generatedPosition = tsDoc.getGeneratedPosition(doc.positionAt(node.start + 1));
    const def = (_a = lang.getDefinitionAtPosition(tsDoc.filePath, tsDoc.offsetAt(generatedPosition))) === null || _a === void 0 ? void 0 : _a[0];
    if (!def) {
        return null;
    }
    return ComponentInfoProvider_1.JsOrTsComponentInfoProvider.create(lang, def);
}
exports.getComponentAtPosition = getComponentAtPosition;
function isComponentAtPosition(doc, tsDoc, originalPosition) {
    if (tsDoc.parserError) {
        return false;
    }
    if ((0, documents_1.isInTag)(originalPosition, doc.scriptInfo) ||
        (0, documents_1.isInTag)(originalPosition, doc.moduleScriptInfo)) {
        // Inside script tags -> not a component
        return false;
    }
    return !!(0, documents_1.getNodeIfIsInComponentStartTag)(doc.html, doc.offsetAt(originalPosition));
}
exports.isComponentAtPosition = isComponentAtPosition;
exports.IGNORE_START_COMMENT = '/*Ωignore_startΩ*/';
exports.IGNORE_END_COMMENT = '/*Ωignore_endΩ*/';
/**
 * Surrounds given string with a start/end comment which marks it
 * to be ignored by tooling.
 */
function surroundWithIgnoreComments(str) {
    return exports.IGNORE_START_COMMENT + str + exports.IGNORE_END_COMMENT;
}
exports.surroundWithIgnoreComments = surroundWithIgnoreComments;
/**
 * Checks if this a section that should be completely ignored
 * because it's purely generated.
 */
function isInGeneratedCode(text, start, end = start) {
    const lastStart = text.lastIndexOf(exports.IGNORE_START_COMMENT, start);
    const lastEnd = text.lastIndexOf(exports.IGNORE_END_COMMENT, start);
    const nextEnd = text.indexOf(exports.IGNORE_END_COMMENT, end);
    // if lastEnd === nextEnd, this means that the str was found at the index
    // up to which is searched for it
    return (lastStart > lastEnd || lastEnd === nextEnd) && lastStart < nextEnd;
}
exports.isInGeneratedCode = isInGeneratedCode;
/**
 * Checks if this is a text span that is inside svelte2tsx-generated code
 * (has no mapping to the original)
 */
function isTextSpanInGeneratedCode(text, span) {
    return isInGeneratedCode(text, span.start, span.start + span.length);
}
exports.isTextSpanInGeneratedCode = isTextSpanInGeneratedCode;
function isPartOfImportStatement(text, position) {
    const line = (0, documents_1.getLineAtPosition)(position, text);
    return /\s*from\s+["'][^"']*/.test(line.slice(0, position.character));
}
exports.isPartOfImportStatement = isPartOfImportStatement;
function isStoreVariableIn$storeDeclaration(text, varStart) {
    return (text.lastIndexOf('__sveltets_2_store_get(', varStart) ===
        varStart - '__sveltets_2_store_get('.length);
}
exports.isStoreVariableIn$storeDeclaration = isStoreVariableIn$storeDeclaration;
function get$storeOffsetOf$storeDeclaration(text, storePosition) {
    return text.lastIndexOf(' =', storePosition) - 1;
}
exports.get$storeOffsetOf$storeDeclaration = get$storeOffsetOf$storeDeclaration;
function is$storeVariableIn$storeDeclaration(text, varStart) {
    return /^\$\w+ = __sveltets_2_store_get/.test(text.substring(varStart));
}
exports.is$storeVariableIn$storeDeclaration = is$storeVariableIn$storeDeclaration;
function getStoreOffsetOf$storeDeclaration(text, $storeVarStart) {
    return text.indexOf(');', $storeVarStart) - 1;
}
exports.getStoreOffsetOf$storeDeclaration = getStoreOffsetOf$storeDeclaration;
class SnapshotMap {
    constructor(resolver) {
        this.resolver = resolver;
        this.map = new fileCollection_1.FileMap();
    }
    set(fileName, snapshot) {
        this.map.set(fileName, snapshot);
    }
    get(fileName) {
        return this.map.get(fileName);
    }
    async retrieve(fileName) {
        let snapshot = this.get(fileName);
        if (!snapshot) {
            const snap = await this.resolver.getSnapshot(fileName);
            this.set(fileName, snap);
            snapshot = snap;
        }
        return snapshot;
    }
}
exports.SnapshotMap = SnapshotMap;
function isAfterSvelte2TsxPropsReturn(text, end) {
    const textBeforeProp = text.substring(0, end);
    // This is how svelte2tsx writes out the props
    if (textBeforeProp.includes('\nreturn { props: {')) {
        return true;
    }
}
exports.isAfterSvelte2TsxPropsReturn = isAfterSvelte2TsxPropsReturn;
function findContainingNode(node, textSpan, predicate) {
    const children = node.getChildren();
    const end = textSpan.start + textSpan.length;
    for (const child of children) {
        if (!(child.getStart() <= textSpan.start && child.getEnd() >= end)) {
            continue;
        }
        if (predicate(child)) {
            return child;
        }
        const foundInChildren = findContainingNode(child, textSpan, predicate);
        if (foundInChildren) {
            return foundInChildren;
        }
    }
}
exports.findContainingNode = findContainingNode;
function findClosestContainingNode(node, textSpan, predicate) {
    let current = findContainingNode(node, textSpan, predicate);
    if (!current) {
        return;
    }
    let closest = current;
    while (current) {
        const foundInChildren = findContainingNode(current, textSpan, predicate);
        closest = current;
        current = foundInChildren;
    }
    return closest;
}
exports.findClosestContainingNode = findClosestContainingNode;
/**
 * Finds node exactly matching span {start, length}.
 */
function findNodeAtSpan(node, span, predicate) {
    const { start, length } = span;
    const end = start + length;
    for (const child of node.getChildren()) {
        const childStart = child.getStart();
        if (end <= childStart) {
            return;
        }
        const childEnd = child.getEnd();
        if (start >= childEnd) {
            continue;
        }
        if (start === childStart && end === childEnd) {
            if (!predicate) {
                return child;
            }
            if (predicate(child)) {
                return child;
            }
        }
        const foundInChildren = findNodeAtSpan(child, span, predicate);
        if (foundInChildren) {
            return foundInChildren;
        }
    }
}
exports.findNodeAtSpan = findNodeAtSpan;
function isSomeAncestor(node, predicate) {
    for (let parent = node.parent; parent; parent = parent.parent) {
        if (predicate(parent)) {
            return true;
        }
    }
    return false;
}
/**
 * Tests a node then its parent and successive ancestors for some respective predicates.
 */
function nodeAndParentsSatisfyRespectivePredicates(selfPredicate, ...predicates) {
    return (node) => {
        let next = node;
        return [selfPredicate, ...predicates].every((predicate) => {
            if (!next) {
                return false;
            }
            const current = next;
            next = next.parent;
            return predicate(current);
        });
    };
}
const isRenderFunction = nodeAndParentsSatisfyRespectivePredicates((node) => { var _a; return typescript_1.default.isFunctionDeclaration(node) && ((_a = node === null || node === void 0 ? void 0 : node.name) === null || _a === void 0 ? void 0 : _a.getText()) === 'render'; }, typescript_1.default.isSourceFile);
const isRenderFunctionBody = nodeAndParentsSatisfyRespectivePredicates(typescript_1.default.isBlock, isRenderFunction);
exports.isReactiveStatement = nodeAndParentsSatisfyRespectivePredicates((node) => typescript_1.default.isLabeledStatement(node) && node.label.getText() === '$', (0, utils_1.or)(
// function render() {
//     $: x2 = __sveltets_2_invalidate(() => x * x)
// }
isRenderFunctionBody, 
// function render() {
//     ;() => {$: x, update();
// }
nodeAndParentsSatisfyRespectivePredicates(typescript_1.default.isBlock, typescript_1.default.isArrowFunction, typescript_1.default.isExpressionStatement, isRenderFunctionBody)));
function findRenderFunction(sourceFile) {
    // only search top level
    for (const child of sourceFile.statements) {
        if (isRenderFunction(child)) {
            return child;
        }
    }
}
exports.findRenderFunction = findRenderFunction;
const isInReactiveStatement = (node) => isSomeAncestor(node, exports.isReactiveStatement);
exports.isInReactiveStatement = isInReactiveStatement;
function gatherDescendants(node, predicate, dest = []) {
    if (predicate(node)) {
        dest.push(node);
    }
    else {
        for (const child of node.getChildren()) {
            gatherDescendants(child, predicate, dest);
        }
    }
    return dest;
}
exports.gatherDescendants = gatherDescendants;
const gatherIdentifiers = (node) => gatherDescendants(node, typescript_1.default.isIdentifier);
exports.gatherIdentifiers = gatherIdentifiers;
function isKitTypePath(path) {
    return !!(path === null || path === void 0 ? void 0 : path.includes('.svelte-kit/types'));
}
exports.isKitTypePath = isKitTypePath;
function getFormatCodeBasis(formatCodeSetting) {
    var _a;
    const { baseIndentSize, indentSize, convertTabsToSpaces } = formatCodeSetting;
    const baseIndent = convertTabsToSpaces
        ? ' '.repeat(baseIndentSize !== null && baseIndentSize !== void 0 ? baseIndentSize : 4)
        : baseIndentSize
            ? '\t'
            : '';
    const indent = convertTabsToSpaces ? ' '.repeat(indentSize !== null && indentSize !== void 0 ? indentSize : 4) : baseIndentSize ? '\t' : '';
    const semi = formatCodeSetting.semicolons === 'remove' ? '' : ';';
    const newLine = (_a = formatCodeSetting.newLineCharacter) !== null && _a !== void 0 ? _a : typescript_1.default.sys.newLine;
    return {
        baseIndent,
        indent,
        semi,
        newLine
    };
}
exports.getFormatCodeBasis = getFormatCodeBasis;
/**
 * https://github.com/microsoft/TypeScript/blob/00dc0b6674eef3fbb3abb86f9d71705b11134446/src/services/utilities.ts#L2452
 */
function getQuotePreference(sourceFile, preferences) {
    var _a;
    const single = "'";
    const double = '"';
    if (preferences.quotePreference && preferences.quotePreference !== 'auto') {
        return preferences.quotePreference === 'single' ? single : double;
    }
    const firstModuleSpecifier = (_a = Array.from(sourceFile.statements).find((statement) => typescript_1.default.isImportDeclaration(statement) && typescript_1.default.isStringLiteral(statement.moduleSpecifier))) === null || _a === void 0 ? void 0 : _a.moduleSpecifier;
    return firstModuleSpecifier
        ? sourceFile.getText()[firstModuleSpecifier.pos] === '"'
            ? double
            : single
        : double;
}
exports.getQuotePreference = getQuotePreference;
function findChildOfKind(node, kind) {
    for (const child of node.getChildren()) {
        if (child.kind === kind) {
            return child;
        }
        const foundInChildren = findChildOfKind(child, kind);
        if (foundInChildren) {
            return foundInChildren;
        }
    }
}
exports.findChildOfKind = findChildOfKind;
function getNewScriptStartTag(lsConfig) {
    const lang = lsConfig.svelte.defaultScriptLanguage;
    const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`;
    return `<script${scriptLang}>${typescript_1.default.sys.newLine}`;
}
exports.getNewScriptStartTag = getNewScriptStartTag;
//# sourceMappingURL=utils.js.map