"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.gatherIdentifiers = exports.isInReactiveStatement = exports.isReactiveStatement = exports.SnapshotMap = exports.IGNORE_POSITION_COMMENT = exports.IGNORE_END_COMMENT = exports.IGNORE_START_COMMENT = void 0;
exports.getComponentAtPosition = getComponentAtPosition;
exports.isComponentAtPosition = isComponentAtPosition;
exports.surroundWithIgnoreComments = surroundWithIgnoreComments;
exports.isInGeneratedCode = isInGeneratedCode;
exports.startsWithIgnoredPosition = startsWithIgnoredPosition;
exports.isTextSpanInGeneratedCode = isTextSpanInGeneratedCode;
exports.isPartOfImportStatement = isPartOfImportStatement;
exports.isStoreVariableIn$storeDeclaration = isStoreVariableIn$storeDeclaration;
exports.get$storeOffsetOf$storeDeclaration = get$storeOffsetOf$storeDeclaration;
exports.is$storeVariableIn$storeDeclaration = is$storeVariableIn$storeDeclaration;
exports.getStoreOffsetOf$storeDeclaration = getStoreOffsetOf$storeDeclaration;
exports.isAfterSvelte2TsxPropsReturn = isAfterSvelte2TsxPropsReturn;
exports.findContainingNode = findContainingNode;
exports.findClosestContainingNode = findClosestContainingNode;
exports.findNodeAtSpan = findNodeAtSpan;
exports.findRenderFunction = findRenderFunction;
exports.gatherDescendants = gatherDescendants;
exports.isKitTypePath = isKitTypePath;
exports.getFormatCodeBasis = getFormatCodeBasis;
exports.getQuotePreference = getQuotePreference;
exports.findChildOfKind = findChildOfKind;
exports.getNewScriptStartTag = getNewScriptStartTag;
exports.checkRangeMappingWithGeneratedSemi = checkRangeMappingWithGeneratedSemi;
exports.getCustomElementsTags = getCustomElementsTags;
exports.getCustomElementsDocument = getCustomElementsDocument;
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");
const svelte2tsx_1 = require("svelte2tsx");
/**
 * If the given original position is within a Svelte starting tag,
 * return the snapshot of that component.
 */
function getComponentAtPosition(lang, doc, tsDoc, originalPosition) {
    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, doc.offsetAt(originalPosition));
    if (!node) {
        return null;
    }
    const symbolPosWithinNode = node.tag?.includes('.') ? node.tag.lastIndexOf('.') + 1 : 0;
    const generatedPosition = tsDoc.getGeneratedPosition(doc.positionAt(node.start + symbolPosWithinNode + 1));
    const def = lang.getDefinitionAtPosition(tsDoc.filePath, tsDoc.offsetAt(generatedPosition))?.[0];
    if (!def) {
        return null;
    }
    return ComponentInfoProvider_1.JsOrTsComponentInfoProvider.create(lang, def, tsDoc.isSvelte5Plus);
}
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, doc.offsetAt(originalPosition));
}
exports.IGNORE_START_COMMENT = '/*Ωignore_startΩ*/';
exports.IGNORE_END_COMMENT = '/*Ωignore_endΩ*/';
exports.IGNORE_POSITION_COMMENT = '/*Ωignore_positionΩ*/';
/**
 * 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;
}
/**
 * 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;
}
function startsWithIgnoredPosition(text, offset) {
    return text.slice(offset).startsWith(exports.IGNORE_POSITION_COMMENT);
}
/**
 * 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);
}
function isPartOfImportStatement(text, position) {
    const line = (0, documents_1.getLineAtPosition)(position, text);
    return /\s*from\s+["'][^"']*/.test(line.slice(0, position.character));
}
function isStoreVariableIn$storeDeclaration(text, varStart) {
    return (text.lastIndexOf('__sveltets_2_store_get(', varStart) ===
        varStart - '__sveltets_2_store_get('.length);
}
function get$storeOffsetOf$storeDeclaration(text, storePosition) {
    return text.lastIndexOf(' =', storePosition) - 1;
}
function is$storeVariableIn$storeDeclaration(text, varStart) {
    return /^\$\w+ = __sveltets_2_store_get/.test(text.substring(varStart));
}
function getStoreOffsetOf$storeDeclaration(text, $storeVarStart) {
    return text.indexOf(');', $storeVarStart) - 1;
}
class SnapshotMap {
    constructor(resolver, sourceLs) {
        this.resolver = resolver;
        this.sourceLs = sourceLs;
        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) {
            return snapshot;
        }
        const snap = this.sourceLs.snapshotManager.get(fileName) ??
            // should not happen in most cases,
            // the file should be in the project otherwise why would we know about it
            (await this.resolver.getOrCreateSnapshot(fileName));
        this.set(fileName, snap);
        return snap;
    }
}
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;
    }
}
function findContainingNode(node, textSpan, predicate) {
    const end = textSpan.start + textSpan.length;
    // TypeScript will re-parse part of the file in getChildren() to include syntax tokens.
    // But for the use cases of this function, we only need the actual nodes like Identifier.
    // the forEachChild name is a bit misleading too because it function more like find than forEach
    return node.forEachChild((child) => {
        if (child.getStart() <= textSpan.start && child.getEnd() >= end) {
            if (predicate(child)) {
                return child;
            }
            const foundInChildren = findContainingNode(child, textSpan, predicate);
            if (foundInChildren) {
                return foundInChildren;
            }
        }
    });
}
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;
}
/**
 * Finds node exactly matching span {start, length}.
 */
