UNPKG

@ampproject/rollup-plugin-closure-compiler

Version:

Rollup + Google Closure Compiler

1,849 lines 73.1 kB
'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var fs = require('fs');
var googleClosureCompiler = require('google-closure-compiler');
var path = require('path');
var os = require('os');
var crypto = require('crypto');
var uuid = require('uuid');
var MagicString = _interopDefault(require('magic-string'));
var remapping = _interopDefault(require('@ampproject/remapping'));
var acorn = require('acorn');
var estreeWalker = require('estree-walker');

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
async function writeTempFile(content, extension = '', stableName = true) {
    let hash;
    if (stableName) {
        hash = crypto.createHash('sha1')
            .update(content)
            .digest('hex');
    }
    else {
        hash = uuid.v4();
    }
    const fullpath = path.join(os.tmpdir(), hash + extension);
    await fs.promises.mkdir(path.dirname(fullpath), { recursive: true });
    await fs.promises.writeFile(fullpath, content, 'utf-8');
    return fullpath;
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* c8 ignore next 15 */
async function logTransformChain(file, stage, messages) {
    return;
}
/* c8 ignore next 7 */
const log = (preamble, message) => {
    return;
};

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function createDecodedSourceMap(magicstring, source) {
    return {
        ...magicstring.generateDecodedMap({ hires: true, source }),
        version: 3,
    };
}
function createExistingRawSourceMap(maps, file) {
    var _a;
    const remappedSourceMap = remapping(maps, () => null);
    return {
        ...remappedSourceMap,
        sources: remappedSourceMap.sources.map(source => source || ''),
        sourcesContent: ((_a = remappedSourceMap.sourcesContent) === null || _a === void 0 ? void 0 : _a.map(content => content || '')) || undefined,
        file,
    };
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class Transform {
    constructor(context, pluginOptions, mangler, memory, inputOptions, outputOptions) {
        this.name = 'Transform';
        this.context = context;
        this.pluginOptions = pluginOptions;
        this.mangler = mangler;
        this.memory = memory;
        this.inputOptions = inputOptions;
        this.outputOptions = outputOptions;
    }
}
class SourceTransform extends Transform {
    constructor() {
        super(...arguments);
        this.name = 'SourceTransform';
    }
    async transform(id, source) {
        return source;
    }
}
class ChunkTransform extends Transform {
    constructor() {
        super(...arguments);
        this.name = 'ChunkTransform';
    }
    extern(options) {
        return null;
    }
    async pre(fileName, source) {
        return source;
    }
    async post(fileName, source) {
        return source;
    }
}
async function chunkLifecycle(fileName, printableName, method, code, transforms) {
    const log = [];
    const sourcemaps = [];
    let source = new MagicString(code);
    let finalSource = '';
    log.push(['before', code]);
    try {
        for (const transform of transforms) {
            const transformed = await transform[method](fileName, source);
            const transformedSource = transformed.toString();
            sourcemaps.push(createDecodedSourceMap(transformed, fileName));
            source = new MagicString(transformedSource);
            log.push([transform.name, transformedSource]);
        }
        finalSource = source.toString();
    }
    catch (e) {
        log.push(['after', finalSource]);
        await logTransformChain();
        throw e;
    }
    log.push(['after', finalSource]);
    await logTransformChain();
    return {
        code: finalSource,
        map: createExistingRawSourceMap(sourcemaps, fileName),
    };
}
async function sourceLifecycle(id, printableName, code, transforms) {
    const fileName = path.basename(id);
    const log = [];
    const sourcemaps = [];
    let source = new MagicString(code);
    log.push(['before', code]);
    for (const transform of transforms) {
        const transformed = await transform.transform(id, source);
        const transformedSource = transformed.toString();
        sourcemaps.push(createDecodedSourceMap(transformed, id));
        source = new MagicString(transformedSource);
        log.push([transform.name, transformedSource]);
    }
    const finalSource = source.toString();
    log.push(['after', finalSource]);
    await logTransformChain();
    return {
        code: finalSource,
        map: createExistingRawSourceMap(sourcemaps, fileName),
    };
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
 *
 * This transform will remove the hashbang (if present) and ask Ebbinghaus to remember if for after compilation.
 */
class HashbangRemoveTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'HashbangRemoveTransform';
    }
    /**
     * @param source MagicString of source to process post Closure Compilation.
     */
    async pre(fileName, source) {
        const stringified = source.trim().toString();
        const match = /^#!.*/.exec(stringified);
        if (!match) {
            return source;
        }
        this.memory.hashbang = match[0];
        source.remove(0, match[0].length);
        return source;
    }
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
 *
 * This transform will restore the hashbang if Ebbinghaus knows it exists.
 */
class HashbangApplyTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'HashbangApplyTransform';
    }
    /**
     * @param source MagicString of source to process post Closure Compilation.
     */
    async post(fileName, source) {
        if (this.memory.hashbang === null) {
            return source;
        }
        source.prepend(this.memory.hashbang + '\n');
        return source;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const HEADER = `/**
 * @fileoverview Externs built via derived configuration from Rollup or input code.
 * This extern contains the iife name so it does not get mangled at the top level.
 * @externs
 */
`;
/**
 * This Transform will apply only if the Rollup configuration is for a iife output with a defined name.
 *
 * In order to preserve the name of the iife output, derive an extern definition for Closure Compiler.
 * This preserves the name after compilation since Closure now believes it to be a well known global.
 */
class IifeTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'IifeTransform';
    }
    extern(options) {
        if (options.format === 'iife' && options.name) {
            return HEADER + `window['${options.name}'] = ${options.name};\n`;
        }
        return null;
    }
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const HEADER$1 = `/**
* @fileoverview Externs built via derived configuration from Rollup or input code.
* This extern contains the cjs typing info for modules.
* @externs
*/

/**
* @typedef {{
*   __esModule: boolean,
* }}
*/
var exports;`;
/**
 * This Transform will apply only if the Rollup configuration is for a cjs output.
 *
 * In order to preserve the __esModules boolean on an Object, this typedef needs to be present.
 */
class CJSTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'CJSTransform';
    }
    extern(options) {
        if (options.format === 'cjs') {
            return HEADER$1;
        }
        return null;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const acornWalk = require('acorn-walk');
const walk = {
    simple: acornWalk.simple,
    ancestor: acornWalk.ancestor,
};
const DEFAULT_ACORN_OPTIONS = {
    ecmaVersion: 2020,
    sourceType: 'module',
    preserveParens: false,
    ranges: true,
};
async function parse(fileName, source) {
    try {
        return acorn.parse(source, DEFAULT_ACORN_OPTIONS);
    }
    catch (e) {
        log(`parse exception in ${fileName}`, `file://${await writeTempFile(source, '.js')}`);
        throw e;
    }
}
function isIdentifier(node) {
    return node.type === 'Identifier';
}
function isImportDeclaration(node) {
    return node.type === 'ImportDeclaration';
}
function isImportExpression(node) {
    // @types/estree does not yet support 2020 addons to ECMA.
    // This includes ImportExpression ... import("thing")
    return node.type === 'ImportExpression';
}
function isVariableDeclarator(node) {
    return node.type === 'VariableDeclarator';
}
function isBlockStatement(node) {
    return node.type === 'BlockStatement';
}
function isExportNamedDeclaration(node) {
    return node.type === 'ExportNamedDeclaration';
}
function isFunctionDeclaration(node) {
    return node.type === 'FunctionDeclaration';
}
function isVariableDeclaration(node) {
    return node.type === 'VariableDeclaration';
}
function isClassDeclaration(node) {
    return node.type === 'ClassDeclaration';
}
function isProperty(node) {
    return node.type === 'Property';
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Closure Compiler will not transform computed keys with literal values back to the literal value.
 * e.g {[0]: 'value'} => {0: 'value'}
 *
 * This transform does so only if a computed key is a Literal, and thus easily known to be static.
 * @see https://astexplorer.net/#/gist/d2414b45a81db3a41ee6902bfd09947a/d7176ac33a2733e1a4b1f65ec3ac626e24f7b60d
 */
class LiteralComputedKeys extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'LiteralComputedKeysTransform';
    }
    /**
     * @param code source to parse, and modify
     * @return modified input source with computed literal keys
     */
    async post(fileName, source) {
        const program = await parse(fileName, source.toString());
        walk.simple(program, {
            ObjectExpression(node) {
                for (const property of node.properties) {
                    if (isProperty(property) && property.computed && property.key.type === 'Literal') {
                        const [propertyStart] = property.range;
                        const [valueStart] = property.value.range;
                        source.overwrite(propertyStart, valueStart, `${property.key.value}${property.value.type !== 'FunctionExpression' ? ':' : ''}`);
                    }
                }
            },
        });
        return source;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const IMPORT_SPECIFIER = 'ImportSpecifier';
const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier';
const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier';
var ExportClosureMapping;
(function (ExportClosureMapping) {
    ExportClosureMapping[ExportClosureMapping["NAMED_FUNCTION"] = 0] = "NAMED_FUNCTION";
    ExportClosureMapping[ExportClosureMapping["NAMED_CLASS"] = 1] = "NAMED_CLASS";
    ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_FUNCTION"] = 2] = "NAMED_DEFAULT_FUNCTION";
    ExportClosureMapping[ExportClosureMapping["DEFAULT_FUNCTION"] = 3] = "DEFAULT_FUNCTION";
    ExportClosureMapping[ExportClosureMapping["NAMED_DEFAULT_CLASS"] = 4] = "NAMED_DEFAULT_CLASS";
    ExportClosureMapping[ExportClosureMapping["DEFAULT_CLASS"] = 5] = "DEFAULT_CLASS";
    ExportClosureMapping[ExportClosureMapping["NAMED_CONSTANT"] = 6] = "NAMED_CONSTANT";
    ExportClosureMapping[ExportClosureMapping["DEFAULT"] = 7] = "DEFAULT";
    ExportClosureMapping[ExportClosureMapping["DEFAULT_VALUE"] = 8] = "DEFAULT_VALUE";
    ExportClosureMapping[ExportClosureMapping["DEFAULT_OBJECT"] = 9] = "DEFAULT_OBJECT";
})(ExportClosureMapping || (ExportClosureMapping = {}));

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Locally within exporting we always need a name
 * If value passed is not present in the mangler then use original name.
 * @param input
 * @param unmangle
 */
function getName(input, unmangle) {
    if (unmangle) {
        return unmangle(input) || input;
    }
    return input;
}
function NamedDeclaration(node, unmangle) {
    var _a;
    const exportDetails = [];
    const range = node.range;
    const source = typeof ((_a = node.source) === null || _a === void 0 ? void 0 : _a.value) === 'string' ? node.source.value : null;
    const { specifiers, declaration } = node;
    // NamedDeclarations either have specifiers or declarations.
    if (specifiers.length > 0) {
        for (const specifier of specifiers) {
            const exported = getName(specifier.exported.name, unmangle);
            exportDetails.push({
                local: getName(specifier.local.name, unmangle),
                exported,
                type: ExportClosureMapping.NAMED_CONSTANT,
                range,
                source,
            });
        }
        return exportDetails;
    }
    if (declaration) {
        if (isFunctionDeclaration(declaration)) {
            // Only default exports can be missing an identifier.
            exportDetails.push({
                local: getName(declaration.id.name, unmangle),
                exported: getName(declaration.id.name, unmangle),
                type: ExportClosureMapping.NAMED_FUNCTION,
                range,
                source,
            });
        }
        if (isVariableDeclaration(declaration)) {
            for (const eachDeclaration of declaration.declarations) {
                if (isIdentifier(eachDeclaration.id)) {
                    exportDetails.push({
                        local: getName(eachDeclaration.id.name, unmangle),
                        exported: getName(eachDeclaration.id.name, unmangle),
                        type: ExportClosureMapping.NAMED_CONSTANT,
                        range,
                        source,
                    });
                }
            }
        }
        if (isClassDeclaration(declaration)) {
            // Only default exports can be missing an identifier.
            exportDetails.push({
                local: getName(declaration.id.name, unmangle),
                exported: getName(declaration.id.name, unmangle),
                type: ExportClosureMapping.NAMED_CLASS,
                range,
                source,
            });
        }
    }
    return exportDetails;
}
function DefaultDeclaration(defaultDeclaration, unmangle) {
    const { declaration } = defaultDeclaration;
    if (declaration.type === 'Identifier' && declaration.name) {
        return [
            {
                local: getName(declaration.name, unmangle),
                exported: getName(declaration.name, unmangle),
                type: ExportClosureMapping.NAMED_DEFAULT_FUNCTION,
                range: defaultDeclaration.range,
                source: null,
            },
        ];
    }
    return [];
}
function NodeIsPreservedExport(node) {
    return (node.type === 'ExpressionStatement' &&
        node.expression.type === 'AssignmentExpression' &&
        node.expression.left.type === 'MemberExpression' &&
        node.expression.left.object.type === 'Identifier' &&
        node.expression.left.object.name === 'window');
}
function PreservedExportName(node) {
    const { property } = node;
    if (property.type === 'Identifier') {
        return property.name;
    }
    if (property.type === 'Literal' && typeof property.value === 'string') {
        return property.value;
    }
    return null;
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function PreserveFunction(code, source, ancestor, exportDetails, exportInline) {
    // Function Expressions can be inlined instead of preserved as variable references.
    // window['foo'] = function(){}; => export function foo(){} / function foo(){}
    const assignmentExpression = ancestor.expression;
    const memberExpression = assignmentExpression.left;
    const functionExpression = assignmentExpression.right;
    const [memberExpressionObjectStart] = memberExpression.object.range;
    const functionName = exportInline ? exportDetails.exported : exportDetails.local;
    if (functionExpression.params.length > 0) {
        const [paramsStart] = functionExpression.params[0].range;
        // FunctionExpression has parameters.
        source.overwrite(memberExpressionObjectStart, paramsStart, `${exportInline ? 'export ' : ''}function ${functionName}(`);
    }
    else {
        const [bodyStart] = functionExpression.body.range;
        source.overwrite(memberExpressionObjectStart, bodyStart, `${exportInline ? 'export ' : ''}function ${functionName}()`);
    }
    return !exportInline;
}
function PreserveIdentifier(code, source, ancestor, exportDetails, exportInline) {
    const assignmentExpression = ancestor.expression;
    const left = assignmentExpression.left;
    const right = assignmentExpression.right;
    const [ancestorStart, ancestorEnd] = ancestor.range;
    const [leftStart] = left.range;
    const [rightStart, rightEnd] = right.range;
    if (exportInline) {
        const output = (exportDetails.exported === 'default'
            ? `export default `
            : `export var ${exportDetails.exported}=`) + `${code.substring(rightStart, rightEnd)};`;
        source.overwrite(ancestorStart, ancestorEnd, output);
    }
    else if (exportDetails.source === null && 'name' in right) {
        // This is a locally defined identifier with a name we can use.
        exportDetails.local = right.name;
        source.remove(leftStart, ancestorEnd);
        return true;
    }
    else {
        source.overwrite(ancestorStart, ancestorEnd, `var ${exportDetails.local}=${code.substring(rightStart, rightEnd)};`);
    }
    return !exportInline;
}
function PreserveNamedConstant(code, source, ancestor, exportDetails, exportInline) {
    const assignmentExpression = ancestor.expression;
    switch (assignmentExpression.right.type) {
        case 'FunctionExpression':
            return PreserveFunction(code, source, ancestor, exportDetails, exportInline);
        default:
            return PreserveIdentifier(code, source, ancestor, exportDetails, exportInline);
    }
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function PreserveDefault(code, source, ancestor, exportDetails, exportInline) {
    const assignmentExpression = ancestor.expression;
    const [leftStart] = assignmentExpression.left.range;
    const [rightStart] = assignmentExpression.right.range;
    source.overwrite(leftStart, rightStart, 'export default ');
    return false;
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_UNSPECIFIED = 'Providing the warning_level=VERBOSE compile option also requires a valid language_out compile option.';
const ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID = 'Providing the warning_level=VERBOSE and language_out=NO_TRANSPILE compile options will remove warnings.';
const OPTIONS_TO_REMOVE_FOR_CLOSURE = ['remove_strict_directive'];
/**
 * Checks if output format is ESM
 * @param outputOptions
 * @return boolean
 */
const isESMFormat = ({ format }) => format === 'esm' || format === 'es';
/**
 * Throw Errors if compile options will result in unexpected behaviour.
 * @param compileOptions
 */
function validateCompileOptions(compileOptions) {
    if ('warning_level' in compileOptions && compileOptions.warning_level === 'VERBOSE') {
        if (!('language_out' in compileOptions)) {
            throw new Error(ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_UNSPECIFIED);
        }
        if (compileOptions.language_out === 'NO_TRANSPILE') {
            throw new Error(ERROR_WARNINGS_ENABLED_LANGUAGE_OUT_INVALID);
        }
    }
}
/**
 * Normalize the compile options given by the user into something usable.
 * @param compileOptions
 */
function normalizeExternOptions(compileOptions) {
    validateCompileOptions(compileOptions);
    let externs = [];
    if ('externs' in compileOptions) {
        switch (typeof compileOptions.externs) {
            case 'boolean':
                externs = [];
                break;
            case 'string':
                externs = [compileOptions.externs];
                break;
            default:
                externs = compileOptions.externs;
                break;
        }
        delete compileOptions.externs;
    }
    if (compileOptions) {
        for (const optionToDelete of OPTIONS_TO_REMOVE_FOR_CLOSURE) {
            if (optionToDelete in compileOptions) {
                // @ts-ignore
                delete compileOptions[optionToDelete];
            }
        }
    }
    return [externs, compileOptions];
}
/**
 * Pluck the PluginOptions from the CompileOptions
 * @param compileOptions
 */
function pluckPluginOptions(compileOptions) {
    const pluginOptions = {};
    if (!compileOptions) {
        return pluginOptions;
    }
    for (const optionToDelete of OPTIONS_TO_REMOVE_FOR_CLOSURE) {
        if (optionToDelete in compileOptions) {
            // @ts-ignore
            pluginOptions[optionToDelete] = compileOptions[optionToDelete];
        }
    }
    return pluginOptions;
}
/**
 * Generate default Closure Compiler CompileOptions an author can override if they wish.
 * These must be derived from configuration or input sources.
 * @param transformers
 * @param options
 * @return derived CompileOptions for Closure Compiler
 */
const defaults = async (options, providedExterns, transformers) => {
    // Defaults for Rollup Projects are slightly different than Closure Compiler defaults.
    // - Users of Rollup tend to transpile their code before handing it to a minifier,
    // so no transpile is default.
    // - When Rollup output is set to "es|esm" it is expected the code will live in a ES Module,
    // so safely be more aggressive in minification.
    const transformerExterns = [];
    for (const transform of transformers || []) {
        const extern = transform.extern(options);
        if (extern !== null) {
            const writtenExtern = await writeTempFile(extern);
            transformerExterns.push(writtenExtern);
        }
    }
    return {
        language_out: 'NO_TRANSPILE',
        assume_function_wrapper: isESMFormat(options),
        warning_level: 'QUIET',
        module_resolution: 'NODE',
        externs: transformerExterns.concat(providedExterns),
    };
};
/**
 * Compile Options is the final configuration to pass into Closure Compiler.
 * defaultCompileOptions are overrideable by ones passed in directly to the plugin
 * but the js source and sourcemap are not overrideable, since this would break the output if passed.
 * @param compileOptions
 * @param outputOptions
 * @param code
 * @param transforms
 */
async function options (incomingCompileOptions, outputOptions, code, transforms) {
    const mapFile = await writeTempFile('', '', false);
    const [externs, compileOptions] = normalizeExternOptions({ ...incomingCompileOptions });
    const options = {
        ...(await defaults(outputOptions, externs, transforms)),
        ...compileOptions,
        js: await writeTempFile(code),
        create_source_map: mapFile,
    };
    return [options, mapFile];
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const EXTERN_OVERVIEW = `/**
* @fileoverview Externs built via derived configuration from Rollup or input code.
* @externs
*/`;
/**
 * This Transform will apply only if the Rollup configuration is for 'esm' output.
 *
 * In order to preserve the export statements:
 * 1. Create extern definitions for them (to keep them their names from being mangled).
 * 2. Insert additional JS referencing the exported names on the window scope
 * 3. After Closure Compilation is complete, replace the window scope references with the original export statements.
 */
class ExportTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'ExportTransform';
        this.originalExports = new Map();
        this.currentSourceExportCount = 0;
        /**
         * Store an export from a source into the originalExports Map.
         * @param mapping mapping of details from this declaration.
         */
        this.storeExport = (mapping) => mapping.forEach(map => {
            if (map.source === null) {
                this.currentSourceExportCount++;
                this.originalExports.set(map.local, map);
            }
            else {
                this.originalExports.set(map.exported, map);
            }
        });
    }
    static storeExportToAppend(collected, exportDetails) {
        const update = collected.get(exportDetails.source) || [];
        if (exportDetails.exported === exportDetails.local) {
            update.push(exportDetails.exported);
        }
        else {
            update.push(`${exportDetails.local} as ${exportDetails.exported}`);
        }
        collected.set(exportDetails.source, update);
        return collected;
    }
    async deriveExports(fileName, code) {
        const program = await parse(fileName, code);
        walk.simple(program, {
            ExportNamedDeclaration: (node) => {
                this.storeExport(NamedDeclaration(node, this.mangler.getName));
            },
            ExportDefaultDeclaration: (node) => {
                this.storeExport(DefaultDeclaration(node, this.mangler.getName));
            },
            ExportAllDeclaration: () => {
                // TODO(KB): This case `export * from "./import"` is not currently supported.
                this.context.error(new Error(`Rollup Plugin Closure Compiler does not support export all syntax for externals.`));
            },
        });
    }
    extern() {
        if (Array.from(this.originalExports.keys()).length > 0) {
            let output = EXTERN_OVERVIEW;
            for (const key of this.originalExports.keys()) {
                const value = this.originalExports.get(key);
                if (value.source !== null) {
                    output += `function ${value.exported}(){};\n`;
                }
            }
            return output;
        }
        return null;
    }
    /**
     * Before Closure Compiler modifies the source, we need to ensure it has window scoped
     * references to the named exports. This prevents Closure from mangling their names.
     * @param code source to parse, and modify
     * @param chunk OutputChunk from Rollup for this code.
     * @param id Rollup id reference to the source
     * @return modified input source with window scoped references.
     */
    async pre(fileName, source) {
        if (!isESMFormat(this.outputOptions)) {
            return super.pre(fileName, source);
        }
        const code = source.toString();
        await this.deriveExports(fileName, code);
        for (const key of this.originalExports.keys()) {
            const value = this.originalExports.get(key);
            // Remove export statements before Closure Compiler sees the code
            // This prevents CC from transpiling `export` statements when the language_out is set to a value
            // where exports were not part of the language.
            source.remove(...value.range);
            // Window scoped references for each key are required to ensure Closure Compilre retains the code.
            if (value.source === null) {
                source.append(`\nwindow['${value.local}'] = ${value.local};`);
            }
            else {
                source.append(`\nwindow['${value.exported}'] = ${value.exported};`);
            }
        }
        return source;
    }
    /**
     * After Closure Compiler has modified the source, we need to replace the window scoped
     * references we added with the intended export statements
     * @param code source post Closure Compiler Compilation
     * @return Promise containing the repaired source
     */
    async post(fileName, source) {
        if (!isESMFormat(this.outputOptions)) {
            return super.post(fileName, source);
        }
        const code = source.toString();
        const program = await parse(fileName, code);
        let collectedExportsToAppend = new Map();
        source.trimEnd();
        walk.ancestor(program, {
            // We inserted window scoped assignments for all the export statements during `preCompilation`
            // Now we need to find where Closure Compiler moved them, and restore the exports of their name.
            // ASTExporer Link: https://astexplorer.net/#/gist/94f185d06a4105d64828f1b8480bddc8/0fc5885ae5343f964d0cdd33c7d392a70cf5fcaf
            Identifier: (node, ancestors) => {
                if (node.name !== 'window') {
                    return;
                }
                for (const ancestor of ancestors) {
                    if (!NodeIsPreservedExport(ancestor)) {
                        continue;
                    }
                    // Can cast these since they were validated with the `NodeIsPreservedExport` test.
                    const expression = ancestor.expression;
                    const left = expression.left;
                    const exportName = PreservedExportName(left);
                    if (exportName !== null && this.originalExports.get(exportName)) {
                        const exportDetails = this.originalExports.get(exportName);
                        const exportIsLocal = exportDetails.source === null;
                        const exportInline = (exportIsLocal &&
                            this.currentSourceExportCount === 1 &&
                            exportDetails.local === exportDetails.exported) ||
                            exportDetails.exported === 'default';
                        switch (exportDetails.type) {
                            case ExportClosureMapping.NAMED_DEFAULT_FUNCTION:
                            case ExportClosureMapping.DEFAULT:
                                if (PreserveDefault(code, source, ancestor)) ;
                                break;
                            case ExportClosureMapping.NAMED_CONSTANT:
                                if (PreserveNamedConstant(code, source, ancestor, exportDetails, exportInline)) {
                                    collectedExportsToAppend = ExportTransform.storeExportToAppend(collectedExportsToAppend, exportDetails);
                                }
                                break;
                        }
                        if (!exportIsLocal) {
                            const [leftStart] = left.range;
                            const { 1: ancestorEnd } = ancestor.range;
                            source.remove(leftStart, ancestorEnd);
                        }
                        // An Export can only be processed once.
                        this.originalExports.delete(exportName);
                    }
                }
            },
        });
        for (const exportSource of collectedExportsToAppend.keys()) {
            const toAppend = collectedExportsToAppend.get(exportSource);
            if (toAppend && toAppend.length > 0) {
                const names = toAppend.join(',');
                if (exportSource === null) {
                    source.append(`export{${names}}`);
                }
                else {
                    source.prepend(`export{${names}}from'${exportSource}';`);
                }
            }
        }
        return source;
    }
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function literalName(literal) {
    return literal.value;
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function Specifiers(specifiers) {
    var _a;
    const returnable = {
        default: null,
        specific: [],
        local: [],
        namespace: false,
    };
    for (const specifier of specifiers) {
        returnable.local.push(specifier.local.name);
        switch (specifier.type) {
            case IMPORT_SPECIFIER:
                const { name: local } = specifier.local;
                const { name: imported } = ((_a = specifier) === null || _a === void 0 ? void 0 : _a.imported) || {
                    name: specifier.local,
                };
                if (local === imported) {
                    returnable.specific.push(local);
                }
                else {
                    returnable.specific.push(`${imported} as ${local}`);
                }
                break;
            case IMPORT_NAMESPACE_SPECIFIER:
                const { name: namespace } = specifier.local;
                returnable.specific.push(namespace);
                returnable.namespace = true;
                break;
            case IMPORT_DEFAULT_SPECIFIER:
                returnable.default = specifier.local.name;
                break;
        }
    }
    return returnable;
}
function FormatSpecifiers(specifiers, name) {
    const hasDefault = specifiers.default !== null;
    const hasNamespace = specifiers.namespace === true;
    const hasSpecifics = !hasNamespace && specifiers.specific.length > 0;
    const hasLocals = specifiers.local.length > 0;
    const includesFrom = hasNamespace || hasNamespace || hasSpecifics || hasLocals;
    let formatted = 'import';
    let values = [];
    if (hasDefault) {
        values.push(`${specifiers.default}`);
    }
    if (hasNamespace) {
        values.push(`* as ${specifiers.specific[0]}`);
    }
    if (hasSpecifics) {
        values.push(`{${specifiers.specific.join(',')}}`);
    }
    formatted += `${hasDefault || hasNamespace ? ' ' : ''}${values.join(',')}${hasSpecifics ? '' : ' '}${includesFrom ? 'from' : ''}'${name}';`;
    return formatted;
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const DYNAMIC_IMPORT_KEYWORD = 'import';
const DYNAMIC_IMPORT_REPLACEMENT = `import_${new Date().getMilliseconds()}`;
const HEADER$2 = `/**
* @fileoverview Externs built via derived configuration from Rollup or input code.
* This extern contains the external import names, to prevent compilation failures.
* @externs
*/
`;
/**
 * Locally within imports we always need a name
 * If value passed is not present in the mangler then use original name.
 * @param input
 * @param unmangle
 */
function getName$1(input, unmangle) {
    if (unmangle) {
        return unmangle(input) || input;
    }
    return input;
}
class ImportTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.importedExternalsSyntax = {};
        this.importedExternalsLocalNames = [];
        this.dynamicImportPresent = false;
        this.name = 'ImportTransform';
        /**
         * Before Closure Compiler modifies the source, we need to ensure external imports have been removed
         * since Closure will error out when it encounters them.
         * @param code source to parse, and modify
         * @return modified input source with external imports removed.
         */
        this.pre = async (fileName, source) => {
            const code = source.toString();
            let program = await parse(fileName, code);
            let dynamicImportPresent = false;
            let { mangler, importedExternalsSyntax, importedExternalsLocalNames } = this;
            await estreeWalker.asyncWalk(program, {
                enter: async function (node) {
                    if (isImportDeclaration(node)) {
                        const [importDeclarationStart, importDeclarationEnd] = node.range;
                        const originalName = literalName(node.source);
                        let specifiers = Specifiers(node.specifiers);
                        specifiers = {
                            ...specifiers,
                            default: mangler.getName(specifiers.default || '') || specifiers.default,
                            specific: specifiers.specific.map(specific => {
                                if (specific.includes(' as ')) {
                                    const split = specific.split(' as ');
                                    return `${getName$1(split[0])} as ${getName$1(split[1])}`;
                                }
                                return getName$1(specific);
                            }),
                            local: specifiers.local.map(local => getName$1(local)),
                        };
                        const unmangledName = getName$1(originalName);
                        importedExternalsSyntax[unmangledName] = FormatSpecifiers(specifiers, unmangledName);
                        importedExternalsLocalNames.push(...specifiers.local);
                        source.remove(importDeclarationStart, importDeclarationEnd);
                        this.skip();
                    }
                    if (isIdentifier(node)) {
                        const unmangled = mangler.getName(node.name);
                        if (unmangled) {
                            const [identifierStart, identifierEnd] = node.range;
                            source.overwrite(identifierStart, identifierEnd, unmangled);
                        }
                    }
                    if (isImportExpression(node)) {
                        const [dynamicImportStart, dynamicImportEnd] = node.range;
                        dynamicImportPresent = true;
                        // Rename the `import` method to something we can put in externs.
                        // CC doesn't understand dynamic import yet.
                        source.overwrite(dynamicImportStart, dynamicImportEnd, code
                            .substring(dynamicImportStart, dynamicImportEnd)
                            .replace(DYNAMIC_IMPORT_KEYWORD, DYNAMIC_IMPORT_REPLACEMENT));
                    }
                },
            });
            this.dynamicImportPresent = dynamicImportPresent;
            return source;
        };
    }
    /**
     * Generate externs for local names of external imports.
     * Otherwise, advanced mode compilation will fail since the reference is unknown.
     * @return string representing content of generated extern.
     */
    extern() {
        let extern = HEADER$2;
        if (this.importedExternalsLocalNames.length > 0) {
            for (const name of this.importedExternalsLocalNames) {
                extern += `function ${name}(){};\n`;
            }
        }
        if (this.dynamicImportPresent) {
            extern += `
/**
 * @param {string} path
 * @return {!Promise<?>}
 */
function ${DYNAMIC_IMPORT_REPLACEMENT}(path) { return Promise.resolve(path) };
window['${DYNAMIC_IMPORT_REPLACEMENT}'] = ${DYNAMIC_IMPORT_REPLACEMENT};`;
        }
        return extern === HEADER$2 ? null : extern;
    }
    /**
     * After Closure Compiler has modified the source, we need to re-add the external imports
     * @param code source post Closure Compiler Compilation
     * @return Promise containing the repaired source
     */
    async post(fileName, source) {
        const code = source.toString();
        const program = await parse(fileName, code);
        for (const importedExternalSyntax of Object.values(this.importedExternalsSyntax)) {
            source.prepend(importedExternalSyntax);
        }
        walk.simple(program, {
            Identifier(node) {
                if (node.name === DYNAMIC_IMPORT_REPLACEMENT) {
                    const [start, end] = node.range;
                    source.overwrite(start, end, DYNAMIC_IMPORT_KEYWORD);
                }
            },
        });
        return source;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Determines if Strict Mode should be removed from output.
 * @param pluginOptions
 * @param outputOptions
 * @param path
 */
function shouldRemoveStrictModeDeclarations(pluginOptions, outputOptions, path$1) {
    if ('remove_strict_directive' in pluginOptions) {
        const removeDirective = pluginOptions['remove_strict_directive'];
        return removeDirective === undefined || removeDirective === true;
    }
    const isESMOutput = !!(path$1 && path.extname(path$1) === '.mjs');
    return isESMFormat(outputOptions) || isESMOutput;
}
class StrictTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'StrictTransform';
    }
    /**
     * When outputting an es module, runtimes automatically apply strict mode conventions.
     * This means we can safely strip the 'use strict'; declaration from the top of the file.
     * @param code source following closure compiler minification
     * @return code after removing the strict mode declaration (when safe to do so)
     */
    async post(fileName, source) {
        const { file } = this.outputOptions;
        if (shouldRemoveStrictModeDeclarations(this.pluginOptions, this.outputOptions, file)) {
            const program = await parse(fileName, source.toString());
            walk.simple(program, {
                ExpressionStatement(node) {
                    const { type, value } = node.expression;
                    const range = node.range;
                    if (type === 'Literal' && value === 'use strict') {
                        source.remove(...range);
                    }
                },
            });
            return source;
        }
        return source;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class ConstTransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'ConstTransform';
    }
    /**
     * When outputting ES2017+ code there is neglagible differences between `const` and `let` for runtime performance.
     * So, we replace all usages of `const` with `let` to enable more variable folding.
     * @param code source following closure compiler minification
     * @return code after removing the strict mode declaration (when safe to do so)
     */
    async pre(fileName, source) {
        const code = source.toString();
        const program = await parse(fileName, code);
        walk.simple(program, {
            VariableDeclaration(node) {
                const [start, end] = node.range;
                if (node.kind === 'const') {
                    source.overwrite(start, end, code.substring(start, end).replace('const', 'let'));
                }
            },
        });
        return source;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class ASITransform extends ChunkTransform {
    constructor() {
        super(...arguments);
        this.name = 'ASITransform';
    }
    /**
     * Small reduction in semi-colons, removing from end of block statements.
     * @param source source following closure compiler minification
     */
    async post(fileName, source) {
        const code = source.toString();
        const program = await parse(fileName, code);
        if (program.body) {
            const lastStatement = program.body[program.body.length - 1];
            if (lastStatement) {
                const [start, end] = lastStatement.range;
                if (lastStatement.type === 'EmptyStatement') {
                    source.remove(start, end);
                }
                else {
                    const lastStatementSource = code.substring(start, end);
                    if (lastStatementSource.endsWith(';')) {
                        source.overwrite(start, end, code.substring(start, end - 1));
                    }
                }
            }
        }
        return source;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const TRANSFORMS = [
    HashbangRemoveTransform,
    // Acorn can parse content starting here
    ConstTransform,
    IifeTransform,
    CJSTransform,
    LiteralComputedKeys,
    StrictTransform,
    ExportTransform,
    ImportTransform,
    ASITransform,
    // Acorn cannot parse content starting here.
    HashbangApplyTransform,
];
/**
 * Instantiate transform class instances for the plugin invocation.
 * @param context Plugin context to bind for each transform instance.
 * @param requestedCompileOptions Originally requested compile options from configuration.
 * @param mangler Mangle instance used for this transform instance.
 * @param memory Ebbinghaus instance used to store information that could be lost from source.
 * @param inputOptions Rollup input options
 * @param outputOptions Rollup output options
 * @return Instantiated transform class instances for the given entry point.
 */
function create(context, requestedCompileOptions, mangler, memory, inputOptions, outputOptions) {
    const pluginOptions = pluckPluginOptions(requestedCompileOptions);
    return TRANSFORMS.map(transform => new transform(context, pluginOptions, mangler, memory, inputOptions, outputOptions));
}
/**
 * Run each transform's `preCompilation` phase.
 * @param code
 * @param chunk
 * @param transforms
 * @return source code following `preCompilation`
 */
async function preCompilation(source, chunk, transforms) {
    return await chunkLifecycle(chunk.fileName, 'PreCompilation', 'pre', source, transforms);
}
/**
 * Run each transform's `postCompilation` phase.
 * @param code
 * @param chunk
 * @param transforms
 * @return source code following `postCompilation`
 */
async function postCompilation(code, chunk, transforms) {
    return await chunkLifecycle(chunk.fileName, 'PostCompilation', 'post', code, transforms);
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const { getNativeImagePath, getFirstSupportedPlatform, } = require('google-closure-compiler/lib/utils.js');
var Platform;
(function (Platform) {
    Platform["NATIVE"] = "native";
    Platform["JAVA"] = "java";
    Platform["JAVASCRIPT"] = "javascript";
})(Platform || (Platform = {}));
const DEFAULT_PLATFORM_PRECEDENCE = [Platform.NATIVE, Platform.JAVA, Platform.JAVASCRIPT];
/**
 * Splits user `platform` option from compiler options object
 * returns new object containing options and preferred platform.
 * @param {CompileOptions} content - compiler options object
 * @return {Object}
 * @example in rollup.config.js
 *  compiler({
 *    platform: 'javascript',
 *  }),
 */
function filterContent(content) {
    let platform = '';
    if ('platform' in content && typeof content.platform === 'string') {
        platform = content.platform;
        delete content.platform;
    }
    return [content, platform];
}
/**
 * Reorders platform preferences based on configuration.
 * @param {String} platformPreference - preferred platform string
 * @return {Array}
 */
function orderPlatforms(platformPreference) {
    if (platformPreference === '') {
        return DEFAULT_PLATFORM_PRECEDENCE;
    }
    const index = DEFAULT_PLATFORM_PRECEDENCE.indexOf(platformPreference);
    const newPlatformPreferences = DEFAULT_PLATFORM_PRECEDENCE.splice(index, 1);
    return newPlatformPreferences.concat(DEFAULT_PLATFORM_PRECEDENCE);
}
/**
 * Run Closure Compiler and `postCompilation` Transforms on input source.
 * @param compileOptions Closure Compiler CompileOptions, normally derived from Rollup configuration
 * @param transforms Transforms to run rollowing compilation
 * @return Promise<string> source following compilation and Transforms.
 */
function compiler (compileOptions, chunk, transforms) {
    return new Promise((resolve, reject) => {
        const [config, platform] = filterContent(compileOptions);
        const instance = new googleClosureCompiler.compiler(config);
        const firstSupportedPlatform = getFirstSupportedPlatform(orderPlatforms(platform));
        if (firstSupportedPlatform !== Platform.JAVA) {
            // TODO(KB): Provide feedback on this API. It's a little strange to nullify the JAR_PATH
            // and provide a fake java path.
            instance.JAR_PATH = null;
            instance.javaPath = getNativeImagePath();
        }
        instance.run(async (exitCode, code, stdErr) => {
            if ('warning_level' in compileOptions &&
                compileOptions.warning_level === 'VERBOSE' &&
                stdErr !== '') {
                reject(new Error(`Google Closure Compiler ${stdErr}`));
            }
            else if (exitCode !== 0) {
                reject(new Error(`Google Closure Compiler exit ${exitCode}: ${stdErr}`));
            }
            else {
                const postCompiled = await postCompilation(code, chunk, transforms);
                resolve(postCompiled.code);
            }
        });
    });
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
 *
 * This transform will remove the hashbang (if present) and ask Ebbinghaus to remember if for after compilation.
 */
class HashbangTransform extends SourceTransform {
    constructor() {
        super(...arguments);
        this.name = 'HashbangTransform';
        this.transform = async (id, source) => {
            const stringified = source.trim().toString();
            const match = /^#!.*/.exec(stringified);
            if (!match) {
                return source;
            }
            this.memory.hashbang = match[0];
            source.remove(0, match[0].length);
            return source;
        };
    }
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const TRANSFORMS$1 = [HashbangTransform];
// Temporarily disabling many SourceTransforms, aligning for future release.
// ImportTransform, ExportTransform
/**
 * Instantiate transform class instances for the plugin invocation.
 * @param context Plugin context to bind for each transform instance.
 * @param requestedCompileOptions Originally requested compile options from configuration.
 * @param mangler Mangle instance used for this transform instance.
 * @param memory Ebbinghaus instance used to store information that could be lost from source.
 * @param inputOptions Rollup input options
 * @param outputOptions Rollup output options
 * @return Instantiated transform class instances for the given entry point.
 */
const create$1 = (context, requestedCompileOptions, mangler, memory, inputOptions, outputOptions) => TRANSFORMS$1.map(transform => new transform(context, {}, mangler, memory, inputOptions, outputOptions));
/**
 * Run each transform's `transform` lifecycle.
 * @param code
 * @param transforms
 * @return source code following `transform`
 */
async function transform(source, id, transforms) {
    return await sourceLifecycle(id, 'Transform', source, transforms);
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function createId(source) {
    const hash = crypto.createHash('sha1');
    hash.update(source);
    return 'f_' + hash.digest('hex');
}
function mangledValue(name, sourceId) {
    return `${name}_${sourceId}`;
}
class Mangle {
    constructor() {
        this.sourceToId = new Map();
        this.idToSource = new Map();
        this.nameToMangled = new Map();
        this.mangledToName = new Map();
        this.debug = () => {
            log('mangle state', {
                sourceToId: this.sourceToId,
                idToSource: this.idToSource,
                nameToMangled: this.nameToMangled,
                mangledToName: this.mangledToName,
            });
        };
        this.sourceId = (source) => {
            let uuid = this.sourceToId.get(source);
            if (!uuid) {
                this.sourceToId.set(source, (uuid = createId(source)));
                this.idToSource.set(uuid, source);
            }
            return uuid;
        };
        this.mangle = (name, sourceId) => {
            const mangled = mangledValue(name, sourceId);
            const stored = this.nameToMangled.get(name);
            if (stored && stored !== mangled) {
                console.log('SetIdentifier for Mangled Name more than once', { name, sourceId });
            }
            else {
                this.nameToMangled.set(name, mangled);
                this.mangledToName.set(mangled, name);
            }
            return mangled;
        };
        this.getMangledName = (originalName) => {
            return this.nameToMangled.get(originalName);
        };
        this.getName = (maybeMangledName) => {
            return this.mangledToName.get(maybeMangledName);
        };
        this.getSource = (sourceId) => {
            return this.idToSource.get(sourceId);
        };
        this.execute = async (source, program) => {
            const { getMangledName } = this;
            const mangleable = [new Set([...this.nameToMangled.keys()])];
            let insideNamedExport = false;
            await estreeWalker.asyncWalk(program, {
                enter: async function (node) {
                    const currentlyRewriteable = mangleable[mangleable.length - 1];
                    if (isBlockStatement(node)) {
                        mangleable.push(new Set(currentlyRewriteable));
                    }
                    if (isExportNamedDeclaration(node)) {
                        insideNamedExport = true;
                    }
                    if (!insideNamedExport && isVariableDeclarator(node) && isIdentifier(node.id)) {
                        currentlyRewriteable.delete(node.id.name);
                    }
                    if (isIdentifier(node) && currentlyRewriteable.has(node.name)) {
                        const [start, end] = node.range;
                        source.overwrite(start, end, getMangledName(node.name) || node.name);
                    }
                },
                leave: async function (node) {
                    if (isBlockStatement(node)) {
                        mangleable.pop();
                    }
                    if (isExportNamedDeclaration(node)) {
                        insideNamedExport = false;
                    }
                },
            });
        };
    }
}

/**
 * Copyright 2020 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Hermann Ebbinghaus is credited with discovering the forgetting curve.
 *
 * This class stores data the compiler would typically loose to the forgetting curve.
 * For instance:
 *  - original source contained a `hashbang`
 *  - original source used external imports
 *
 * This data can be used later to inform transforms following Closure Compiler.
 *
 * For more information, visit: https://en.wikipedia.org/wiki/Hermann_Ebbinghaus
 */
class Ebbinghaus {
    constructor() {
        this.hashbang = null;
    }
}

/**
 * Copyright 2018 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function closureCompiler(requestedCompileOptions = {}) {
    const mangler = new Mangle();
    const memory = new Ebbinghaus();
    let inputOptions;
    let context;
    let sourceTransforms;
    return {
        name: 'closure-compiler',
        options: options => (inputOptions = options),
        buildStart() {
            context = this;
            sourceTransforms = create$1(context, requestedCompileOptions, mangler, memory, inputOptions, {});
            if ('compilation_level' in requestedCompileOptions &&
                requestedCompileOptions.compilation_level === 'ADVANCED_OPTIMIZATIONS' &&
                Array.isArray(inputOptions.input)) {
                context.warn('Code Splitting with Closure Compiler ADVANCED_OPTIMIZATIONS is not currently supported.');
            }
        },
        transform: async (code, id) => {
            if (sourceTransforms.length > 0) {
                const output = await transform(code, id, sourceTransforms);
                return output || null;
            }
            return null;
        },
        renderChunk: async (code, chunk, outputOptions) => {
            mangler.debug();
            const renderChunkTransforms = create(context, requestedCompileOptions, mangler, memory, inputOptions, outputOptions);
            const preCompileOutput = (await preCompilation(code, chunk, renderChunkTransforms)).code;
            const [compileOptions, mapFile] = await options(requestedCompileOptions, outputOptions, preCompileOutput, renderChunkTransforms);
            try {
                return {
                    code: await compiler(compileOptions, chunk, renderChunkTransforms),
                    map: JSON.parse(await fs.promises.readFile(mapFile, 'utf8')),
                };
            }
            catch (error) {
                throw error;
            }
        },
    };
}

module.exports = closureCompiler;