"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getQuickfixActions = getQuickfixActions;
exports.isIgnorableSvelteDiagnostic = isIgnorableSvelteDiagnostic;
const estree_walker_1 = require("estree-walker");
const os_1 = require("os");
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../../lib/documents");
const utils_1 = require("../../../../utils");
/**
 * Get applicable quick fixes.
 */
async function getQuickfixActions(svelteDoc, svelteDiagnostics) {
    const textDocument = vscode_languageserver_1.OptionalVersionedTextDocumentIdentifier.create((0, utils_1.pathToUrl)(svelteDoc.getFilePath()), null);
    const { ast } = await svelteDoc.getCompiled();
    const transpiled = await svelteDoc.getTranspiled();
    const content = transpiled.getText();
    const lineOffsets = (0, documents_1.getLineOffsets)(content);
    const codeActions = [];
    for (const diagnostic of svelteDiagnostics) {
        codeActions.push(...(await createQuickfixActions(textDocument, transpiled, content, lineOffsets, ast, diagnostic)));
    }
    return codeActions;
}
async function createQuickfixActions(textDocument, transpiled, content, lineOffsets, ast, diagnostic) {
    const { range: { start, end } } = diagnostic;
    const generatedStart = transpiled.getGeneratedPosition(start);
    const generatedEnd = transpiled.getGeneratedPosition(end);
    const diagnosticStartOffset = (0, documents_1.offsetAt)(generatedStart, content, lineOffsets);
    const diagnosticEndOffset = (0, documents_1.offsetAt)(generatedEnd, content, lineOffsets);
    const offsetRange = {
        pos: diagnosticStartOffset,
        end: diagnosticEndOffset
    };
    const { html, instance, module } = ast;
    const tree = [instance, module, html].find((part) => {
        return (part &&
            part.start !== null &&
            part.end !== null &&
            offsetRange.pos >= part.start &&
            offsetRange.pos <= part.end &&
            offsetRange.end <= part.end &&
            offsetRange.end >= part.start);
    });
    const node = findTagForRange(tree, offsetRange, tree === html);
    const codeActions = [];
    if (diagnostic.code == 'security-anchor-rel-noreferrer') {
        codeActions.push(createSvelteAnchorMissingAttributeQuickfixAction(textDocument, transpiled, content, lineOffsets, node));
    }
    codeActions.push(createSvelteIgnoreQuickfixAction(textDocument, transpiled, content, lineOffsets, node, diagnostic, tree === html));
    return codeActions;
}
function createSvelteAnchorMissingAttributeQuickfixAction(textDocument, transpiled, content, lineOffsets, node) {
    // Assert non-null because the node target attribute is required for 'security-anchor-rel-noreferrer'
    const targetAttribute = node.attributes.find((i) => i.name == 'target');
    const relAttribute = node.attributes.find((i) => i.name == 'rel');
    const codeActionTextEdit = relAttribute
        ? vscode_languageserver_1.TextEdit.insert((0, documents_1.positionAt)(relAttribute.end - 1, content, lineOffsets), ' noreferrer')
        : vscode_languageserver_1.TextEdit.insert((0, documents_1.positionAt)(targetAttribute.end, content, lineOffsets), ' rel="noreferrer"');
    return vscode_languageserver_1.CodeAction.create('(svelte) Add missing attribute rel="noreferrer"', {
        documentChanges: [
            vscode_languageserver_1.TextDocumentEdit.create(textDocument, [
                (0, documents_1.mapObjWithRangeToOriginal)(transpiled, codeActionTextEdit)
            ])
        ]
    }, vscode_languageserver_1.CodeActionKind.QuickFix);
}
function createSvelteIgnoreQuickfixAction(textDocument, transpiled, content, lineOffsets, node, diagnostic, isHtml) {
    return vscode_languageserver_1.CodeAction.create(getCodeActionTitle(diagnostic), {
        documentChanges: [
            vscode_languageserver_1.TextDocumentEdit.create(textDocument, [
                getSvelteIgnoreEdit(transpiled, content, lineOffsets, node, diagnostic, isHtml)
            ])
        ]
    }, vscode_languageserver_1.CodeActionKind.QuickFix);
}
function getCodeActionTitle(diagnostic) {
    // make it distinguishable with eslint's code action
    return `(svelte) Disable ${diagnostic.code} for this line`;
}
/**
 * Whether or not the given diagnostic can be ignored via a
 * <!-- svelte-ignore <code> -->
 */
function isIgnorableSvelteDiagnostic(diagnostic) {
    const { source, severity, code } = diagnostic;
    return (code &&
        !nonIgnorableWarnings.includes(code) &&
        source === 'svelte' &&
        severity !== vscode_languageserver_1.DiagnosticSeverity.Error);
}
const nonIgnorableWarnings = [
    'missing-custom-element-compile-options',
    'unused-export-let',
    'css-unused-selector'
];
function getSvelteIgnoreEdit(transpiled, content, lineOffsets, node, diagnostic, isHtml) {
    const { code } = diagnostic;
    const nodeStartPosition = (0, documents_1.positionAt)(node.start, content, lineOffsets);
    const nodeLineStart = (0, documents_1.offsetAt)({
        line: nodeStartPosition.line,
        character: 0
    }, content, lineOffsets);
    const afterStartLineStart = content.slice(nodeLineStart);
    const indent = (0, utils_1.getIndent)(afterStartLineStart);
    // TODO: Make all code action's new line consistent
    let ignore = `${indent}// svelte-ignore ${code}${os_1.EOL}`;
    if (isHtml) {
        ignore = `${indent}<!-- svelte-ignore ${code} -->${os_1.EOL}`;
    }
    const position = vscode_languageserver_1.Position.create(nodeStartPosition.line, 0);
    return (0, documents_1.mapObjWithRangeToOriginal)(transpiled, vscode_languageserver_1.TextEdit.insert(position, ignore));
}
const elementOrComponent = ['Component', 'Element', 'InlineComponent'];
function findTagForRange(ast, range, isHtml) {
    let nearest = ast;
    (0, estree_walker_1.walk)(ast, {
        enter(node, parent) {
            if (isHtml) {
                const { type } = node;
                const isBlock = 'block' in node || node.type.toLowerCase().includes('block');
                const isFragment = type === 'Fragment';
                const keepLooking = isFragment || elementOrComponent.includes(type) || isBlock;
                if (!keepLooking) {
                    this.skip();
                    return;
                }
            }
            if (within(node, range) && parent === nearest) {
                nearest = node;
            }
        }
    });
    return nearest;
}
function within(node, range) {
    return node.end >= range.end && node.start <= range.pos;
}
//# sourceMappingURL=getQuickfixes.js.map