Full Code of indutny/common-shake for AI

master 1d9104588c62 cached
15 files
48.7 KB
13.1k tokens
8 symbols
1 requests
Download .txt
Repository: indutny/common-shake
Branch: master
Commit: 1d9104588c62
Files: 15
Total size: 48.7 KB

Directory structure:
gitextract_xe61hc_u/

├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── README.md
├── lib/
│   ├── shake/
│   │   ├── analyzer.js
│   │   ├── eval.js
│   │   ├── graph.js
│   │   ├── module.js
│   │   └── walk.js
│   └── shake.js
├── package.json
└── test/
    ├── analyzer-test.js
    ├── eval-test.js
    ├── fixtures.js
    └── graph-test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.js
================================================
module.exports = {
  'env': {
    'browser': false,
    'commonjs': true,
    'node': true,
    'es6': true
  },
  'parserOptions': {
    'ecmaVersion': 8
  },
  'extends': 'eslint:recommended',
  'rules': {
    'indent': [
      'error',
      2,
      {
        'FunctionDeclaration': {
          'parameters': 'first'
        },
        'FunctionExpression': {
          'parameters': 'first'
        },
        'CallExpression': {
          'arguments': 'first'
        }
      }
    ],
    'linebreak-style': [
      'error',
      'unix'
    ],
    'quotes': [
      'error',
      'single'
    ],
    'semi': [
      'error',
      'always'
    ],
    'max-len': [
      'error',
      80,
      2
    ]
  }
};


================================================
FILE: .gitignore
================================================
node_modules/
npm-debug.log
.nyc_output/
coverage/


================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
node_js:
  - "stable"


