function transformEsModuleToAmd(script, jsOptions) {
    // We're not a module anymore.
    dom5.removeAttribute(script, 'type');
    if (html_splitter_1.scriptWasSplitByHtmlSplitter(script)) {
        // Nothing else to do here. If we're using HtmlSplitter, the JsTransformer
        // is responsible for doing this transformation.
        return;
    }
    const isExternal = dom5.hasAttribute(script, 'src');
    if (isExternal) {
        const src = dom5.getAttribute(script, 'src');
        dom5.removeAttribute(script, 'src');
        dom5.setTextContent(script, `define(['${src}']);`);
    }
    else {
        // Transform inline scripts with the AMD Babel plugin transformer.
        const newJs = js_transform_1.jsTransform(dom5.getTextContent(script), Object.assign({}, jsOptions, { transformModulesToAmd: true }));
        dom5.setTextContent(script, newJs);
    }
}
 !allScripts.some((node) => dom5.hasAttribute(node, 'nomodule'));
/**
 * Transform some HTML according to the given options.
 */
function htmlTransform(html, options) {
    if (options.js && options.js.moduleResolution === 'node' &&
        !options.js.filePath) {
        throw new Error('Cannot perform node module resolution without filePath.');
    }
    const document = parse5.parse(html, {
        locationInfo: true,
    });
    removeFakeNodes(document);
    const allScripts = [...dom5.queryAll(document, isJsScript)];
    const shouldTransformEsModuleToAmd = options.js &&
        options.js.transformModulesToAmd &&
        // Assume that if this document has a nomodule script, the author is
        // already handling browsers that don't support modules, and we don't
        // need to transform them (even if the configuration was set).
        // TODO(aomarks) Check this for HtmlSplitter case too.
        !allScripts.some((node) => dom5.hasAttribute(node, 'nomodule'));
    let wctScript, firstModuleScript;
    for (const script of allScripts) {
        const isModule = dom5.getAttribute(script, 'type') === 'module';
        if (isModule) {
            if (firstModuleScript === undefined) {
                firstModuleScript = script;
            }
            if (shouldTransformEsModuleToAmd) {
                transformEsModuleToAmd(script, options.js);
                continue; // Bypass the standard jsTransform below.
            }
        }
        const isInline = !dom5.hasAttribute(script, 'src');
        if (isInline) {
            // Note that scripts split by HtmlSplitter are always external, so we
            // don't have to check for that case here.
            const newJs = js_transform_1.jsTransform(dom5.getTextContent(script), Object.assign({}, options.js, { transformModulesToAmd: false }));
            dom5.setTextContent(script, newJs);
        }
        else if (wctScript === undefined) {
            const src = dom5.getAttribute(script, 'src') || '';
            if (src.includes('web-component-tester/browser.js') ||
                src.includes('wct-browser-legacy/browser.js') ||
                src.includes('wct-mocha/wct-mocha.js')) {
                wctScript = script;
            }
        }
    }
    if (options.injectAmdLoader && shouldTransformEsModuleToAmd &&
        firstModuleScript !== undefined) {
        const fragment = parse5.parseFragment('<script></script>\n');
        dom5.setTextContent(fragment.childNodes[0], externalJs.getAmdLoader());
        const amdLoaderScript = fragment.childNodes[0];
        // Inject as late as possible (just before the first module is declared, if
        // there is one) because there may be some UMD dependencies that we want to
        // continue to load in global mode instead of AMD mode (which is detected by
        // the presence of the `require` global).
        // TODO(aomarks) If we don't define require, we can inject earlier.
        dom5.insertBefore(firstModuleScript.parentNode, firstModuleScript, fragment);
        if (wctScript !== undefined) {
            addWctTimingHack(wctScript, amdLoaderScript);
        }
    }
    const injectScript = (js) => {
        const fragment = parse5.parseFragment('<script></script>\n');
        dom5.setTextContent(fragment.childNodes[0], js);
        const firstJsScriptOrHtmlImport = dom5.query(document, isJsScriptOrHtmlImport);
        if (firstJsScriptOrHtmlImport) {
            dom5.insertBefore(firstJsScriptOrHtmlImport.parentNode, firstJsScriptOrHtmlImport, fragment);
        }
        else {
            const headOrDocument = dom5.query(document, dom5.predicates.hasTagName('head')) || document;
            dom5.append(headOrDocument, fragment);
        }
    };
    let babelHelpers;
    switch (options.injectBabelHelpers) {
        case undefined:
        case 'none':
            break;
        case 'full':
            babelHelpers = externalJs.getBabelHelpersFull();
            break;
        case 'amd':
            babelHelpers = externalJs.getBabelHelpersAmd();
            break;
        default:
            const never = options.injectBabelHelpers;
            throw new Error(`Unknown injectBabelHelpers value: ${never}`);
    }
    if (babelHelpers !== undefined) {
        injectScript(babelHelpers);
    }
    if (options.injectRegeneratorRuntime === true) {
        injectScript(externalJs.getRegeneratorRuntime());
    }
    html = parse5.serialize(document);
    if (options.minifyHtml) {
        html = htmlMinifier.minify(html, {
            collapseWhitespace: true,
            removeComments: true,
        });
    }
    return html;
}
 return __awaiter(this, void 0, void 0, function* () {
     const warnings = [];
     const elements = document.getFeatures({ kind: 'element' });
     for (const element of elements) {
         // Look for selector errors in locally defined elements.
         const result = determineMigrationDescriptors(element);
         if (!result.success) {
             for (const error of result.value) {
                 if (error.slot.astNode === undefined ||
                     error.slot.astNode.language !== 'html') {
                     continue;
                 }
                 warnings.push(new polymer_analyzer_1.Warning({
                     code: 'invalid-old-content-selector',
                     parsedDocument,
                     severity: polymer_analyzer_1.Severity.WARNING,
                     message: error.message,
                     sourceRange: parsedDocument.sourceRangeForAttributeValue(error.slot.astNode.node, 'old-content-selector') ||
                         parsedDocument.sourceRangeForStartTag(error.slot.astNode.node)
                 }));
             }
         }
     }
     const references = document.getFeatures({ kind: 'element-reference' });
     for (const reference of references) {
         const contentDescriptors = getMigrationDescriptors(reference.tagName, document);
         if (!contentDescriptors) {
             continue;
         }
         const fix = [];
         const matchedSoFar = new Set();
         for (const { predicate, slot } of contentDescriptors) {
             const matchingLightContents = [];
             function matchChildNodes(node) {
                 for (const child of node.childNodes || []) {
                     if (child.tagName === 'template') {
                         const content = parse5_1.treeAdapters.default.getTemplateContent(child);
                         matchChildNodes(content);
                     }
                     else if (predicate(child)) {
                         matchingLightContents.push(child);
                     }
                 }
             }
             matchChildNodes(reference.astNode.node);
             for (const lightContent of matchingLightContents) {
                 if (dom5.hasAttribute(lightContent, 'slot')) {
                     continue;
                 }
                 const range = parsedDocument.sourceRangeForStartTag(lightContent);
                 if (!range) {
                     continue;
                 }
                 if (matchedSoFar.has(lightContent)) {
                     continue;
                 }
                 matchedSoFar.add(lightContent);
                 const [startOffset, endOffset] = parsedDocument.sourceRangeToOffsets(range);
                 const originalText = parsedDocument.contents.slice(startOffset, endOffset);
                 if (!originalText.endsWith('>')) {
                     // Something weird is going on, don't make any changes.
                     continue;
                 }
                 let justBeforeTagClose = -1;
                 let tagCloseSyntax = '>';
                 if (originalText.endsWith('/>')) {
                     justBeforeTagClose = -2;
                     tagCloseSyntax = '/>';
                 }
                 const withSlotAttr = originalText.slice(0, justBeforeTagClose) +
                     ` slot="${slot}"${tagCloseSyntax}`;
                 fix.push({ range, replacementText: withSlotAttr });
             }
         }
         if (fix.length > 0) {
             warnings.push(new polymer_analyzer_1.Warning({
                 code: 'content-to-slot-usage-site',
                 message: `Deprecated <content>-based distribution into ` +
                     `<${reference.tagName}>. ` +
                     `Must use the \`slot\` attribute for named distribution."`,
                 parsedDocument,
                 severity: polymer_analyzer_1.Severity.WARNING,
                 sourceRange: parsedDocument.sourceRangeForStartTag(reference.astNode.node),
                 fix
             }));
         }
     }
     return warnings;
 });