Skip to content

Commit d9b4c71

Browse files
TimothyGuForbesLindesay
authored andcommitted
Rewrite with Babylon
1 parent 0c8a374 commit d9b4c71

File tree

3 files changed

+112
-69
lines changed

3 files changed

+112
-69
lines changed

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# constantinople
22

3-
Determine whether a JavaScript expression evaluates to a constant (using acorn). Here it is assumed to be safe to underestimate how constant something is.
3+
Determine whether a JavaScript expression evaluates to a constant (using Babylon). Here it is assumed to be safe to underestimate how constant something is.
44

5-
[![Build Status](https://img.shields.io/travis/ForbesLindesay/constantinople/master.svg)](https://travis-ci.org/ForbesLindesay/constantinople)
6-
[![Dependency Status](https://img.shields.io/david/ForbesLindesay/constantinople.svg)](https://david-dm.org/ForbesLindesay/constantinople)
5+
[![Build Status](https://img.shields.io/travis/pugjs/constantinople/master.svg)](https://travis-ci.org/pugjs/constantinople)
6+
[![Dependency Status](https://img.shields.io/david/pugjs/constantinople.svg)](https://david-dm.org/pugjs/constantinople)
77
[![NPM version](https://img.shields.io/npm/v/constantinople.svg)](https://www.npmjs.org/package/constantinople)
88

99
## Installation
@@ -25,18 +25,22 @@ if (isConstant('Math.floor(10.5)', {Math: Math})) {
2525

2626
## API
2727

28-
### isConstant(src, [constants])
28+
### isConstant(src, [constants, [options]])
2929

3030
Returns `true` if `src` evaluates to a constant, `false` otherwise. It will also return `false` if there is a syntax error, which makes it safe to use on potentially ES6 code.
3131

3232
Constants is an object mapping strings to values, where those values should be treated as constants. Note that this makes it a pretty bad idea to have `Math` in there if the user might make use of `Math.random` and a pretty bad idea to have `Date` in there.
3333

34-
### toConstant(src, [constants])
34+
Options are directly passed-through to [Babylon](https://github.com/babel/babylon#options).
35+
36+
### toConstant(src, [constants, [options]])
3537

3638
Returns the value resulting from evaluating `src`. This method throws an error if the expression is not constant. e.g. `toConstant("Math.random()")` would throw an error.
3739

3840
Constants is an object mapping strings to values, where those values should be treated as constants. Note that this makes it a pretty bad idea to have `Math` in there if the user might make use of `Math.random` and a pretty bad idea to have `Date` in there.
3941

42+
Options are directly passed-through to [Babylon](https://github.com/babel/babylon#options).
43+
4044
## License
4145

4246
MIT

index.js

Lines changed: 99 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict'
22

3-
var acorn = require('acorn');
4-
var walk = require('acorn/dist/walk');
5-
var isExpression = require('is-expression');
3+
var walk = require('babylon-walk');
4+
var getExpression = require('is-expression-babylon').getExpression;
5+
var t = require('babel-types');
66

77
var lastSRC = '(null)';
88
var lastRes = true;
@@ -12,80 +12,118 @@ var STATEMENT_WHITE_LIST = {
1212
'EmptyStatement': true,
1313
'ExpressionStatement': true,
1414
};
15+
// See require('babel-types').EXPRESSION_TYPES
1516
var EXPRESSION_WHITE_LIST = {
16-
'ParenthesizedExpression': true,
17-
'ArrayExpression': true,
18-
'ObjectExpression': true,
19-
'SequenceExpression': true,
20-
'TemplateLiteral': true,
21-
'UnaryExpression': true,
22-
'BinaryExpression': true,
23-
'LogicalExpression': true,
24-
'ConditionalExpression': true,
25-
'Identifier': true,
26-
'Literal': true,
27-
'ComprehensionExpression': true,
28-
'TaggedTemplateExpression': true,
29-
'MemberExpression': true,
30-
'CallExpression': true,
31-
'NewExpression': true,
17+
ArrayExpression: true,
18+
// AssignmentExpression: false,
19+
BinaryExpression: true,
20+
CallExpression: true,
21+
ConditionalExpression: true,
22+
// FunctionExpression: false,
23+
Identifier: true,
24+
StringLiteral: true,
25+
NumericLiteral: true,
26+
NullLiteral: true,
27+
BooleanLiteral: true,
28+
RegExpLiteral: true,
29+
LogicalExpression: true,
30+
MemberExpression: true,
31+
NewExpression: true,
32+
ObjectExpression: true,
33+
SequenceExpression: true,
34+
// ThisExpression: false,
35+
UnaryExpression: true,
36+
// UpdateExpression: false,
37+
// ArrowFunctionExpression: false,
38+
// ClassExpression: false,
39+
// MetaProperty: false,
40+
// Super: false,
41+
TaggedTemplateExpression: true,
42+
TemplateLiteral: true,
43+
// YieldExpression: false,
44+
TypeCastExpression: true,
45+
JSXElement: true,
46+
JSXEmptyExpression: true,
47+
JSXIdentifier: true,
48+
JSXMemberExpression: true,
49+
ParenthesizedExpression: true,
50+
// AwaitExpression: false,
51+
BindExpression: true,
52+
// DoExpression: false,
53+
};
54+
var visitors = {
55+
Statement: function (node, state) {
56+
if (!state.stop && !STATEMENT_WHITE_LIST[node.type]) {
57+
state.stop = true;
58+
}
59+
},
60+
Expression: function (node, state) {
61+
if (!state.stop && !EXPRESSION_WHITE_LIST[node.type]) {
62+
state.stop = true;
63+
}
64+
},
65+
'MemberExpression|JSXMemberExpression': function (node, state) {
66+
if (state.stop) return;
67+
if (node.computed) return state.stop = true;
68+
else if (node.property.name[0] === '_') return state.stop = true;
69+
},
70+
'Identifier|JSXIdentifier': function (node, state, parents) {
71+
if (state.stop) return;
72+
var lastParent = parents[parents.length - 2];
73+
if (lastParent && !isReferenced(node, lastParent)) return;
74+
if (!(state.constants && node.name in state.constants)) {
75+
state.stop = true;
76+
}
77+
},
3278
};
3379
module.exports = isConstant;
34-
function isConstant(src, constants) {
35-
src = '(' + src + ')';
80+
function isConstant(src, constants, options) {
3681
if (lastSRC === src && lastConstants === constants) return lastRes;
3782
lastSRC = src;
3883
lastConstants = constants;
39-
if (!isExpression(src)) return lastRes = false;
4084
var ast;
4185
try {
42-
ast = acorn.parse(src, {
43-
ecmaVersion: 6,
44-
allowReturnOutsideFunction: true,
45-
allowImportExportEverywhere: true,
46-
allowHashBang: true
47-
});
86+
ast = getExpression(src, options);
4887
} catch (ex) {
4988
return lastRes = false;
5089
}
51-
var isConstant = true;
52-
walk.simple(ast, {
53-
Statement: function (node) {
54-
if (isConstant) {
55-
if (STATEMENT_WHITE_LIST[node.type] !== true) {
56-
isConstant = false;
57-
}
58-
}
59-
},
60-
Expression: function (node) {
61-
if (isConstant) {
62-
if (EXPRESSION_WHITE_LIST[node.type] !== true) {
63-
isConstant = false;
64-
}
65-
}
66-
},
67-
MemberExpression: function (node) {
68-
if (isConstant) {
69-
if (node.computed) isConstant = false;
70-
else if (node.property.name[0] === '_') isConstant = false;
71-
}
72-
},
73-
Identifier: function (node) {
74-
if (isConstant) {
75-
if (!constants || !(node.name in constants)) {
76-
isConstant = false;
77-
}
78-
}
79-
},
80-
});
81-
return lastRes = isConstant;
90+
var state = {
91+
constants: constants,
92+
stop: false
93+
};
94+
walk.ancestor(ast, visitors, state);
95+
96+
return lastRes = !state.stop;
8297
}
8398
isConstant.isConstant = isConstant;
8499

85100
isConstant.toConstant = toConstant;
86-
function toConstant(src, constants) {
87-
if (!isConstant(src, constants)) throw new Error(JSON.stringify(src) + ' is not constant.');
101+
function toConstant(src, constants, options) {
102+
if (!isConstant(src, constants, options)) throw new Error(JSON.stringify(src) + ' is not constant.');
88103
return Function(Object.keys(constants || {}).join(','), 'return (' + src + ')').apply(null, Object.keys(constants || {}).map(function (key) {
89104
return constants[key];
90105
}));
91106
}
107+
108+
function isReferenced(node, parent) {
109+
switch (parent.type) {
110+
// yes: { [NODE]: '' }
111+
// yes: { NODE }
112+
// no: { NODE: '' }
113+
case 'ObjectProperty':
114+
return parent.value === node || parent.computed;
115+
116+
// no: break NODE;
117+
// no: continue NODE;
118+
case 'BreakStatement':
119+
case 'ContinueStatement':
120+
return false;
121+
122+
// yes: left = NODE;
123+
// yes: NODE = right;
124+
case 'AssignmentExpression':
125+
return true;
126+
}
127+
128+
return t.isReferenced(node, parent);
129+
}

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
"tooling"
99
],
1010
"dependencies": {
11-
"acorn": "^3.1.0",
12-
"is-expression": "^2.0.1"
11+
"babel-types": "^6.16.0",
12+
"babylon-walk": "^1.0.2",
13+
"is-expression-babylon": "^1.1.0"
1314
},
1415
"devDependencies": {
1516
"mocha": "*"
@@ -23,4 +24,4 @@
2324
},
2425
"author": "ForbesLindesay",
2526
"license": "MIT"
26-
}
27+
}

0 commit comments

Comments
 (0)