Skip to content

Commit

Permalink
support initial state as Identifier. fixes #14
Browse files Browse the repository at this point in the history
  • Loading branch information
vstirbu committed Apr 3, 2019
1 parent e475eb8 commit 1cc7dfe
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 66 deletions.
28 changes: 15 additions & 13 deletions CHANGELOG.md
@@ -1,35 +1,37 @@
# HEAD

- support initial state as `Identifier`

# 2.2.0 / 2018-03-29

* support for quoted keys in callbacks
- support for quoted keys in callbacks

# 2.1.0 / 2018-02-16

* support import statement
* support dynamic initial state
* support JSX elements in file
- support import statement
- support dynamic initial state
- support JSX elements in file

# 2.0.1 / 2018-01-30

* parse state machines having activity with same name in two states.
- parse state machines having activity with same name in two states.

# 2.0.0 / 2018-01-24

* detect custom callback prefix
* return meta property with callback functions location in the source file
- detect custom callback prefix
- return meta property with callback functions location in the source file

# 1.3.0 / 2018-01-09

* added arrow functions support
- added arrow functions support

# 1.2.0 / 2018-01-06

* moved pug rendering from lib to cli
- moved pug rendering from lib to cli

# 1.1.0 / 2016-11-25

* transitioned from jade to pug
* split code into cli and lib
* added [nomnoml](http://www.nomnoml.com) renderer
* added [mermaid](http://knsv.github.io/mermaid/#mermaid) renderer
- transitioned from jade to pug
- split code into cli and lib
- added [nomnoml](http://www.nomnoml.com) renderer
- added [mermaid](http://knsv.github.io/mermaid/#mermaid) renderer
104 changes: 76 additions & 28 deletions lib/finder.js
@@ -1,30 +1,46 @@
var esprima = require('esprima'),
escodegen = require('escodegen'),
estraverse = require('estraverse');
escodegen = require('escodegen'),
estraverse = require('estraverse');

module.exports = function (content) {
module.exports = function(content) {
var syntax = esprima.parseModule(content, { loc: true, jsx: true }),
target,
fsm;
target,
fsm;

const callbackLocs = {};
let callbackPrefix = 'on';

estraverse.traverse(syntax, {
enter: function(node, parent) {
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments[0].value === 'fsm-as-promised') {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments[0].value === 'fsm-as-promised'
) {
fsm = parent.id.name;
}

if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments[0].value === 'javascript-state-machine') {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments[0].value === 'javascript-state-machine'
) {
fsm = parent.id.name;
}

if (node.type === 'ImportDeclaration' && node.source.value === 'fsm-as-promised') {
if (
node.type === 'ImportDeclaration' &&
node.source.value === 'fsm-as-promised'
) {
fsm = node.specifiers[0].local.name;
}

if (node.type === 'MemberExpression' && node.property.name === 'callbackPrefix') {
if (
node.type === 'MemberExpression' &&
node.property.name === 'callbackPrefix'
) {
callbackPrefix = parent.right.value;
}
},
Expand All @@ -33,11 +49,23 @@ module.exports = function (content) {

estraverse.traverse(syntax, {
enter: function(node, parent) {
if (fsm && node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === fsm && node.arguments[0].type === 'ObjectExpression') {
if (
fsm &&
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === fsm &&
node.arguments[0].type === 'ObjectExpression'
) {
target = node;
}

if (fsm && node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && node.callee.object.name === 'StateMachine' && node.callee.property.name === 'create') {
if (
fsm &&
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'StateMachine' &&
node.callee.property.name === 'create'
) {
target = node;
}
},
Expand All @@ -49,11 +77,18 @@ module.exports = function (content) {
}

estraverse.traverse(target, {
enter: function (node, parent) {
if (node.type === 'Identifier' && parent.type === 'Property' && (parent.value.type === 'FunctionExpression' || parent.value.type === 'ArrowFunctionExpression')) {
enter: function(node, parent) {
if (
node.type === 'Identifier' &&
parent.type === 'Property' &&
(parent.value.type === 'FunctionExpression' ||
parent.value.type === 'ArrowFunctionExpression')
) {
parent.value = {
type: 'Identifier',
name: parent.value.id ? '"' + parent.value.id.name + '"' : '"' + parent.key.name + '"'
name: parent.value.id
? '"' + parent.value.id.name + '"'
: '"' + parent.key.name + '"'
};

const functionName = parent.value.name.replace(/\"/g, '');
Expand All @@ -62,17 +97,28 @@ module.exports = function (content) {
}
}

if (node.type === 'Property' && node.key.name === 'condition' && node.value.type === 'CallExpression') {
if (
node.type === 'Property' &&
node.key.name === 'condition' &&
node.value.type === 'CallExpression'
) {
node.value = {
type: 'Identifier',
name: 'condition',
loc: node.value.loc
}
};
}

// if initial is function, remove initial from configuration
if (node.type === 'Property' && node.key.type === 'Identifier' && node.key.name === 'initial' && node.value.type === 'CallExpression') {
parent.properties = parent.properties.filter(property => property!== node);
if (
node.type === 'Property' &&
node.key.type === 'Identifier' &&
node.key.name === 'initial' &&
['CallExpression', 'Identifier'].includes(node.value.type)
) {
parent.properties = parent.properties.filter(
property => property !== node
);
}

// converts keyed properties from Literal to Identifier
Expand All @@ -81,27 +127,29 @@ module.exports = function (content) {
type: 'Identifier',
name: node.key.value,
loc: node.key.loc
}
};
}
},
leave: function (node, parent) {
leave: function(node, parent) {
if (node.type === 'Identifier') {
node.name = '"' + node.name + '"';
}
}
});

const configuration = JSON.parse(escodegen.generate(target.arguments[0], {
format: {
json: true,
quotes: 'single',
compact: true
}
}));
const configuration = JSON.parse(
escodegen.generate(target.arguments[0], {
format: {
json: true,
quotes: 'single',
compact: true
}
})
);

return {
callbackLocs,
callbackPrefix,
configuration
};
}
};
1 change: 1 addition & 0 deletions test/expected/json/initial-identifier.json
@@ -0,0 +1 @@
{ "choices": [], "states": [], "final": [], "events": [] }
7 changes: 7 additions & 0 deletions test/fixtures/initial-identifier.js
@@ -0,0 +1,7 @@
const fsm = require('fsm-as-promised');

const initial = 'new';

fsm({
initial: initial
});
58 changes: 33 additions & 25 deletions test/test.js
Expand Up @@ -5,13 +5,20 @@ const fs = require('fs');
const graph = require('../lib/graph.js');

function test(testcase) {
const input = fs.readFileSync('test/fixtures/' + testcase + '.js', { encoding: 'utf8' });
const input = fs.readFileSync('test/fixtures/' + testcase + '.js', {
encoding: 'utf8'
});
// console.log(JSON.stringify(graph(input)));
const expected = fs.readFileSync('test/expected/json/' + testcase + '.json', { encoding: 'utf8' });
return assert.equal(JSON.stringify(graph(input).fsm), expected);
const expected = fs.readFileSync('test/expected/json/' + testcase + '.json', {
encoding: 'utf8'
});
return assert.equal(
JSON.stringify(graph(input).fsm),
JSON.stringify(JSON.parse(expected))
);
}

describe('states', function () {
describe('states', function() {
it('should identify the initial state', function() {
test('initial');
});
Expand All @@ -36,81 +43,82 @@ describe('states', function () {
test('simple-state');
});

it('should handle event with multiple from values', function () {
it('should handle event with multiple from values', function() {
test('from-array');
});

it('should handle event without to state', function () {
it('should handle event without to state', function() {
test('event-without-to');
});
});

describe('conditional transition', function () {
it('FunctionExpression', function () {
describe('conditional transition', function() {
it('FunctionExpression', function() {
test('conditional-function');
});

it('ArrowFunctionExpression', function () {
it('ArrowFunctionExpression', function() {
test('conditional-arrow');
});

it('CallExpression', function () {
it('CallExpression', function() {
test('conditional-call');
});
});

describe('activity handlers', function () {
it('should identify handler name when anonymous function', function () {
describe('activity handlers', function() {
it('should identify handler name when anonymous function', function() {
test('anonymous');
});

it('should identify handler name when function has name', function () {
it('should identify handler name when function has name', function() {
test('name');
});

it('should identify reference name', function () {
it('should identify reference name', function() {
test('reference');
});

it('should identify activities that do not have callbacks', function () {
it('should identify activities that do not have callbacks', function() {
test('event-without-callbacks');
});

it('should identify activities with same name in two states', function () {
it('should identify activities with same name in two states', function() {
test('activity-in-two-states');
});
});

describe('callbacks', function () {
it('with key with quotes', function () {
describe('callbacks', function() {
it('with key with quotes', function() {
test('callback-key-with-quotes');
});
});

describe('es6', function() {
it('with import statement', function () {
it('with import statement', function() {
test('es6-import');
});

it('with arrow functions', function () {
it('with arrow functions', function() {
test('arrow-functions');
});
});

describe('customization', function() {
it('with custom callback prefix', function () {
it('with custom callback prefix', function() {
test('callback-prefix');
});
});

describe('dynamic', function () {
it('with dynamic initial state', function () {
describe('dynamic', function() {
it('with dynamic initial state', function() {
test('initial-function');
test('initial-identifier');
});
});

describe('Miscellaneous', function () {
it('with JSX elements', function () {
describe('Miscellaneous', function() {
it('with JSX elements', function() {
test('jsx-element');
});
});

0 comments on commit 1cc7dfe

Please sign in to comment.