<commit_before>var cp = require('child_process');

var $branch = function (cmd, args, prefix) {
    //return ['STUB-BRANCH-NAME'];
    try {
        // TODO THIS RELIES ON BASH SYNTAX AND IS NOT CROSS PLATFORM
        var branches = cp.execSync('git for-each-ref "refs/heads/' + prefix + '*" "--format=%(refname:short)"', {cwd: process.cwd()});
        return branches.toString().trim().split('\n');
    } catch (ex) {
        console.log(ex);
    }
};

var $commit = function () {
    //return ['STUB-COMMIT-ID'];
    return [];
};

var $path = function() {
    //return ['STUB-PATH'];
    return [];
};

module.exports = function (completer) {

    var cfg = completer.cmdConfig;

    cfg.cd = completer.$dirName;

    cfg.git = {
        add: ['-n', '-v', '--force', '-f', '--interactive', '-i', '--patch', '-p',
            '--edit', '-e', '--all', '--no-all', '--ignore-removal', '--no-ignore-removal', '--update', '-u',
            '--intent-to-add', '-N', '--refresh', '--ignore-errors', '--ignore-missing',
            '--', '<pathspec>'],
        branch: [

            '--color', '--color=<when>', '--no-color', '-r', '-a', '--list', '-v', '--abbrev=<length>', '--no-abbrev',
            '--column', '--column=<options>', '--no-column', '--merged', '--no-merged', '--contains', '<commit>', '<pattern>',

            '--set-upstream', '--track', '--no-track', '-l', '-f', '<branchname>', '<start-point>',

            '--set-upstream-to=<upstream>', '-u <upstream>', '--unset-upstream [<branchname>]', '-m', '-M', '-d', '-D', '-r', '--edit-description'

        ],
        /* Let's try to make a real case out of checkout */
        checkout: [
            '-q',
            '-f',
            '-m',
            $branch,
            $commit,
            {'--detach': [$branch, $commit]},
            {'-b': $branch},
            {'-B': $branch},
            {'--orphan': $branch},
            '--ours', '--theirs', '--conflict=<style>',
            {'--': $path},
            '-p', '--patch'
        ],
        clone: [],
        commit: [
            '-a',
            '--interactive',
            '--patch',
            '-s',
            '-v',
            '-u',
            '--amend',
            '--dry-run',
            '-c',
            '-C',
            '--fixup',
            '--squash',
            {'-F': completer.$fileName},
            '-m',
            '--reset-author',
            '--allow-empty',
            '--allow-empty-message',
            '--no-verify',
            '-e',
            '--author=',
            '--date=',
            '--cleanup=',
            '--status',
            '--no-status',
            '-i',
            '-o',
            '-S',
            {'--': completer.$fileName},
            completer.$fileName
        ],
        diff: [],
        fetch: [],
        grep: [],
        init: [],
        log: [],
        merge: [
                '--abort', '-n', '--stat', '--no-commit', '--squash', '--edit', '--no-edit', {'strategy': '$strategy'},
                {'-X': '$strategyOptions'}, '-S', '--rerere-autoupdate', '--no-rerere-autoupdate', '-m', '$commit', 'HEAD'
            ],
        mv: [],
        pull: [],
        push: [],
        rebase: [],
        reset: [],
        rm: [],
        show: [],
        stash: {
            list: '$logOptions',
            show: '$stash',
            drop: ['-q', '--quiet', '$stash'],
            pop: ['-q', '--quiet', '$stash', '--index'],
            apply: ['-q', '--quiet', '$stash', '--index'],
            branch: ['$branch', '$stash'],
            save: ['-p', '--patch', '-k', '--keep-index', '--no-keep-index', '-q', '--quiet', '-u', '--include-untracked', '-a', '--all'],
            clear: [],
            create: [],
            store: ['-m', '--message', '-q', '--quiet', '$commit']
        },
        status: [],
        help: [
            'add',
            'branch',
            'checkout',
            'clone',
            'commit',
            'diff',
            'fetch',
            'grep',
            'init',
            'log',
            'merge',
            'mv',
            'pull',
            'push',
            'rebase',
            'reset',
            'rm',
            'show',
            'status']
    };

};
<commit_msg>Added completions for rails<commit_after>var cp = require('child_process');

var $branch = function (cmd, args, prefix) {
    //return ['STUB-BRANCH-NAME'];
    try {
        // TODO THIS RELIES ON BASH SYNTAX AND IS NOT CROSS PLATFORM
        var branches = cp.execSync('git for-each-ref "refs/heads/' + prefix + '*" "--format=%(refname:short)"', {cwd: process.cwd()});
        return branches.toString().trim().split('\n');
    } catch (ex) {
        console.log(ex);
    }
};

var $commit = function () {
    //return ['STUB-COMMIT-ID'];
    return [];
};

var $path = function() {
    //return ['STUB-PATH'];
    return [];
};

module.exports = function (completer) {

    var cfg = completer.cmdConfig;

    cfg.cd = completer.$dirName;

    cfg.git = {
        add: ['-n', '-v', '--force', '-f', '--interactive', '-i', '--patch', '-p',
            '--edit', '-e', '--all', '--no-all', '--ignore-removal', '--no-ignore-removal', '--update', '-u',
            '--intent-to-add', '-N', '--refresh', '--ignore-errors', '--ignore-missing',
            '--', '<pathspec>'],
        branch: [

            '--color', '--color=<when>', '--no-color', '-r', '-a', '--list', '-v', '--abbrev=<length>', '--no-abbrev',
            '--column', '--column=<options>', '--no-column', '--merged', '--no-merged', '--contains', '<commit>', '<pattern>',

            '--set-upstream', '--track', '--no-track', '-l', '-f', '<branchname>', '<start-point>',

            '--set-upstream-to=<upstream>', '-u <upstream>', '--unset-upstream [<branchname>]', '-m', '-M', '-d', '-D', '-r', '--edit-description'

        ],
        /* Let's try to make a real case out of checkout */
        checkout: [
            '-q',
            '-f',
            '-m',
            $branch,
            $commit,
            {'--detach': [$branch, $commit]},
            {'-b': $branch},
            {'-B': $branch},
            {'--orphan': $branch},
            '--ours', '--theirs', '--conflict=<style>',
            {'--': $path},
            '-p', '--patch'
        ],
        clone: [],
        commit: [
            '-a',
            '--interactive',
            '--patch',
            '-s',
            '-v',
            '-u',
            '--amend',
            '--dry-run',
            '-c',
            '-C',
            '--fixup',
            '--squash',
            {'-F': completer.$fileName},
            '-m',
            '--reset-author',
            '--allow-empty',
            '--allow-empty-message',
            '--no-verify',
            '-e',
            '--author=',
            '--date=',
            '--cleanup=',
            '--status',
            '--no-status',
            '-i',
            '-o',
            '-S',
            {'--': completer.$fileName},
            completer.$fileName
        ],
        diff: [],
        fetch: [],
        grep: [],
        init: [],
        log: [],
        merge: [
                '--abort', '-n', '--stat', '--no-commit', '--squash', '--edit', '--no-edit', {'strategy': '$strategy'},
                {'-X': '$strategyOptions'}, '-S', '--rerere-autoupdate', '--no-rerere-autoupdate', '-m', '$commit', 'HEAD'
            ],
        mv: [],
        pull: [],
        push: [],
        rebase: [],
        reset: [],
        rm: [],
        show: [],
        stash: {
            list: '$logOptions',
            show: '$stash',
            drop: ['-q', '--quiet', '$stash'],
            pop: ['-q', '--quiet', '$stash', '--index'],
            apply: ['-q', '--quiet', '$stash', '--index'],
            branch: ['$branch', '$stash'],
            save: ['-p', '--patch', '-k', '--keep-index', '--no-keep-index', '-q', '--quiet', '-u', '--include-untracked', '-a', '--all'],
            clear: [],
            create: [],
            store: ['-m', '--message', '-q', '--quiet', '$commit']
        },
        status: [],
        help: [
            'add',
            'branch',
            'checkout',
            'clone',
            'commit',
            'diff',
            'fetch',
            'grep',
            'init',
            'log',
            'merge',
            'mv',
            'pull',
            'push',
            'rebase',
            'reset',
            'rm',
            'show',
            'status']
    };

    /**
     * Ruby on Rails
     */
    cfg.rails = {
        generate: [],
        console: [],
        server: [],
        dbconsole: [],
        'new': [],
        application: [],
        destroy: [],
        plugin: 'new',
        runner: []
    };
};
<|endoftext|>


<commit_before>var util = require('util');
var path = require('path');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var mkdirp = require('mkdirp');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');

var hasGit;

// Check if git is installed
try {
    which.sync('git');
    hasGit = true;
} catch (ex) {
    hasGit = false;
}

function GitResolver(decEndpoint, config, logger) {
    // Set template dir to the empty directory so that user templates are not run
    // This environment variable is not multiple config aware but it's not documented
    // anyway
    mkdirp.sync(config.storage.empty);
    process.env.GIT_TEMPLATE_DIR = config.storage.empty;
    process.env.GIT_SSL_NO_VERIFY = (!config.strictSsl).toString();
    process.env.GIT_TERMINAL_PROMPT = config.interactive ? '1' : '0';

    Resolver.call(this, decEndpoint, config, logger);

    if (!hasGit) {
        throw createError('git is not installed or not in the PATH', 'ENOGIT');
    }
}

