"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SvelteCheckTSGoDiagnosticsProvider = void 0;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = require("node:path");
const path_1 = __importDefault(require("path"));
const svelte2tsx_1 = require("svelte2tsx");
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const importPackage_1 = require("../../../importPackage");
const documents_1 = require("../../../lib/documents");
const configLoader_1 = require("../../../lib/documents/configLoader");
const fileCollection_1 = require("../../../lib/documents/fileCollection");
const logger_1 = require("../../../logger");
const utils_1 = require("../../../utils");
const DocumentSnapshot_1 = require("../../typescript/DocumentSnapshot");
const DiagnosticsProvider_1 = require("../../typescript/features/DiagnosticsProvider");
const utils_2 = require("../../typescript/features/utils");
const svelte_ast_utils_1 = require("../../typescript/svelte-ast-utils");
const utils_3 = require("../../typescript/utils");
const utils_4 = require("./utils");
const VIRTUAL_SUFFIX = '_virtual__';
const SVELTE_EXT_LENGTH = '.svelte'.length;
const D_SVELTE_TS_EXTENSION = '.d.svelte.ts';
const D_SVELTE_TS_LENGTH = D_SVELTE_TS_EXTENSION.length;
const SVELTE_DTS_EXTENSION = '.svelte.d.ts';
class SvelteCheckTSGoDiagnosticsProvider {
    constructor(apiModule, tsAstModule, tsconfigPath, ambientTypesSource, createDocument) {
        this.files = new fileCollection_1.FileMap();
        this.virtualFiles = new fileCollection_1.FileMap();
        this.pendingChanges = {
            created: new Set(),
            changed: new Set(),
            deleted: new Set()
        };
        this.tsApiModule = apiModule;
        this.tsAstModule = tsAstModule;
        this.tsconfigPath = tsconfigPath;
        this.virtualTsconfigPath = path_1.default.join(path_1.default.dirname(tsconfigPath), `tsconfig_virtual_config.json`);
        this.createDocument = createDocument;
        this.ambientTypesSource = ambientTypesSource;
        const sveltePackage = (0, importPackage_1.importSvelte)(tsconfigPath);
        this.snapshotOptions = {
            parse: sveltePackage.parse,
            transformOnTemplateError: false,
            typingsNamespace: 'svelteHTML',
            version: sveltePackage.VERSION,
            emitJsDoc: true
        };
        this.api = new apiModule.API({
            fs: this.createFsProxy()
        });
        this.projectConfig = this.parseConfig();
        if (this.projectConfig?.raw.svelteOptions?.namespace) {
            this.snapshotOptions.typingsNamespace = this.projectConfig.raw.svelteOptions.namespace;
        }
        this.pendingConfigLoading = configLoader_1.configLoader.loadConfigs(path_1.default.dirname(tsconfigPath));
        this.writeVirtualTsconfig();
    }
    async getDiagnostics(document, cancellationToken) {
        const filePath = document.getFilePath();
        if (!filePath) {
            return [];
        }
        const project = await this.getProject();
        if (!project) {
            return [];
        }
        const tsDoc = this.files.get((0, utils_1.normalizePath)(filePath));
        if (!tsDoc || !(tsDoc instanceof DocumentSnapshot_1.SvelteDocumentSnapshot)) {
            return [];
        }
        if (['coffee', 'coffeescript'].includes(document.getLanguageAttribute('script')) ||
            cancellationToken?.isCancellationRequested) {
            return [];
        }
        // Document preprocessing failed, show parser error instead
        const parserErrorDiag = getParserErrorDiagnostic(tsDoc);
        if (parserErrorDiag) {
            return [parserErrorDiag];
        }
        const virtualPath = toVirtualPath(tsDoc);
        const diagnostics = [];
        const program = project.program;
        diagnostics.push(...(program.getSyntacticDiagnostics(virtualPath) || []));
        diagnostics.push(...(program.getSuggestionDiagnostics(virtualPath) || []));
        diagnostics.push(...(program.getSemanticDiagnostics(virtualPath) || []));
        return mapAndFilterDiagnostics(this.tsAstModule, this.tsApiModule, project, diagnostics, document, tsDoc);
    }
    async getProject() {
        if (this.pendingConfigLoading) {
            await this.pendingConfigLoading;
            this.pendingConfigLoading = undefined;
        }
        const snapshot = this.api.updateSnapshot({
            openProject: this.virtualTsconfigPath,
            fileChanges: {
                created: Array.from(this.pendingChanges.created),
                changed: Array.from(this.pendingChanges.changed),
                deleted: Array.from(this.pendingChanges.deleted)
            }
        });
        this.pendingChanges.created.clear();
        this.pendingChanges.changed.clear();
        this.pendingChanges.deleted.clear();
        const project = snapshot.getProject(this.virtualTsconfigPath);
        return project;
    }
    getAllSvelteFiles() {
        return Array.from(this.files.keys()).filter(utils_3.isSvelteFilePath);
    }
    mapAndFilterDiagnostics(project, diagnostics) {
        const byFile = new Map();
        for (const diag of diagnostics) {
            const key = toRealPath(diag.fileName ?? this.tsconfigPath);
            let bucket = byFile.get(key);
            if (!bucket) {
                bucket = [];
                byFile.set(key, bucket);
            }
            bucket.push(diag);
        }
        const result = [];
        const virtualConfigPath = (0, utils_1.normalizePath)(this.virtualTsconfigPath);
        for (const [fileName, diags] of byFile) {
            const tsDoc = this.files.get((0, utils_1.normalizePath)(fileName));
            if (!tsDoc) {
                if (fileName === virtualConfigPath) {
                    result.push(this.reportVirtualConfigError(diags));
                }
                else {
                    result.push(this.covertDiagnosticsForUnopenedFile(fileName, diags));
                }
                continue;
            }
            if (tsDoc instanceof DocumentSnapshot_1.SvelteDocumentSnapshot) {
                const mappedDiags = mapAndFilterDiagnostics(this.tsAstModule, this.tsApiModule, project, diags, tsDoc.parent, tsDoc);
                result.push({
                    filePath: fileName,
                    text: tsDoc.parent.getText(),
                    diagnostics: mappedDiags
                });
            }
            else if (tsDoc instanceof DocumentSnapshot_1.JSOrTSDocumentSnapshot) {
                const mappedDiags = diags.map((diag) => {
                    return {
                        range: {
                            start: tsDoc.getOriginalPosition(tsDoc.positionAt(diag.pos)),
                            end: tsDoc.getOriginalPosition(tsDoc.positionAt(diag.end))
                        },
                        message: flattenDiagnosticMessage(diag),
                        severity: (0, utils_3.mapSeverity)(diag.category),
                        source: diag.fileName?.endsWith('js') ? 'js' : 'ts',
                        tags: getDiagnosticTag(diag),
                        code: diag.code
                    };
                });
                result.push({
                    filePath: fileName,
                    text: tsDoc.originalText,
                    diagnostics: mappedDiags
                });
            }
            else {
                result.push(this.covertDiagnosticsForUnopenedFile(fileName, diags));
            }
        }
        return result;
    }
    reportVirtualConfigError(diags) {
        return {
            filePath: this.tsconfigPath,
            diagnostics: diags.map((diag) => ({
                range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
                message: flattenDiagnosticMessage(diag),
                severity: (0, utils_3.mapSeverity)(diag.category),
                source: 'ts',
                tags: getDiagnosticTag(diag),
                code: diag.code,
                data: {
                    positionUnknown: true
                }
            })),
            text: ''
        };
    }
    async getDiagnosticsForPullMode(document) {
        return {
            items: await this.getDiagnostics(document),
            kind: 'full'
        };
    }
    watchUpdate(doc, kind) {
        const filePath = (0, utils_1.urlToPath)(doc.uri) || '';
        const normalizedPath = (0, utils_1.normalizePath)(filePath);
        if (kind === 'created') {
            if ((0, utils_3.isSvelteFilePath)(normalizedPath)) {
                // trigger project files invalidation should be enough
                this.pendingChanges.created.add(changeExtension(normalizedPath, D_SVELTE_TS_EXTENSION));
            }
            else {
                this.pendingChanges.created.add(normalizedPath);
            }
        }
        else if (kind === 'deleted') {
            this.pendingChanges.deleted.add(normalizedPath);
        }
        else if (kind === 'changed') {
            let changedPath = normalizedPath;
            if (this.files.has(normalizedPath)) {
                const newSnapshot = DocumentSnapshot_1.DocumentSnapshot.fromFilePath(filePath, this.createDocument, this.snapshotOptions, typescript_1.default.sys);
                if (newSnapshot instanceof DocumentSnapshot_1.SvelteDocumentSnapshot) {
                    changedPath = toVirtualPath(newSnapshot);
                }
                this.virtualFiles.set(changedPath, newSnapshot.getFullText());
                this.files.set(normalizedPath, newSnapshot);
            }
            this.pendingChanges.changed.add(changedPath);
        }
    }
    getProjectConfig() {
        return this.projectConfig;
    }
    createFsProxy() {
        const service = this;
        const kitFiles = {
            serverHooksPath: 'src/hooks.client',
            clientHooksPath: 'src/hooks.client',
            universalHooksPath: 'src/hooks',
            paramsPath: 'src/params'
        };
        return {
            getAccessibleEntries(directory) {
                const realFiles = [];
                const directories = [];
                const resultFiles = [];
                try {
                    const entries = node_fs_1.default.readdirSync(directory, { withFileTypes: true });
                    for (const entry of entries) {
                        if (entry.isFile()) {
                            realFiles.push(entry.name);
                        }
                        else if (entry.isDirectory()) {
                            directories.push(entry.name);
                        }
                        else if (entry.isSymbolicLink()) {
                            const fullPath = path_1.default.join(directory, entry.name);
                            const stats = node_fs_1.default.statSync(node_fs_1.default.realpathSync(fullPath));
                            if (stats.isFile()) {
                                realFiles.push(entry.name);
                            }
                            else if (stats.isDirectory()) {
                                directories.push(entry.name);
                            }
                        }
                    }
                    for (const file of realFiles) {
                        addFileEntry(path_1.default.join(directory, file), realFiles, resultFiles);
                    }
                    return {
                        files: resultFiles,
                        directories
                    };
                }
                catch (error) {
                    logger_1.Logger.error(`Error reading directory ${directory}:`, error);
                    return undefined;
                }
            },
            readFile(path) {
                const normalizedPath = (0, utils_1.normalizePath)(path);
                const virtualEntry = service.virtualFiles.get(normalizedPath);
                if (virtualEntry !== undefined) {
                    return virtualEntry;
                }
                if (normalizedPath.endsWith('node_modules/svelte/types/ambient.d.ts')) {
                    return '';
                }
                if (normalizedPath.endsWith('node_modules/svelte/types/index.d.ts') ||
                    svelte2tsx_1.internalHelpers.isKitFile(normalizedPath, kitFiles)) {
                    const snapshot = DocumentSnapshot_1.DocumentSnapshot.fromFilePath(normalizedPath, service.createDocument, service.snapshotOptions, typescript_1.default.sys);
                    service.files.set(normalizedPath, snapshot);
                    return snapshot.getFullText();
                }
                // undefined to signal api to read from disk by itself
                return undefined;
            },
            fileExists(filePath) {
                const virtualFiles = service.virtualFiles;
                if (filePath.includes(VIRTUAL_SUFFIX) &&
                    virtualFiles.has((0, utils_1.normalizePath)(filePath))) {
                    return true;
                }
                if (filePath.endsWith(D_SVELTE_TS_EXTENSION)) {
                    if (virtualFiles.has((0, utils_1.normalizePath)(filePath)) || node_fs_1.default.existsSync(filePath)) {
                        return true;
                    }
                    const withoutExtension = filePath.slice(0, -D_SVELTE_TS_LENGTH);
                    const svelteDtsPath = withoutExtension + SVELTE_DTS_EXTENSION;
                    if (node_fs_1.default.existsSync(svelteDtsPath)) {
                        service.addDtsRedirect(filePath, path_1.default.basename(svelteDtsPath));
                        return true;
                    }
                    const targetSvelteFile = withoutExtension + '.svelte';
                    if (node_fs_1.default.existsSync(targetSvelteFile)) {
                        const virtualBaseName = service.addVirtualSvelteFile(targetSvelteFile);
                        service.addDtsRedirect(filePath, virtualBaseName);
                        return true;
                    }
                }
                // undefined to signal api to check existence by itself
                return undefined;
            }
        };
        function addFileEntry(fullPath, realFiles, resultFiles) {
            const name = path_1.default.basename(fullPath);
            if (!fullPath.endsWith('.svelte')) {
                resultFiles.push(name);
                return;
            }
            const virtualBaseName = service.addVirtualSvelteFile(fullPath);
            resultFiles.push(virtualBaseName);
            const dSvelteTsPath = changeExtension(fullPath, D_SVELTE_TS_EXTENSION);
            const dSvelteTsName = path_1.default.basename(dSvelteTsPath);
            if (realFiles.includes(dSvelteTsName)) {
                return;
            }
            const svelteDtsName = changeExtension(name, SVELTE_DTS_EXTENSION);
            const dSvelteTsTarget = realFiles.includes(svelteDtsName)
                ? svelteDtsName
                : virtualBaseName;
            service.addDtsRedirect(dSvelteTsPath, dSvelteTsTarget);
            resultFiles.push(dSvelteTsName);
        }
    }
    parseConfig() {
        const commandLine = typescript_1.default.parseJsonSourceFileConfigFileContent(typescript_1.default.parseJsonText(this.tsconfigPath, typescript_1.default.sys.readFile(this.tsconfigPath) || ''), {
            ...typescript_1.default.sys,
            readDirectory() {
                // skip project file searching
                return [];
            }
        }, path_1.default.dirname(this.tsconfigPath));
        return commandLine;
    }
    writeVirtualTsconfig() {
        const commandLine = this.projectConfig;
        const tsconfigPath = this.tsconfigPath;
        const sveltePackageInfo = (0, importPackage_1.getPackageInfo)('svelte', this.virtualTsconfigPath);
        let svelteTsPath;
        try {
            // For when svelte2tsx/svelte-check is part of node_modules, for example VS Code extension
            svelteTsPath = (0, node_path_1.dirname)(require.resolve(this.ambientTypesSource));
        }
        catch (e) {
            // Fall back to dirname
            svelteTsPath = __dirname;
        }
        const svelteTsxFiles = svelte2tsx_1.internalHelpers.get_global_types(typescript_1.default.sys, sveltePackageInfo.version.major === 3, sveltePackageInfo.path, svelteTsPath, undefined);
        const filesConfig = commandLine.raw.files;
        const rebasedFiles = [];
        if (filesConfig) {
            for (const file of filesConfig) {
                if (file.endsWith('.svelte')) {
                    const virtualBaseName = this.addVirtualSvelteFile(path_1.default.join(path_1.default.dirname(tsconfigPath), file));
                    rebasedFiles.push(virtualBaseName);
                    // let fileExists handle the .d.svelte.ts redirection
                }
                else {
                    rebasedFiles.push(file);
                }
            }
        }
        const virtualTsConfigContent = JSON.stringify({
            extends: './' + path_1.default.basename(tsconfigPath),
            compilerOptions: { allowArbitraryExtensions: true, noEmit: true },
            files: rebasedFiles.concat(svelteTsxFiles),
            // otherwise only "files" will be included and not the default "everything"
            include: commandLine.raw.include ? undefined : ['**/*']
        });
        this.virtualFiles.set((0, utils_1.normalizePath)(this.virtualTsconfigPath), virtualTsConfigContent);
    }
    addDtsRedirect(dSvelteTsPath, targetFileName) {
        const specifierFileName = targetFileName.endsWith(SVELTE_DTS_EXTENSION)
            ? targetFileName.replace(SVELTE_DTS_EXTENSION, '.svelte.js')
            : changeExtension(targetFileName, '.js');
        const dtsImportPath = './' + specifierFileName;
        const dtsContent = `export { default } from "${dtsImportPath}";\nexport * from "${dtsImportPath}";\n`;
        this.virtualFiles.set((0, utils_1.normalizePath)(dSvelteTsPath), dtsContent);
    }
    addVirtualSvelteFile(filePath) {
        const svelteFile = DocumentSnapshot_1.DocumentSnapshot.fromFilePath(filePath, this.createDocument, this.snapshotOptions, typescript_1.default.sys);
        const normalizedPath = (0, utils_1.normalizePath)(filePath);
        this.files.set(normalizedPath, svelteFile);
        const virtualPath = toVirtualPath(svelteFile);
        this.virtualFiles.set(virtualPath, svelteFile.getFullText());
        const virtualBasename = path_1.default.basename(virtualPath);
        return virtualBasename;
    }
    covertDiagnosticsForUnopenedFile(filePath, diagnostics) {
        const text = typescript_1.default.sys.readFile(filePath) ?? '';
        const result = [];
        const lineOffsets = (0, documents_1.getLineOffsets)(text);
        for (const diag of diagnostics) {
            result.push({
                range: {
                    start: (0, documents_1.positionAt)(diag.pos, text, lineOffsets),
                    end: (0, documents_1.positionAt)(diag.end, text, lineOffsets)
                },
                severity: (0, utils_3.mapSeverity)(diag.category),
                message: flattenDiagnosticMessage(diag),
                code: diag.code,
                source: diag.fileName?.endsWith('js') ? 'js' : 'ts',
                tags: getDiagnosticTag(diag)
            });
        }
        return { filePath, diagnostics: result, text };
    }
    deduplicateDiagnostics(diagnostics) {
        return diagnostics.filter(dedupDiagnostics());
    }
    dispose() {
        this.api.close();
    }
}
exports.SvelteCheckTSGoDiagnosticsProvider = SvelteCheckTSGoDiagnosticsProvider;
function changeExtension(path, newExtension) {
    return path.slice(0, path.lastIndexOf('.')) + newExtension;
}
function getParserErrorDiagnostic(tsDoc) {
    if (!tsDoc.parserError) {
        return;
    }
    return {
        range: tsDoc.parserError.range,
        severity: vscode_languageserver_1.DiagnosticSeverity.Error,
        source: tsDoc.scriptKind === typescript_1.default.ScriptKind.TSX || tsDoc.scriptKind === typescript_1.default.ScriptKind.TS
            ? 'ts'
            : 'js',
        message: tsDoc.parserError.message,
        code: tsDoc.parserError.code
    };
}
function toVirtualPath(snapshot) {
    const ext = snapshot.scriptKind === typescript_1.default.ScriptKind.TS ? '.ts' : '.js';
    return (0, utils_1.normalizePath)(snapshot.filePath.slice(0, -SVELTE_EXT_LENGTH) + VIRTUAL_SUFFIX + ext);
}
function toRealPath(path) {
    if (!path.includes(VIRTUAL_SUFFIX)) {
        return path;
    }
    return path.slice(0, -3).replace(VIRTUAL_SUFFIX, '') + '.svelte';
}
function mapAndFilterDiagnostics(tsAstModule, tsApiModule, tsApiProject, diagnostics, document, tsDoc) {
    // For svelte-check tsgo, we called api to get all diagnostics instead of calling getDiagnostics for each file separately. So we also need to check parser error or coffeescript files here.
    if (['coffee', 'coffeescript'].includes(document.getLanguageAttribute('script'))) {
        return [];
    }
    // Document preprocessing failed, show parser error instead
    const parserErrorDiag = getParserErrorDiagnostic(tsDoc);
    if (parserErrorDiag) {
        return [parserErrorDiag];
    }
    const notGenerated = isNotGenerated(tsDoc.getFullText());
    const additionalStoreDiagnostics = [];
    for (const diagnostic of diagnostics) {
        if ((diagnostic.code === DiagnosticsProvider_1.DiagnosticCode.NO_OVERLOAD_MATCHES_CALL ||
            diagnostic.code === DiagnosticsProvider_1.DiagnosticCode.ARG_TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y) &&
            !notGenerated(diagnostic) &&
            diagnostic.fileName) {
            if ((0, utils_2.isStoreVariableIn$storeDeclaration)(tsDoc.getFullText(), diagnostic.pos)) {
                const storeName = tsDoc.getFullText().substring(diagnostic.pos, diagnostic.end);
                const symbol = tsApiProject.checker.getSymbolAtPosition(diagnostic.fileName, (0, utils_2.get$storeOffsetOf$storeDeclaration)(tsDoc.getFullText(), diagnostic.pos));
                if (!symbol) {
                    continue;
                }
                const sourceFile = tsApiProject.program.getSourceFile(diagnostic.fileName);
                if (!sourceFile) {
                    continue;
                }
                const storeUsages = tsApiProject.checker.getReferencesToSymbolInFile(diagnostic.fileName, symbol);
                for (const storeUsage of storeUsages) {
                    const node = storeUsage.resolve(tsApiProject);
                    if (node) {
                        additionalStoreDiagnostics.push({
                            ...diagnostic,
                            text: `Cannot use '${storeName}' as a store. '${storeName}' needs to be an object with a subscribe method on it.\n\n` +
                                diagnostic.text,
                            pos: (0, utils_4.getStartOfNode)(tsAstModule, node, sourceFile),
                            end: node.end
                        });
                    }
                }
            }
        }
    }
    diagnostics.push(...additionalStoreDiagnostics);
    diagnostics = diagnostics
        .filter(notGenerated)
        .filter((diagnostic) => !isUnusedReactiveStatementLabel(tsAstModule, tsApiProject, diagnostic))
        .filter((diagnostic) => !expectedTransitionThirdArgument(tsAstModule, diagnostic, tsDoc, tsApiProject));
    diagnostics = resolveNoopsInReactiveStatements(tsAstModule, tsApiProject, diagnostics);
    const source = tsDoc.scriptKind === typescript_1.default.ScriptKind.TSX || tsDoc.scriptKind === typescript_1.default.ScriptKind.TS
        ? 'ts'
        : 'js';
    const mapRange = rangeMapper(tsAstModule, tsApiModule, tsApiProject, tsDoc, document);
    const noFalsePositive = isNoFalsePositive(document, tsDoc);
    const converted = [];
    for (const tsDiag of diagnostics) {
        let diagnostic = {
            range: { start: tsDoc.positionAt(tsDiag.pos), end: tsDoc.positionAt(tsDiag.end) },
            severity: (0, utils_3.mapSeverity)(tsDiag.category),
            source,
            message: flattenDiagnosticMessage(tsDiag),
            code: tsDiag.code,
            tags: getDiagnosticTag(tsDiag)
        };
        diagnostic = mapRange(diagnostic);
        moveBindingErrorMessage(tsDiag, tsDoc, diagnostic, document);
        if (!hasNoNegativeLines(diagnostic) || !noFalsePositive(diagnostic)) {
            continue;
        }
        diagnostic = adjustIfNecessary(diagnostic, tsDoc.isSvelte5Plus);
        diagnostic = swapDiagRangeStartEndIfNecessary(diagnostic);
        converted.push(diagnostic);
    }
    return converted;
}
function flattenDiagnosticMessage(diag, level = 0) {
    if (!diag.messageChain) {
        return diag.text;
    }
    let messages = [diag.text];
    for (let i = 0; i < diag.messageChain.length; i++) {
        const chainedDiag = diag.messageChain[i];
        const indent = '  '.repeat(level + 1);
        messages.push(indent + flattenDiagnosticMessage(chainedDiag, level + 1));
    }
    return messages.join('\n');
}
function moveBindingErrorMessage(tsDiag, tsDoc, diagnostic, document) {
    if (tsDiag.code === DiagnosticsProvider_1.DiagnosticCode.TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y &&
        tsDiag.pos &&
        tsDoc.getText(tsDiag.pos, tsDiag.end).endsWith('.$$bindings')) {
        let node = tsDoc.svelteNodeAt(diagnostic.range.start);
        while (node && node.type !== 'InlineComponent') {
            node = node.parent;
        }
        if (node) {
            let name = tsDoc.getText(tsDiag.end, tsDiag.end + 100);
            const quoteIdx = name.indexOf("'");
            name = name.substring(quoteIdx + 1, name.indexOf("'", quoteIdx + 1));
            const binding = node.attributes.find((attr) => attr.type === 'Binding' && attr.name === name);
            if (binding) {
                // try to make the error more readable for english users
                if (diagnostic.message.startsWith("Type '") &&
                    diagnostic.message.includes("is not assignable to type '")) {
                    const idx = diagnostic.message.indexOf(`Type '"`) + `Type '"`.length;
                    const propName = diagnostic.message.substring(idx, diagnostic.message.indexOf('"', idx));
                    diagnostic.message =
                        "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\n" +
                            `To mark a property as bindable: 'let { ${propName} = $bindable() } = $props()'`;
                }
                else {
                    diagnostic.message =
                        "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\n" +
                            `To mark a property as bindable: 'let { prop = $bindable() } = $props()'\n\n` +
                            diagnostic.message;
                }
                diagnostic.range = {
                    start: document.positionAt(binding.start),
                    end: document.positionAt(binding.end)
                };
            }
        }
    }
}
function rangeMapper(tsAstModule, tsApiModule, project, snapshot, document) {
    const get$$PropsDefWithCache = (0, utils_1.memoize)(() => get$$PropsDef(tsAstModule, project, snapshot));
    const get$$PropsAliasInfoWithCache = (0, utils_1.memoize)(() => get$$PropsAliasForInfo(tsAstModule, tsApiModule, project, get$$PropsDefWithCache, document));
    return (diagnostic) => {
        let range = (0, documents_1.mapRangeToOriginal)(snapshot, diagnostic.range);
        if (range.start.line < 0) {
            range =
                movePropsErrorRangeBackIfNecessary(tsAstModule, diagnostic, snapshot, get$$PropsDefWithCache, get$$PropsAliasInfoWithCache) ?? range;
        }
        if (([DiagnosticsProvider_1.DiagnosticCode.MISSING_PROP, DiagnosticsProvider_1.DiagnosticCode.MISSING_PROPS].includes(diagnostic.code) ||
            (DiagnosticsProvider_1.DiagnosticCode.TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y &&
                diagnostic.message.includes("'Properties<"))) &&
            !(0, utils_3.hasNonZeroRange)({ range })) {
            const node = (0, documents_1.getNodeIfIsInStartTag)(document.html, document.offsetAt(range.start));
            if (node) {
                // This is a "some prop missing" error on a component -> remap
                range.start = document.positionAt(node.start + 1);
                range.end = document.positionAt(node.start + 1 + (node.tag?.length || 1));
            }
        }
        return { ...diagnostic, range };
    };
}
function findDiagnosticNode(tsAstModule, diagnostic, sourceFile) {
    const touchingNode = tsAstModule.getTouchingToken(sourceFile, diagnostic.pos);
    if (touchingNode.end === diagnostic.end) {
        return touchingNode;
    }
    let current = touchingNode.parent;
    while (current.pos === touchingNode.pos) {
        if (current.end === diagnostic.end) {
            return current;
        }
        current = current.parent;
    }
}
function copyDiagnosticAndChangeNode(tsAstModule, diagnostic, sourceFile) {
    return (node) => {
        const start = (0, utils_4.getStartOfNode)(tsAstModule, node, sourceFile);
        return {
            ...diagnostic,
            pos: start,
            end: node.end
        };
    };
}
/**
 * In some rare cases mapping of diagnostics does not work and produces negative lines.
 * We filter out these diagnostics with negative lines because else the LSP
 * apparently has a hickup and does not show any diagnostics at all.
 */
