Skip to content

Commit 93dabe1

Browse files
Merge pull request #2561 from RobbieTheWagner/feat/native-route-class-order-support
Add native class support for order-in-routes
2 parents a2c8796 + 002b18f commit 93dabe1

5 files changed

Lines changed: 219 additions & 9 deletions

File tree

docs/rules/order-in-routes.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,4 @@ export default Route.extend({
150150
});
151151
```
152152

153-
## Help Wanted
154-
155-
| Issue | Link |
156-
| :----------------------------------------- | :------------------------------------------------------------------ |
157-
| ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) |
153+
This rule checks ordering only; it does not enforce indentation or other whitespace formatting.

lib/rules/order-in-routes.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,40 @@ module.exports = {
125125
scopeManager
126126
);
127127
},
128+
ClassDeclaration(node) {
129+
if (!ember.isEmberRoute(context, node)) {
130+
return;
131+
}
132+
133+
reportUnorderedProperties(
134+
node,
135+
context,
136+
'route',
137+
order,
138+
importedEmberName,
139+
importedInjectName,
140+
importedObserverName,
141+
importedControllerName,
142+
scopeManager
143+
);
144+
},
145+
ClassExpression(node) {
146+
if (!ember.isEmberRoute(context, node)) {
147+
return;
148+
}
149+
150+
reportUnorderedProperties(
151+
node,
152+
context,
153+
'route',
154+
order,
155+
importedEmberName,
156+
importedInjectName,
157+
importedObserverName,
158+
importedControllerName,
159+
scopeManager
160+
);
161+
},
128162
};
129163
},
130164
};

lib/utils/ember.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,11 +631,18 @@ function isRouteProperty(name) {
631631
}
632632

633633
function isRouteDefaultProp(property) {
634-
return (
635-
types.isProperty(property) &&
634+
const isRouteClassField =
635+
types.isClassPropertyOrPropertyDefinition(property) &&
636636
types.isIdentifier(property.key) &&
637637
isRouteProperty(property.key.name) &&
638-
property.key.name !== 'actions'
638+
property.key.name !== 'actions';
639+
640+
return (
641+
isRouteClassField ||
642+
(types.isProperty(property) &&
643+
types.isIdentifier(property.key) &&
644+
isRouteProperty(property.key.name) &&
645+
property.key.name !== 'actions')
639646
);
640647
}
641648

@@ -711,6 +718,10 @@ function isMultiLineFn(property, importedEmberName, importedObserverName) {
711718
}
712719

713720
function isFunctionExpression(property) {
721+
if (!property) {
722+
return false;
723+
}
724+
714725
return (
715726
types.isFunctionExpression(property) ||
716727
types.isArrowFunctionExpression(property) ||

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

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ const RuleTester = require('eslint').RuleTester;
1010
// ------------------------------------------------------------------------------
1111

1212
const eslintTester = new RuleTester({
13-
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
13+
parserOptions: {
14+
ecmaVersion: 2022,
15+
sourceType: 'module',
16+
babelOptions: {
17+
configFile: require.resolve('../../../.babelrc'),
18+
},
19+
},
1420
parser: require.resolve('@babel/eslint-parser'),
1521
});
1622

@@ -170,6 +176,56 @@ eslintTester.run('order-in-routes', rule, {
170176
],
171177
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
172178
},
179+
`import Route from '@ember/routing/route';
180+
import { inject as service } from '@ember/service';
181+
export default class UserRoute extends Route {
182+
@service currentUser;
183+
queryParams = {};
184+
customProp = 'test';
185+
beforeModel() {}
186+
model() {}
187+
}`,
188+
{
189+
code: `import Route from '@ember/routing/route';
190+
import { inject as service } from '@ember/service';
191+
export default class UserRoute extends Route {
192+
model() {}
193+
beforeModel() {}
194+
@service currentUser;
195+
}`,
196+
options: [
197+
{
198+
order: ['model', 'lifecycle-hook', 'service'],
199+
},
200+
],
201+
},
202+
{
203+
code: `import Route from '@ember/routing/route';
204+
import { inject as service } from '@ember/service';
205+
export default class UserRoute extends Route {
206+
deactivate() {}
207+
setupController() {}
208+
beforeModel() {}
209+
@service currentUser;
210+
model() {}
211+
}`,
212+
options: [
213+
{
214+
order: [['deactivate', 'setupController', 'beforeModel'], 'service', 'model'],
215+
},
216+
],
217+
},
218+
// spacing/indentation is intentionally not validated by this rule;
219+
// only member ordering should matter.
220+
`import Route from '@ember/routing/route';
221+
import { inject as service } from '@ember/service';
222+
export default class UserRoute extends Route {
223+
@service currentUser;
224+
queryParams = {};
225+
customProp = 'test';
226+
227+
beforeModel() {}
228+
}`,
173229
],
174230
invalid: [
175231
{
@@ -793,5 +849,108 @@ eslintTester.run('order-in-routes', rule, {
793849
},
794850
],
795851
},
852+
{
853+
code: `import Route from '@ember/routing/route';
854+
import { inject as service } from '@ember/service';
855+
export default class UserRoute extends Route {
856+
queryParams = {};
857+
@service currentUser;
858+
customProp = 'test';
859+
beforeModel() {}
860+
model() {}
861+
}`,
862+
output: `import Route from '@ember/routing/route';
863+
import { inject as service } from '@ember/service';
864+
export default class UserRoute extends Route {
865+
@service currentUser;
866+
queryParams = {};
867+
customProp = 'test';
868+
beforeModel() {}
869+
model() {}
870+
}`,
871+
errors: [
872+
{
873+
message:
874+
'The "currentUser" service injection should be above the inherited "queryParams" property on line 4',
875+
line: 5,
876+
},
877+
],
878+
},
879+
{
880+
code: `import Route from '@ember/routing/route';
881+
export default class UserRoute extends Route {
882+
customProp = 'test';
883+
queryParams = {};
884+
model() {}
885+
beforeModel() {}
886+
}`,
887+
output: `import Route from '@ember/routing/route';
888+
export default class UserRoute extends Route {
889+
queryParams = {};
890+
customProp = 'test';
891+
model() {}
892+
beforeModel() {}
893+
}`,
894+
errors: [
895+
{
896+
message:
897+
'The inherited "queryParams" property should be above the "customProp" property on line 3',
898+
line: 4,
899+
},
900+
{
901+
message: 'The "beforeModel" lifecycle hook should be above the "model" hook on line 5',
902+
line: 6,
903+
},
904+
],
905+
},
906+
{
907+
code: `import Route from '@ember/routing/route';
908+
export default class UserRoute extends Route {
909+
customProp = 'test';
910+
model() {}
911+
beforeModel() {}
912+
}`,
913+
output: `import Route from '@ember/routing/route';
914+
export default class UserRoute extends Route {
915+
customProp = 'test';
916+
beforeModel() {}
917+
model() {}
918+
}`,
919+
errors: [
920+
{
921+
message: 'The "beforeModel" lifecycle hook should be above the "model" hook on line 4',
922+
line: 5,
923+
},
924+
],
925+
},
926+
{
927+
code: `import Route from '@ember/routing/route';
928+
import { inject as service } from '@ember/service';
929+
export default class UserRoute extends Route {
930+
model() {}
931+
beforeModel() {}
932+
@service currentUser;
933+
queryParams = {};
934+
}`,
935+
output: `import Route from '@ember/routing/route';
936+
import { inject as service } from '@ember/service';
937+
export default class UserRoute extends Route {
938+
@service currentUser;
939+
model() {}
940+
beforeModel() {}
941+
queryParams = {};
942+
}`,
943+
options: [
944+
{
945+
order: ['service', 'model', 'lifecycle-hook', 'inherited-property'],
946+
},
947+
],
948+
errors: [
949+
{
950+
message: 'The "currentUser" service injection should be above the "model" hook on line 4',
951+
line: 6,
952+
},
953+
],
954+
},
796955
],
797956
});

tests/lib/utils/ember-test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,16 @@ describe('isRouteLifecycleHook', () => {
15451545
node = getProperty('test = { deactivate() {} }');
15461546
expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();
15471547
});
1548+
1549+
it('should not crash when class field has no initializer', () => {
1550+
const classField = {
1551+
key: { name: 'beforeModel' },
1552+
value: null,
1553+
};
1554+
1555+
expect(() => emberUtils.isRouteLifecycleHook(classField)).not.toThrow();
1556+
expect(emberUtils.isRouteLifecycleHook(classField)).toBeFalsy();
1557+
});
15481558
});
15491559

15501560
describe('isActionsProp', () => {

0 commit comments

Comments
 (0)