================================================
FILE: README.md
================================================
# CommonJS Tree Shaker
[![NPM version](https://badge.fury.io/js/common-shake.svg)](http://badge.fury.io/js/common-shake)
[![Build Status](https://secure.travis-ci.org/indutny/common-shake.svg)](http://travis-ci.org/indutny/common-shake)

See [webpack-common-shake][0] for [webpack][1] plugin.

## Usage

```js
const acorn = require('acorn');
const Analyzer = require('common-shake').Analyzer;

const a = new Analyzer();

a.run(acorn.parse(`
  'use strict';
  const lib = require('./a.js');
  exports.a = lib.a;
`, { locations: true }), 'index.js');

a.run(acorn.parse(`
  'use strict';
  exports.a = 42;
`, { locations: true }), 'a.js');

a.resolve('index.js', './a.js', 'a.js');
console.log(a.isSuccess(), a.bailouts);
// true false

console.log(a.getModule('index.js').getInfo());
// { bailouts: false, declarations: [ 'a' ], uses: [] }

console.log(a.getModule('a.js').getInfo());
// { bailouts: false, declarations: [ 'a' ], uses: [ 'a' ] }

const module = a.getModule('a.js');
a.getDeclarations().forEach((decl) => {
  console.log(module.isUsed(decl.name) ? 'used' : 'not used');
  console.log(decl.name, decl.ast);
});

// If you want to mark all exported values of module as used:
a.getModule('root').forceExport();
```

## Graphviz

For debugging and inspection purposes a graph in [dot][2] format may be
generated from the modules hierarchy using following API:

```js
const Graph = require('common-shake').Graph;
const graph = new Graph('/path/to/working/dir');

console.log(graph.generate(analyzer.getModules()));
```

## LICENSE

This software is licensed under the MIT License.

Copyright Fedor Indutny, 2017.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

[0]: https://github.com/indutny/webpack-common-shake
[1]: https://webpack.github.io/
[2]: http://www.graphviz.org/content/dot-language


================================================
FILE: lib/shake/analyzer.js
================================================
'use strict';

const escope = require('escope');
const debug = require('debug')('common-shake:analyzer');

const shake = require('../shake');
const walk = shake.walk;
const Module = shake.Module;

function Analyzer() {
  // All `Module` instances by resource
  this.modules = new Map();

  // All unresolved `Module` instances by parent resource + path
  this.unresolved = new Map();

  // Uses of required module. Map from ast node to `Module` instance
  this.moduleUses = null;

  // Uses of `exports` in module. A collection of AST nodes.
  this.exportsUses = null;

  // Uses of `require` in module. A collection of AST nodes.
  this.requireUses = null;

  // Any global bailouts
  this.bailouts = false;
}
module.exports = Analyzer;

// Public API

Analyzer.prototype.run = function run(ast, resource) {
  this.requireUses = new Set();
  this.exportsUses = new Set();
  this.moduleUses = new Map();

  const current = this.getModule(resource);

  this.gather(ast, current);
  this.sift(ast, current);

  this.moduleUses = null;
  this.exportsUses = null;
  this.requireUses = null;

  return current;
};

Analyzer.prototype.resolve = function resolve(issuer, name, to) {
  debug('resolve %j:%j => %j', issuer, name, to);

  const unresolved = this.getUnresolvedModule(issuer, name);
  const resolved = this.getModule(to);

  // Already resolved
  if (unresolved === resolved)
    return;

  resolved.mergeFrom(unresolved);
  resolved.addIssuer(this.getModule(issuer));
  this.unresolved.get(issuer).set(name, to);
};

Analyzer.prototype.getModule = function getModule(resource) {
  let module;
  if (this.modules.has(resource)) {
    module = this.modules.get(resource);
  } else {
    module = new Module(resource);
    this.modules.set(resource, module);
  }
  return module;
};

Analyzer.prototype.getModules = function getModules() {
  return Array.from(this.modules.values());
};

Analyzer.prototype.isSuccess = function isSuccess() {
  return this.bailouts === false;
};

// Private API

Analyzer.prototype.gather = function gather(ast, current) {
  const manager = escope.analyze(ast, {
    ecmaVersion: 6,
    sourceType: 'module',
    optimistic: true,
    ignoreEval: true,
    impliedStrict: true
  });

  const scope = manager.acquireAll(ast);

  const declarations = [];

  const queue = scope.slice();
  while (queue.length !== 0) {
    const scope = queue.shift();

    for (let i = 0; i < scope.childScopes.length; i++)
      queue.push(scope.childScopes[i]);

    // Skip variables declared in dynamic scopes
    if (scope.dynamic)
      continue;

    for (let i = 0; i < scope.variables.length; i++)
      declarations.push(scope.variables[i]);
  }

  // Just to avoid double-bailouts
  const seenDefs = new Set();
  for (let i = 0; i < declarations.length; i++) {
    const decl = declarations[i];

    const defs = decl.defs.filter(def => this.isRequireDef(def, decl));
    if (defs.length === 0)
      continue;

    if (decl.defs.length !== 1) {
      defs.forEach((def) => {
        if (seenDefs.has(def.node))
          return;
        seenDefs.add(def.node);

        const name = def.node.init.arguments[0].value;
        const module = this.getUnresolvedModule(current.resource, name);

        module.bailout('`require` variable override', def.node.loc,
                       current.resource);
      });
      continue;
    }

    const node = defs[0].node;
    if (seenDefs.has(node))
      continue;
    seenDefs.add(node);

    const name = shake.evaluateConst(node.init.arguments[0]);
    const module = this.getUnresolvedModule(current.resource, name);

    // Destructuring
    if (node.id.type === 'ObjectPattern') {
      this.gatherDestructured(module, node.id, current);
      continue;
    }

    if (node.id.type !== 'Identifier') {
      module.bailout('`require` used in unknown way', node.loc,
                     current.resource);
      continue;
    }

    for (let i = 0; i < decl.references.length; i++) {
      const ref = decl.references[i];
      if (ref.identifier !== node.id)
        this.moduleUses.set(ref.identifier, module);
    }
  }
};

Analyzer.prototype.gatherDestructured = function gatherDestructured(module,
                                                                    id,
                                                                    current) {
  for (let i = 0; i < id.properties.length; i++) {
    const prop = id.properties[i];

    if (prop.key.type !== (prop.computed ? 'Literal' : 'Identifier')) {
      module.bailout('Dynamic properties in `require` destructuring', id.loc,
                     current.resource);
      continue;
    }

    const key = prop.key.name || prop.key.value;
    module.use(key, current, false);
  }
};

Analyzer.prototype.isRequireDef = function isRequireDef(def, decl) {
  if (def.type !== 'Variable')
    return false;

  const node = def.node;
  if (node.type !== 'VariableDeclarator')
    return false;

  if (node.id.type === 'Identifier') {
    if (node.id.name === 'exports') {
      this.markOverriddenUses(this.exportsUses, decl.references);
      return false;
    } else if (node.id.name === 'require') {
      this.markOverriddenUses(this.requireUses, decl.references);
      return false;
    }
  }

  const init = node.init;
  if (!init || init.type !== 'CallExpression')
    return false;

  if (init.callee.type !== 'Identifier' || init.callee.name !== 'require')
    return false;

  const args = init.arguments;
  if (args.length < 1)
    return false;

  try {
    shake.evaluateConst(args[0]);
  } catch (e) {
    return false;
  }

  // Overridden `require`
  if (this.requireUses.has(init.callee))
    return false;
  this.requireUses.add(init.callee);

  return true;
};

Analyzer.prototype.markOverriddenUses = function markOverriddenUses(set, refs) {
  for (let i = 0; i < refs.length; i++)
    set.add(refs[i].identifier);
};

Analyzer.prototype.isExports = function isExports(node) {
  // `exports`
  if (node.type === 'Identifier' && node.name === 'exports')
    return true;

  // `module.exports`
  if (node.type !== 'MemberExpression')
    return false;

  const isStatic =
      node.property.type === (node.computed ? 'Literal' : 'Identifier');
  if (!isStatic)
    return false;

  const key = node.property.name || node.property.value;
  return key === 'exports';
};

Analyzer.prototype.sift = function sift(ast, current) {
  walk(ast, {
    AssignmentExpression: node => this.siftAssignment(node, current),
    MemberExpression: node => this.siftMember(node, current, false),
    Identifier: node => this.siftRequireUse(node, current),
    CallExpression: node => this.siftCall(node, current),
    NewExpression: node => this.siftNew(node, current),
    UnaryExpression: node => this.siftUnaryExpression(node),
  });

  this.moduleUses.forEach((module, use) => {
    module.bailout('Escaping value or unknown use', use.loc, current.resource);
  });
};

Analyzer.prototype.siftAssignment = function siftAssignment(node, current) {
  if (node.left.type === 'Identifier') {
    if (node.left.name === 'exports') {
      if (this.exportsUses.has(node.left))
        return;
      this.exportsUses.add(node.left);

      current.bailout('`exports` assignment', node.loc);
      return;
    }
    if (node.left.name === 'require') {
      if (this.requireUses.has(node.left))
        return;

      this.requireUses.add(node.left);
      current.bailout('`require` assignment', node.loc);
      return;
    }
  }

  if (node.left.type !== 'MemberExpression')
    return;

  const member = node.left;

  if (this.moduleUses.has(member.object)) {
    const module = this.moduleUses.get(member.object);
    this.moduleUses.delete(member.object);
    module.bailout('Module property assignment', node.loc, current.resource);
    return;
  }

  const isExports = this.isExports(member.object);
  if (!isExports && member.object.type !== 'Identifier')
    return;

  const object = isExports ? 'exports' : member.object.name;
  if (object !== 'exports' && object !== 'module')
    return;

  if (member.property.type !== (member.computed ? 'Literal' : 'Identifier')) {
    if (object === 'exports') {
      if (this.exportsUses.has(member.object))
        return;
      this.exportsUses.add(member.object);

      current.bailout('Dynamic CommonJS export', member.loc);
    } else {
      current.bailout('Dynamic `module` use', member.loc);
    }
    return;
  }

  const name = member.property.name || member.property.value;

  if (object === 'module') {
    if (name !== 'exports')
      return;

    this.siftModuleExports(node, current);
    return;
  }

  if (this.exportsUses.has(member.object))
    return;
  this.exportsUses.add(member.object);

  // `exports.a = imported.b`
  if (node.right.type === 'MemberExpression')
    this.siftMember(node.right, current, name);

  const decl = {
    type: 'exports',
    name,
    ast: node
  };

  if (!current.declare(decl)) {
    current.bailout('Simultaneous assignment to both `exports` and ' +
                    '`module.exports`', node.loc);
  }
};

Analyzer.prototype.siftModuleExports = function siftModuleExports(node,
                                                                  current) {
  if (node.right.type !== 'ObjectExpression') {
    current.bailout('`module.exports` assignment', node.loc, null, 'info');
    return;
  }

  // `module.exports = {}`
  const props = node.right.properties;
  const pairs = [];
  for (let i = 0; i < props.length; i++) {
    const prop = props[i];

    if (prop.computed ||
        (prop.key.type !== 'Literal' && prop.key.type !== 'Identifier')) {
      current.bailout('Dynamic `module.exports` property', prop.loc, null);
      continue;
    }

    const key = prop.key.name || prop.key.value;

    // `module.exports = { a: imported.b }`
    if (prop.kind === 'init' && prop.value.type === 'MemberExpression')
      this.siftMember(prop.value, current, key);

    pairs.push({
      type: 'module.exports',
      name: key,
      ast: prop
    });
  }

  if (!current.multiDeclare(pairs)) {
    current.bailout('Simultaneous assignment to both `exports` and ' +
                    '`module.exports`', node.loc);
  }
};

Analyzer.prototype.siftMember = function siftMember(node, current, recursive) {
  let module;
  if (this.isExports(node.object)) {
    // Do not track assignments twice
    if (this.exportsUses.has(node.object))
      return;
    this.exportsUses.add(node.object);

    module = current;
  } else if (node.object.type === 'Identifier' &&
             node.object.name === 'require') {
    // It is ok to use `require` properties
    this.requireUses.add(node.object);
    return;
  } else if (this.moduleUses.has(node.object)) {
    module = this.moduleUses.get(node.object);
    this.moduleUses.delete(node.object);
  } else if (node.object.type === 'CallExpression') {
    module = this.siftRequireCall(node.object, current);
    if (!module)
      return;
  } else {
    return;
  }

  if (node.property.type !== (node.computed ? 'Literal' : 'Identifier')) {
    if (module === current) {
      module.bailout('Dynamic CommonJS use', node.loc);
    } else {
      const reason = 'Dynamic CommonJS import';
      module.bailout(reason, node.loc, current.resource);
    }
    return;
  }

  // TODO(indutny): build dependency tree for self-uses. They should not retain
  // themselves or others if unused.
  const prop = node.property.name || node.property.value;
  module.use(prop, current, recursive);

  // For recursive imports
  return { module, property: prop };
};

Analyzer.prototype.siftCall = function siftCall(node, current) {
  // `lib()`
  if (this.moduleUses.has(node.callee)) {
    const module = this.moduleUses.get(node.callee);
    this.moduleUses.delete(node.callee);
    module.bailout('Imported library call', node.loc, current.resource, 'info');
    return;
  }

  const module = this.siftRequireCall(node, current);
  if (!module)
    return;

  // TODO(indutny): support `var lib; lib = require('...')`
  module.bailout('Escaping `require` call', node.loc, current.resource);
};

Analyzer.prototype.siftNew = function siftNew(node, current) {
  // `new lib()`
  if (!this.moduleUses.has(node.callee))
    return;

  const module = this.moduleUses.get(node.callee);
  this.moduleUses.delete(node.callee);
  module.bailout('Imported library new call', node.loc, current.resource,
                 'info');
};

Analyzer.prototype.siftUnaryExpression = function siftUnaryExpression(node) {
  if (node.operator !== 'typeof')
    return false;

  // Mark `typeof require` as a valid use of `require`
  const argument = node.argument;
  if (argument.type !== 'Identifier' || argument.name !== 'require')
    return false;

  if (this.requireUses.has(argument))
    return false;
  this.requireUses.add(argument);
};

Analyzer.prototype.siftRequireCall = function siftRequireCall(node, current) {
  const callee = node.callee;
  if (callee.type !== 'Identifier' || callee.name !== 'require')
    return false;

  // Valid `require` use
  if (this.requireUses.has(callee))
    return false;
  this.requireUses.add(callee);

  const args = node.arguments;
  if (args.length < 1)
    return false;

  let arg;
  let fail = false;
  try {
    arg = shake.evaluateConst(args[0]);
  } catch (e) {
    fail = true;
  }

  if (fail || typeof arg !== 'string') {
    const msg = 'Dynamic argument of `require`';
    current.bailout(msg, node.loc);
    this.bailout(msg, node.loc, current.resource);
    return false;
  }

  // TODO(indutny): support `require('./lib')()`
  return this.getUnresolvedModule(current.resource, arg);
};

Analyzer.prototype.siftRequireUse = function siftRequireUse(node, current) {
  if (node.type !== 'Identifier' || node.name !== 'require')
    return;

  if (this.requireUses.has(node))
    return;
  this.requireUses.add(node);

  current.bailout('Invalid use of `require`', node.loc);
  this.bailout('Invalid use of `require`', node.loc, current.resource);
};

Analyzer.prototype.getUnresolvedModule = function getUnresolvedModule(issuer,
                                                                      name) {
  let issuerMap;
  if (this.unresolved.has(issuer)) {
    issuerMap = this.unresolved.get(issuer);
  } else {
    issuerMap = new Map();
    this.unresolved.set(issuer, issuerMap);
  }

  let module;
  if (issuerMap.has(name)) {
    module = issuerMap.get(name);
  } else {
    module = new Module(name);
    issuerMap.set(name, module);
  }

  // Already resolved
  if (typeof module === 'string')
    return this.getModule(module);

  return module;
};

Analyzer.prototype.bailout = function bailout(reason, loc, source) {
  if (this.bailouts)
    this.bailouts.push({ reason, loc, source });
  else
    this.bailouts = [ { reason, loc, source } ];
};


================================================
FILE: lib/shake/eval.js
================================================
'use strict';

function evaluateBinary(node) {
  const op = node.operator;

  const left = evaluateConst(node.left);
  const right = evaluateConst(node.right);

  if (op === '+')
    return left + right;

  throw new Error(`Unsupported binary operation: "${op}"`);
}

function evaluateConst(node) {
  if (node.type === 'Literal')
    return node.value;

  if (node.type === 'BinaryExpression')
    return evaluateBinary(node);

  throw new Error(`Unsupported node type: "${node.type}"`);
}
module.exports = evaluateConst;


================================================
FILE: lib/shake/graph.js
================================================
'use strict';

const path = require('path');

function Graph(dir) {
  this.dir = dir || '.';
  this.relativeCache = new Map();
}
module.exports = Graph;

Graph.prototype.generate = function generate(modules) {
  const seen = new Set();
  const queue = modules.slice();

  let out = 'digraph {\n';
  out += '  ranksep=1.2;\n';
  while (queue.length !== 0) {
    const module = queue.shift();

    if (seen.has(module))
      continue;
    seen.add(module);

    out += this.generateModule(module);
  }
  out += '}\n';
  return out;
};

Graph.prototype.relative = function relative(file) {
  file = file || '';
  if (this.relativeCache.has(file))
    return this.relativeCache.get(file);

  const relative = path.relative(this.dir, file);
  this.relativeCache.set(file, relative);
  return relative;
};

Graph.prototype.escape = function escape(str) {
  return `"${str.replace(/"/g, '\\"')}"`;
};

Graph.prototype.declarationId = function declarationId(module, name) {
  return this.escape(`{${this.relative(module.resource)}}[${name}]`);
};

Graph.prototype.moduleId = function moduleId(module) {
  return this.escape(`{${this.relative(module.resource)}}/require`);
};

Graph.prototype.generateModule = function generateModule(module) {
  const resource = this.escape('cluster://' + this.relative(module.resource));
  const label = this.escape(this.relative(module.resource));

  let out = '';

  const color = module.bailouts === false ? 'black' : 'red';
  let cluster = `  subgraph ${resource} {\n`;
  cluster += `    label=${label};\n`;
  cluster += `    color=${color};\n`;
  cluster += `    ${this.moduleId(module)} [label=require shape=diamond];\n`;

  const issuersSeen = new Set();
  const declarationsSeen = new Set();

  const declare = (name) => {
    const id = this.declarationId(module, name);
    if (declarationsSeen.has(name))
      return id;

    declarationsSeen.add(name);

    const color = module.bailouts === false ?
      module.isUsed(name) ? 'black' : 'blue' :
      'red';

    const shortId = this.escape(`${name}`);
    cluster += `    ${id} [label=${shortId} color=${color}];\n`;

    return id;
  };

  // Add all declarations
  module.declarations.forEach((declaration) => {
    declare(declaration.name);
  });

  // Add uses
  module.uses.forEach((issuers, name) => {
    issuers.forEach((issuer) => {
      issuersSeen.add(issuer);

      out += `  ${this.moduleId(issuer)} -> ${declare(name)};\n`;
    });
  });

  // Add dynamic issuer edges (without particular uses)
  module.issuers.forEach((issuer) => {
    if (issuersSeen.has(issuer))
      return;
    issuersSeen.add(issuer);

    out += `  ${this.moduleId(issuer)} -> ${declare('[*]')};\n`;
  });

  cluster += '  }\n';

  return cluster + out;
};


================================================
FILE: lib/shake/module.js
================================================
'use strict';

const debug = require('debug')('common-shake:module');

function Module(resource) {
  this.resource = resource;
  this.bailouts = false;
  this.issuers = new Set();
  this.uses = new Map();
  this.declarations = [];

  this.pendingUses = [];
  this.computing = false;
  this.forced = false;
}
module.exports = Module;

// Public API

Module.prototype.forceExport = function forceExport() {
  this.forced = true;
};

Module.prototype.isUsed = function isUsed(name) {
  this.compute();

  if (this.bailouts || this.forced)
    return true;

  // Detect loops
  if (this.computing) {
    const pending = this.pendingUses.some(use => use.property === name);
    if (pending)
      return true;
  }

  return this.uses.has(name);
};

Module.prototype.getInfo = function getInfo() {
  this.compute();

  return {
    bailouts: this.bailouts,
    declarations: this.declarations.map(decl => decl.name),
    uses: Array.from(this.uses.keys())
  };
};

Module.prototype.getDeclarations = function getDeclarations() {
  return this.declarations.slice();
};

// Private API

Module.prototype.bailout = function bailout(reason, loc, source, level) {
  const bail = {
    reason,
    loc,
    source: source || null,
    level: level || 'warning'
  };
  if (this.bailouts)
    this.bailouts.push(bail);
  else
    this.bailouts = [ bail ];
  this.sealed = false;
};

Module.prototype.use = function use(property, from, recursive) {
  if (recursive !== false) {
    debug('pending use this=%j property=%j from=%j recursive=%j',
          this.resource, property, from.resource, recursive);
    this.pendingUses.push({ property, from, recursive });
    return;
  }

  debug('use this=%j property=%j from=%j recursive=%j',
        this.resource, property, from.resource, recursive);

  if (this.uses.has(property))
    this.uses.get(property).add(from);
  else
    this.uses.set(property, new Set([ from ]));
};

Module.prototype.seal = function seal() {
  this.sealed = true;
};

Module.prototype.declare = function declare(property) {
  this.declarations.push(property);

  return !this.sealed;
};

Module.prototype.multiDeclare = function multiDeclare(declarations) {
  const success = this.declarations.length === 0 && !this.sealed;
  this.seal();
  for (let i = 0; i < declarations.length; i++)
    this.declarations.push(declarations[i]);
  return success;
};

Module.prototype.mergeFrom = function mergeFrom(unresolved) {
  if (unresolved.bailouts) {
    unresolved.bailouts.forEach((b) => {
      this.bailout(b.reason, b.loc, b.source, b.level);
    });
  }

  unresolved.uses.forEach((from, property) => {
    from.forEach(resource => this.use(property, resource, false));
  });
  unresolved.declarations.forEach(declaration => this.declare(declaration));
  this.pendingUses = this.pendingUses.concat(unresolved.pendingUses);

  unresolved.clear();
};

Module.prototype.addIssuer = function addIssuer(issuer) {
  this.issuers.add(issuer);
};

Module.prototype.clear = function clear() {
  this.uses = null;
  this.declarations = null;
  this.pendingUses = null;
};

Module.prototype.compute = function compute() {
  // Already computed or cleared
  if (this.pendingUses === null)
    return;

  if (this.computing)
    return;
  this.computing = true;
  debug('compute this=%j pending=%d', this.resource, this.pendingUses.length);

  // Do several passes until it will stabilize
  // TODO(indutny): what is complexity of this? Exponential?
  let before;
  do {
    before = this.pendingUses.length;

    // NOTE: it is important to overwrite this, since recursive lookups will
    // get to it.
    this.pendingUses = this.pendingUses.filter((use) => {
      return use.from.isUsed(use.recursive);
    });
    debug('compute pass this=%j before=%d after=%d',
          this.resource, before, this.pendingUses.length);
  } while (this.pendingUses.length !== before);

  this.pendingUses.forEach(use => this.use(use.property, use.from, false));

  this.pendingUses = null;
  this.computing = false;
};


================================================
FILE: lib/shake/walk.js
================================================
'use strict';

const walk = require('acorn/dist/walk');

const BASE = Object.assign({
  // acorn-dynamic-import support
  Import: () => {}
}, walk.base);

// Pre-order walker
module.exports = (node, visitors) => {
  const state = null;
  const override = false;
  !function c(node, st, override) {
    var type = override || node.type, found = visitors[type];
    if (found) found(node, st);
    BASE[type](node, st, c);
  }(node, state, override);
};


================================================
FILE: lib/shake.js
================================================
'use strict';

exports.walk = require('./shake/walk');
exports.evaluateConst = require('./shake/eval');

exports.Module = require('./shake/module');
exports.Analyzer = require('./shake/analyzer');
exports.Graph = require('./shake/graph');


================================================
FILE: package.json
================================================
{
  "name": "common-shake",
  "version": "2.1.0",
  "description": "CommonJS Tree Shake",
  "main": "lib/shake.js",
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/indutny/common-shake.git"
  },
  "scripts": {
    "lint": "eslint lib/*.js lib/**/*.js test/*.js",
    "test": "nyc --reporter=html mocha --reporter=spec test/*-test.js && npm run lint"
  },
  "files": [
    "lib"
  ],
  "keywords": [
    "commonjs",
    "tree",
    "shake"
  ],
  "author": "Fedor Indutny <fedor@indutny.com> (http://darksi.de/)",
  "license": "MIT",
  "dependencies": {
    "acorn": "^5.1.1",
    "debug": "^2.6.8",
    "escope": "^3.6.0"
  },
  "devDependencies": {
    "acorn-dynamic-import": "^2.0.2",
    "assert-text": "^1.1.2",
    "eslint": "^4.1.1",
    "mocha": "^3.4.2",
    "nyc": "^11.0.3"
  }
}


================================================
FILE: test/analyzer-test.js
================================================
'use strict';
/* globals describe it beforeEach afterEach */

const assert = require('assert');
const fixtures = require('./fixtures');
const parse = fixtures.parse;

const shake = require('../');
const Analyzer = shake.Analyzer;

const EMPTY = {
  bailouts: false,
  uses: [],
  declarations: []
};

function simplifyDecl(decl) {
  return {
    type: decl.type,
    name: decl.name,
    ast: decl.ast.type
  };
}

describe('Analyzer', () => {
  let analyzer;

  beforeEach(() => {
    analyzer = new Analyzer();
  });

  afterEach(() => {
    analyzer = null;
  });

  it('should find all exported values', () => {
    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;

      !function() {
        module.exports.c = 3;
      }();
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo(), {
      bailouts: false,
      uses: [],
      declarations: [ 'a', 'b', 'c' ]
    });

    const decls = analyzer.getModule('root').getDeclarations();

    assert.deepEqual(decls.map(simplifyDecl), [
      { type: 'exports', name: 'a', ast: 'AssignmentExpression' },
      { type: 'exports', name: 'b', ast: 'AssignmentExpression' },
      { type: 'exports', name: 'c', ast: 'AssignmentExpression' }
    ]);
  });

  it('should find all imported values', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      lib.a();
      lib.b();
      require('./a').c();
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
      exports.c = 3;
      exports.d = 4;
    `), 'a');

    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo(), {
      bailouts: false,
      uses: [ 'a', 'b', 'c' ],
      declarations: [ 'a', 'b', 'c', 'd' ]
    });
  });

  it('should find all self-used values', () => {
    analyzer.run(parse(`
      exports.a = 1;
      exports.b = () => {};

      exports.c = () => {
        return exports.b();
      };

      exports.d = () => {
        return module.exports.c();
      };
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo(), {
      bailouts: false,
      uses: [ 'b', 'c' ],
      declarations: [ 'a', 'b', 'c', 'd' ]
    });
  });

  it('should not count disguised `exports` use as export', () => {
    analyzer.run(parse(`
      function a() {
        var exports = {};
        exports.a = a;
      }

      exports.b = 1;
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo(), {
      bailouts: false,
      uses: [],
      declarations: [ 'b' ]
    });
  });

  it('should support object destructuring', () => {
    analyzer.run(parse(`
      const { a, b } = require('./a');
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
      exports.c = 3;
    `), 'a');

    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo(), {
      bailouts: false,
      uses: [ 'a', 'b' ],
      declarations: [ 'a', 'b', 'c' ]
    });
  });

  it('should not support dynamic object destructuring', () => {
    analyzer.run(parse(`
      const prop = 'a';
      const { [prop]: name } = require('./a');
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
      exports.c = 3;
    `), 'a');

    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 12, line: 3 },
          end: { column: 28, line: 3 }
        },
        source: 'root',
        reason: 'Dynamic properties in `require` destructuring',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should not support array destructuring', () => {
    analyzer.run(parse(`
      const [ a, b ] = require('./a');
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
      exports.c = 3;
    `), 'a');

    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 12, line: 2 },
          end: { column: 37, line: 2 }
        },
        source: 'root',
        reason: '`require` used in unknown way',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should not count disguised `require` use as import', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      lib.a();
      function a() {
        const require = () => {};
        const lib = require('./a');
        lib.b();
      }
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
    `), 'a');

    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo(), {
      bailouts: false,
      uses: [ 'a' ],
      declarations: [ 'a', 'b' ]
    });
  });

  it('should not count redefined variable as import', () => {
    analyzer.run(parse(`
      var lib = require('./a');

      lib.a();

      var lib = require('./b');
      lib.b();
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
    `), 'a');

    analyzer.resolve('root', './a', 'a');
    analyzer.resolve('root', './b', 'b');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo(), {
      bailouts: [
        {
          loc: {
            start: { column: 10, line: 2 },
            end: { column: 30, line: 2 }
          },
          source: 'root',
          reason: '`require` variable override',
          level: 'warning'
        }
      ],
      uses: [],
      declarations: [ 'a', 'b' ]
    });
    assert.deepEqual(analyzer.getModule('b').getInfo(), {
      bailouts: [
        {
          loc: {
            start: { column: 10, line: 6 },
            end: { column: 30, line: 6 }
          },
          source: 'root',
          reason: '`require` variable override',
          level: 'warning'
        }
      ],
      uses: [],
      declarations: []
    });
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on assignment to `exports`', () => {
    analyzer.run(parse(`
      exports = {};
      exports.a = 1;
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 2 },
          end: { column: 18, line: 2 }
        },
        source: null,
        reason: '`exports` assignment',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on assignment to `require`', () => {
    analyzer.run(parse(`
      require = () => {};
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 2 },
          end: { column: 24, line: 2 }
        },
        source: null,
        reason: '`require` assignment',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on dynamic `require`', () => {
    analyzer.run(parse(`
      const lib = require(Math.random());
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 18, line: 2 },
          end: { column: 40, line: 2 }
        },
        source: null,
        reason: 'Dynamic argument of `require`',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, [
      {
        loc: {
          start: { column: 18, line: 2 },
          end: { column: 40, line: 2 }
        },
        source: 'root',
        reason: 'Dynamic argument of `require`'
      }
    ]);
  });

  it('should not bailout use of `require` properties', () => {
    analyzer.run(parse(`
      require.cache[a] = 1;
    `), 'root');

    assert(analyzer.isSuccess());
    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
  });

  it('should bailout on invalide use of `require`', () => {
    analyzer.run(parse(`
      escape(require);
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 13, line: 2 },
          end: { column: 20, line: 2 }
        },
        source: null,
        reason: 'Invalid use of `require`',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, [
      {
        loc: {
          start: { column: 13, line: 2 },
          end: { column: 20, line: 2 }
        },
        source: 'root',
        reason: 'Invalid use of `require`'
      }
    ]);
  });

  it('should not bailout on `typeof require`', () => {
    analyzer.run(parse(`
      if (typeof require === 'function') {
        console.log("ok");
      }
    `), 'root');

    assert.strictEqual(analyzer.getModule('root').getInfo().bailouts, false);
  });

  it('should bailout on assignment to `module.exports`', () => {
    analyzer.run(parse(`
      module.exports = () => {};
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 2 },
          end: { column: 31, line: 2 }
        },
        source: null,
        reason: '`module.exports` assignment',
        level: 'info'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should not bailout on assignment to other `module` properties', () => {
    analyzer.run(parse(`
      module.lamports = () => {};
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, false);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should support object literal in `module.exports`', () => {
    analyzer.run(parse(`
      module.exports = {
        a: 1,
        "b": 2
      };
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo(), {
      bailouts: false,
      uses: [],
      declarations: [ 'a', 'b' ]
    });

    const decls = analyzer.getModule('root').getDeclarations();
    assert.deepEqual(decls.map(simplifyDecl), [
      { type: 'module.exports', name: 'a', ast: 'Property' },
      { type: 'module.exports', name: 'b', ast: 'Property' }
    ]);
  });

  it('should bailout on dynamic keys in `module.exports`', () => {
    analyzer.run(parse(`
      module.exports = {
        [a]: 1,
        "b": 2
      };
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 8, line: 3 },
          end: { column: 14, line: 3 }
        },
        source: null,
        reason: 'Dynamic `module.exports` property',
        level: 'warning'
      }
    ]);
  });

  it('should not support simultaneous `module.exports` and `exports`', () => {
    analyzer.run(parse(`
      exports.c = 1;
      module.exports = {
        a: 2,
        b: 3
      };
    `), 'root');

    analyzer.run(parse(`
      module.exports = {
        a: 2,
        b: 3
      };
      exports.c = 1;
    `), 'rev-root');

    assert.deepEqual(analyzer.getModule('root').getInfo(), {
      bailouts: [ {
        loc: {
          start: { column: 6, line: 3 },
          end: { column: 7, line: 6 }
        },
        source: null,
        reason: 'Simultaneous assignment to both `exports` and ' +
                '`module.exports`',
        level: 'warning'
      } ],
      uses: [],
      declarations: [ 'c', 'a', 'b' ]
    });

    assert.deepEqual(analyzer.getModule('rev-root').getInfo(), {
      bailouts: [ {
        loc: {
          start: { column: 6, line: 6 },
          end: { column: 19, line: 6 }
        },
        source: null,
        reason: 'Simultaneous assignment to both `exports` and ' +
                '`module.exports`',
        level: 'warning'
      } ],
      uses: [],
      declarations: [ 'a', 'b', 'c' ]
    });
  });

  it('should bailout on dynamic export', () => {
    analyzer.run(parse(`
      exports[Math.random()] = 1;
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 2 },
          end: { column: 28, line: 2 }
        },
        source: null,
        reason: 'Dynamic CommonJS export',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on dynamic `module` use', () => {
    analyzer.run(parse(`
      module[Math.random()] = 1;
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 2 },
          end: { column: 27, line: 2 }
        },
        source: null,
        reason: 'Dynamic `module` use',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on dynamic self-use', () => {
    analyzer.run(parse(`
      exports[Math.random()]();
      module.exports[Math.random()]();
    `), 'root');

    assert.deepEqual(analyzer.getModule('root').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 2 },
          end: { column: 28, line: 2 }
        },
        source: null,
        reason: 'Dynamic CommonJS use',
        level: 'warning'
      },
      {
        loc: {
          start: { column: 6, line: 3 },
          end: { column: 35, line: 3 }
        },
        source: null,
        reason: 'Dynamic CommonJS use',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on dynamic import', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      lib[Math.random()]();
    `), 'root');

    analyzer.run(parse(''), 'a');
    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 4 },
          end: { column: 24, line: 4 }
        },
        source: 'root',
        reason: 'Dynamic CommonJS import',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on assignment to imported library', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      lib.override = true;
    `), 'root');

    analyzer.run(parse(''), 'a');
    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 4 },
          end: { column: 25, line: 4 }
        },
        source: 'root',
        reason: 'Module property assignment',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on escaping imported library', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      send(lib);
    `), 'root');

    analyzer.run(parse(''), 'a');
    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 11, line: 4 },
          end: { column: 14, line: 4 }
        },
        source: 'root',
        reason: 'Escaping value or unknown use',
        level: 'warning'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on imported library call', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      lib();
    `), 'root');

    analyzer.run(parse(''), 'a');
    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 4 },
          end: { column: 11, line: 4 }
        },
        source: 'root',
        reason: 'Imported library call',
        level: 'info'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on imported library new call', () => {
    analyzer.run(parse(`
      const lib = require('./a');

      new lib();
    `), 'root');

    analyzer.run(parse(''), 'a');
    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 6, line: 4 },
          end: { column: 15, line: 4 }
        },
        source: 'root',
        reason: 'Imported library new call',
        level: 'info'
      }
    ]);
    assert.deepEqual(analyzer.bailouts, false);
  });

  it('should bailout on deferred require', () => {
    analyzer.run(parse(`
      var lib;
      lib = require('./a');

      lib.a();
      lib.b();
    `), 'root');

    analyzer.run(parse(`
      exports.a = 1;
      exports.b = 2;
      exports.c = 3;
    `), 'a');

    analyzer.resolve('root', './a', 'a');

    assert.deepEqual(analyzer.getModule('root').getInfo(), EMPTY);
    assert.deepEqual(analyzer.getModule('a').getInfo().bailouts, [
      {
        loc: {
          start: { column: 12, line: 3 },
          end: { column: 26, line: 3 }
        },
        source: 'root',
        reason: 'Escaping `require` call',
        level: 'warning'
      }
    ]);
  });

  it('should not bailout on const require argument', () => {
    analyzer.run(parse(`
      const lib = require('./a' + 'b');

      lib.a();
    `), 'root');

    analyzer.run(parse('exports.a = 1;'), 'ab');
    analyzer.resolve('root', './ab', 'ab');

    assert.deepEqual(analyzer.getModule('ab').getInfo(), {
      bailouts: false,
      declarations: [ 'a' ],
      uses: [ 'a' ]
    });
    assert(analyzer.isSuccess());
  });

  it('should not fail on dynamic import', () => {
    assert.doesNotThrow(() => {
      analyzer.run(parse('import("ohai")'), 'root');
    });
  });

  it('should not throw on double-resolve', () => {
    assert.doesNotThrow(() => {
      analyzer.resolve('root', './a', 'a');
      analyzer.resolve('root', './a', 'a');
      analyzer.resolve('root', './a', 'a');
    });
  });

  it('should find recursive dependencies', () => {
    analyzer.run(parse(`
      const lib = require('./a');
      const mlib = require('./ma');

      exports.a = lib.a;
      exports.b = mlib.a;
    `), 'root');

    analyzer.run(parse(`
      exports.a = require('./b').a;
      exports.c = require('./b').b;
      exports.b = exports.c;
    `), 'a');

    analyzer.run(parse(`
      module.exports = {
        a: require('./mb').a,
        b: require('./mb').b
      };
    `), 'ma');

    analyzer.getModule('root').forceExport();

    analyzer.resolve('root', './a', 'a');
    analyzer.resolve('root', './ma', 'ma');
    analyzer.resolve('a', './b', 'b');
    analyzer.resolve('ma', './mb', 'mb');

    assert.deepEqual(analyzer.getModule('a').getInfo(), {
      bailouts: false,
      uses: [ 'a' ],
      declarations: [ 'a', 'c', 'b' ]
    });

    assert.deepEqual(analyzer.getModule('b').getInfo(), {
      bailouts: false,
      uses: [ 'a' ],
      declarations: []
    });

    assert.deepEqual(analyzer.getModule('ma').getInfo(), {
      bailouts: false,
      uses: [ 'a' ],
      declarations: [ 'a', 'b' ]
    });

    assert.deepEqual(analyzer.getModule('mb').getInfo(), {
      bailouts: false,
      uses: [ 'a' ],
      declarations: []
    });
  });

  it('should not choke on async/await', () => {
    assert.doesNotThrow(() => {
      analyzer.run(parse(`
        'use strict';

        const fn = async function() {
          await other();
        };
      `), 'root');
    });
  });
});