function findNodeAtSpan(node, span, predicate) {
    const { start, length } = span;
    const end = start + length;
    return node.forEachChild((child) => {
        const childStart = child.getStart();
        if (end <= childStart) {
            return;
        }
        const childEnd = child.getEnd();
        if (start >= childEnd) {
            return;
        }
        if (start === childStart && end === childEnd) {
            if (!predicate) {
                return child;
            }
            if (predicate(child)) {
                return child;
            }
        }
        const foundInChildren = findNodeAtSpan(child, span, predicate);
        if (foundInChildren) {
            return foundInChildren;
        }
    });
}
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) => typescript_1.default.isFunctionDeclaration(node) && node?.name?.getText() === svelte2tsx_1.internalHelpers.renderName, 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;
        }
    }
}
const isInReactiveStatement = (node) => isSomeAncestor(node, exports.isReactiveStatement);
exports.isInReactiveStatement = isInReactiveStatement;
function gatherDescendants(node, predicate, dest = []) {
    if (predicate(node)) {
        dest.push(node);
    }
    else {
        node.forEachChild((child) => {
            gatherDescendants(child, predicate, dest);
        });
    }
    return dest;
}
const gatherIdentifiers = (node) => gatherDescendants(node, typescript_1.default.isIdentifier);
exports.gatherIdentifiers = gatherIdentifiers;
function isKitTypePath(path) {
    if (!path)
        return false;
    return path.endsWith('/$types.js') || path.endsWith('/$types') || path.endsWith('/$types.d.ts');
}
function getFormatCodeBasis(formatCodeSetting) {
    const { baseIndentSize, indentSize, convertTabsToSpaces } = formatCodeSetting;
    const baseIndent = convertTabsToSpaces
        ? ' '.repeat(baseIndentSize ?? 4)
        : baseIndentSize
            ? '\t'
            : '';
    const indent = convertTabsToSpaces ? ' '.repeat(indentSize ?? 4) : baseIndentSize ? '\t' : '';
    const semi = formatCodeSetting.semicolons === 'remove' ? '' : ';';
    const newLine = formatCodeSetting.newLineCharacter ?? typescript_1.default.sys.newLine;
    return {
        baseIndent,
        indent,
        semi,
        newLine
    };
}
/**
 * https://github.com/microsoft/TypeScript/blob/00dc0b6674eef3fbb3abb86f9d71705b11134446/src/services/utilities.ts#L2452
 */