util.inherits(GitResolver, Resolver);
mout.object.mixIn(GitResolver, Resolver);

// -----------------

GitResolver.prototype._hasNew = function (pkgMeta) {
    var oldResolution = pkgMeta._resolution || {};

    return this._findResolution()
    .then(function (resolution) {
        // Check if resolution types are different
        if (oldResolution.type !== resolution.type) {
            return true;
        }

        // If resolved to a version, there is new content if the tags are not equal
        if (resolution.type === 'version' && semver.neq(resolution.tag, oldResolution.tag)) {
            return true;
        }

        // As last check, we compare both commit hashes
        return resolution.commit !== oldResolution.commit;
    });
};

GitResolver.prototype._resolve = function () {
    var that = this;

    return this._findResolution()
    .then(function () {
        return that._checkout()
        // Always run cleanup after checkout to ensure that .git is removed!
        // If it's not removed, problems might arise when the "tmp" module attempts
        // to delete the temporary folder
        .fin(function () {
            return that._cleanup();
        });
    });
};

// -----------------

// Abstract functions that should be implemented by concrete git resolvers
GitResolver.prototype._checkout = function () {
    throw new Error('_checkout not implemented');
};

GitResolver.refs = function (source) {
    throw new Error('refs not implemented');
};

// -----------------

GitResolver.prototype._findResolution = function (target) {
    var err;
    var self = this.constructor;
    var that = this;

    target = target || this._target || '*';

    // Target is a commit, so it's a stale target (not a moving target)
    // There's nothing to do in this case
    if ((/^[a-f0-9]{40}$/).test(target)) {
        this._resolution = { type: 'commit', commit: target };
        return Q.resolve(this._resolution);
    }

    // Target is a range/version
    if (semver.validRange(target)) {
        return self.versions(this._source, true)
        .then(function (versions) {
            var versionsArr,
                version,
                index;

            versionsArr = versions.map(function (obj) { return obj.version; });

            // If there are no tags and target is *,
            // fallback to the latest commit on master
            if (!versions.length && target === '*') {
                return that._findResolution('master');
            }

            versionsArr = versions.map(function (obj) { return obj.version; });
            // Find a satisfying version, enabling strict match so that pre-releases
            // have lower priority over normal ones when target is *
            index = semver.maxSatisfyingIndex(versionsArr, target, true);
            if (index !== -1) {
                version = versions[index];
                return that._resolution = { type: 'version', tag: version.tag, commit: version.commit };
            }

            // Check if there's an exact branch/tag with this name as last resort
            return Q.all([
                self.branches(that._source),
                self.tags(that._source)
            ])
            .spread(function (branches, tags) {
                // Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
                if (mout.object.hasOwn(tags, target)) {
                    return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
                }
                if (mout.object.hasOwn(branches, target)) {
                    return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
                }

                throw createError('No tag found that was able to satisfy ' + target, 'ENORESTARGET', {
                    details: !versions.length ?
                        'No versions found in ' + that._source :
                        'Available versions in ' + that._source + ': ' + versions.map(function (version) { return version.version; }).join(', ')
                });
            });
        });
    }

    // Otherwise, target is either a tag or a branch
    return Q.all([
        self.branches(that._source),
        self.tags(that._source)
    ])
    .spread(function (branches, tags) {
        // Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
        if (mout.object.hasOwn(tags, target)) {
            return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
        }
        if (mout.object.hasOwn(branches, target)) {
            return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
        }

        if ((/^[a-f0-9]{4,40}$/).test(target)) {
            if (target.length < 12) {
                that._logger.warn(
                    'short-sha',
                    'Consider using longer commit SHA to avoid conflicts'
                );
            }

            that._resolution = { type: 'commit', commit: target };
            return that._resolution;
        }

        branches = Object.keys(branches);
        tags = Object.keys(tags);

        err = createError('Tag/branch ' + target + ' does not exist', 'ENORESTARGET');
        err.details = !tags.length ?
                'No tags found in ' + that._source :
                'Available tags: ' + tags.join(', ');
        err.details += '\n';
        err.details += !branches.length ?
                'No branches found in ' + that._source :
                'Available branches: ' + branches.join(', ');

        throw err;
    });
};

GitResolver.prototype._cleanup = function () {
    var gitFolder = path.join(this._tempDir, '.git');

    return Q.nfcall(rimraf, gitFolder);
};

GitResolver.prototype._savePkgMeta = function (meta) {
    var version;

    if (this._resolution.type === 'version') {
        version = semver.clean(this._resolution.tag);

        // Warn if the package meta version is different than the resolved one
        if (typeof meta.version === 'string' && semver.neq(meta.version, version)) {
            this._logger.warn('mismatch', 'Version declared in the json (' + meta.version + ') is different than the resolved one (' + version + ')', {
                resolution: this._resolution,
                pkgMeta: meta
            });
        }

        // Ensure package meta version is the same as the resolution
        meta.version = version;
    } else {
        // If resolved to a target that is not a version,
        // remove the version from the meta
        delete meta.version;
    }

    // Save version/tag/commit in the release
    // Note that we can't store branches because _release is supposed to be
    // an unique id of this ref.
    meta._release = version ||
                    this._resolution.tag ||
                    this._resolution.commit.substr(0, 10);

    // Save resolution to be used in hasNew later
    meta._resolution = this._resolution;

    return Resolver.prototype._savePkgMeta.call(this, meta);
};

// ------------------------------

GitResolver.versions = function (source, extra) {
    var value = this._cache.versions.get(source);

    if (value) {
        return Q.resolve(value)
        .then(function () {
            var versions = this._cache.versions.get(source);

            // If no extra information was requested,
            // resolve simply with the versions
            if (!extra) {
                versions = versions.map(function (version) {
                    return version.version;
                });
            }

            return versions;
        }.bind(this));
    }

    value = this.tags(source)
    .then(function (tags) {
        var tag;
        var version;
        var versions = [];

        // For each tag
        for (tag in tags) {
            version = semver.clean(tag);
            if (version) {
                versions.push({ version: version, tag: tag, commit: tags[tag] });
            }
        }

        // Sort them by DESC order
        versions.sort(function (a, b) {
            return semver.rcompare(a.version, b.version);
        });

        this._cache.versions.set(source, versions);

        // Call the function again to keep it DRY
        return this.versions(source, extra);
    }.bind(this));

    // Store the promise to be reused until it resolves
    // to a specific value
    this._cache.versions.set(source, value);

    return value;
};

GitResolver.tags = function (source) {
    var value = this._cache.tags.get(source);

    if (value) {
        return Q.resolve(value);
    }

    value = this.refs(source)
    .then(function (refs) {
        var tags = {};

        // For each line in the refs, match only the tags
        refs.forEach(function (line) {
            var match = line.match(/^([a-f0-9]{40})\s+refs\/tags\/(\S+)/);

            if (match && !mout.string.endsWith(match[2], '^{}')) {
                tags[match[2]] = match[1];
            }
        });

        this._cache.tags.set(source, tags);

        return tags;
    }.bind(this));

    // Store the promise to be reused until it resolves
    // to a specific value
    this._cache.tags.set(source, value);

    return value;
};

GitResolver.branches = function (source) {
    var value = this._cache.branches.get(source);

    if (value) {
        return Q.resolve(value);
    }

    value = this.refs(source)
    .then(function (refs) {
        var branches = {};

        // For each line in the refs, extract only the heads
        // Organize them in an object where keys are branches and values
        // the commit hashes
        refs.forEach(function (line) {
            var match = line.match(/^([a-f0-9]{40})\s+refs\/heads\/(\S+)/);

            if (match) {
                branches[match[2]] = match[1];
            }
        });

        this._cache.branches.set(source, branches);

        return branches;
    }.bind(this));

    // Store the promise to be reused until it resolves
    // to a specific value
    this._cache.branches.set(source, value);

    return value;
};

GitResolver.clearRuntimeCache = function () {
    // Reset cache for branches, tags, etc
    mout.object.forOwn(GitResolver._cache, function (lru) {
        lru.reset();
    });
};

GitResolver._cache = {
    branches: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
    tags: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
    versions: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
    refs: new LRU({ max: 50, maxAge: 5 * 60 * 1000 })
};