================================================
FILE: test/eval-test.js
================================================
'use strict';
/* globals describe it */

const assert = require('assert');
const fixtures = require('./fixtures');

const shake = require('../');
const evaluateConst = shake.evaluateConst;

const parse = (source) => {
  return fixtures.parse(source).body[0].expression;
};

describe('Evaluator', () => {
  it('should evaluate number literal', () => {
    assert.strictEqual(evaluateConst(parse('1')), 1);
  });

  it('should evaluate string literal', () => {
    assert.strictEqual(evaluateConst(parse('"1"')), '1');
  });

  it('should evaluate binary addition', () => {
    assert.strictEqual(evaluateConst(parse('"1" + "2"')), '12');
  });

  it('should throw on unknown binary operation', () => {
    assert.throws(() => {
      evaluateConst(parse('"1" / "2"'));
    });
  });

  it('should throw on unknown node type', () => {
    assert.throws(() => {
      evaluateConst(parse('a()'));
    });
  });
});


================================================
FILE: test/fixtures.js
================================================
'use strict';

const acorn = require('acorn-dynamic-import').default;

exports.parse = (source) => {
  return acorn.parse(source, {
    locations: true,
    sourceType: 'module',
    ecmaVersion: 2017,
    plugins: {
      dynamicImport: true
    }
  });
};