function hasNoNegativeLines(diagnostic) {
    return diagnostic.range.start.line >= 0 && diagnostic.range.end.line >= 0;
}
const generatedVarRegex = /'\$\$_\w+(\.\$on)?'/;
function isNoFalsePositive(document, tsDoc) {
    const text = document.getText();
    const usesPug = document.getLanguageAttribute('template') === 'pug';
    return (diagnostic) => {
        if ([DiagnosticsProvider_1.DiagnosticCode.MULTIPLE_PROPS_SAME_NAME, DiagnosticsProvider_1.DiagnosticCode.DUPLICATE_IDENTIFIER].includes(diagnostic.code)) {
            const node = tsDoc.svelteNodeAt(diagnostic.range.start);
            if ((0, svelte_ast_utils_1.isAttributeName)(node, 'Element') || (0, svelte_ast_utils_1.isEventHandler)(node, 'Element')) {
                return false;
            }
        }
        if (diagnostic.code === DiagnosticsProvider_1.DiagnosticCode.DEPRECATED_SIGNATURE &&
            generatedVarRegex.test(diagnostic.message)) {
            // Svelte 5: $on and constructor is deprecated, but we don't want to show this warning for generated code
            return false;
        }
        return (isNoUsedBeforeAssigned(diagnostic, text, tsDoc) &&
            (!usesPug || isNoPugFalsePositive(diagnostic, document)));
    };
}
/**
 * All diagnostics inside the template tag and the unused import/variable diagnostics
 * are marked as false positive.
 */
