"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SvelteCheck = void 0;
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("./lib/documents");
const logger_1 = require("./logger");
const ls_config_1 = require("./ls-config");
const plugins_1 = require("./plugins");
const FileSystemProvider_1 = require("./plugins/css/FileSystemProvider");
const service_1 = require("./plugins/css/service");
const utils_1 = require("./plugins/typescript/features/utils");
const utils_2 = require("./plugins/typescript/utils");
const utils_3 = require("./utils");
/**
 * Small wrapper around PluginHost's Diagnostic Capabilities
 * for svelte-check, without the overhead of the lsp.
 */
class SvelteCheck {
    constructor(workspacePath, options = {}) {
        this.options = options;
        this.docManager = new documents_1.DocumentManager((textDocument) => new documents_1.Document(textDocument.uri, textDocument.text));
        this.configManager = new ls_config_1.LSConfigManager();
        this.pluginHost = new plugins_1.PluginHost(this.docManager);
        logger_1.Logger.setLogErrorsOnly(true);
        this.initialize(workspacePath, options);
    }
    async initialize(workspacePath, options) {
        if (options.tsconfig && !(0, path_1.isAbsolute)(options.tsconfig)) {
            throw new Error('tsconfigPath needs to be absolute, got ' + options.tsconfig);
        }
        this.configManager.update({
            svelte: {
                compilerWarnings: options.compilerWarnings
            }
        });
        // No HTMLPlugin, it does not provide diagnostics
        if (shouldRegister('svelte')) {
            this.pluginHost.register(new plugins_1.SveltePlugin(this.configManager));
        }
        if (shouldRegister('css')) {
            const services = (0, service_1.createLanguageServices)({
                fileSystemProvider: new FileSystemProvider_1.FileSystemProvider()
            });
            const workspaceFolders = [
                {
                    name: '',
                    uri: (0, utils_3.pathToUrl)(workspacePath)
                }
            ];
            this.pluginHost.register(new plugins_1.CSSPlugin(this.docManager, this.configManager, workspaceFolders, services));
        }
        if (shouldRegister('js') || options.tsconfig) {
            const workspaceUris = [(0, utils_3.pathToUrl)(workspacePath)];
            this.lsAndTSDocResolver = new plugins_1.LSAndTSDocResolver(this.docManager, workspaceUris, this.configManager, {
                tsconfigPath: options.tsconfig,
                isSvelteCheck: true,
                onProjectReloaded: options.onProjectReload,
                watchTsConfig: options.watch
            });
            this.pluginHost.register(new plugins_1.TypeScriptPlugin(this.configManager, this.lsAndTSDocResolver, workspaceUris));
        }
        function shouldRegister(source) {
            return !options.diagnosticSources || options.diagnosticSources.includes(source);
        }
    }
    /**
     * Creates/updates given document
     *
     * @param doc Text and Uri of the document
     * @param isNew Whether or not this is the creation of the document
     */
    async upsertDocument(doc, isNew) {
        const filePath = (0, utils_3.urlToPath)(doc.uri) || '';
        if (this.options.tsconfig) {
            const lsContainer = await this.getLSContainer(this.options.tsconfig);
            if (!lsContainer.fileBelongsToProject(filePath, isNew)) {
                return;
            }
        }
        if (doc.uri.endsWith('.ts') ||
            doc.uri.endsWith('.js') ||
            doc.uri.endsWith('.tsx') ||
            doc.uri.endsWith('.jsx') ||
            doc.uri.endsWith('.mjs') ||
            doc.uri.endsWith('.cjs') ||
            doc.uri.endsWith('.mts') ||
            doc.uri.endsWith('.cts')) {
            this.pluginHost.updateTsOrJsFile(filePath, [
                {
                    range: vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(Number.MAX_VALUE, Number.MAX_VALUE)),
                    text: doc.text
                }
            ]);
        }
        else {
            this.docManager.openDocument({
                text: doc.text,
                uri: doc.uri
            });
            this.docManager.markAsOpenedInClient(doc.uri);
        }
    }
    /**
     * Removes/closes document
     *
     * @param uri Uri of the document
     */
    async removeDocument(uri) {
        if (!this.docManager.get(uri)) {
            return;
        }
        this.docManager.closeDocument(uri);
        this.docManager.releaseDocument(uri);
        if (this.options.tsconfig) {
            const lsContainer = await this.getLSContainer(this.options.tsconfig);
            lsContainer.deleteSnapshot((0, utils_3.urlToPath)(uri) || '');
        }
    }
    /**
     * Gets the diagnostics for all currently open files.
     */
    async getDiagnostics() {
        if (this.options.tsconfig) {
            return this.getDiagnosticsForTsconfig(this.options.tsconfig);
        }
        return await Promise.all(this.docManager.getAllOpenedByClient().map(async (doc) => {
            const uri = doc[1].uri;
            return await this.getDiagnosticsForFile(uri);
        }));
    }
    async getDiagnosticsForTsconfig(tsconfigPath) {
        const lsContainer = await this.getLSContainer(tsconfigPath);
        const noInputsFoundError = lsContainer.configErrors?.find((e) => e.code === 18003);
        if (noInputsFoundError) {
            throw new Error(noInputsFoundError.messageText.toString());
        }
        const lang = lsContainer.getService();
        const files = lang.getProgram()?.getSourceFiles() || [];
        const options = lang.getProgram()?.getCompilerOptions() || {};
        return await Promise.all(files.map((file) => {
            const uri = (0, utils_3.pathToUrl)(file.fileName);
            const doc = this.docManager.get(uri);
            if (doc) {
                this.docManager.markAsOpenedInClient(uri);
                return this.getDiagnosticsForFile(uri);
            }
            else {
                // This check is done inside TS mostly, too, but for some diagnostics like suggestions it
                // doesn't apply to all code paths. That's why we do it here, too.
                const skipDiagnosticsForFile = (options.skipLibCheck && file.isDeclarationFile) ||
                    (options.skipDefaultLibCheck && file.hasNoDefaultLib) ||
                    // ignore JS files in node_modules
                    /\/node_modules\/.+\.(c|m)?js$/.test(file.fileName);
                const snapshot = lsContainer.snapshotManager.get(file.fileName);
                const isKitFile = snapshot?.kitFile ?? false;
                const diagnostics = [];
                const map = (diagnostic, range) => ({
                    range: range ??
                        (0, utils_2.convertRange)({ positionAt: file.getLineAndCharacterOfPosition.bind(file) }, diagnostic),
                    severity: (0, utils_2.mapSeverity)(diagnostic.category),
                    source: diagnostic.source,
                    message: typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
                    code: diagnostic.code,
                    tags: (0, utils_2.getDiagnosticTag)(diagnostic)
                });
                if (!skipDiagnosticsForFile) {
                    const originalDiagnostics = [
                        ...lang.getSyntacticDiagnostics(file.fileName),
                        ...lang.getSuggestionDiagnostics(file.fileName),
                        ...lang.getSemanticDiagnostics(file.fileName)
                    ];
                    for (let diagnostic of originalDiagnostics) {
                        if (!diagnostic.start || !diagnostic.length || !isKitFile) {
                            diagnostics.push(map(diagnostic));
                            continue;
                        }
                        let range = undefined;
                        const inGenerated = (0, utils_1.isInGeneratedCode)(file.text, diagnostic.start, diagnostic.start + diagnostic.length);
                        if (inGenerated && snapshot) {
                            const pos = snapshot.getOriginalPosition(snapshot.positionAt(diagnostic.start));
                            range = {
                                start: pos,
                                end: {
                                    line: pos.line,
                                    // adjust length so it doesn't spill over to the next line
                                    character: pos.character + 1
                                }
                            };
                            // If not one of the specific error messages then filter out
                            if (diagnostic.code === 2307) {
                                diagnostic = {
                                    ...diagnostic,
                                    messageText: typeof diagnostic.messageText === 'string' &&
                                        diagnostic.messageText.includes('./$types')
                                        ? diagnostic.messageText +
                                            ` (this likely means that SvelteKit's type generation didn't run yet - try running it by executing 'npm run dev' or 'npm run build')`
                                        : diagnostic.messageText
                                };
                            }
                            else if (diagnostic.code === 2694) {
                                diagnostic = {
                                    ...diagnostic,
                                    messageText: typeof diagnostic.messageText === 'string' &&
                                        diagnostic.messageText.includes('/$types')
                                        ? diagnostic.messageText +
                                            ` (this likely means that SvelteKit's generated types are out of date - try rerunning it by executing 'npm run dev' or 'npm run build')`
                                        : diagnostic.messageText
                                };
                            }
                            else if (diagnostic.code !==
                                2355 /*  A function whose declared type is neither 'void' nor 'any' must return a value */) {
                                continue;
                            }
                        }
                        diagnostics.push(map(diagnostic, range));
                    }
                }
                return {
                    filePath: file.fileName,
                    text: snapshot?.originalText ?? file.text,
                    diagnostics
                };
            }
        }));
    }
    async getDiagnosticsForFile(uri) {
        const diagnostics = await this.pluginHost.getDiagnostics({ uri });
        return {
            filePath: (0, utils_3.urlToPath)(uri) || '',
            text: this.docManager.get(uri)?.getText() || '',
            diagnostics
        };
    }
    getLSContainer(tsconfigPath) {
        if (!this.lsAndTSDocResolver) {
            throw new Error('Cannot run with tsconfig path without LS/TSdoc resolver');
        }
        return this.lsAndTSDocResolver.getTSService(tsconfigPath);
    }
}
exports.SvelteCheck = SvelteCheck;
//# sourceMappingURL=svelte-check.js.map