Skip to content

Commit c2d9b18

Browse files
Add order-in-routes rule
1 parent 5bbee4b commit c2d9b18

File tree

5 files changed

+178
-7
lines changed

5 files changed

+178
-7
lines changed

config/recommended.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ module.exports = {
9191
'ship-shape/order-in-components': 'error',
9292
'ship-shape/order-in-controllers': 'error',
9393
'ship-shape/order-in-models': 'error',
94+
'ship-shape/order-in-routes': 'error',
9495
'ship-shape/prefer-destructuring': 'error',
9596
'ship-shape/query-params-on-top': 'error',
9697
'ship-shape/require-access-in-comments': 'error',

lib/rules/order-in-routes.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
var utils = require('../../lib/utils/utils');
4+
var ember = require('../../lib/utils/ember');
5+
6+
//------------------------------------------------------------------------------
7+
// Organizing - Organize your routes and keep order in objects
8+
//------------------------------------------------------------------------------
9+
10+
module.exports = function(context) {
11+
var message = 'Check order of properties';
12+
13+
function report(node) {
14+
context.report(node, message);
15+
}
16+
17+
return {
18+
CallExpression: function(node) {
19+
if (!ember.isEmberRoute(node)) return;
20+
21+
var properties = ember.getModuleProperties(node);
22+
var mappedProperties = properties.map(function(property) {
23+
return {
24+
node: property,
25+
order: getOrderValue(property)
26+
};
27+
});
28+
29+
var unorderedProperty = utils.findUnorderedProperty(mappedProperties);
30+
31+
if (unorderedProperty) {
32+
report(unorderedProperty.node);
33+
}
34+
}
35+
};
36+
};
37+
38+
function getOrderValue(property) {
39+
var val = null;
40+
41+
if (ember.isInjectedServiceProp(property.value)) {
42+
val = 10;
43+
} else if (ember.isRouteDefaultProp(property)) {
44+
val = 20;
45+
} else if (ember.isCustomProp(property)) {
46+
val = 30;
47+
} else if (ember.isModelProp(property)) {
48+
val = 40;
49+
} else if (ember.isRouteDefaultMethod(property)) {
50+
val = 50;
51+
} else if (ember.isActionsProp(property)) {
52+
val = 60;
53+
} else if (ember.isRouteCustomFunction(property)) {
54+
val = 70;
55+
}
56+
57+
return val;
58+
}

lib/utils/ember.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ function isObserverProp(node) {
102102
return isPropOfType(node, 'observer');
103103
}
104104

105+
function isComputedProp(node) {
106+
return isModule(node, 'computed');
107+
}
108+
105109
function isArrayProp(node) {
106110
if (utils.isNewExpression(node.value)) {
107111
var parsedCallee = utils.parseCallee(node.value);
@@ -120,10 +124,6 @@ function isObjectProp(node) {
120124
return utils.isObjectExpression(node.value);
121125
}
122126

123-
function isComputedProp(node) {
124-
return isModule(node, 'computed');
125-
}
126-
127127
function isCustomProp(property) {
128128
var value = property.value;
129129
var isCustomObjectProp = utils.isObjectExpression(property.value) && property.key.name !== 'actions';
@@ -267,25 +267,33 @@ function isControllerDefaultProp(property) {
267267
}
268268

269269
function getModuleProperties(module) {
270-
return utils.findNodes(module.arguments, 'ObjectExpression')[0].properties;
270+
var firstObjectExpressionNode = utils.findNodes(module.arguments, 'ObjectExpression')[0];
271+
return firstObjectExpressionNode ? firstObjectExpressionNode.properties : [];
271272
}
272273

273274
function isSingleLineFn(property) {
274275
return utils.isCallExpression(property.value) &&
275276
utils.getSize(property.value) === 1 &&
276277
!isObserverProp(property.value) &&
277-
!utils.isCallWithFunctionExpression(property.value);
278+
(
279+
isComputedProp(property.value) ||
280+
!utils.isCallWithFunctionExpression(property.value)
281+
);
278282
}
279283

280284
function isMultiLineFn(property) {
281285
return utils.isCallExpression(property.value) &&
282286
utils.getSize(property.value) > 1 &&
283287
!isObserverProp(property.value) &&
284-
!utils.isCallWithFunctionExpression(property.value);
288+
(
289+
isComputedProp(property.value) ||
290+
!utils.isCallWithFunctionExpression(property.value)
291+
);
285292
}
286293

287294
function isFunctionExpression(property) {
288295
return utils.isFunctionExpression(property) ||
296+
utils.isArrowFunctionExpression(property) ||
289297
utils.isCallWithFunctionExpression(property);
290298
}
291299

lib/utils/utils.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
isObjectExpression: isObjectExpression,
88
isArrayExpression: isArrayExpression,
99
isFunctionExpression: isFunctionExpression,
10+
isArrowFunctionExpression: isArrowFunctionExpression,
1011
isNewExpression: isNewExpression,
1112
isCallWithFunctionExpression: isCallWithFunctionExpression,
1213
isThisExpression: isThisExpression,
@@ -105,6 +106,16 @@ function isFunctionExpression(node) {
105106
return node !== undefined && node.type === 'FunctionExpression';
106107
}
107108

109+
/**
110+
* Check whether or not a node is an ArrowFunctionExpression.
111+
*
112+
* @param {Object} node The node to check.
113+
* @returns {boolean} Whether or not the node is an ArrowFunctionExpression.
114+
*/
115+
function isArrowFunctionExpression(node) {
116+
return node !== undefined && node.type === 'ArrowFunctionExpression';
117+
}
118+
108119
/**
109120
* Check whether or not a node is an NewExpression.
110121
*

tests/lib/rules/order-in-routes.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use strict';
2+
3+
// ------------------------------------------------------------------------------
4+
// Requirements
5+
// ------------------------------------------------------------------------------
6+
7+
var rule = require('../../../lib/rules/order-in-routes');
8+
var RuleTester = require('eslint').RuleTester;
9+
10+
// ------------------------------------------------------------------------------
11+
// Tests
12+
// ------------------------------------------------------------------------------
13+
14+
var eslintTester = new RuleTester();
15+
eslintTester.run('order-in-routes', rule, {
16+
valid: [
17+
{
18+
code: 'export default Route.extend();',
19+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
20+
},
21+
{
22+
code: 'export default Route.extend({currentUser: service(), queryParams: {}, customProp: "test", model() {}, beforeModel() {}, actions: {}, _customAction() {}, _customAction2: function() {}, tSomeTask: task(function* () {}) });',
23+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
24+
},
25+
{
26+
code: 'export default Route.extend({model() {}, actions: { test() { return this._customAction() } }, _customAction() {} });',
27+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
28+
},
29+
{
30+
code: 'export default Route.extend({model() {}, render() {}, init() {} });',
31+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
32+
},
33+
{
34+
code: 'export default Route.extend({mergedProperties: {}, model() {}, actions: {} });',
35+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
36+
},
37+
{
38+
code: 'export default Route.extend({mergedProperties: {}, test: "asd", model() {} });',
39+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
40+
},
41+
],
42+
invalid: [
43+
{
44+
code: 'export default Route.extend({queryParams: {}, currentUser: service(), customProp: "test", model() {}, beforeModel() {}, actions: {}, _customAction() {} });',
45+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
46+
errors: [{
47+
message: 'Check order of properties',
48+
}],
49+
},
50+
{
51+
code: 'export default Route.extend({customProp: "test", queryParams: {}, model() {}, beforeModel() {}, actions: {}, _customAction() {} });',
52+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
53+
errors: [{
54+
message: 'Check order of properties',
55+
}],
56+
},
57+
{
58+
code: 'export default Route.extend({customProp: "test", queryParams: {}, beforeModel() {}, model() {}, actions: {}, _customAction() {} });',
59+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
60+
errors: [{
61+
message: 'Check order of properties',
62+
}],
63+
},
64+
{
65+
code: 'export default Route.extend({queryParams: {}, customProp: "test", model() {}, _customAction() {}, actions: {} });',
66+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
67+
errors: [{
68+
message: 'Check order of properties',
69+
}],
70+
},
71+
{
72+
code: 'export default Route.extend({model() {}, customProp: "test", actions: {} });',
73+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
74+
errors: [{
75+
message: 'Check order of properties',
76+
}],
77+
},
78+
{
79+
code: 'export default Route.extend({test: "asd", mergedProperties: {}, model() {} });',
80+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
81+
errors: [{
82+
message: 'Check order of properties',
83+
}],
84+
},
85+
{
86+
code: 'export default Route.extend({test: "asd", _test2() {}, model() {} });',
87+
parserOptions: {ecmaVersion: 6, sourceType: "module"},
88+
errors: [{
89+
message: 'Check order of properties',
90+
}],
91+
},
92+
]
93+
});

0 commit comments

Comments
 (0)