module.exports = GitResolver;
<commit_msg>Improve handling of non-semver versions in git resolver (#2316)<commit_after>var util = require('util');
var path = require('path');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var mkdirp = require('mkdirp');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');

var hasGit;

// Check if git is installed
try {
    which.sync('git');
    hasGit = true;
} catch (ex) {
    hasGit = false;
}

function GitResolver(decEndpoint, config, logger) {
    // Set template dir to the empty directory so that user templates are not run
    // This environment variable is not multiple config aware but it's not documented
    // anyway
    mkdirp.sync(config.storage.empty);
    process.env.GIT_TEMPLATE_DIR = config.storage.empty;
    process.env.GIT_SSL_NO_VERIFY = (!config.strictSsl).toString();
    process.env.GIT_TERMINAL_PROMPT = config.interactive ? '1' : '0';

    Resolver.call(this, decEndpoint, config, logger);

    if (!hasGit) {
        throw createError('git is not installed or not in the PATH', 'ENOGIT');
    }
}

util.inherits(GitResolver, Resolver);
mout.object.mixIn(GitResolver, Resolver);

// -----------------

GitResolver.prototype._hasNew = function (pkgMeta) {
    var oldResolution = pkgMeta._resolution || {};

    return this._findResolution()
    .then(function (resolution) {
        // Check if resolution types are different
        if (oldResolution.type !== resolution.type) {
            return true;
        }

        // If resolved to a version, there is new content if the tags are not equal
        if (resolution.type === 'version' && semver.neq(resolution.tag, oldResolution.tag)) {
            return true;
        }

        // As last check, we compare both commit hashes
        return resolution.commit !== oldResolution.commit;
    });
};

GitResolver.prototype._resolve = function () {
    var that = this;

    return this._findResolution()
    .then(function () {
        return that._checkout()
        // Always run cleanup after checkout to ensure that .git is removed!
        // If it's not removed, problems might arise when the "tmp" module attempts
        // to delete the temporary folder
        .fin(function () {
            return that._cleanup();
        });
    });
};

// -----------------

// Abstract functions that should be implemented by concrete git resolvers
GitResolver.prototype._checkout = function () {
    throw new Error('_checkout not implemented');
};

GitResolver.refs = function (source) {
    throw new Error('refs not implemented');
};

// -----------------

GitResolver.prototype._findResolution = function (target) {
    var err;
    var self = this.constructor;
    var that = this;

    target = target || this._target || '*';

    // Target is a commit, so it's a stale target (not a moving target)
    // There's nothing to do in this case
    if ((/^[a-f0-9]{40}$/).test(target)) {
        this._resolution = { type: 'commit', commit: target };
        return Q.resolve(this._resolution);
    }

    // Target is a range/version
    if (semver.validRange(target)) {
        return self.versions(this._source, true)
        .then(function (versions) {
            var versionsArr,
                version,
                index;

            versionsArr = versions.map(function (obj) { return obj.version; });

            // If there are no tags and target is *,
            // fallback to the latest commit on master
            if (!versions.length && target === '*') {
                return that._findResolution('master');
            }

            versionsArr = versions.map(function (obj) { return obj.version; });
            // Find a satisfying version, enabling strict match so that pre-releases
            // have lower priority over normal ones when target is *
            index = semver.maxSatisfyingIndex(versionsArr, target, true);
            if (index !== -1) {
                version = versions[index];
                return that._resolution = { type: 'version', tag: version.tag, commit: version.commit };
            }

            // Check if there's an exact branch/tag with this name as last resort
            return Q.all([
                self.branches(that._source),
                self.tags(that._source)
            ])
            .spread(function (branches, tags) {
                // Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
                if (mout.object.hasOwn(tags, target)) {
                    return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
                }
                if (mout.object.hasOwn(branches, target)) {
                    return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
                }

                throw createError('No tag found that was able to satisfy ' + target, 'ENORESTARGET', {
                    details: !versions.length ?
                        'No versions found in ' + that._source :
                        'Available versions in ' + that._source + ': ' + versions.map(function (version) { return version.version; }).join(', ')
                });
            });
        });
    }

    // Otherwise, target is either a tag or a branch
    return Q.all([
        self.branches(that._source),
        self.tags(that._source)
    ])
    .spread(function (branches, tags) {
        // Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
        if (mout.object.hasOwn(tags, target)) {
            return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
        }
        if (mout.object.hasOwn(branches, target)) {
            return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
        }

        if ((/^[a-f0-9]{4,40}$/).test(target)) {
            if (target.length < 12) {
                that._logger.warn(
                    'short-sha',
                    'Consider using longer commit SHA to avoid conflicts'
                );
            }

            that._resolution = { type: 'commit', commit: target };
            return that._resolution;
        }

        branches = Object.keys(branches);
        tags = Object.keys(tags);

        err = createError('Tag/branch ' + target + ' does not exist', 'ENORESTARGET');
        err.details = !tags.length ?
                'No tags found in ' + that._source :
                'Available tags: ' + tags.join(', ');
        err.details += '\n';
        err.details += !branches.length ?
                'No branches found in ' + that._source :
                'Available branches: ' + branches.join(', ');

        throw err;
    });
};

GitResolver.prototype._cleanup = function () {
    var gitFolder = path.join(this._tempDir, '.git');

    return Q.nfcall(rimraf, gitFolder);
};

GitResolver.prototype._savePkgMeta = function (meta) {
    var version;

    if (this._resolution.type === 'version') {
        version = semver.clean(this._resolution.tag);

        // Warn if the package meta version is different than the resolved one
        if (typeof meta.version === 'string' && semver.valid(meta.version) && semver.neq(meta.version, version)) {
            this._logger.warn('mismatch', 'Version declared in the json (' + meta.version + ') is different than the resolved one (' + version + ')', {
                resolution: this._resolution,
                pkgMeta: meta
            });
        }

        // Ensure package meta version is the same as the resolution
        meta.version = version;
    } else {
        // If resolved to a target that is not a version,
        // remove the version from the meta
        delete meta.version;
    }

    // Save version/tag/commit in the release
    // Note that we can't store branches because _release is supposed to be
    // an unique id of this ref.
    meta._release = version ||
                    this._resolution.tag ||
                    this._resolution.commit.substr(0, 10);

    // Save resolution to be used in hasNew later
    meta._resolution = this._resolution;

    return Resolver.prototype._savePkgMeta.call(this, meta);
};

// ------------------------------

GitResolver.versions = function (source, extra) {
    var value = this._cache.versions.get(source);

    if (value) {
        return Q.resolve(value)
        .then(function () {
            var versions = this._cache.versions.get(source);

            // If no extra information was requested,
            // resolve simply with the versions
            if (!extra) {
                versions = versions.map(function (version) {
                    return version.version;
                });
            }

            return versions;
        }.bind(this));
    }

    value = this.tags(source)
    .then(function (tags) {
        var tag;
        var version;
        var versions = [];

        // For each tag
        for (tag in tags) {
            version = semver.clean(tag);
            if (version) {
                versions.push({ version: version, tag: tag, commit: tags[tag] });
            }
        }

        // Sort them by DESC order
        versions.sort(function (a, b) {
            return semver.rcompare(a.version, b.version);
        });

        this._cache.versions.set(source, versions);

        // Call the function again to keep it DRY
        return this.versions(source, extra);
    }.bind(this));

    // Store the promise to be reused until it resolves
    // to a specific value
    this._cache.versions.set(source, value);

    return value;
};

GitResolver.tags = function (source) {
    var value = this._cache.tags.get(source);

    if (value) {
        return Q.resolve(value);
    }

    value = this.refs(source)
    .then(function (refs) {
        var tags = {};

        // For each line in the refs, match only the tags
        refs.forEach(function (line) {
            var match = line.match(/^([a-f0-9]{40})\s+refs\/tags\/(\S+)/);

            if (match && !mout.string.endsWith(match[2], '^{}')) {
                tags[match[2]] = match[1];
            }
        });

        this._cache.tags.set(source, tags);

        return tags;
    }.bind(this));

    // Store the promise to be reused until it resolves
    // to a specific value
    this._cache.tags.set(source, value);

    return value;
};

GitResolver.branches = function (source) {
    var value = this._cache.branches.get(source);

    if (value) {
        return Q.resolve(value);
    }

    value = this.refs(source)
    .then(function (refs) {
        var branches = {};

        // For each line in the refs, extract only the heads
        // Organize them in an object where keys are branches and values
        // the commit hashes
        refs.forEach(function (line) {
            var match = line.match(/^([a-f0-9]{40})\s+refs\/heads\/(\S+)/);

            if (match) {
                branches[match[2]] = match[1];
            }
        });

        this._cache.branches.set(source, branches);

        return branches;
    }.bind(this));

    // Store the promise to be reused until it resolves
    // to a specific value
    this._cache.branches.set(source, value);

    return value;
};

GitResolver.clearRuntimeCache = function () {
    // Reset cache for branches, tags, etc
    mout.object.forOwn(GitResolver._cache, function (lru) {
        lru.reset();
    });
};

GitResolver._cache = {
    branches: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
    tags: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
    versions: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
    refs: new LRU({ max: 50, maxAge: 5 * 60 * 1000 })
};

module.exports = GitResolver;
<|endoftext|>


<commit_before>/* eslint-disable no-case-declarations, no-restricted-modules */

const os = require('os');
const path = require('path');
const cluster = require('cluster');
const EventEmitter = require('events').EventEmitter;
const oe = require('obj-ease');

let time;
let timeEnd;

class Core extends EventEmitter {
    constructor(config, log) {
        super();
        /* istanbul ignore next */
        config = config || {};
        this.log = log;
        this.db = {};
        this.queries = {};
        this.views = {};
        this.viewsEnv = {};
        /* istanbul ignore next */
        this.name = config.name || 'db';
        /* istanbul ignore next */
        this.backend = config.backend || 'file';
        /* istanbul ignore next */
        this.scriptTimeout = config.scriptTimeout || 10000;

        /* istanbul ignore else */
        if (config.verbosity === 'debug') {
            time = console.time;
            timeEnd = console.timeEnd;
        } else {
            time = () => {};
            timeEnd = () => {};
        }

        require(path.join(__dirname, 'backend', this.backend + '.js'))(this);

        this.workers = [];

        /* istanbul ignore next */
        this.numWorkers = config.workers || os.cpus().length;

        cluster.setupMaster({
            exec: path.join(__dirname, 'worker.js'),
            args: [],
            silent: false
        });

        cluster.on('online', worker => {
            this.workers.push(worker);
            log.info('worker ' + worker.process.pid + ' is online');
            worker.on('message', msg => {
                switch (msg.type) {
                    case 'view':
                        if (msg.payload === '') {
                            delete this.views[msg.id];
                        } else {
                            this.views[msg.id] = msg.payload;
                        }
                        this.emit('view', msg.id, msg.payload);
                        break;

                    /* istanbul ignore next */
                    default:
                }
            });
            /* istanbul ignore next */
            worker.on('disconnect', () => {
                log.error('worker', worker.process.pid, 'died');
                log.error('exiting');
                process.exit(1); // eslint-disable-line unicorn/no-process-exit
            });
            if (this.workers.length === this.numWorkers) {
                log.info('all workers ready');
                startInit();
            }
        });

        const env = {};

        /* istanbul ignore else */
        if (config.verbosity === 'debug') {
            env.DEBUG = '1';
        }
        /* istanbul ignore else */
        if (config.scriptTimeout !== 'undefined') {
            env.SCRIPT_TIMEOUT = config.scriptTimeout;
        }
        for (let i = 0; i < this.numWorkers; i++) {
            cluster.fork(env);
        }

        this.on('update', (id, payload) => {
            this.workers.forEach(worker => {
                time('sending update to worker ' + worker.process.pid);
                worker.send({type: 'update', id, payload, rev: this.rev});
                timeEnd('sending update to worker ' + worker.process.pid);
            });
        });

        this.on('ready', () => {
            startInit();
        });

        let eventCount = 0;
        function startInit() {
            eventCount += 1;
            if (eventCount === 2) {
                init();
            }
        }

        const that = this;

        function init() {
            that.workers.forEach(worker => {
                time('sending db to worker ' + worker.process.pid);
                worker.send({type: 'db', db: that.db, rev: that.rev});
                timeEnd('sending db to worker ' + worker.process.pid);
            });
            Object.keys(that.queries).forEach(id => {
                that.query(id, that.queries[id], true);
            });
            log.info('init complete');
        }
    }

    getRev(id) {
        if (this.db[id] && (typeof this.db[id]._rev !== 'undefined')) {
            const rev = this.db[id]._rev;
            delete this.db[id]._rev;
            return rev;
        }
        return -1;
    }

    setRev(id, rev) {
        /* istanbul ignore else */
        if (this.db[id]) {
            this.db[id]._rev = rev;
        }
    }

    incRev(id, rev) {
        /* istanbul ignore else */
        if (this.db[id]) {
            this.db[id]._rev = rev + 1;
        }
    }

    set(id, payload) {
        if (payload === '') {
            return this.del(id);
        }
        /* istanbul ignore if */
        if (typeof payload !== 'object') {
            return false;
        }
        delete payload._rev;
        delete payload._id;
        const rev = this.getRev(id);
        if (this.db[id]) {
            delete this.db[id]._id;
        }
        if (!oe.equal(this.db[id], payload)) {
            this.db[id] = payload;
            this.db[id]._id = id;
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.db[id]._id = id;

        this.setRev(id, rev);
        return false;
    }

    prop(id, payload) {
        let rev;
        switch (payload.method) {
            case 'set':
                rev = this.getRev(id);
                if (oe.setProp(this.db[id], payload.prop, payload.val)) {
                    delete this.db[id]._id;
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                this.setRev(id, rev);
                return false;

            case 'create':
                if (typeof oe.getProp(this.db[id], payload.prop) === 'undefined') {
                    rev = this.getRev(id);
                    oe.setProp(this.db[id], payload.prop, payload.val);
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                return false;

            case 'del':
                rev = this.getRev(id);
                /* istanbul ignore else */
                if (oe.delProp(this.db[id], payload.prop)) {
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                /* istanbul ignore next */
                this.setRev(id, rev);
                /* istanbul ignore next */
                return false;

            /* istanbul ignore next */
            default:
        }
    }

    extend(id, payload) {
        /* istanbul ignore if */
        if (!payload || typeof payload !== 'object') {
            return false;
        }
        const rev = this.getRev(id);
        delete payload._id;
        delete payload._rev;
        if (this.db[id]) {
            delete this.db[id]._id;
        } else {
            this.db[id] = {};
        }
        const change = oe.extend(this.db[id], payload);
        this.db[id]._id = id;
        if (change) {
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.setRev(id, rev);
        return false;
    }

    del(id) {
        delete this.db[id];
        this.emit('update', id, '');
    }

    getWorker() {
        this.nextWorker = this.nextWorker || 0;
        const id = this.nextWorker;
        this.nextWorker += 1;
        /* istanbul ignore if */
        if (this.nextWorker >= this.numWorkers) {
            this.nextWorker = 0;
        }
        return id;
    }

    query(id, payload, init) {
        if (!this.viewsEnv[id]) {
            this.viewsEnv[id] = {};
        }
        let workerId;
        if (!this.viewsEnv[id] || typeof this.viewsEnv[id].worker === 'undefined' || this.viewsEnv[id].worker >= this.numWorkers) {
            workerId = this.getWorker();
            this.viewsEnv[id].worker = workerId;
        } else {
            workerId = this.viewsEnv[id].worker;
        }

        if (payload === '') {
            delete this.queries[id];
            delete this.viewsEnv[id];
            delete this.views[id];
        } else {
            this.queries[id] = payload;
        }

        if (!init) {
            this.emit('query');
        }

        this.log.debug('sending query', id, 'to worker', workerId, payload);
        this.workers[workerId].send({type: 'query', id, payload, init: init ? this.views[id] : undefined});
    }

}

module.exports = Core;
<commit_msg>debug output on msg from worker<commit_after>/* eslint-disable no-case-declarations, no-restricted-modules */

const os = require('os');
const path = require('path');
const cluster = require('cluster');
const EventEmitter = require('events').EventEmitter;
const oe = require('obj-ease');

let time;
let timeEnd;

class Core extends EventEmitter {
    constructor(config, log) {
        super();
        /* istanbul ignore next */
        config = config || {};
        this.log = log;
        this.db = {};
        this.queries = {};
        this.views = {};
        this.viewsEnv = {};
        /* istanbul ignore next */
        this.name = config.name || 'db';
        /* istanbul ignore next */
        this.backend = config.backend || 'file';
        /* istanbul ignore next */
        this.scriptTimeout = config.scriptTimeout || 10000;

        /* istanbul ignore else */
        if (config.verbosity === 'debug') {
            time = console.time;
            timeEnd = console.timeEnd;
        } else {
            time = () => {};
            timeEnd = () => {};
        }

        require(path.join(__dirname, 'backend', this.backend + '.js'))(this);

        this.workers = [];

        /* istanbul ignore next */
        this.numWorkers = config.workers || os.cpus().length;

        cluster.setupMaster({
            exec: path.join(__dirname, 'worker.js'),
            args: [],
            silent: false
        });

        cluster.on('online', worker => {
            this.workers.push(worker);
            log.info('worker ' + worker.process.pid + ' is online');
            worker.on('message', msg => {
                log.debug('message from worker', worker.process.pid, msg.type, msg.id);
                switch (msg.type) {
                    case 'view':
                        if (msg.payload === '') {
                            delete this.views[msg.id];
                        } else {
                            this.views[msg.id] = msg.payload;
                        }
                        this.emit('view', msg.id, msg.payload);
                        break;

                    /* istanbul ignore next */
                    default:
                }
            });
            /* istanbul ignore next */
            worker.on('disconnect', () => {
                log.error('worker', worker.process.pid, 'died');
                log.error('exiting');
                process.exit(1); // eslint-disable-line unicorn/no-process-exit
            });
            if (this.workers.length === this.numWorkers) {
                log.info('all workers ready');
                startInit();
            }
        });

        const env = {};

        /* istanbul ignore else */
        if (config.verbosity === 'debug') {
            env.DEBUG = '1';
        }
        /* istanbul ignore else */
        if (config.scriptTimeout !== 'undefined') {
            env.SCRIPT_TIMEOUT = config.scriptTimeout;
        }
        for (let i = 0; i < this.numWorkers; i++) {
            cluster.fork(env);
        }

        this.on('update', (id, payload) => {
            this.workers.forEach(worker => {
                time('sending update to worker ' + worker.process.pid);
                worker.send({type: 'update', id, payload, rev: this.rev});
                timeEnd('sending update to worker ' + worker.process.pid);
            });
        });

        this.on('ready', () => {
            startInit();
        });

        let eventCount = 0;
        function startInit() {
            eventCount += 1;
            if (eventCount === 2) {
                init();
            }
        }

        const that = this;

        function init() {
            that.workers.forEach(worker => {
                time('sending db to worker ' + worker.process.pid);
                worker.send({type: 'db', db: that.db, rev: that.rev});
                timeEnd('sending db to worker ' + worker.process.pid);
            });
            Object.keys(that.queries).forEach(id => {
                that.query(id, that.queries[id], true);
            });
            log.info('init complete');
        }
    }

    getRev(id) {
        if (this.db[id] && (typeof this.db[id]._rev !== 'undefined')) {
            const rev = this.db[id]._rev;
            delete this.db[id]._rev;
            return rev;
        }
        return -1;
    }

    setRev(id, rev) {
        /* istanbul ignore else */
        if (this.db[id]) {
            this.db[id]._rev = rev;
        }
    }

    incRev(id, rev) {
        /* istanbul ignore else */
        if (this.db[id]) {
            this.db[id]._rev = rev + 1;
        }
    }

    set(id, payload) {
        if (payload === '') {
            return this.del(id);
        }
        /* istanbul ignore if */
        if (typeof payload !== 'object') {
            return false;
        }
        delete payload._rev;
        delete payload._id;
        const rev = this.getRev(id);
        if (this.db[id]) {
            delete this.db[id]._id;
        }
        if (!oe.equal(this.db[id], payload)) {
            this.db[id] = payload;
            this.db[id]._id = id;
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.db[id]._id = id;

        this.setRev(id, rev);
        return false;
    }

    prop(id, payload) {
        let rev;
        switch (payload.method) {
            case 'set':
                rev = this.getRev(id);
                if (oe.setProp(this.db[id], payload.prop, payload.val)) {
                    delete this.db[id]._id;
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                this.setRev(id, rev);
                return false;

            case 'create':
                if (typeof oe.getProp(this.db[id], payload.prop) === 'undefined') {
                    rev = this.getRev(id);
                    oe.setProp(this.db[id], payload.prop, payload.val);
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                return false;

            case 'del':
                rev = this.getRev(id);
                /* istanbul ignore else */
                if (oe.delProp(this.db[id], payload.prop)) {
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                /* istanbul ignore next */
                this.setRev(id, rev);
                /* istanbul ignore next */
                return false;

            /* istanbul ignore next */
            default:
        }
    }

    extend(id, payload) {
        /* istanbul ignore if */
        if (!payload || typeof payload !== 'object') {
            return false;
        }
        const rev = this.getRev(id);
        delete payload._id;
        delete payload._rev;
        if (this.db[id]) {
            delete this.db[id]._id;
        } else {
            this.db[id] = {};
        }
        const change = oe.extend(this.db[id], payload);
        this.db[id]._id = id;
        if (change) {
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.setRev(id, rev);
        return false;
    }

    del(id) {
        delete this.db[id];
        this.emit('update', id, '');
    }

    getWorker() {
        this.nextWorker = this.nextWorker || 0;
        const id = this.nextWorker;
        this.nextWorker += 1;
        /* istanbul ignore if */
        if (this.nextWorker >= this.numWorkers) {
            this.nextWorker = 0;
        }
        return id;
    }

    query(id, payload, init) {
        if (!this.viewsEnv[id]) {
            this.viewsEnv[id] = {};
        }
        let workerId;
        if (!this.viewsEnv[id] || typeof this.viewsEnv[id].worker === 'undefined' || this.viewsEnv[id].worker >= this.numWorkers) {
            workerId = this.getWorker();
            this.viewsEnv[id].worker = workerId;
        } else {
            workerId = this.viewsEnv[id].worker;
        }

        if (payload === '') {
            delete this.queries[id];
            delete this.viewsEnv[id];
            delete this.views[id];
        } else {
            this.queries[id] = payload;
        }

        if (!init) {
            this.emit('query');
        }

        this.log.debug('sending query', id, 'to worker', workerId, payload);
        this.workers[workerId].send({type: 'query', id, payload, init: init ? this.views[id] : undefined});
    }

}

module.exports = Core;
<|endoftext|>


<commit_before>'use strict';

var d           = require('es5-ext/lib/Object/descriptor')
  , forEach     = require('es5-ext/lib/Object/for-each')
  , contains    = require('es5-ext/lib/String/prototype/contains')
  , nextTick    = require('next-tick')
  , unserialize = require('./unserialize')
  , objects     = require('./objects')
  , signals     = require('./signal')

  , history = signals.history
  , create = objects._create, createObject = objects._createObject
  , defineProperties = Object.defineProperties

  , waiting = {}
  , byStamp = function (a, b) { return b._stamp - a._stamp; }

  , Imported, schedule, idsByStamp;

idsByStamp = function (a, b) { return signals[b]._stamp - signals[a]._stamp; };

Imported = function (sourceId, stamp) {
	defineProperties(this, {
		_sourceId: d(sourceId),
		_stamp: d(stamp),
		_order: d([])
	});
	signals[sourceId + ':' + stamp] = this;
};

schedule = (function () {
	var scheduled, process;
	process = function () {
		scheduled = false;
		forEach(waiting, function self(signalId, objId) {
			var signal, obj, value;
			delete waiting[objId];
			if (typeof signalId !== 'string') {
				signalId.sort(idsByStamp).forEach(function (signalId) {
					self(signalId, objId);
				});
				return;
			}
			signal = signals[signalId];
			if (history[objId]) {
				if (history[objId][0] && (history[objId][0]._stamp > signal._stamp)) {
					// Outdated update
					history[objId].unshift(signal);
					history[objId].sort(byStamp);
					return;
				}
			} else {
				history[objId] = [];
			}
			obj = objects[objId];
			value = signal[objId];
			if (contains.call(objId, ':')) {
				if (!obj) obj = create(objId);
				history[objId].unshift(signal);
				obj.$setValue(value);
			} else {
				if (!obj) obj = createObject(objId, value);
				history[objId].unshift(signal);
				obj.$proto(value);
			}
		});
	};
	return function () {
		if (scheduled) return;
		scheduled = true;
		nextTick(process);
	};
}());

//                         Where     When   Who    What  …no need to know Why
module.exports = function (sourceId, stamp, objId, value) {
	var id = sourceId + ':' + stamp
	  , signal = signals[sourceId + ':' + stamp];

	if (!signal) signal = new Imported(sourceId, stamp);

	// Unserialize value
	if (contains.call(objId, ':')) {
		value = unserialize(value);
	} else {
		value = value ? (objects[value] || createObject(value)) : undefined;
	}
	if (signal.hasOwnProperty(objId) && (signal[objId] === value)) return;

	signal[objId] = value;
	signal._order.push(value);

	// Set for dispatch and schedule for next tick
	if (waiting[objId]) {
		if (typeof waiting[objId] === 'string') waiting[objId] = [waiting[objId]];
		waiting[objId].push(id);
	} else {
		waiting[objId] = id;
	}
	schedule();
};
<commit_msg>Move string db import into dbjs internals<commit_after>'use strict';

var d                = require('es5-ext/lib/Object/descriptor')
  , forEach          = require('es5-ext/lib/Object/for-each')
  , contains         = require('es5-ext/lib/String/prototype/contains')
  , nextTick         = require('next-tick')
  , getValueEndIndex = require('./get-value-end-index')
  , unserialize      = require('./unserialize')
  , objects          = require('./objects')
  , signals          = require('./signal')

  , history = signals.history
  , create = objects._create, createObject = objects._createObject
  , defineProperties = Object.defineProperties

  , isItemId = RegExp.prototype.test.bind(/^[\0-\-\/-\uffff]*:\d/)
  , waiting = {}
  , byStamp = function (a, b) { return b._stamp - a._stamp; }

  , Imported, schedule, idsByStamp;

idsByStamp = function (a, b) { return signals[b]._stamp - signals[a]._stamp; };

Imported = function (sourceId, stamp) {
	defineProperties(this, {
		_sourceId: d(sourceId),
		_stamp: d(stamp),
		_order: d([])
	});
	signals[sourceId + ':' + stamp] = this;
};

schedule = (function () {
	var scheduled, process;
	process = function () {
		scheduled = false;
		forEach(waiting, function self(signalId, objId) {
			var signal, obj, value;
			delete waiting[objId];
			if (typeof signalId !== 'string') {
				signalId.sort(idsByStamp).forEach(function (signalId) {
					self(signalId, objId);
				});
				return;
			}
			signal = signals[signalId];
			if (history[objId]) {
				if (history[objId][0] && (history[objId][0]._stamp > signal._stamp)) {
					// Outdated update
					history[objId].unshift(signal);
					history[objId].sort(byStamp);
					return;
				}
			} else {
				history[objId] = [];
			}
			obj = objects[objId];
			value = signal[objId];
			if (contains.call(objId, ':')) {
				if (!obj) obj = create(objId);
				history[objId].unshift(signal);
				obj.$setValue(value);
			} else {
				if (!obj) obj = createObject(objId, value);
				history[objId].unshift(signal);
				obj.$proto(value);
			}
		});
	};
	return function () {
		if (scheduled) return;
		scheduled = true;
		nextTick(process);
	};
}());

//                         Where     When   Who    What  …no need to know Why
module.exports = exports = function (sourceId, stamp, objId, value) {
	var id = sourceId + ':' + stamp
	  , signal = signals[sourceId + ':' + stamp];

	if (!signal) signal = new Imported(sourceId, stamp);

	// Unserialize value
	if (contains.call(objId, ':')) {
		value = unserialize(value);
	} else {
		value = value ? (objects[value] || createObject(value)) : undefined;
	}
	if (signal.hasOwnProperty(objId) && (signal[objId] === value)) return;

	signal[objId] = value;
	signal._order.push(value);

	// Set for dispatch and schedule for next tick
	if (waiting[objId]) {
		if (typeof waiting[objId] === 'string') waiting[objId] = [waiting[objId]];
		waiting[objId].push(id);
	} else {
		waiting[objId] = id;
	}
	schedule();
};

exports.fromString = function (str) {
	var data, i;

	data = str.split('.');
	data[1] = Number(data[1]);

	if (data.length === 4) {
		exports.apply(null, data);
		return;
	}

	str = str.slice(data[0].length + String(data[1]).length + 2);
	if (isItemId(str[0])) {
		// Set item property
		i = getValueEndIndex(str);
		exports(data[0], data[1], str.slice(0, i), str.slice(i + 2));
		return;
	}

	// '.' within value
	exports(data[0], data[1], data[2], str.slice(data[2].length + 1));
};

<|endoftext|>


<commit_before>/* @flow */

import * as t from "../index";

import {
  BINARY_OPERATORS,
  LOGICAL_OPERATORS,
  UNARY_OPERATORS,
  UPDATE_OPERATORS,
} from "../constants";

import defineType, {
  assertValueType,
  assertNodeType,
  assertEach,
  chain,
  assertOneOf,
} from "./index";

defineType("ArrayExpression", {
  fields: {
    elements: {
      validate: assertValueType("array")
    }
  },
  visitor: ["elements"],
  aliases: ["Expression"]
});

defineType("AssignmentExpression", {
  fields: {
    operator: {
      validate: assertValueType("string")
    },
    left: {
      validate: assertNodeType("LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Expression"]
});

defineType("BinaryExpression", {
  builder: ["operator", "left", "right"],
  fields: {
    operator: {
      validate: assertOneOf(...BINARY_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"]
});

defineType("Directive", {
  visitor: ["value"],
  fields: {
    value: {
      validate: assertNodeType("DirectiveLiteral")
    }
  }
});

defineType("DirectiveLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  }
});

defineType("BlockStatement", {
  builder: ["body", "directives"],
  visitor: ["directives", "body"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "Statement"]
});

defineType("BreakStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("CallExpression", {
  visitor: ["callee", "arguments"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: assertValueType("array")
    }
  },
  aliases: ["Expression"]
});

defineType("CatchClause", {
  visitor: ["param", "body"],
  fields: {
    param: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  },
  aliases: ["Scopable"]
});

defineType("ConditionalExpression", {
  visitor: ["test", "consequent", "alternate"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Expression")
    },
    alternate: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Expression", "Conditional"]
});

defineType("ContinueStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("DebuggerStatement", {
  aliases: ["Statement"]
});

defineType("DoWhileStatement", {
  visitor: ["test", "body"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  },
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"]
});

defineType("EmptyStatement", {
  aliases: ["Statement"]
});

defineType("ExpressionStatement", {
  visitor: ["expression"],
  fields: {
    expression: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Statement", "ExpressionWrapper"]
});

defineType("File", {
  builder: ["program", "comments", "tokens"],
  visitor: ["program"],
  fields: {
    program: {
      validate: assertNodeType("Program")
    }
  }
});

defineType("ForInStatement", {
  visitor: ["left", "right", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"],
  fields: {
    left: {
      validate: assertNodeType("VariableDeclaration", "LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("ForStatement", {
  visitor: ["init", "test", "update", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop"],
  fields: {
    init: {
      validate: assertNodeType("VariableDeclaration", "Expression"),
      optional: true
    },
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    update: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("FunctionDeclaration", {
  builder: ["id", "params", "body", "generator", "async"],
  visitor: ["id", "params", "body", "returnType", "typeParameters"],
  fields: {
    id: {
      validate: assertNodeType("Identifier")
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  aliases: [
    "Scopable",
    "Function",
    "BlockParent",
    "FunctionParent",
    "Statement",
    "Pureish",
    "Declaration"
  ]
});

defineType("FunctionExpression", {
  inherits: "FunctionDeclaration",
  aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Expression", "Pureish"],
  fields: {
    id: {
      validate: assertNodeType("Identifier"),
      optional: true
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  }
});

defineType("Identifier", {
  builder: ["name"],
  visitor: ["typeAnnotation"],
  aliases: ["Expression", "LVal"],
  fields: {
    name: {
      validate(node, key, val) {
        if (!t.isValidIdentifier(val)) {
          // todo
        }
      }
    }
  }
});

defineType("IfStatement", {
  visitor: ["test", "consequent", "alternate"],
  aliases: ["Statement", "Conditional"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Statement")
    },
    alternate: {
      optional: true,
      validate: assertNodeType("Statement")
    }
  }
});

defineType("LabeledStatement", {
  visitor: ["label", "body"],
  aliases: ["Statement"],
  fields: {
    label: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("StringLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NumericLiteral", {
  builder: ["value"],
  deprecatedAlias: "NumberLiteral",
  fields: {
    value: {
      validate: assertValueType("number")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NullLiteral", {
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("BooleanLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("boolean")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("RegExpLiteral", {
  builder: ["pattern", "flags"],
  deprecatedAlias: "RegexLiteral",
  aliases: ["Expression", "Literal"],
  fields: {
    pattern: {
      validate: assertValueType("string")
    },
    flags: {
      validate: assertValueType("string"),
      default: ""
    }
  }
});

defineType("LogicalExpression", {
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"],
  fields: {
    operator: {
      validate: assertOneOf(...LOGICAL_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  }
});

defineType("MemberExpression", {
  builder: ["object", "property", "computed"],
  visitor: ["object", "property"],
  aliases: ["Expression", "LVal"],
  fields: {
    object: {
      validate: assertNodeType("Expression")
    },
    property: {
      validate(node, key, val) {
        let expectedType = node.computed ? "Expression" : "Identifier";
        assertNodeType(expectedType)(node, key, val);
      }
    },
    computed: {
      default: false
    }
  }
});

defineType("NewExpression", {
  visitor: ["callee", "arguments"],
  aliases: ["Expression"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression")))
    }
  }
});

defineType("Program", {
  visitor: ["directives", "body"],
  builder: ["body", "directives"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "FunctionParent"]
});

defineType("ObjectExpression", {
  visitor: ["properties"],
  aliases: ["Expression"],
  fields: {
    properties: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("ObjectMethod", "ObjectProperty", "SpreadProperty")))
    }
  }
});

defineType("ObjectMethod", {
  builder: ["kind", "key", "params", "body", "computed"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("method", "get", "set")),
      default: "method"
    },
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  visitor: ["key", "params", "body", "decorators", "returnType", "typeParameters"],
  aliases: ["UserWhitespacable", "Function", "Scopable", "BlockParent", "FunctionParent", "Method"]
});

defineType("ObjectProperty", {
  builder: ["key", "value", "computed", "shorthand", "decorators"],
  fields: {
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    value: {
      validate: assertNodeType("Expression")
    },
    shorthand: {
      validate: assertValueType("boolean"),
      default: false
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator"))),
      optional: true
    }
  },
  visitor: ["key", "value", "decorators"],
  aliases: ["UserWhitespacable", "Property"]
});

defineType("RestElement", {
  visitor: ["argument", "typeAnnotation"],
  aliases: ["LVal"],
  fields: {
    argument: {
      validate: assertNodeType("LVal")
    }
  }
});

defineType("ReturnStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression"),
      optional: true
    }
  }
});

defineType("SequenceExpression", {
  visitor: ["expressions"],
  fields: {
    expressions: { validate: assertValueType("array") }
  },
  aliases: ["Expression"]
});

defineType("SwitchCase", {
  visitor: ["test", "consequent"],
  fields: {
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    consequent: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  }
});

defineType("SwitchStatement", {
  visitor: ["discriminant", "cases"],
  aliases: ["Statement", "BlockParent", "Scopable"],
  fields: {
    discriminant: {
      validate: assertNodeType("Expression")
    },
    cases: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("SwitchCase")))
    }
  }
});

defineType("ThisExpression", {
  aliases: ["Expression"]
});

defineType("ThrowStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression")
    }
  }
});

// todo: at least handler or finalizer should be set to be valid
defineType("TryStatement", {
  visitor: ["block", "handler", "finalizer"],
  aliases: ["Statement"],
  fields: {
    body: {
      validate: assertNodeType("BlockStatement")
    },
    handler: {
      optional: true,
      handler: assertNodeType("BlockStatement")
    },
    finalizer: {
      optional: true,
      validate: assertNodeType("BlockStatement")
    }
  }
});

defineType("UnaryExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UNARY_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["UnaryLike", "Expression"]
});

defineType("UpdateExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UPDATE_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["Expression"]
});

defineType("VariableDeclaration", {
  builder: ["kind", "declarations"],
  visitor: ["declarations"],
  aliases: ["Statement", "Declaration"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("var", "let", "const"))
    },
    declarations: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("VariableDeclarator")))
    }
  }
});

defineType("VariableDeclarator", {
  visitor: ["id", "init"],
  fields: {
    id: {
      validate: assertNodeType("LVal")
    },
    init: {
      optional: true,
      validate: assertNodeType("Expression")
    }
  }
});

defineType("WhileStatement", {
  visitor: ["test", "body"],
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement", "Statement")
    }
  }
});

defineType("WithStatement", {
  visitor: ["object", "body"],
  aliases: ["Statement"],
  fields: {
    object: {
      object: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  }
});
<commit_msg>Add ObjectMember abstract type<commit_after>/* @flow */

import * as t from "../index";

import {
  BINARY_OPERATORS,
  LOGICAL_OPERATORS,
  UNARY_OPERATORS,
  UPDATE_OPERATORS,
} from "../constants";

import defineType, {
  assertValueType,
  assertNodeType,
  assertEach,
  chain,
  assertOneOf,
} from "./index";

defineType("ArrayExpression", {
  fields: {
    elements: {
      validate: assertValueType("array")
    }
  },
  visitor: ["elements"],
  aliases: ["Expression"]
});

defineType("AssignmentExpression", {
  fields: {
    operator: {
      validate: assertValueType("string")
    },
    left: {
      validate: assertNodeType("LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Expression"]
});

defineType("BinaryExpression", {
  builder: ["operator", "left", "right"],
  fields: {
    operator: {
      validate: assertOneOf(...BINARY_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"]
});

defineType("Directive", {
  visitor: ["value"],
  fields: {
    value: {
      validate: assertNodeType("DirectiveLiteral")
    }
  }
});

defineType("DirectiveLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  }
});

defineType("BlockStatement", {
  builder: ["body", "directives"],
  visitor: ["directives", "body"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "Statement"]
});

defineType("BreakStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("CallExpression", {
  visitor: ["callee", "arguments"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: assertValueType("array")
    }
  },
  aliases: ["Expression"]
});

defineType("CatchClause", {
  visitor: ["param", "body"],
  fields: {
    param: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  },
  aliases: ["Scopable"]
});

defineType("ConditionalExpression", {
  visitor: ["test", "consequent", "alternate"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Expression")
    },
    alternate: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Expression", "Conditional"]
});

defineType("ContinueStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("DebuggerStatement", {
  aliases: ["Statement"]
});

defineType("DoWhileStatement", {
  visitor: ["test", "body"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  },
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"]
});

defineType("EmptyStatement", {
  aliases: ["Statement"]
});

defineType("ExpressionStatement", {
  visitor: ["expression"],
  fields: {
    expression: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Statement", "ExpressionWrapper"]
});

defineType("File", {
  builder: ["program", "comments", "tokens"],
  visitor: ["program"],
  fields: {
    program: {
      validate: assertNodeType("Program")
    }
  }
});

defineType("ForInStatement", {
  visitor: ["left", "right", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"],
  fields: {
    left: {
      validate: assertNodeType("VariableDeclaration", "LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("ForStatement", {
  visitor: ["init", "test", "update", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop"],
  fields: {
    init: {
      validate: assertNodeType("VariableDeclaration", "Expression"),
      optional: true
    },
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    update: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("FunctionDeclaration", {
  builder: ["id", "params", "body", "generator", "async"],
  visitor: ["id", "params", "body", "returnType", "typeParameters"],
  fields: {
    id: {
      validate: assertNodeType("Identifier")
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  aliases: [
    "Scopable",
    "Function",
    "BlockParent",
    "FunctionParent",
    "Statement",
    "Pureish",
    "Declaration"
  ]
});

defineType("FunctionExpression", {
  inherits: "FunctionDeclaration",
  aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Expression", "Pureish"],
  fields: {
    id: {
      validate: assertNodeType("Identifier"),
      optional: true
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  }
});

defineType("Identifier", {
  builder: ["name"],
  visitor: ["typeAnnotation"],
  aliases: ["Expression", "LVal"],
  fields: {
    name: {
      validate(node, key, val) {
        if (!t.isValidIdentifier(val)) {
          // todo
        }
      }
    }
  }
});

defineType("IfStatement", {
  visitor: ["test", "consequent", "alternate"],
  aliases: ["Statement", "Conditional"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Statement")
    },
    alternate: {
      optional: true,
      validate: assertNodeType("Statement")
    }
  }
});

defineType("LabeledStatement", {
  visitor: ["label", "body"],
  aliases: ["Statement"],
  fields: {
    label: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("StringLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NumericLiteral", {
  builder: ["value"],
  deprecatedAlias: "NumberLiteral",
  fields: {
    value: {
      validate: assertValueType("number")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NullLiteral", {
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("BooleanLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("boolean")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("RegExpLiteral", {
  builder: ["pattern", "flags"],
  deprecatedAlias: "RegexLiteral",
  aliases: ["Expression", "Literal"],
  fields: {
    pattern: {
      validate: assertValueType("string")
    },
    flags: {
      validate: assertValueType("string"),
      default: ""
    }
  }
});

defineType("LogicalExpression", {
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"],
  fields: {
    operator: {
      validate: assertOneOf(...LOGICAL_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  }
});

defineType("MemberExpression", {
  builder: ["object", "property", "computed"],
  visitor: ["object", "property"],
  aliases: ["Expression", "LVal"],
  fields: {
    object: {
      validate: assertNodeType("Expression")
    },
    property: {
      validate(node, key, val) {
        let expectedType = node.computed ? "Expression" : "Identifier";
        assertNodeType(expectedType)(node, key, val);
      }
    },
    computed: {
      default: false
    }
  }
});

defineType("NewExpression", {
  visitor: ["callee", "arguments"],
  aliases: ["Expression"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression")))
    }
  }
});

defineType("Program", {
  visitor: ["directives", "body"],
  builder: ["body", "directives"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "FunctionParent"]
});

defineType("ObjectExpression", {
  visitor: ["properties"],
  aliases: ["Expression"],
  fields: {
    properties: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("ObjectMethod", "ObjectProperty", "SpreadProperty")))
    }
  }
});

defineType("ObjectMethod", {
  builder: ["kind", "key", "params", "body", "computed"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("method", "get", "set")),
      default: "method"
    },
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  visitor: ["key", "params", "body", "decorators", "returnType", "typeParameters"],
  aliases: ["UserWhitespacable", "Function", "Scopable", "BlockParent", "FunctionParent", "Method", "ObjectMember"]
});

defineType("ObjectProperty", {
  builder: ["key", "value", "computed", "shorthand", "decorators"],
  fields: {
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    value: {
      validate: assertNodeType("Expression")
    },
    shorthand: {
      validate: assertValueType("boolean"),
      default: false
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator"))),
      optional: true
    }
  },
  visitor: ["key", "value", "decorators"],
  aliases: ["UserWhitespacable", "Property", "ObjectMember"]
});

defineType("RestElement", {
  visitor: ["argument", "typeAnnotation"],
  aliases: ["LVal"],
  fields: {
    argument: {
      validate: assertNodeType("LVal")
    }
  }
});

defineType("ReturnStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression"),
      optional: true
    }
  }
});

defineType("SequenceExpression", {
  visitor: ["expressions"],
  fields: {
    expressions: { validate: assertValueType("array") }
  },
  aliases: ["Expression"]
});

defineType("SwitchCase", {
  visitor: ["test", "consequent"],
  fields: {
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    consequent: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  }
});

defineType("SwitchStatement", {
  visitor: ["discriminant", "cases"],
  aliases: ["Statement", "BlockParent", "Scopable"],
  fields: {
    discriminant: {
      validate: assertNodeType("Expression")
    },
    cases: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("SwitchCase")))
    }
  }
});

defineType("ThisExpression", {
  aliases: ["Expression"]
});

defineType("ThrowStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression")
    }
  }
});

// todo: at least handler or finalizer should be set to be valid
defineType("TryStatement", {
  visitor: ["block", "handler", "finalizer"],
  aliases: ["Statement"],
  fields: {
    body: {
      validate: assertNodeType("BlockStatement")
    },
    handler: {
      optional: true,
      handler: assertNodeType("BlockStatement")
    },
    finalizer: {
      optional: true,
      validate: assertNodeType("BlockStatement")
    }
  }
});

defineType("UnaryExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UNARY_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["UnaryLike", "Expression"]
});

defineType("UpdateExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UPDATE_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["Expression"]
});

defineType("VariableDeclaration", {
  builder: ["kind", "declarations"],
  visitor: ["declarations"],
  aliases: ["Statement", "Declaration"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("var", "let", "const"))
    },
    declarations: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("VariableDeclarator")))
    }
  }
});

defineType("VariableDeclarator", {
  visitor: ["id", "init"],
  fields: {
    id: {
      validate: assertNodeType("LVal")
    },
    init: {
      optional: true,
      validate: assertNodeType("Expression")
    }
  }
});

defineType("WhileStatement", {
  visitor: ["test", "body"],
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement", "Statement")
    }
  }
});

defineType("WithStatement", {
  visitor: ["object", "body"],
  aliases: ["Statement"],
  fields: {
    object: {
      object: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  }
});
<|endoftext|>


<commit_before>'use strict';
var core = {};
var util = require('./util.js');
var fs = require('fs');
var mkdirp = require('mkdirp');
var config = require('nconf');
var winston = require('winston');
var CronJob = require('cron').CronJob;
var getLoggerSettings = function(consoleLog) {
  var settings = {
    transports: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ],
    exceptionHandlers: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ]
  };

  if (consoleLog) {
    settings.transports.push(new winston.transports.Console({colorize: true, timestamp: true}));
    settings.exceptionHandlers.push(new winston.transports.Console({colorize: true, timestamp: true}));
  }

  return settings;
};

var logger = new (winston.Logger)(getLoggerSettings(false));
var pluginPaths = [__dirname + '/plugin'];

var parseRecipeFile = function(path) {
  util.parseSettingFile(path, function(err, config){
    if (err) {
      logger.error("Error parsing your evac recipe file.");
      process.exit(1);
    }

    core.parseRecipeJson(config, function(err, message){
      if (err) {
        logger.error(message + " plugin - raise error.");
        logger.error(err);
        process.exit(1);
      }
    });
  });
};

var parseCronFile = function(path) {
  util.parseSettingFile(path, function(err, cronConfig){
    if (err) {
      logger.error("Error parsing your cron configuration file.");
      process.exit(1);
    }

    var jobs = [];

    for(var config in cronConfig) {
      var oneConfig = cronConfig[config];

      for (var schedule in oneConfig) {
        var job = new CronJob({
          cronTime: schedule,
          onTick: function() {
            parseRecipeFile(oneConfig[schedule]);
          },
          start: true
        });
      }
    }
  });
};

core.parseRecipeJson = function(recipe, callback) {
  if (!recipe.filter) {
    recipe.filter = {
      "through": {}
    };
  }

  var pluginType = ['out', 'filter', 'in'];
  var plugins = {};
  var args = {};

  pluginType.forEach(function(type) {
    for (var plugin in recipe[type]) {
      pluginPaths.forEach(function(path) {
        var pluginFileName = path + '/' + type + '/' + plugin.toLowerCase() + '.js';
        logger.debug("Search plugin : " + pluginFileName);

        if (fs.existsSync(pluginFileName)) {
          plugins[type] = require(pluginFileName);
          args[type] = recipe[type][plugin];
        }
      });
    }

    if (!plugins[type]) {
      callback("Plugin not found.", "Not defined.");
      return;
    }
  });

  try {
    plugins['in'].load(args['in'], function(err, outputs){
      if (err) {
        callback(err, "input");
        return;
      }

      logger.info("Input plugin called.");

      var filtering = function(passedWord, target) {
        plugins['filter'].filter(args['filter'], passedWord, function(err, output){
          if (err) {
            callback(err, "filter");
            return;
          }

          logger.info("Filter plugin called.");

          var nextTarget = target - 1;

          if (output == "") {
            logger.info("Filter plugin no output.");

            if (nextTarget == 0) {
              callback(false, null);
            } else {
              filtering(outputs[nextTarget - 1], nextTarget);
            }
          } else {
            plugins['out'].output(args['out'], output, function(err, output){
              if (err) {
                callback(err, "output");
                return;
              }

              logger.info("Output plugin received: " + output);
              logger.info("Output finished.");

              if (nextTarget == 0) {
                callback(false, null);
              } else {
                filtering(outputs[nextTarget - 1], nextTarget);
              }

              return;
            });
          }
        });
      };

      logger.info("Input filter passed %d words.", outputs.length);

      if (outputs.length == 0) {
        return;
      }

      filtering(outputs[outputs.length - 1], outputs.length);
    });
  } catch(e) {
    logger.error(e.message);
    callback(e, e.message);
    return;
  }
};

core.start = function(program, args) {
  mkdirp.sync(process.env.HOME + "/.evac");

  logger = new (winston.Logger)(getLoggerSettings(program.verbose));

  if (program.path) {
    pluginPaths.push(program.path);
  }

  if (program.cron) {
    // cron mode
    parseCronFile(args[0]);
  } else {
    // normal mode
    logger.info('Settings are loaded from a file :' + args[0]);
    parseRecipeFile(args[0]);
  }
}

module.exports = core;
<commit_msg>プラグイン毎の呼び出しをリファクタリング<commit_after>'use strict';
var core = {};
var util = require('./util.js');
var fs = require('fs');
var mkdirp = require('mkdirp');
var config = require('nconf');
var winston = require('winston');
var CronJob = require('cron').CronJob;
var getLoggerSettings = function(consoleLog) {
  var settings = {
    transports: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ],
    exceptionHandlers: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ]
  };

  if (consoleLog) {
    settings.transports.push(new winston.transports.Console({colorize: true, timestamp: true}));
    settings.exceptionHandlers.push(new winston.transports.Console({colorize: true, timestamp: true}));
  }

  return settings;
};

