"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;
exports.mapSvelteCheckDiagnostics = mapSvelteCheckDiagnostics;
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 configLoader_1 = require("./lib/documents/configLoader");
const logger_1 = require("./logger");
const ls_config_1 = require("./ls-config");
const plugins_1 = require("./plugins");
const FileSystemProvider_1 = require("./lib/FileSystemProvider");
const service_1 = require("./plugins/css/service");
const DocumentSnapshot_1 = require("./plugins/typescript/DocumentSnapshot");
const utils_1 = require("./plugins/typescript/features/utils");
const DiagnosticsProvider_1 = require("./plugins/typescript/features/DiagnosticsProvider");
const utils_2 = require("./plugins/typescript/utils");
const utils_3 = require("./utils");
const DiagnosticsProvider_2 = require("./plugins/typescript-go/features/DiagnosticsProvider");
function mapSvelteCheckDiagnostics(sourcePath, sourceText, tsDiagnostics, options) {
    logger_1.Logger.setLogErrorsOnly(true);
    const document = new documents_1.Document((0, utils_3.pathToUrl)(sourcePath), sourceText, /* skipConfigLoading */ true);
    const snapshot = DocumentSnapshot_1.DocumentSnapshot.fromDocument(document, {
        parse: document.compiler?.parse,
        version: document.compiler?.VERSION,
        transformOnTemplateError: false,
        typingsNamespace: 'svelteHTML',
        emitJsDoc: true,
        rewriteExternalImports: options?.rewriteExternalImports
    });
    return (0, DiagnosticsProvider_1.mapAndFilterDiagnostics)(tsDiagnostics, document, snapshot);
}
/**
 * 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);
        }
        if (options.configPath && !(0, path_1.isAbsolute)(options.configPath)) {
            throw new Error('configPath needs to be absolute, got ' + options.configPath);
        }
        configLoader_1.configLoader.setExplicitConfigScope(options.configPath
            ? {
                configPath: options.configPath,
                rootDirectory: options.tsconfig ? (0, path_1.dirname)(options.tsconfig) : workspacePath
            }
            : undefined);
        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)];
            if (options.experimental?.tsgo && options.tsconfig) {
                const { apiModule, astModule } = options.experimental.tsgo;
                if (!apiModule.API || !('ScriptKind' in astModule)) {
                    throw new Error('Unsupported typescript-go version');
                }
                this.tsGoDiagnosticsProvider = new DiagnosticsProvider_2.SvelteCheckTSGoDiagnosticsProvider(apiModule, astModule, options.tsconfig, 'svelte-check', (filePath, text) => this.docManager.openDocument({
                    text: text,
                    uri: (0, utils_3.pathToUrl)(filePath)
                }, 
                /* openedByClient */ true));
            }
            else {
                this.lsAndTSDocResolver = new plugins_1.LSAndTSDocResolver(this.docManager, workspaceUris, this.configManager, {
                    tsconfigPath: options.tsconfig,
                    isSvelteCheck: true,
                    onProjectReloaded: options.onProjectReload,
                    watch: options.watch,
                    onFileSnapshotCreated: options.onFileSnapshotCreated
                });
                this.pluginHost.register(new plugins_1.TypeScriptPlugin(this.configManager, this.lsAndTSDocResolver, workspaceUris, this.docManager));
            }
        }
        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) || '';
        // in tsgo mode, let typescript check whether the file belongs to the project
        if (this.tsGoDiagnosticsProvider) {
            this.tsGoDiagnosticsProvider.watchUpdate(doc, isNew ? 'created' : 'changed');
            return;
        }
        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.openClientDocument({
                text: doc.text,
                uri: 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) {
            if (this.tsGoDiagnosticsProvider) {
                this.tsGoDiagnosticsProvider.watchUpdate({ text: '', uri }, 'deleted');
            }
            else {
                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) {
            if (this.tsGoDiagnosticsProvider) {
                return this.getDiagnosticsForTsconfigTsGo();
            }
            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 normalizedTsconfigPath = (0, utils_3.normalizePath)(tsconfigPath);
        const map = (diagnostic, range) => {
            const file = diagnostic.file;
            range ??= file
                ? (0, utils_2.convertRange)({ positionAt: file.getLineAndCharacterOfPosition.bind(file) }, diagnostic)
                : { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
            return {
                range: range,
                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),
                data: {
                    positionUnknown: !diagnostic.start || !diagnostic.length
                }
            };
        };
        const isErrorCategory = (diagnostic) => diagnostic.category === typescript_1.default.DiagnosticCategory.Error;
        if (lsContainer.configErrors.some(isErrorCategory)) {
            return reportConfigError(lsContainer.configErrors);
        }
        const lang = lsContainer.getService();
        if (lsContainer.configErrors.some(isErrorCategory)) {
            return reportConfigError(lsContainer.configErrors);
        }
        const program = lang.getProgram();
        const globalOrConfigFileDiagnostics = program
            ? [...program.getGlobalDiagnostics(), ...program.getOptionsDiagnostics()]
            : [];
        // TODO: enable this in svelte-check v5. For now, we report these as warnings along with other diagnostics.
        // if (globalOrConfigFileDiagnostics.some(isErrorCategory)) {
        //     return reportConfigError(globalOrConfigFileDiagnostics);
        // }
        const files = lang.getProgram()?.getSourceFiles() || [];
        const options = lang.getProgram()?.getCompilerOptions() || {};
        const diagnostics = 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) ||
                    lsContainer.isShimFiles(file.fileName) ||
                    // 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 = [];
                if (!skipDiagnosticsForFile) {
                    const diagnosticSources = [
                        'getSyntacticDiagnostics',
                        'getSuggestionDiagnostics',
                        'getSemanticDiagnostics'
                    ];
                    for (const diagnosticSource of diagnosticSources) {
                        for (let diagnostic of lang[diagnosticSource](file.fileName)) {
                            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
                };
            }
        }));
        const configErrors = lsContainer.configErrors
            // TODO: remove this in svelte-check v5.
            .concat(globalOrConfigFileDiagnostics.map((diagnostic) => ({
            ...diagnostic,
            category: diagnostic.category === typescript_1.default.DiagnosticCategory.Error
                ? typescript_1.default.DiagnosticCategory.Warning
                : diagnostic.category
        })));
        if (configErrors.length) {
            diagnostics.push(...reportConfigError(configErrors));
        }
        return diagnostics;
        function reportConfigError(errors) {
            const grouped = (0, utils_3.groupBy)(errors, (error) => error.file?.fileName ?? normalizedTsconfigPath);
            const lspDiagnostics = errors.map((diagnostic) => map(diagnostic));
            return Object.entries(grouped).map(([filePath, errors]) => ({
                filePath,
                text: lspDiagnostics.some((diagnostic) => diagnostic.data?.positionUnknown)
                    ? (typescript_1.default.sys?.readFile(filePath) ?? '')
                    : '',
                diagnostics: lspDiagnostics
            }));
        }
    }
    async getDiagnosticsForTsconfigTsGo() {
        if (!this.tsGoDiagnosticsProvider) {
            throw new Error('Cannot get diagnostics for tsconfig without TSGo diagnostics provider');
        }
        const project = await this.tsGoDiagnosticsProvider.getProject();
        if (!project) {
            throw new Error('Expected to have api project');
        }
        let allTsDiagnostics = Array.from(project.program.getConfigFileParsingDiagnostics());
        const configFileParsingDiagnosticsLength = allTsDiagnostics?.length ?? 0;
        allTsDiagnostics = allTsDiagnostics.concat(project.program.getSyntacticDiagnostics());
        // doesn't exist in the API yet
        // allDiagnostics = allDiagnostics.concat(project.program.getProgramDiagnostics());
        if (allTsDiagnostics.length == configFileParsingDiagnosticsLength) {
            allTsDiagnostics = allTsDiagnostics.concat(project.program.getSemanticDiagnostics());
        }
        const result = this.tsGoDiagnosticsProvider.mapAndFilterDiagnostics(project, allTsDiagnostics);
        const map = new Map();
        for (const diag of result) {
            map.set(diag.filePath, diag);
        }
        for (const filePath of this.tsGoDiagnosticsProvider.getAllSvelteFiles()) {
            const uri = (0, utils_3.pathToUrl)(filePath);
            if (!uri) {
                continue;
            }
            const doc = this.docManager.get(uri);
            if (!doc) {
                continue;
            }
            const nonTsDiagnostics = await this.getDiagnosticsForFile(uri);
            let existing = map.get(filePath);
            if (existing) {
                existing.diagnostics = existing.diagnostics.concat(nonTsDiagnostics.diagnostics);
            }
            else {
                map.set(filePath, nonTsDiagnostics);
            }
        }
        return Array.from(map.values());
    }
    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);
    }
    /**
     * Gets the watch directories based on the tsconfig include patterns.
     * Returns null if no tsconfig is specified.
     */
    async getWatchDirectories() {
        if (!this.options.tsconfig) {
            return null;
        }
        let projectConfig;
        if (this.tsGoDiagnosticsProvider) {
            projectConfig = this.tsGoDiagnosticsProvider.getProjectConfig();
        }
        else {
            const lsContainer = await this.getLSContainer(this.options.tsconfig);
            projectConfig = lsContainer.getProjectConfig();
        }
        if (!projectConfig.wildcardDirectories) {
            return null;
        }
        return Object.entries(projectConfig.wildcardDirectories).map(([dir, flags]) => ({
            path: dir,
            recursive: !!(flags & typescript_1.default.WatchDirectoryFlags.Recursive)
        }));
    }
}
exports.SvelteCheck = SvelteCheck;
//# sourceMappingURL=svelte-check.js.map