function isNoPugFalsePositive(diagnostic, document) {
    return (!(0, documents_1.isRangeInTag)(diagnostic.range, document.templateInfo) &&
        diagnostic.code !== DiagnosticsProvider_1.DiagnosticCode.NEVER_READ &&
        diagnostic.code !== DiagnosticsProvider_1.DiagnosticCode.ALL_IMPORTS_UNUSED);
}
/**
 * Variable used before being assigned, can happen when  you do `export let x`
 * without assigning a value in strict mode. Should not throw an error here
 * but on the component-user-side ("you did not set a required prop").
 */
function isNoUsedBeforeAssigned(diagnostic, text, tsDoc) {
    if (diagnostic.code !== DiagnosticsProvider_1.DiagnosticCode.USED_BEFORE_ASSIGNED) {
        return true;
    }
    return !tsDoc.hasProp((0, documents_1.getTextInRange)(diagnostic.range, text));
}
/**
 * Some diagnostics have JSX-specific or confusing nomenclature. Enhance/adjust them for more clarity.
 */
function adjustIfNecessary(diagnostic, isSvelte5Plus) {
    if (diagnostic.code === DiagnosticsProvider_1.DiagnosticCode.ARG_TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y &&
        diagnostic.message.includes('ConstructorOfATypedSvelteComponent')) {
        return {
            ...diagnostic,
            message: diagnostic.message +
                '\n\nPossible causes:\n' +
                '- You use the instance type of a component where you should use the constructor type\n' +
                '- Type definitions are missing for this Svelte Component. ' +
                (isSvelte5Plus
                    ? ''
                    : 'If you are using Svelte 3.31+, use SvelteComponentTyped to add a definition:\n' +
                        '  import type { SvelteComponentTyped } from "svelte";\n' +
                        '  class ComponentName extends SvelteComponentTyped<{propertyName: string;}> {}')
        };
    }
    if (diagnostic.code === DiagnosticsProvider_1.DiagnosticCode.MODIFIERS_CANNOT_APPEAR_HERE) {
        return {
            ...diagnostic,
            message: diagnostic.message +
                '\nIf this is a declare statement, move it into <script context="module">..</script>'
        };
    }
    return diagnostic;
}
/**
 * Due to source mapping, some ranges may be swapped: Start is end. Swap back in this case.
 */
