"use strict";
/**
 * @license
 * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * The complete set of authors may be found at
 * http://polymer.github.io/AUTHORS.txt
 * The complete set of contributors may be found at
 * http://polymer.github.io/CONTRIBUTORS.txt
 * Code distributed by Google as part of the polymer project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */
Object.defineProperty(exports, "__esModule", { value: true });
const escodegen = require("escodegen");
const estraverse_1 = require("estraverse");
const document_1 = require("../parser/document");
/**
 * estree.Node#type is one of around a hundred string literals. We don't have
 * a direct reference to the type that represents any of those string literals
 * though. We can get a reference by taking a Node and using the `typeof`
 * operator, and it doesn't need to be a real Node as all of this happens at
 * analysis time, and nothing happens at runtime.
 */
const __exampleNode = null;
class JavaScriptDocument extends document_1.ParsedDocument {
    constructor(from) {
        super(from);
        this.type = 'js';
        this.visitorSkips = new Map();
        this.parsedAsSourceType = from.parsedAsSourceType;
    }
    visit(visitors) {
        /**
         * Applies all visiting callbacks from `visitors`.
         */
        const applyScanners = (callbackName, node, parent) => {
            for (const visitor of visitors) {
                if (_shouldSkip(visitor, callbackName, node.type)) {
                    continue;
                }
                if (callbackName in visitor) {
                    // TODO(rictic): is there a maintainable way to enforce the
                    //     mapping between callback names and the types of the first
                    //     arg?
                    const result = visitor[callbackName](node, parent);
                    if (result) {
                        handleVisitorResult(result, callbackName, visitor, node.type);
                    }
                }
            }
        };
        // a visitor to break early, or to skip a subtree of the AST. We need to
        // track this ourselves because we're running all the visitors at once.
        const _shouldSkip = (visitor, callbackName, nodeType) => {
            const skipRecord = this.visitorSkips.get(visitor);
            if (!skipRecord) {
                return false;
            }
            if (callbackName === `enter${nodeType}`) {
                skipRecord.depth += 1;
                return true;
            }
            else if (callbackName === `leave${nodeType}`) {
                skipRecord.depth -= 1;
                if (skipRecord.depth === 0) {
                    this.visitorSkips.delete(visitor);
                    // Note that we don't `continue` here. This is deliberate so that
                    // we call the leave handler for the node where we started
                    // skipping.
                }
                else {
                    return true;
                }
            }
            else {
                return true;
            }
        };
        const handleVisitorResult = (visitorOption, callbackName, visitor, nodeType) => {
            switch (visitorOption) {
                case estraverse_1.VisitorOption.Remove:
                    throw new Error(`estraverse.VisitorOption.Remove not ` +
                        `supported by JavascriptDocument`);
                case estraverse_1.VisitorOption.Break:
                    visitors = visitors.filter((v) => v !== visitor);
                    break;
                case estraverse_1.VisitorOption.Skip:
                    if (callbackName.startsWith('leave')) {
                        throw new Error(`estraverse.VisitorOption.Skip was returned from ` +
                            `${callbackName} but it's not supported in a leave method`);
                    }
                    this.visitorSkips.set(visitor, { type: nodeType, depth: 1 });
                    break;
            }
        };
        estraverse_1.traverse(this.ast, {
            enter(node, parent) {
                applyScanners(`enter${node.type}`, node, parent);
            },
            leave(node, parent) {
                applyScanners(`leave${node.type}`, node, parent);
            },
            fallback: 'iteration',
        });
    }
    forEachNode(callback) {
        estraverse_1.traverse(this.ast, {
            enter(node, _parent) {
                callback(node);
            },
            fallback: 'iteration',
        });
    }
    _sourceRangeForNode(node) {
        if (!node || !node.loc) {
            return;
        }
        return {
            file: this.url,
            // Note: estree uses 1-indexed lines, but SourceRange uses 0 indexed.
            start: { line: (node.loc.start.line - 1), column: node.loc.start.column },
            end: { line: (node.loc.end.line - 1), column: node.loc.end.column }
        };
    }
    stringify(options) {
        options = options || {};
        const formatOptions = {
            comment: true,
            format: { indent: { style: '  ', adjustMultilineComment: true, base: 0 } }
        };
        if (options.indent != null) {
            formatOptions.format.indent.base = options.indent;
        }
        return escodegen.generate(this.ast, formatOptions) + '\n';
    }
}
exports.JavaScriptDocument = JavaScriptDocument;

//# sourceMappingURL=javascript-document.js.map