================================================
FILE: test/graph-test.js
================================================
'use strict';
/* globals describe it beforeEach afterEach */

const assertText = require('assert-text');
const fixtures = require('./fixtures');
const parse = fixtures.parse;

assertText.options.trim = true;

const shake = require('../');
const Analyzer = shake.Analyzer;
const Graph = shake.Graph;

describe('Graph', () => {
  let analyzer;
  let graph;

  beforeEach(() => {
    analyzer = new Analyzer();
    graph = new Graph(__dirname);
  });

  afterEach(() => {
    analyzer = null;
    graph = null;
  });

  it('should find all exported values', () => {
    analyzer.run(parse(`
      // Import all
      require('./a')[K];

      require('./b').bprop;

      exports.prop = 1;
    `), 'root');

    analyzer.run(parse(`
      exports.aprop = 1;
    `), 'a');

    analyzer.run(parse(`
      exports.bprop = 1;
    `), 'b');

    analyzer.resolve('root', './a', 'a');
    analyzer.resolve('root', './b', 'b');

    const out = graph.generate(analyzer.getModules());
    assertText.equal(out, `digraph {
      ranksep=1.2;
      subgraph "cluster://../root" {
        label="../root";
        color=black;
        "{../root}/require" [label=require shape=diamond];
        "{../root}[prop]" [label="prop" color=blue];
      }
      subgraph "cluster://../a" {
        label="../a";
        color=red;
        "{../a}/require" [label=require shape=diamond];
        "{../a}[aprop]" [label="aprop" color=red];
        "{../a}[[*]]" [label="[*]" color=red];
      }
      "{../root}/require" -> "{../a}[[*]]";
      subgraph "cluster://../b" {
        label="../b";
        color=black;
        "{../b}/require" [label=require shape=diamond];
        "{../b}[bprop]" [label="bprop" color=black];
      }
      "{../root}/require" -> "{../b}[bprop]";
    }`);
  });
});
Download .txt
gitextract_xe61hc_u/