var logger = new (winston.Logger)(getLoggerSettings(false));
var pluginPaths = [__dirname + '/plugin'];

var parseRecipeFile = function(path) {
  util.parseSettingFile(path, function(err, config){
    if (err) {
      logger.error("Error parsing your evac recipe file.");
      process.exit(1);
    }

    core.parseRecipeJson(config, function(err, message){
      if (err) {
        logger.error(message + " plugin - raise error.");
        logger.error(err);
        process.exit(1);
      }
    });
  });
};

var parseCronFile = function(path) {
  util.parseSettingFile(path, function(err, cronConfig){
    if (err) {
      logger.error("Error parsing your cron configuration file.");
      process.exit(1);
    }

    var jobs = [];

    for(var config in cronConfig) {
      var oneConfig = cronConfig[config];

      for (var schedule in oneConfig) {
        var job = new CronJob({
          cronTime: schedule,
          onTick: function() {
            parseRecipeFile(oneConfig[schedule]);
          },
          start: true
        });
      }
    }
  });
};

core.parseRecipeJson = function(recipe, callback) {
  if (!recipe.filter) {
    recipe.filter = {
      "through": {}
    };
  }

  var pluginType = ['out', 'filter', 'in'];
  var plugins = {};
  var args = {};

  pluginType.forEach(function(type) {
    for (var plugin in recipe[type]) {
      pluginPaths.forEach(function(path) {
        var pluginFileName = path + '/' + type + '/' + plugin.toLowerCase() + '.js';
        logger.debug("Search plugin : " + pluginFileName);

        if (fs.existsSync(pluginFileName)) {
          plugins[type] = require(pluginFileName);
          args[type] = recipe[type][plugin];
        }
      });
    }

    if (!plugins[type]) {
      callback("Plugin not found.", "Not defined.");
      return;
    }
  });

  try {
    plugins['in'].load(args['in'], function(err, outputs){
      if (err) {
        callback(err, "input");
        return;
      }

      logger.info("Input plugin called.");

      var filtering = function() {
        var passedWord = outputs.shift();

        plugins['filter'].filter(args['filter'], passedWord, function(err, output){
          logger.info("Filter plugin received: " + passedWord);

          if (err) {
            callback(err, "filter");
            return;
          }

          logger.info("Filter plugin called.");

          if (output == "") {
            logger.info("Filter plugin no output.");

            if (outputs.length == 0) {
              callback(false, null);
            } else {
              filtering();
            }
          } else {
            plugins['out'].output(args['out'], output, function(err, output){
              if (err) {
                callback(err, "output");
                return;
              }

              logger.info("Output plugin received: " + output);
              logger.info("Output finished.");

              if (outputs.length == 0) {
                callback(false, null);
              } else {
                filtering();
              }

              return;
            });
          }
        });
      };

      logger.info("Input filter passed %d words.", outputs.length);

      if (outputs.length == 0) {
        return;
      }

      filtering();
    });
  } catch(e) {
    logger.error(e.message);
    callback(e, e.message);
    return;
  }
};

core.start = function(program, args) {
  mkdirp.sync(process.env.HOME + "/.evac");

  logger = new (winston.Logger)(getLoggerSettings(program.verbose));

  if (program.path) {
    pluginPaths.push(program.path);
  }

  if (program.cron) {
    // cron mode
    parseCronFile(args[0]);
  } else {
    // normal mode
    logger.info('Settings are loaded from a file :' + args[0]);
    parseRecipeFile(args[0]);
  }
}

module.exports = core;
<|endoftext|>