function getQuotePreference(sourceFile, preferences) {
    const single = "'";
    const double = '"';
    if (preferences.quotePreference && preferences.quotePreference !== 'auto') {
        return preferences.quotePreference === 'single' ? single : double;
    }
    const firstModuleSpecifier = Array.from(sourceFile.statements).find((statement) => typescript_1.default.isImportDeclaration(statement) && typescript_1.default.isStringLiteral(statement.moduleSpecifier))?.moduleSpecifier;
    return firstModuleSpecifier
        ? sourceFile.getText()[firstModuleSpecifier.pos] === '"'
            ? double
            : single
        : double;
}
function findChildOfKind(node, kind) {
    // this one we do want to use getChildren() because we also want to find syntax tokens,
    for (const child of node.getChildren()) {
        if (child.kind === kind) {
            return child;
        }
        const foundInChildren = findChildOfKind(child, kind);
        if (foundInChildren) {
            return foundInChildren;
        }
    }
}
function getNewScriptStartTag(lsConfig, newLine) {
    const lang = lsConfig.svelte.defaultScriptLanguage;
    const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`;
    return `<script${scriptLang}>${newLine}`;
}
function checkRangeMappingWithGeneratedSemi(originalRange, generatedRange, tsDoc) {
    const originalLength = originalRange.end.character - originalRange.start.character;
    const generatedLength = generatedRange.end.character - generatedRange.start.character;
    // sourcemap off by one character issue + a generated semicolon
    if (originalLength === generatedLength - 2 &&
        tsDoc.getFullText()[tsDoc.offsetAt(generatedRange.end) - 1] === ';') {
        originalRange.end.character += 1;
    }
}
/** Returns the list of registered custom elements and their description (from JSDoc) */
function getCustomElementsTags(lang, lsContainer, tsDoc) {
    const info = getCustomElementDocumentationSymbols(lang, lsContainer, tsDoc);
    if (!info) {
        return [];
    }
    const symbols = info.type.getProperties().filter((s) => typescript_1.default.symbolName(s).includes('-'));
    return symbols.map((s) => s.name);
}
function getCustomElementsDocument(lang, lsContainer, tsDoc, tagName) {
    const info = getCustomElementDocumentationSymbols(lang, lsContainer, tsDoc);
    if (!info) {
        return null;
    }
    const tag = info.type.getProperty(tagName);
    return tag && tag.name.includes('-')
        ? typescript_1.default.displayPartsToString(tag.getDocumentationComment(info.typeChecker))
        : null;
}
function getCustomElementDocumentationSymbols(lang, lsContainer, tsDoc) {
    const program = lang.getProgram();
    const sourceFile = program?.getSourceFile(tsDoc.filePath);
    const typeChecker = program?.getTypeChecker();
    if (!typeChecker || !sourceFile) {
        return null;
    }
    const typingsNamespace = lsContainer.getTsConfigSvelteOptions().namespace;
    const typingsNamespaceSymbol = findTypingsNamespaceSymbol(typingsNamespace, typeChecker, sourceFile);
    if (!typingsNamespaceSymbol) {
        return null;
    }
    const elements = typeChecker
        .getExportsOfModule(typingsNamespaceSymbol)
        .find((symbol) => symbol.name === 'IntrinsicElements');
    if (!elements || !(elements.flags & typescript_1.default.SymbolFlags.Interface)) {
        return null;
    }
    const type = typeChecker.getDeclaredTypeOfSymbol(elements);
    return { type, typeChecker };
}
function findTypingsNamespaceSymbol(namespaceExpression, typeChecker, sourceFile) {
    if (!namespaceExpression || typeof namespaceExpression !== 'string') {
        return;
    }
    const [first, ...rest] = namespaceExpression.split('.');
    let symbol = typeChecker
        .getSymbolsInScope(sourceFile, typescript_1.default.SymbolFlags.Namespace)
        .find((symbol) => symbol.name === first);
    for (const part of rest) {
        if (!symbol) {
            return;
        }
        symbol = typeChecker.getExportsOfModule(symbol).find((symbol) => symbol.name === part);
    }
    return symbol;
}
//# sourceMappingURL=utils.js.map