├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── README.md
├── lib/
│   ├── shake/
│   │   ├── analyzer.js
│   │   ├── eval.js
│   │   ├── graph.js
│   │   ├── module.js
│   │   └── walk.js
│   └── shake.js
├── package.json
└── test/
    ├── analyzer-test.js
    ├── eval-test.js
    ├── fixtures.js
    └── graph-test.js
Download .txt
SYMBOL INDEX (8 symbols across 6 files)

FILE: lib/shake/analyzer.js
  function Analyzer (line 10) | function Analyzer() {

FILE: lib/shake/eval.js
  function evaluateBinary (line 3) | function evaluateBinary(node) {
  function evaluateConst (line 15) | function evaluateConst(node) {

FILE: lib/shake/graph.js
  function Graph (line 5) | function Graph(dir) {

FILE: lib/shake/module.js
  function Module (line 5) | function Module(resource) {

FILE: lib/shake/walk.js
  constant BASE (line 5) | const BASE = Object.assign({

FILE: test/analyzer-test.js
  constant EMPTY (line 11) | const EMPTY = {
  function simplifyDecl (line 17) | function simplifyDecl(decl) {
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
  {
    "path": ".eslintrc.js",
    "chars": 718,
    "preview": "module.exports = {\n  'env': {\n    'browser': false,\n    'commonjs': true,\n    'node': true,\n    'es6': true\n  },\n  'pars"
  },
  {
    "path": ".gitignore",
    "chars": 51,
    "preview": "node_modules/\nnpm-debug.log\n.nyc_output/\ncoverage/\n"
  },
  {
    "path": ".travis.yml",
    "chars": 52,
    "preview": "sudo: false\nlanguage: node_js\nnode_js:\n  - \"stable\"\n"
  },
  {
    "path": "README.md",
    "chars": 2783,
    "preview": "# CommonJS Tree Shaker\n[![NPM version](https://badge.fury.io/js/common-shake.svg)](http://badge.fury.io/js/common-shake)"
  },
  {
    "path": "lib/shake/analyzer.js",
    "chars": 14793,
    "preview": "'use strict';\n\nconst escope = require('escope');\nconst debug = require('debug')('common-shake:analyzer');\n\nconst shake ="
  },
  {
    "path": "lib/shake/eval.js",
    "chars": 522,
    "preview": "'use strict';\n\nfunction evaluateBinary(node) {\n  const op = node.operator;\n\n  const left = evaluateConst(node.left);\n  c"
  },
  {
    "path": "lib/shake/graph.js",
    "chars": 2743,
    "preview": "'use strict';\n\nconst path = require('path');\n\nfunction Graph(dir) {\n  this.dir = dir || '.';\n  this.relativeCache = new "
  },
  {
    "path": "lib/shake/module.js",
    "chars": 4009,
    "preview": "'use strict';\n\nconst debug = require('debug')('common-shake:module');\n\nfunction Module(resource) {\n  this.resource = res"
  },
  {
    "path": "lib/shake/walk.js",
    "chars": 452,
    "preview": "'use strict';\n\nconst walk = require('acorn/dist/walk');\n\nconst BASE = Object.assign({\n  // acorn-dynamic-import support\n"
  },
  {
    "path": "lib/shake.js",
    "chars": 239,
    "preview": "'use strict';\n\nexports.walk = require('./shake/walk');\nexports.evaluateConst = require('./shake/eval');\n\nexports.Module "
  },
  {
    "path": "package.json",
    "chars": 817,
    "preview": "{\n  \"name\": \"common-shake\",\n  \"version\": \"2.1.0\",\n  \"description\": \"CommonJS Tree Shake\",\n  \"main\": \"lib/shake.js\",\n  \"r"
  },
  {
    "path": "test/analyzer-test.js",
    "chars": 19793,
    "preview": "'use strict';\n/* globals describe it beforeEach afterEach */\n\nconst assert = require('assert');\nconst fixtures = require"
  },
  {
    "path": "test/eval-test.js",
    "chars": 912,
    "preview": "'use strict';\n/* globals describe it */\n\nconst assert = require('assert');\nconst fixtures = require('./fixtures');\n\ncons"
  },
  {
    "path": "test/fixtures.js",
    "chars": 258,
    "preview": "'use strict';\n\nconst acorn = require('acorn-dynamic-import').default;\n\nexports.parse = (source) => {\n  return acorn.pars"
  },
  {
    "path": "test/graph-test.js",
    "chars": 1774,
    "preview": "'use strict';\n/* globals describe it beforeEach afterEach */\n\nconst assertText = require('assert-text');\nconst fixtures "
  }
]

About this extraction

This page contains the full source code of the indutny/common-shake GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (48.7 KB), approximately 13.1k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!