function swapDiagRangeStartEndIfNecessary(diag) {
    diag.range = (0, utils_1.swapRangeStartEndIfNecessary)(diag.range);
    return diag;
}
/**
 * Checks if diagnostic is not within a section that should be completely ignored
 * because it's purely generated.
 */
function isNotGenerated(text) {
    return (diagnostic) => {
        return !(0, utils_2.isInGeneratedCode)(text, diagnostic.pos, diagnostic.end);
    };
}
function isUnusedReactiveStatementLabel(tsApiModule, project, diagnostic) {
    if (diagnostic.code !== DiagnosticsProvider_1.DiagnosticCode.UNUSED_LABEL) {
        return false;
    }
    const sourceFile = diagnostic.fileName && project.program.getSourceFile(diagnostic.fileName);
    if (!sourceFile) {
        return false;
    }
    const diagNode = findDiagnosticNode(tsApiModule, diagnostic, sourceFile);
    if (!diagNode) {
        return false;
    }
    // TS warning targets the identifier
    if (!tsApiModule.isIdentifier(diagNode)) {
        return false;
    }
    if (!diagNode.parent) {
        return false;
    }
    return (0, utils_4.isReactiveStatement)(tsApiModule, diagNode.parent);
}
/**
 * Checks if diagnostics should be ignored because they report an unused expression* in
 * a reactive statement, and those actually have side effects in Svelte (hinting deps).
 *
 *     $: x, update()
 *
 * Only `let` (i.e. reactive) variables are ignored. For the others, new diagnostics are
 * emitted, centered on the (non reactive) identifiers in the initial warning.
 */
