Skip to content
Please note that GitHub no longer supports Internet Explorer.

We recommend upgrading to the latest Microsoft Edge, Google Chrome, or Firefox.

Learn more
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time. Cannot retrieve contributors at this time
829 lines (708 sloc) 19.3 KB
'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');
});
});
});
You can’t perform that action at this time.