Skip to content

Commit c7d3cd8

Browse files
committed
Allow var names to be customized, fixes #5
1 parent feb3422 commit c7d3cd8

13 files changed

+127
-95
lines changed

README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ Then, in your babel configuration (usually in your `.babelrc` file), add `"contr
5353
```
5454

5555
The above example configuration will remove all contracts when `NODE_ENV=production`, which is often preferable for performance reasons.
56+
You can customize the names of the labels and identifiers by specifying a `names` option, e.g.
57+
58+
```json
59+
{
60+
"plugins": [
61+
["contracts", {
62+
"names": {
63+
"assert": "assert",
64+
"precondition": "pre",
65+
"postcondition": "post",
66+
"invariant": "invariant",
67+
"return": "it",
68+
"old": "old"
69+
}
70+
}]
71+
]
72+
}
73+
```
5674

5775
## Examples
5876

@@ -203,7 +221,7 @@ The above example configuration will remove all contracts when `NODE_ENV=product
203221
Now if a contract fails, the error object will have a descriptive message.
204222

205223
# Migrating from Contractual.
206-
This plugin uses a very similar syntax to our earlier Design by Contract library, [contractual](https://github.com/codemix/contractual).
224+
This plugin uses a very similar syntax to our earlier Design by Contract library, [contractual](https://github.com/codemix/contractual).
207225
If you're migrating your project there are some differences to be aware of:
208226

209227
1. There is no longer a `main:` section. Anything outside of a contract is considered to be part of the normal program code.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"babel-plugin-syntax-flow": "^6.0.14",
4343
"babel-plugin-transform-es2015-modules-commonjs": "^6.1.3",
4444
"babel-plugin-transform-flow-strip-types": "^6.0.14",
45-
"babel-plugin-typecheck": "^3.2.1",
45+
"babel-plugin-typecheck": "^3.9.0",
4646
"babel-polyfill": "^6.0.16",
4747
"babel-preset-es2015": "^6.1.0",
4848
"babel-preset-react": "^6.1.0",

src/index.js

+81-71
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ type NodePath = {
4343
*/
4444
export default function ({types: t, template, options}: PluginParams): Plugin {
4545

46-
const PRECONDITION_NAME = 'pre';
47-
const POSTCONDITION_NAME = 'post';
48-
const INVARIANT_NAME = 'invariant';
49-
const ASSERT_NAME = 'assert';
50-
const RETURN_NAME = 'it';
51-
const OLD_VALUE_NAME = 'old';
52-
const returnId: Identifier = t.identifier(RETURN_NAME);
46+
const defaultNames = {
47+
assert: 'assert',
48+
precondition: 'pre',
49+
postcondition: 'post',
50+
invariant: 'invariant',
51+
return: 'it',
52+
old: 'old'
53+
};
54+
55+
let NAMES = Object.assign({}, defaultNames);
5356

5457
const guard: (ids: {[key: string]: Node}) => Node = template(`
5558
if (!condition) {
@@ -147,7 +150,7 @@ export default function ({types: t, template, options}: PluginParams): Plugin {
147150
CallExpression (call: NodePath): void {
148151
const callee: NodePath = call.get('callee');
149152
const args: NodePath[] = call.get('arguments');
150-
if (!callee.isIdentifier() || callee.node.name !== OLD_VALUE_NAME || call.scope.hasBinding(OLD_VALUE_NAME) || args.length === 0) {
153+
if (!callee.isIdentifier() || callee.node.name !== NAMES.old || call.scope.hasBinding(NAMES.old) || args.length === 0) {
151154
return;
152155
}
153156
const argument: NodePath = args[0];
@@ -180,7 +183,7 @@ export default function ({types: t, template, options}: PluginParams): Plugin {
180183
fn.get('body').get('body')[0].insertBefore(guardFn({
181184
id,
182185
conditions,
183-
it: returnId
186+
it: t.identifier(NAMES.return)
184187
}));
185188

186189
path.remove();
@@ -296,7 +299,7 @@ export default function ({types: t, template, options}: PluginParams): Plugin {
296299
path.parentPath.get('body')[0].insertBefore(guardFn({
297300
id,
298301
conditions,
299-
it: returnId
302+
it: t.identifier(NAMES.return)
300303
}));
301304
path.remove();
302305
return id;
@@ -322,79 +325,86 @@ export default function ({types: t, template, options}: PluginParams): Plugin {
322325

323326
return {
324327
visitor: {
325-
Function (fn: NodePath, {opts}): void {
326-
if (fn.isArrowFunctionExpression() && !fn.get('body').isBlockStatement()) {
327-
// Naked arrow functions cannot contain contracts.
328-
return;
328+
Program (path: NodePath, {opts}: any) {
329+
if (opts != null && opts.names !== undefined) {
330+
NAMES = Object.assign({}, defaultNames, opts.names);
329331
}
330-
fn.traverse({
331-
Function (path: NodePath): void {
332-
// This will be handled by the outer visitor, so skip it.
333-
path.skip();
334-
},
335-
336-
LabeledStatement (path: NodePath): void {
337-
const label: NodePath = path.get('label');
338-
if (opts.strip || (opts.env && opts.env[process.env.NODE_ENV] && opts.env[process.env.NODE_ENV].strip)) {
339-
if (label.node.name === PRECONDITION_NAME || label.node.name === POSTCONDITION_NAME || label.node.name === INVARIANT_NAME || label.node.name === ASSERT_NAME) {
340-
path.remove();
341-
}
342-
return;
343-
}
344-
345-
346-
let id: ?Identifier;
347-
let children: ?NodePath[];
348-
let parent: NodePath = fn;
349-
if (label.node.name === PRECONDITION_NAME) {
350-
assemblePrecondition(path);
332+
return path.traverse({
333+
Function (fn: NodePath): void {
334+
if (fn.isArrowFunctionExpression() && !fn.get('body').isBlockStatement()) {
335+
// Naked arrow functions cannot contain contracts.
351336
return;
352337
}
353-
else if (label.node.name === POSTCONDITION_NAME) {
354-
id = assemblePostcondition(path);
355-
children = fn.get('body').get('body');
356-
}
357-
else if (label.node.name === ASSERT_NAME) {
358-
assembleAssertion(path);
359-
return;
360-
}
361-
else if (label.node.name === INVARIANT_NAME) {
362-
id = assembleInvariant(path);
363-
parent = path.findParent(t.isBlockStatement);
364-
children = parent.get('body');
365-
const first: NodePath = children[0];
366-
first.insertAfter(t.expressionStatement(t.callExpression(id, [])))
367-
}
368-
parent.traverse({
338+
fn.traverse({
369339
Function (path: NodePath): void {
370340
// This will be handled by the outer visitor, so skip it.
371341
path.skip();
372342
},
373-
ReturnStatement (statement: NodePath): void {
374-
statement.get('argument').replaceWith(t.callExpression(id, [statement.node.argument]));
343+
344+
LabeledStatement (path: NodePath): void {
345+
const label: NodePath = path.get('label');
346+
if (opts.strip || (opts.env && opts.env[process.env.NODE_ENV] && opts.env[process.env.NODE_ENV].strip)) {
347+
if (label.node.name === NAMES.precondition || label.node.name === NAMES.postcondition || label.node.name === NAMES.invariant || label.node.name === NAMES.assert) {
348+
path.remove();
349+
}
350+
return;
351+
}
352+
353+
354+
let id: ?Identifier;
355+
let children: ?NodePath[];
356+
let parent: NodePath = fn;
357+
if (label.node.name === NAMES.precondition) {
358+
assemblePrecondition(path);
359+
return;
360+
}
361+
else if (label.node.name === NAMES.postcondition) {
362+
id = assemblePostcondition(path);
363+
children = fn.get('body').get('body');
364+
}
365+
else if (label.node.name === NAMES.assert) {
366+
assembleAssertion(path);
367+
return;
368+
}
369+
else if (label.node.name === NAMES.invariant) {
370+
id = assembleInvariant(path);
371+
parent = path.findParent(t.isBlockStatement);
372+
children = parent.get('body');
373+
const first: NodePath = children[0];
374+
first.insertAfter(t.expressionStatement(t.callExpression(id, [])))
375+
}
376+
parent.traverse({
377+
Function (path: NodePath): void {
378+
// This will be handled by the outer visitor, so skip it.
379+
path.skip();
380+
},
381+
ReturnStatement (statement: NodePath): void {
382+
statement.get('argument').replaceWith(t.callExpression(id, [statement.node.argument]));
383+
}
384+
});
385+
const last: NodePath = children[children.length - 1];
386+
if (!last.isReturnStatement()) {
387+
last.insertAfter(t.expressionStatement(t.callExpression(id, [])));
388+
}
375389
}
376390
});
377-
const last: NodePath = children[children.length - 1];
378-
if (!last.isReturnStatement()) {
379-
last.insertAfter(t.expressionStatement(t.callExpression(id, [])));
380-
}
381-
}
382-
});
383-
},
391+
},
384392

385-
LabeledStatement (path: NodePath, {opts}): void {
386-
const label: NodePath = path.get('label');
393+
LabeledStatement (path: NodePath): void {
394+
const label: NodePath = path.get('label');
387395

388-
if (label.node.name === ASSERT_NAME) {
389-
if (opts.strip || (opts.env && opts.env[process.env.NODE_ENV] && opts.env[process.env.NODE_ENV].strip)) {
390-
path.remove();
391-
}
392-
else {
393-
assembleAssertion(path);
396+
if (label.node.name === NAMES.assert) {
397+
if (opts.strip || (opts.env && opts.env[process.env.NODE_ENV] && opts.env[process.env.NODE_ENV].strip)) {
398+
path.remove();
399+
}
400+
else {
401+
assembleAssertion(path);
402+
}
403+
return;
404+
}
394405
}
395-
return;
396-
}
406+
});
397407
}
398408
}
399-
};
409+
}
400410
}

test/fixtures/example-2.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function items (a, b) {
99
return c;
1010

1111
post: {
12-
Array.isArray(it);
13-
it.length > 0;
12+
Array.isArray(retVal);
13+
retVal.length > 0;
1414
}
1515
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function demo (input) {
22
post: {
3-
it === true;
3+
retVal === true;
44
}
55
return input ? true : false;
66
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export default function demo (input) {
2-
post: typeof it === 'string', 'Expected string';
2+
post: typeof retVal === 'string', 'Expected string';
33
return input;
44
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export default function demo (input) {
2-
post: typeof it === 'string';
2+
post: typeof retVal === 'string';
33
return input;
44
}

test/fixtures/postcondition-with-if-inside.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export default function demo (input) {
22
post: {
3-
if (typeof it === 'string') {
4-
it.length > 2;
3+
if (typeof retVal === 'string') {
4+
retVal.length > 2;
55
}
66
}
77
if (true) {

test/fixtures/postcondition-with-if.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function demo (input) {
22
post: {
3-
typeof it === 'string';
3+
typeof retVal === 'string';
44
}
55
if (true) {
66
return input;

test/fixtures/postcondition.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function demo (input) {
22
post: {
3-
typeof it === 'string';
3+
typeof retVal === 'string';
44
}
55
return input;
66
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function demo (input) {
22
pre: typeof input === 'string';
3-
post: typeof it === 'number';
4-
post: it > 2;
3+
post: typeof retVal === 'number';
4+
post: retVal > 2;
55
return input.length;
66
}

test/fixtures/precondition-and-postcondition.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ export default function demo (input) {
33
typeof input === 'string';
44
}
55
post: {
6-
typeof it === 'number';
7-
it > 2;
6+
typeof retVal === 'number';
7+
retVal > 2;
88
}
99
return input.length;
1010
}

test/index.js

+13-9
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('Typecheck', function () {
3838
ok('example-2', 1, 2);
3939
ok('example-2', 1);
4040
ok('example-2', 0, 2);
41-
failWith(`Function "items" postcondition failed: it.length > 0`, 'example-2');
41+
failWith(`Function "items" postcondition failed: retVal.length > 0`, 'example-2');
4242
ok('example-3', {balance: 100, overdraftLimit: 100}, 10);
4343
failWith(`Function "withdraw" precondition failed: fromAccount.balance - amount > -fromAccount.overdraftLimit`, 'example-3', {balance: 100, overdraftLimit: 100}, 1000);
4444
ok('example-4', {balance: 100, overdraftLimit: 100}, 10);
@@ -62,29 +62,29 @@ describe('Typecheck', function () {
6262
failWith(`Expected string`, 'precondition-no-block-with-message', false);
6363

6464
ok('postcondition', 'foo');
65-
failWith(`Function "demo" postcondition failed: typeof it === 'string'`, 'postcondition', false);
65+
failWith(`Function "demo" postcondition failed: typeof retVal === 'string'`, 'postcondition', false);
6666
ok('postcondition-with-if', 'foo');
67-
failWith(`Function "demo" postcondition failed: typeof it === 'string'`, 'postcondition-with-if', false);
67+
failWith(`Function "demo" postcondition failed: typeof retVal === 'string'`, 'postcondition-with-if', false);
6868
ok('postcondition-with-if-inside', 'foo');
69-
failWith(`Function "demo" postcondition failed: it.length > 2`, 'postcondition-with-if-inside', 'no');
69+
failWith(`Function "demo" postcondition failed: retVal.length > 2`, 'postcondition-with-if-inside', 'no');
7070
ok('postcondition-no-return', 'foo');
7171
failWith(`Function "demo" postcondition failed: typeof input === 'string'`, 'postcondition-no-return', false);
7272
ok('postcondition-conditional', true);
73-
failWith(`Function "demo" postcondition failed: it === true`, 'postcondition-conditional', false);
73+
failWith(`Function "demo" postcondition failed: retVal === true`, 'postcondition-conditional', false);
7474

7575
ok('postcondition-no-block', 'foo');
76-
failWith(`Function "demo" postcondition failed: typeof it === 'string'`, 'postcondition-no-block', false);
76+
failWith(`Function "demo" postcondition failed: typeof retVal === 'string'`, 'postcondition-no-block', false);
7777

7878
ok('postcondition-no-block-with-message', 'foo');
7979
failWith(`Expected string`, 'postcondition-no-block-with-message', false);
8080

8181
ok('precondition-and-postcondition', 'foo');
8282
failWith(`Function "demo" precondition failed: typeof input === 'string'`, 'precondition-and-postcondition', true);
83-
failWith(`Function "demo" postcondition failed: it > 2`, 'precondition-and-postcondition', 'no');
83+
failWith(`Function "demo" postcondition failed: retVal > 2`, 'precondition-and-postcondition', 'no');
8484

8585
ok('precondition-and-postcondition-no-block', 'foo');
8686
failWith(`Function "demo" precondition failed: typeof input === 'string'`, 'precondition-and-postcondition-no-block', true);
87-
failWith(`Function "demo" postcondition failed: it > 2`, 'precondition-and-postcondition-no-block', 'no');
87+
failWith(`Function "demo" postcondition failed: retVal > 2`, 'precondition-and-postcondition-no-block', 'no');
8888

8989
ok('invariant', 'hello world');
9090
ok('loop-invariant', 'hello world');
@@ -109,7 +109,11 @@ function loadInternal (basename) {
109109
"stage-0",
110110
],
111111
plugins: [
112-
[contracts],
112+
[contracts, {
113+
names: {
114+
return: 'retVal',
115+
}
116+
}],
113117
'transform-flow-strip-types',
114118
'syntax-class-properties'
115119
]

0 commit comments

Comments
 (0)