function resolveNoopsInReactiveStatements(tsAstModule, project, diagnostics) {
    const notLet = (node) => {
        const declaration = project.checker.getSymbolAtLocation(node)?.valueDeclaration;
        if (!declaration || declaration.kind !== tsAstModule.SyntaxKind.VariableDeclaration) {
            return true;
        }
        const declarationNode = declaration.resolve(project);
        if (!declarationNode || !tsAstModule.isVariableDeclarationList(declarationNode.parent)) {
            return true;
        }
        return (declarationNode.parent.flags & tsAstModule.NodeFlags.Let) === 0;
    };
    const expandRemainingNoopWarnings = (diagnostic) => {
        const { code, fileName } = diagnostic;
        // guard: not target error
        const isNoopDiag = code === DiagnosticsProvider_1.DiagnosticCode.NOOP_IN_COMMAS;
        if (!isNoopDiag) {
            return;
        }
        const sourceFile = fileName && project.program.getSourceFile(fileName);
        if (!sourceFile) {
            return;
        }
        const diagNode = findDiagnosticNode(tsAstModule, diagnostic, sourceFile);
        if (!diagNode) {
            return;
        }
        if (!(0, utils_4.isInReactiveStatement)(tsAstModule, diagNode)) {
            return;
        }
        const copyWorker = copyDiagnosticAndChangeNode(tsAstModule, diagnostic, sourceFile);
        return (
        // for all identifiers in diagnostic node
        (0, utils_4.gatherIdentifiers)(tsAstModule, diagNode)
            // ignore `let` (i.e. reactive) variables
            .filter(notLet)
            // and create targeted diagnostics just for the remaining ids
            .map(copyWorker));
    };
    const expandedDiagnostics = (0, utils_1.passMap)(diagnostics, expandRemainingNoopWarnings).flat();
    return expandedDiagnostics.length === diagnostics.length
        ? expandedDiagnostics
        : // This can generate duplicate diagnostics
            expandedDiagnostics.filter(dedupDiagnostics());
}
function dedupDiagnostics() {
    const hashDiagnostic = (diag) => [diag.pos, diag.end, diag.category, diag.fileName, diag.code]
        .map((x) => JSON.stringify(x))
        .join(':');
    const known = new Set();
    return (diag) => {
        const key = hashDiagnostic(diag);
        if (known.has(key)) {
            return false;
        }
        else {
            known.add(key);
            return true;
        }
    };
}
function get$$PropsAliasForInfo(tsAstModule, tsApiModule, project, get$$PropsDefWithCache, document) {
    if (!/type\s+\$\$Props[\s\n]+=/.test(document.getText())) {
        return;
    }
    const propsDef = get$$PropsDefWithCache();
    if (!propsDef || !tsAstModule.isTypeAliasDeclaration(propsDef)) {
        return;
    }
    const type = project.checker.getTypeAtLocation(propsDef.name);
    if (!type) {
        return;
    }
    // TS says symbol is always defined but it's not
    // TODO no API for getting the aliased symbol?
    // const rootSymbolName = (type.aliasSymbol ?? type.symbol)?.name;
    const rootSymbol = type.getSymbol();
    if (!rootSymbol) {
        return;
    }
    if (rootSymbol.flags & tsApiModule.SymbolFlags.TypeLiteral) {
        const node = rootSymbol.declarations?.[0]?.resolve(project);
        if (!node || !tsAstModule.isTypeAliasDeclaration(node.parent) || !node.parent.name) {
            return;
        }
        return [node.parent.name.text, propsDef];
    }
    const rootSymbolName = type.getSymbol()?.name;
    if (!rootSymbolName) {
        return;
    }
    return [rootSymbolName, propsDef];
}
function get$$PropsDef(tsAstModule, project, snapshot) {
    const program = project.program;
    const sourceFile = program.getSourceFile(toVirtualPath(snapshot));
    if (!program || !sourceFile) {
        return undefined;
    }
    const renderFunction = sourceFile.statements.find((statement) => tsAstModule.isFunctionDeclaration(statement) &&
        statement.name?.text === svelte2tsx_1.internalHelpers.renderName);
    return renderFunction?.body?.statements.find((node) => (tsAstModule.isTypeAliasDeclaration(node) ||
        tsAstModule.isInterfaceDeclaration(node)) &&
        node.name.text === '$$Props');
}
function movePropsErrorRangeBackIfNecessary(tsAstModule, diagnostic, snapshot, get$$PropsDefWithCache, get$$PropsAliasForWithCache) {
    const possibly$$PropsError = (0, utils_2.isAfterSvelte2TsxPropsReturn)(snapshot.getFullText(), snapshot.offsetAt(diagnostic.range.start));
    if (!possibly$$PropsError) {
        return;
    }
    if (diagnostic.message.includes('$$Props')) {
        const propsDef = get$$PropsDefWithCache();
        if (!propsDef) {
            return;
        }
        const generatedPropsStart = (0, utils_4.getStartOfNode)(tsAstModule, propsDef.name, propsDef.getSourceFile());
        const propsStart = snapshot.getOriginalPosition(snapshot.positionAt(generatedPropsStart));
        if (propsStart) {
            return {
                start: propsStart,
                end: { ...propsStart, character: propsStart.character + '$$Props'.length }
            };
        }
        return;
    }
    const aliasForInfo = get$$PropsAliasForWithCache();
    if (!aliasForInfo) {
        return;
    }
    const [aliasFor, propsDef] = aliasForInfo;
    if (diagnostic.message.includes(aliasFor)) {
        return (0, documents_1.mapRangeToOriginal)(snapshot, {
            start: snapshot.positionAt((0, utils_4.getStartOfNode)(tsAstModule, propsDef.name, propsDef.getSourceFile())),
            end: snapshot.positionAt(propsDef.name.end)
        });
    }
}
function expectedTransitionThirdArgument(tsAstModule, diagnostic, tsDoc, project) {
    if (diagnostic.code !== DiagnosticsProvider_1.DiagnosticCode.EXPECTED_N_ARGUMENTS ||
        !tsDoc.getText(0, diagnostic.pos).endsWith('__sveltets_2_ensureTransition(')) {
        return false;
    }
    const sourceFile = diagnostic.fileName && project.program.getSourceFile(diagnostic.fileName);
    if (!sourceFile) {
        return false;
    }
    const node = findDiagnosticNode(tsAstModule, diagnostic, sourceFile);
    if (!node || !tsAstModule.isIdentifier(node)) {
        return false;
    }
    if (!node.parent || !tsAstModule.isCallExpression(node.parent)) {
        return false;
    }
    const callExpression = node.parent;
    const signature = callExpression && project.checker.getResolvedSignature(callExpression);
    if (!signature) {
        return false;
    }
    // TypeScript 7 rc and older nightly have parameters field instead of getParameters method
    const parameters = 'getParameters' in signature ? signature.getParameters() : signature.parameters;
    return (parameters?.filter((parameter) => !(parameter.flags & typescript_1.default.SymbolFlags.Optional)).length === 3);
}
function getDiagnosticTag(tsDiag) {
    const tags = [];
    if (tsDiag.reportsUnnecessary) {
        tags.push(vscode_languageserver_1.DiagnosticTag.Unnecessary);
    }
    if (tsDiag.reportsDeprecated) {
        tags.push(vscode_languageserver_1.DiagnosticTag.Deprecated);
    }
    return tags;
}
//# sourceMappingURL=DiagnosticsProvider.js.map